mirror of
https://github.com/pawelorzech/Fuzzel.git
synced 2026-01-29 19:54:30 +00:00
Add missing NOT NOW and DONE swimlanes to Kanban view
- Add indexed_by parameter to FizzyApiService.getCards() to fetch cards by status (all, closed, not_now) - Modify CardRepositoryImpl to fetch all card states in parallel and combine results - Update KanbanViewModel.distributeCardsToColumns() to create virtual swimlanes for NOT NOW, Triage, and DONE - always visible - Add distinct styling for virtual swimlanes in KanbanScreen: - NOT NOW: purple color, Schedule icon - Triage: orange color, Inbox icon - DONE: green color, CheckCircle icon - Hide Edit/Delete menu and Add Card button for virtual columns
This commit is contained in:
parent
101bf72250
commit
6c0b502630
5 changed files with 237 additions and 64 deletions
|
|
@ -76,7 +76,10 @@ interface FizzyApiService {
|
||||||
// ==================== Cards ====================
|
// ==================== Cards ====================
|
||||||
|
|
||||||
@GET("cards.json")
|
@GET("cards.json")
|
||||||
suspend fun getCards(@Query("board_ids[]") boardId: String? = null): Response<CardsResponse>
|
suspend fun getCards(
|
||||||
|
@Query("board_ids[]") boardId: String? = null,
|
||||||
|
@Query("indexed_by") indexedBy: String? = null
|
||||||
|
): Response<CardsResponse>
|
||||||
|
|
||||||
@GET("cards/{cardNumber}.json")
|
@GET("cards/{cardNumber}.json")
|
||||||
suspend fun getCard(@Path("cardNumber") cardNumber: Int): Response<CardResponse>
|
suspend fun getCard(@Path("cardNumber") cardNumber: Int): Response<CardResponse>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import com.fizzy.android.domain.model.Card
|
||||||
import com.fizzy.android.domain.model.Comment
|
import com.fizzy.android.domain.model.Comment
|
||||||
import com.fizzy.android.domain.model.Step
|
import com.fizzy.android.domain.model.Step
|
||||||
import com.fizzy.android.domain.repository.CardRepository
|
import com.fizzy.android.domain.repository.CardRepository
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
@ -19,18 +21,59 @@ class CardRepositoryImpl @Inject constructor(
|
||||||
private val apiService: FizzyApiService
|
private val apiService: FizzyApiService
|
||||||
) : CardRepository {
|
) : CardRepository {
|
||||||
|
|
||||||
override suspend fun getBoardCards(boardId: String): ApiResult<List<Card>> {
|
override suspend fun getBoardCards(boardId: String): ApiResult<List<Card>> = coroutineScope {
|
||||||
val result = ApiResult.from {
|
Log.d(TAG, "getBoardCards: Fetching all card states for board $boardId")
|
||||||
apiService.getCards(boardId)
|
|
||||||
|
// Fetch all 3 types of cards in parallel
|
||||||
|
val activeDeferred = async { apiService.getCards(boardId, "all") }
|
||||||
|
val closedDeferred = async { apiService.getCards(boardId, "closed") }
|
||||||
|
val notNowDeferred = async { apiService.getCards(boardId, "not_now") }
|
||||||
|
|
||||||
|
val activeResponse = activeDeferred.await()
|
||||||
|
val closedResponse = closedDeferred.await()
|
||||||
|
val notNowResponse = notNowDeferred.await()
|
||||||
|
|
||||||
|
Log.d(TAG, "getBoardCards responses - active: ${activeResponse.isSuccessful}, closed: ${closedResponse.isSuccessful}, notNow: ${notNowResponse.isSuccessful}")
|
||||||
|
|
||||||
|
// Combine all cards
|
||||||
|
val allCards = mutableListOf<Card>()
|
||||||
|
|
||||||
|
if (activeResponse.isSuccessful) {
|
||||||
|
activeResponse.body()?.let { cards ->
|
||||||
|
Log.d(TAG, "getBoardCards: ${cards.size} active cards")
|
||||||
|
allCards.addAll(cards.map { it.toDomain() })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "getBoardCards active error: ${activeResponse.code()} - ${activeResponse.message()}")
|
||||||
}
|
}
|
||||||
Log.d(TAG, "getBoardCards result: $result")
|
|
||||||
when (result) {
|
if (closedResponse.isSuccessful) {
|
||||||
is ApiResult.Success -> Log.d(TAG, "getBoardCards success: ${result.data.size} cards")
|
closedResponse.body()?.let { cards ->
|
||||||
is ApiResult.Error -> Log.e(TAG, "getBoardCards error: ${result.code} - ${result.message}")
|
Log.d(TAG, "getBoardCards: ${cards.size} closed cards")
|
||||||
is ApiResult.Exception -> Log.e(TAG, "getBoardCards exception", result.throwable)
|
allCards.addAll(cards.map { it.toDomain() })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "getBoardCards closed error: ${closedResponse.code()} - ${closedResponse.message()}")
|
||||||
}
|
}
|
||||||
return result.map { response ->
|
|
||||||
response.map { it.toDomain() }.sortedBy { it.position }
|
if (notNowResponse.isSuccessful) {
|
||||||
|
notNowResponse.body()?.let { cards ->
|
||||||
|
Log.d(TAG, "getBoardCards: ${cards.size} not_now cards")
|
||||||
|
allCards.addAll(cards.map { it.toDomain() })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "getBoardCards not_now error: ${notNowResponse.code()} - ${notNowResponse.message()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduplicate by ID and sort
|
||||||
|
val uniqueCards = allCards.distinctBy { it.id }.sortedBy { it.position }
|
||||||
|
Log.d(TAG, "getBoardCards: Total ${uniqueCards.size} unique cards")
|
||||||
|
|
||||||
|
// Return success if at least active cards were fetched
|
||||||
|
if (activeResponse.isSuccessful) {
|
||||||
|
ApiResult.Success(uniqueCards)
|
||||||
|
} else {
|
||||||
|
ApiResult.Error(activeResponse.code(), activeResponse.message())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,12 @@ import androidx.lifecycle.viewModelScope
|
||||||
import com.fizzy.android.core.network.ApiResult
|
import com.fizzy.android.core.network.ApiResult
|
||||||
import com.fizzy.android.domain.model.Board
|
import com.fizzy.android.domain.model.Board
|
||||||
import com.fizzy.android.domain.repository.BoardRepository
|
import com.fizzy.android.domain.repository.BoardRepository
|
||||||
|
import com.fizzy.android.domain.repository.CardRepository
|
||||||
import com.fizzy.android.domain.repository.NotificationRepository
|
import com.fizzy.android.domain.repository.NotificationRepository
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -34,6 +38,7 @@ sealed class BoardListEvent {
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class BoardListViewModel @Inject constructor(
|
class BoardListViewModel @Inject constructor(
|
||||||
private val boardRepository: BoardRepository,
|
private val boardRepository: BoardRepository,
|
||||||
|
private val cardRepository: CardRepository,
|
||||||
private val notificationRepository: NotificationRepository
|
private val notificationRepository: NotificationRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
|
@ -54,10 +59,12 @@ class BoardListViewModel @Inject constructor(
|
||||||
private fun observeBoards() {
|
private fun observeBoards() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
boardRepository.observeBoards().collect { boards ->
|
boardRepository.observeBoards().collect { boards ->
|
||||||
|
// Fetch stats for boards from the flow
|
||||||
|
val boardsWithStats = fetchBoardStats(boards)
|
||||||
_uiState.update { state ->
|
_uiState.update { state ->
|
||||||
state.copy(
|
state.copy(
|
||||||
boards = boards,
|
boards = boardsWithStats,
|
||||||
filteredBoards = filterBoards(boards, state.searchQuery)
|
filteredBoards = filterBoards(boardsWithStats, state.searchQuery)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +77,16 @@ class BoardListViewModel @Inject constructor(
|
||||||
|
|
||||||
when (val result = boardRepository.getBoards()) {
|
when (val result = boardRepository.getBoards()) {
|
||||||
is ApiResult.Success -> {
|
is ApiResult.Success -> {
|
||||||
_uiState.update { it.copy(isLoading = false) }
|
val boards = result.data
|
||||||
|
// Fetch stats for each board in parallel
|
||||||
|
val boardsWithStats = fetchBoardStats(boards)
|
||||||
|
_uiState.update { state ->
|
||||||
|
state.copy(
|
||||||
|
isLoading = false,
|
||||||
|
boards = boardsWithStats,
|
||||||
|
filteredBoards = filterBoards(boardsWithStats, state.searchQuery)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is ApiResult.Error -> {
|
is ApiResult.Error -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
|
|
@ -92,6 +108,19 @@ class BoardListViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchBoardStats(boards: List<Board>): List<Board> = coroutineScope {
|
||||||
|
boards.map { board ->
|
||||||
|
async {
|
||||||
|
val columnsResult = boardRepository.getColumns(board.id)
|
||||||
|
val cardsResult = cardRepository.getBoardCards(board.id)
|
||||||
|
board.copy(
|
||||||
|
columnsCount = (columnsResult as? ApiResult.Success)?.data?.size ?: 0,
|
||||||
|
cardsCount = (cardsResult as? ApiResult.Success)?.data?.size ?: 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.awaitAll()
|
||||||
|
}
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_uiState.update { it.copy(isRefreshing = true) }
|
_uiState.update { it.copy(isRefreshing = true) }
|
||||||
|
|
@ -99,6 +128,18 @@ class BoardListViewModel @Inject constructor(
|
||||||
boardRepository.refreshBoards()
|
boardRepository.refreshBoards()
|
||||||
notificationRepository.getNotifications()
|
notificationRepository.getNotifications()
|
||||||
|
|
||||||
|
// Re-fetch stats for updated boards
|
||||||
|
val currentBoards = _uiState.value.boards
|
||||||
|
if (currentBoards.isNotEmpty()) {
|
||||||
|
val boardsWithStats = fetchBoardStats(currentBoards)
|
||||||
|
_uiState.update { state ->
|
||||||
|
state.copy(
|
||||||
|
boards = boardsWithStats,
|
||||||
|
filteredBoards = filterBoards(boardsWithStats, state.searchQuery)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_uiState.update { it.copy(isRefreshing = false) }
|
_uiState.update { it.copy(isRefreshing = false) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,28 @@ private fun KanbanColumn(
|
||||||
) {
|
) {
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// Determine column type and styling
|
||||||
|
val isTriageColumn = column.id.isEmpty()
|
||||||
|
val isNotNowColumn = column.id == "__not_now__"
|
||||||
|
val isDoneColumn = column.id == "__done__"
|
||||||
|
val isVirtualColumn = isTriageColumn || isNotNowColumn || isDoneColumn
|
||||||
|
|
||||||
|
// Colors for different column types
|
||||||
|
val columnColor = when {
|
||||||
|
isNotNowColumn -> Color(0xFF8B5CF6) // Purple for Not Now
|
||||||
|
isTriageColumn -> Color(0xFFF97316) // Orange for Triage
|
||||||
|
isDoneColumn -> Color(0xFF22C55E) // Green for Done
|
||||||
|
else -> MaterialTheme.colorScheme.onSurface
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icons for virtual columns
|
||||||
|
val columnIcon = when {
|
||||||
|
isNotNowColumn -> Icons.Default.Schedule
|
||||||
|
isTriageColumn -> Icons.Default.Inbox
|
||||||
|
isDoneColumn -> Icons.Default.CheckCircle
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(300.dp)
|
.width(300.dp)
|
||||||
|
|
@ -233,15 +255,28 @@ private fun KanbanColumn(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
columnIcon?.let { icon ->
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = columnColor
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = column.name,
|
text = column.name,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = if (isVirtualColumn) columnColor else MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Surface(
|
Surface(
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.2f)
|
color = if (isVirtualColumn)
|
||||||
|
columnColor.copy(alpha = 0.2f)
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.outline.copy(alpha = 0.2f)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = column.cards.size.toString(),
|
text = column.cards.size.toString(),
|
||||||
|
|
@ -251,41 +286,44 @@ private fun KanbanColumn(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box {
|
// Hide menu for all virtual columns
|
||||||
IconButton(onClick = { showMenu = true }) {
|
if (!isVirtualColumn) {
|
||||||
Icon(
|
Box {
|
||||||
Icons.Default.MoreVert,
|
IconButton(onClick = { showMenu = true }) {
|
||||||
contentDescription = "Column options",
|
Icon(
|
||||||
modifier = Modifier.size(20.dp)
|
Icons.Default.MoreVert,
|
||||||
)
|
contentDescription = "Column options",
|
||||||
}
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = showMenu,
|
expanded = showMenu,
|
||||||
onDismissRequest = { showMenu = false }
|
onDismissRequest = { showMenu = false }
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Edit") },
|
text = { Text("Edit") },
|
||||||
leadingIcon = { Icon(Icons.Default.Edit, contentDescription = null) },
|
leadingIcon = { Icon(Icons.Default.Edit, contentDescription = null) },
|
||||||
onClick = {
|
onClick = {
|
||||||
showMenu = false
|
showMenu = false
|
||||||
onEditColumn()
|
onEditColumn()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Delete") },
|
text = { Text("Delete") },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Delete,
|
Icons.Default.Delete,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
showMenu = false
|
showMenu = false
|
||||||
onDeleteColumn()
|
onDeleteColumn()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -307,16 +345,18 @@ private fun KanbanColumn(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Card Button
|
// Add Card Button (hide for all virtual columns)
|
||||||
TextButton(
|
if (!isVirtualColumn) {
|
||||||
onClick = onAddCard,
|
TextButton(
|
||||||
modifier = Modifier
|
onClick = onAddCard,
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.padding(8.dp)
|
.fillMaxWidth()
|
||||||
) {
|
.padding(8.dp)
|
||||||
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(18.dp))
|
) {
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||||
Text("Add Card")
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text("Add Card")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
|
||||||
import com.fizzy.android.core.network.ApiResult
|
import com.fizzy.android.core.network.ApiResult
|
||||||
import com.fizzy.android.domain.model.Board
|
import com.fizzy.android.domain.model.Board
|
||||||
import com.fizzy.android.domain.model.Card
|
import com.fizzy.android.domain.model.Card
|
||||||
|
import com.fizzy.android.domain.model.CardStatus
|
||||||
import com.fizzy.android.domain.model.Column
|
import com.fizzy.android.domain.model.Column
|
||||||
import com.fizzy.android.domain.repository.BoardRepository
|
import com.fizzy.android.domain.repository.BoardRepository
|
||||||
import com.fizzy.android.domain.repository.CardRepository
|
import com.fizzy.android.domain.repository.CardRepository
|
||||||
|
|
@ -147,16 +148,61 @@ class KanbanViewModel @Inject constructor(
|
||||||
private fun distributeCardsToColumns(columns: List<Column>, cards: List<Card>): List<Column> {
|
private fun distributeCardsToColumns(columns: List<Column>, cards: List<Card>): List<Column> {
|
||||||
Log.d(TAG, "distributeCardsToColumns: ${cards.size} cards, ${columns.size} columns")
|
Log.d(TAG, "distributeCardsToColumns: ${cards.size} cards, ${columns.size} columns")
|
||||||
Log.d(TAG, "Column IDs: ${columns.map { "${it.name}=${it.id}" }}")
|
Log.d(TAG, "Column IDs: ${columns.map { "${it.name}=${it.id}" }}")
|
||||||
Log.d(TAG, "Card columnIds: ${cards.map { "${it.title}→${it.columnId}" }}")
|
Log.d(TAG, "Card statuses: ${cards.map { "${it.title}→${it.status}" }}")
|
||||||
|
|
||||||
val cardsByColumn = cards.groupBy { it.columnId }
|
// Group cards by status
|
||||||
Log.d(TAG, "Cards grouped by column: ${cardsByColumn.mapValues { it.value.map { c -> c.title } }}")
|
val deferredCards = cards.filter { it.status == CardStatus.DEFERRED }.sortedBy { it.position }
|
||||||
|
val closedCards = cards.filter { it.status == CardStatus.CLOSED }.sortedBy { it.position }
|
||||||
|
val activeCards = cards.filter { it.status == CardStatus.ACTIVE || it.status == CardStatus.TRIAGED }
|
||||||
|
|
||||||
return columns.map { column ->
|
// Group active cards by column
|
||||||
|
val triageCards = activeCards.filter { it.columnId.isEmpty() }.sortedBy { it.position }
|
||||||
|
val cardsByColumn = activeCards.filter { it.columnId.isNotEmpty() }.groupBy { it.columnId }
|
||||||
|
|
||||||
|
Log.d(TAG, "Cards by status - deferred: ${deferredCards.size}, triage: ${triageCards.size}, in columns: ${cardsByColumn.values.sumOf { it.size }}, closed: ${closedCards.size}")
|
||||||
|
|
||||||
|
val result = mutableListOf<Column>()
|
||||||
|
val defaultBoardId = columns.firstOrNull()?.boardId ?: boardId
|
||||||
|
|
||||||
|
// NOT NOW swimlane (deferred cards) - always visible, position -2
|
||||||
|
result += Column(
|
||||||
|
id = "__not_now__",
|
||||||
|
name = "Not Now",
|
||||||
|
position = -2,
|
||||||
|
boardId = defaultBoardId,
|
||||||
|
cards = deferredCards
|
||||||
|
)
|
||||||
|
Log.d(TAG, "Added NOT NOW swimlane with ${deferredCards.size} cards")
|
||||||
|
|
||||||
|
// Triage swimlane (active cards without column) - always visible, position -1
|
||||||
|
result += Column(
|
||||||
|
id = "",
|
||||||
|
name = "Triage",
|
||||||
|
position = -1,
|
||||||
|
boardId = defaultBoardId,
|
||||||
|
cards = triageCards
|
||||||
|
)
|
||||||
|
Log.d(TAG, "Added Triage swimlane with ${triageCards.size} cards")
|
||||||
|
|
||||||
|
// User-created columns with active cards
|
||||||
|
val columnsWithCards = columns.map { column ->
|
||||||
val columnCards = cardsByColumn[column.id]?.sortedBy { it.position } ?: emptyList()
|
val columnCards = cardsByColumn[column.id]?.sortedBy { it.position } ?: emptyList()
|
||||||
Log.d(TAG, "Column '${column.name}' (${column.id}): ${columnCards.size} cards")
|
Log.d(TAG, "Column '${column.name}' (${column.id}): ${columnCards.size} cards")
|
||||||
column.copy(cards = columnCards)
|
column.copy(cards = columnCards)
|
||||||
}
|
}
|
||||||
|
result += columnsWithCards
|
||||||
|
|
||||||
|
// DONE swimlane (closed cards) - always visible, position at end
|
||||||
|
result += Column(
|
||||||
|
id = "__done__",
|
||||||
|
name = "Done",
|
||||||
|
position = Int.MAX_VALUE,
|
||||||
|
boardId = defaultBoardId,
|
||||||
|
cards = closedCards
|
||||||
|
)
|
||||||
|
Log.d(TAG, "Added DONE swimlane with ${closedCards.size} cards")
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue