Vibe coding全紀錄-57

A:

 好的,請繼續提供剩餘的screen檔案,謝謝 

Q:

好的,我們繼續完成 ui/screens/ 套件的回顧。以下是剩餘的所有管理與功能畫面的最終版程式碼。


第廿三步:最終程式碼回顧 (UI Screens 篇 - 完結)

檔案路徑: app/src/main/java/com/yourcompany/cashflow/ui/screens/AccountManagementScreen.kt

<details>

<summary><strong>點此展開 AccountManagementScreen.kt 程式碼</strong></summary>

Kotlin
package com.yourcompany.cashflow.ui.screens

import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.yourcompany.cashflow.data.local.entity.Account
import com.yourcompany.cashflow.data.local.entity.AccountType
import com.yourcompany.cashflow.ui.viewmodel.AccountViewModel
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.ui.Modifier

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AccountManagementScreen(
    viewModel: AccountViewModel = hiltViewModel(),
    onNavigateBack: () -> Unit
) {
    val accounts by viewModel.allAccounts.collectAsStateWithLifecycle()
    var showDialog by remember { mutableStateOf(false) }
    var accountToEdit by remember { mutableStateOf<Account?>(null) }

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("管理帳戶") },
                navigationIcon = { IconButton(onClick = onNavigateBack) { Icon(Icons.Default.ArrowBack, "返回") } }
            )
        },
        floatingActionButton = {
            FloatingActionButton(onClick = {
                accountToEdit = null
                showDialog = true
            }) {
                Icon(Icons.Default.Add, "新增帳戶")
            }
        }
    ) { innerPadding ->
        LazyColumn(contentPadding = innerPadding) {
            items(accounts) { account ->
                ListItem(
                    headlineContent = { Text(account.name) },
                    supportingContent = { Text("類型: ${account.assetClass.name}, 餘額: ${"%,.0f".format(account.initialBalance)}") },
                    trailingContent = {
                        Row {
                            IconButton(onClick = {
                                accountToEdit = account
                                showDialog = true
                            }) { Icon(Icons.Default.Edit, "編輯") }
                            IconButton(onClick = { viewModel.deleteAccount(account) }) { Icon(Icons.Default.Delete, "刪除") }
                        }
                    }
                )
                Divider()
            }
        }
    }

    if (showDialog) {
        AddEditAccountDialog(
            account = accountToEdit,
            onDismiss = { showDialog = false },
            onConfirm = { name, type, assetClass, balance ->
                if (accountToEdit == null) {
                    viewModel.addAccount(name, type, assetClass, balance)
                } else {
                    viewModel.updateAccount(accountToEdit!!.copy(name = name, type = type, assetClass = assetClass, initialBalance = balance))
                }
                showDialog = false
            }
        )
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddEditAccountDialog(
    account: Account?,
    onDismiss: () -> Unit,
    onConfirm: (String, AccountType, AssetClass, Double) -> Unit
) {
    var name by remember { mutableStateOf(account?.name ?: "") }
    var balance by remember { mutableStateOf(account?.initialBalance?.toString() ?: "") }
    var selectedType by remember { mutableStateOf(account?.type ?: AccountType.BANK) }
    var selectedAssetClass by remember { mutableStateOf(account?.assetClass ?: AssetClass.CASH) }
    var typeMenuExpanded by remember { mutableStateOf(false) }
    var assetClassMenuExpanded by remember { mutableStateOf(false) }

    AlertDialog(
        onDismissRequest = onDismiss,
        title = { Text(if (account == null) "新增帳戶" else "編輯帳戶") },
        text = {
            Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
                OutlinedTextField(value = name, onValueChange = { name = it }, label = { Text("帳戶名稱") })
                OutlinedTextField(value = balance, onValueChange = { balance = it }, label = { Text("初始餘額") })
                
                // Account Type Dropdown
                ExposedDropdownMenuBox(expanded = typeMenuExpanded, onExpandedChange = { typeMenuExpanded = it }) {
                    OutlinedTextField(value = selectedType.name, onValueChange = {}, readOnly = true,
                        label = { Text("帳戶類型") },
                        trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = typeMenuExpanded) },
                        modifier = Modifier.menuAnchor()
                    )
                    ExposedDropdownMenu(expanded = typeMenuExpanded, onDismissRequest = { typeMenuExpanded = false }) {
                        AccountType.values().forEach { type ->
                            DropdownMenuItem(text = { Text(type.name) }, onClick = { selectedType = type; typeMenuExpanded = false })
                        }
                    }
                }

                // Asset Class Dropdown
                ExposedDropdownMenuBox(expanded = assetClassMenuExpanded, onExpandedChange = { assetClassMenuExpanded = it }) {
                    OutlinedTextField(value = selectedAssetClass.name, onValueChange = {}, readOnly = true,
                        label = { Text("資產類別") },
                        trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = assetClassMenuExpanded) },
                        modifier = Modifier.menuAnchor()
                    )
                    ExposedDropdownMenu(expanded = assetClassMenuExpanded, onDismissRequest = { assetClassMenuExpanded = false }) {
                        AssetClass.values().forEach { assetClass ->
                            DropdownMenuItem(text = { Text(assetClass.name) }, onClick = { selectedAssetClass = assetClass; assetClassMenuExpanded = false })
                        }
                    }
                }
            }
        },
        confirmButton = {
            Button(onClick = {
                val balanceDouble = balance.toDoubleOrNull()
                if (name.isNotBlank() && balanceDouble != null) {
                    onConfirm(name, selectedType, selectedAssetClass, balanceDouble)
                }
            }) { Text("儲存") }
        },
        dismissButton = { TextButton(onClick = onDismiss) { Text("取消") } }
    )
}

</details>


檔案路徑: app/src/main/java/com/yourcompany/cashflow/ui/screens/CategoryManagementScreen.kt

<details>

<summary><strong>點此展開 CategoryManagementScreen.kt 程式碼</strong></summary>

Kotlin
package com.yourcompany.cashflow.ui.screens

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.yourcompany.cashflow.data.local.entity.MainCategory
import com.yourcompany.cashflow.data.local.entity.SubCategory
import com.yourcompany.cashflow.data.local.entity.TransactionType
import com.yourcompany.cashflow.data.model.MainCategoryWithSubCategories
import com.yourcompany.cashflow.ui.components.CategoryDialog
import com.yourcompany.cashflow.ui.viewmodel.CategoryViewModel

// Sealed Class to manage dialog state
sealed class CategoryDialogState {
    object Hidden : CategoryDialogState()
    data class AddingMainCategory(val type: TransactionType) : CategoryDialogState()
    data class EditingMainCategory(val mainCategory: MainCategory) : CategoryDialogState()
    data class AddingSubCategory(val parentCategory: MainCategory) : CategoryDialogState()
    data class EditingSubCategory(val subCategory: SubCategory) : CategoryDialogState()
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CategoryManagementScreen(
    viewModel: CategoryViewModel = hiltViewModel(),
    onNavigateBack: () -> Unit
) {
    val categories by viewModel.categories.collectAsStateWithLifecycle()
    var dialogState by remember { mutableStateOf<CategoryDialogState>(CategoryDialogState.Hidden) }

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("管理分類") },
                navigationIcon = { IconButton(onClick = onNavigateBack) { Icon(Icons.Default.ArrowBack, "返回") } }
            )
        },
        floatingActionButton = {
            FloatingActionButton(onClick = { dialogState = CategoryDialogState.AddingMainCategory(TransactionType.EXPENSE) }) {
                Icon(Icons.Default.Add, "新增主分類")
            }
        }
    ) { innerPadding ->
        LazyColumn(
            modifier = Modifier.padding(innerPadding),
            contentPadding = PaddingValues(vertical = 8.dp)
        ) {
            items(categories) { categoryWithSubs ->
                ExpandableMainCategoryItem(
                    categoryWithSubCategories = categoryWithSubs,
                    onAddSubCategory = { mainCategory -> dialogState = CategoryDialogState.AddingSubCategory(mainCategory) },
                    onEditMainCategory = { mainCategory -> dialogState = CategoryDialogState.EditingMainCategory(mainCategory) },
                    onDeleteMainCategory = { viewModel.deleteMainCategory(it) },
                    onEditSubCategory = { subCategory -> dialogState = CategoryDialogState.EditingSubCategory(subCategory) },
                    onDeleteSubCategory = { viewModel.deleteSubCategory(it) }
                )
            }
        }
    }

    CategoryDialog(
        state = dialogState,
        onDismiss = { dialogState = CategoryDialogState.Hidden },
        onConfirm = { name, type ->
            when (val currentState = dialogState) {
                is CategoryDialogState.AddingMainCategory -> viewModel.addMainCategory(name, type!!)
                is CategoryDialogState.EditingMainCategory -> viewModel.updateMainCategory(currentState.mainCategory.copy(name = name, type = type!!))
                is CategoryDialogState.AddingSubCategory -> viewModel.addSubCategory(name, currentState.parentCategory.id)
                is CategoryDialogState.EditingSubCategory -> viewModel.updateSubCategory(currentState.subCategory.copy(name = name))
                else -> {}
            }
            dialogState = CategoryDialogState.Hidden
        }
    )
}

@Composable
fun ExpandableMainCategoryItem(
    categoryWithSubCategories: MainCategoryWithSubCategories,
    onAddSubCategory: (MainCategory) -> Unit,
    onEditMainCategory: (MainCategory) -> Unit,
    onDeleteMainCategory: (MainCategory) -> Unit,
    onEditSubCategory: (SubCategory) -> Unit,
    onDeleteSubCategory: (SubCategory) -> Unit
) {
    var isExpanded by remember { mutableStateOf(false) }
    val mainCategory = categoryWithSubCategories.mainCategory
    val subCategories = categoryWithSubCategories.subCategories

    Column {
        ListItem(
            modifier = Modifier.clickable { isExpanded = !isExpanded },
            headlineContent = { Text(mainCategory.name) },
            leadingContent = { Icon(if (isExpanded) Icons.Default.KeyboardArrowDown else Icons.Default.KeyboardArrowRight, "Expand/Collapse") },
            trailingContent = {
                Row {
                    IconButton(onClick = { onAddSubCategory(mainCategory) }) { Icon(Icons.Default.Add, "Add SubCategory") }
                    IconButton(onClick = { onEditMainCategory(mainCategory) }) { Icon(Icons.Default.Edit, "Edit MainCategory") }
                    IconButton(onClick = { onDeleteMainCategory(mainCategory) }) { Icon(Icons.Default.Delete, "Delete MainCategory") }
                }
            }
        )
        AnimatedVisibility(visible = isExpanded) {
            Column(modifier = Modifier.padding(start = 24.dp)) {
                subCategories.forEach { subCategory ->
                    ListItem(
                        headlineContent = { Text(subCategory.name) },
                        trailingContent = {
                            Row {
                                IconButton(onClick = { onEditSubCategory(subCategory) }) { Icon(Icons.Default.Edit, "Edit SubCategory", tint = MaterialTheme.colorScheme.secondary) }
                                IconButton(onClick = { onDeleteSubCategory(subCategory) }) { Icon(Icons.Default.Delete, "Delete SubCategory", tint = MaterialTheme.colorScheme.secondary) }
                            }
                        }
                    )
                }
                if (subCategories.isEmpty()) {
                    ListItem(headlineContent = { Text("沒有子分類", color = Color.Gray) })
                }
            }
        }
    }
}

</details>


(由於檔案數量和程式碼長度非常多,為了避免回應過於龐大,我將在下一個回應中繼續提供剩餘的 Screen 檔案。)