Vibe coding全紀錄-52

A:

要請幫忙一步一步詳細仔細的列出最終版的下列程式碼,謝謝

​data/local/*

​AppDatabase.kt (version 3)

​Converters.kt

​Migrations.kt (MIGRATION_1_2, MIGRATION_2_3)

data/model/*

​FinancialSummary.kt (包含所有統計與資產負債表用的模型)

​MainCategoryWithSubCategories.kt

​TransactionWithDetails.kt

​data/preferences/UserPreferencesRepository.kt 

Q:

好的,我們繼續進行專案的完整程式碼回顧。

接下來,我將列出 data/local/data/model/data/preferences/ 這幾個套件底下的所有檔案。這些檔案定義了我們 App 的資料庫主體、遷移邏輯、所有自訂的查詢結果模型,以及使用者偏好設定的儲存機制。它們是數據層的骨架與大腦。


第廿三步:最終程式碼回顧 (Data Local, Model, Preferences 篇)

套件: data/local/

檔案路徑: app/src/main/java/com/yourcompany/cashflow/data/local/AppDatabase.kt

<details>

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

Kotlin
package com.yourcompany.cashflow.data.local

import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.yourcompany.cashflow.data.local.dao.*
import com.yourcompany.cashflow.data.local.entity.*

@Database(
    entities = [
        Account::class,
        AccountSnapshot::class,
        MainCategory::class,
        SubCategory::class,
        Project::class,
        RecurringTransaction::class,
        Transaction::class
    ],
    version = 3, // 最終版本號
    exportSchema = false
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun accountDao(): AccountDao
    abstract fun categoryDao(): CategoryDao
    abstract fun transactionDao(): TransactionDao
    abstract fun recurringTransactionDao(): RecurringTransactionDao
    abstract fun snapshotDao(): SnapshotDao
    abstract fun projectDao(): ProjectDao
}

</details>


檔案路徑: app/src/main/java/com/yourcompany/cashflow/data/local/Converters.kt

<details>

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

Kotlin
package com.yourcompany.cashflow.data.local

import androidx.room.TypeConverter
import com.yourcompany.cashflow.data.local.entity.*

class Converters {
    @TypeConverter fun fromAccountType(value: AccountType): String = value.name
    @TypeConverter fun toAccountType(value: String): AccountType = AccountType.valueOf(value)

    @TypeConverter fun fromTransactionType(value: TransactionType): String = value.name
    @TypeConverter fun toTransactionType(value: String): TransactionType = TransactionType.valueOf(value)

    @TypeConverter fun fromFrequencyType(value: FrequencyType): String = value.name
    @TypeConverter fun toFrequencyType(value: String): FrequencyType = FrequencyType.valueOf(value)
    
    @TypeConverter fun fromAssetClass(value: AssetClass): String = value.name
    @TypeConverter fun toAssetClass(value: String): AssetClass = AssetClass.valueOf(value)

    @TypeConverter fun fromProjectStatus(value: ProjectStatus): String = value.name
    @TypeConverter fun toProjectStatus(value: String): ProjectStatus = ProjectStatus.valueOf(value)
}

</details>


檔案路徑: app/src/main/java/com/yourcompany/cashflow/data/local/Migrations.kt

<details>

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

Kotlin
package com.yourcompany.cashflow.data.local

import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase

/**
 * 從版本 1 遷移到版本 2
 * 變更點:
 * 1. 在 `accounts` 表中新增 `assetClass` 欄位。
 * 2. 建立新的 `account_snapshots` 表。
 */
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        // 1. 為 accounts 表新增 assetClass 欄位,並提供 NOT NULL 和 DEFAULT 值
        db.execSQL("ALTER TABLE accounts ADD COLUMN assetClass TEXT NOT NULL DEFAULT 'CASH'")
        
        // 2. 建立新的 account_snapshots 表
        db.execSQL("""
            CREATE TABLE IF NOT EXISTS `account_snapshots` (
                `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
                `accountId` INTEGER NOT NULL,
                `snapshotDate` INTEGER NOT NULL,
                `value` REAL NOT NULL,
                FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
            )
        """)
    }
}

/**
 * 從版本 2 遷移到版本 3
 * 變更點:
 * 1. 建立新的 `projects` 表。
 */
val MIGRATION_2_3 = object : Migration(2, 3) {
    override fun migrate(db: SupportSQLiteDatabase) {
        // 建立新的 projects 表
        db.execSQL("""
            CREATE TABLE IF NOT EXISTS `projects` (
                `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
                `name` TEXT NOT NULL,
                `budget` REAL NOT NULL,
                `description` TEXT,
                `status` TEXT NOT NULL DEFAULT 'ACTIVE'
            )
        """)
    }
}

</details>


套件: data/model/

檔案路徑: app/src/main/java/com/yourcompany/cashflow/data/model/FinancialSummary.kt

<details>

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

Kotlin
package com.yourcompany.cashflow.data.model

import androidx.room.Embedded
import com.yourcompany.cashflow.data.local.entity.Account
import com.yourcompany.cashflow.data.local.entity.AssetClass

/**
 * 用於統計圖表:代表某個主分類或專案的總額
 */
data class CategorySum(
    val categoryName: String,
    val amount: Double
)

/**
 * 用於統計圖表鑽取:代表某個子分類的總額
 */
data class SubCategorySum(
    val subCategoryName: String,
    val amount: Double
)

/**
 * 用於資產負債表年度趨勢圖:代表某個月份的資產總額
 */
data class MonthlyTotal(
    val yearMonth: String, // 格式: "2025-08"
    val totalValue: Double
)

/**
 * 用於資產負債表資產配置圖:代表某個資產類別的總額
 */
data class AssetAllocation(
    val assetClass: AssetClass,
    val totalValue: Double
)

/**
 * 用於資產負債表下方的帳戶列表
 */
data class AccountWithLatestValue(
    @Embedded
    val account: Account,
    val latestValue: Double? // 最新的快照餘額,可能為 null
)

/**
 * 用於承載最新淨值的查詢結果
 */
data class NetWorth(
    val totalAssets: Double,
    val totalLiabilities: Double
) {
    val value: Double
        get() = totalAssets - totalLiabilities
}

</details>


檔案路徑: app/src/main/java/com/yourcompany/cashflow/data/model/MainCategoryWithSubCategories.kt

<details>

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

Kotlin
package com.yourcompany.cashflow.data.model

import androidx.room.Embedded
import androidx.room.Relation
import com.yourcompany.cashflow.data.local.entity.MainCategory
import com.yourcompany.cashflow.data.local.entity.SubCategory

/**
 * 用於分類管理頁面,一次性查詢出主分類及其所有子分類
 */
data class MainCategoryWithSubCategories(
    @Embedded
    val mainCategory: MainCategory,

    @Relation(
        parentColumn = "id",
        entityColumn = "mainCategoryId"
    )
    val subCategories: List<SubCategory>
)

</details>


檔案路徑: app/src/main/java/com/yourcompany/cashflow/data/model/TransactionWithDetails.kt

<details>

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

Kotlin
package com.yourcompany.cashflow.data.model

import androidx.room.Embedded
import androidx.room.Relation
import com.yourcompany.cashflow.data.local.entity.Account
import com.yourcompany.cashflow.data.local.entity.SubCategory
import com.yourcompany.cashflow.data.local.entity.Transaction

/**
 * 用於交易明細列表,一次性查詢出交易紀錄及其關聯的帳戶和子分類資訊
 */
data class TransactionWithDetails(
    @Embedded
    val transaction: Transaction,

    @Relation(
        parentColumn = "subCategoryId",
        entityColumn = "id"
    )
    val subCategory: SubCategory,

    @Relation(
        parentColumn = "accountId",
        entityColumn = "id"
    )
    val account: Account
)

</details>


套件: data/preferences/

檔案路徑: app/src/main/java/com/yourcompany/cashflow/data/preferences/UserPreferencesRepository.kt

<details>

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

Kotlin
package com.yourcompany.cashflow.data.preferences

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.doublePreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton

// 定義使用者預算的資料模型
data class UserBudget(
    val annualBudget: Double,
    val monthlyBudget: Double
)

// 建立一個 Context 的擴充屬性來取得 DataStore 實例
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "user_preferences")

@Singleton
class UserPreferencesRepository @Inject constructor(
    @ApplicationContext private val context: Context
) {
    // 定義我們要儲存的數據的 Key
    private object Keys {
        val ANNUAL_BUGET = doublePreferencesKey("annual_budget")
        val MONTHLY_BUGET = doublePreferencesKey("monthly_budget")
    }

    // 提供一個 Flow 來讀取預算數據
    val userBudget: Flow<UserBudget> = context.dataStore.data
        .map { preferences ->
            UserBudget(
                annualBudget = preferences[Keys.ANNUAL_BUGET] ?: 0.0,
                monthlyBudget = preferences[Keys.MONTHLY_BUGET] ?: 0.0
            )
        }

    // 提供一個 suspend 函式來儲存預算
    suspend fun saveBudget(annual: Double, monthly: Double) {
        context.dataStore.edit { preferences ->
            preferences[Keys.ANNUAL_BUGET] = annual
            preferences[Keys.MONTHLY_BUGET] = monthly
        }
    }
}

</details>


以上就是我們專案 data/local/data/model/data/preferences/ 套件下所有檔案的最終版本。它們共同構成了我們 App 數據層的靜態結構與定義,是所有業務邏輯的基礎。