diff --git a/app/src/main/java/com/yovinchen/bookkeeping/data/BookkeepingDatabase.kt b/app/src/main/java/com/yovinchen/bookkeeping/data/BookkeepingDatabase.kt index 3fd3ccc..bd5105c 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/data/BookkeepingDatabase.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/data/BookkeepingDatabase.kt @@ -13,14 +13,15 @@ import com.yovinchen.bookkeeping.model.BookkeepingRecord import com.yovinchen.bookkeeping.model.Category import com.yovinchen.bookkeeping.model.Converters import com.yovinchen.bookkeeping.model.Member +import com.yovinchen.bookkeeping.model.Settings import com.yovinchen.bookkeeping.model.TransactionType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @Database( - entities = [BookkeepingRecord::class, Category::class, Member::class], - version = 4, + entities = [BookkeepingRecord::class, Category::class, Member::class, Settings::class], + version = 5, exportSchema = false ) @TypeConverters(Converters::class) @@ -28,6 +29,7 @@ abstract class BookkeepingDatabase : RoomDatabase() { abstract fun bookkeepingDao(): BookkeepingDao abstract fun categoryDao(): CategoryDao abstract fun memberDao(): MemberDao + abstract fun settingsDao(): SettingsDao companion object { private const val TAG = "BookkeepingDatabase" @@ -124,6 +126,28 @@ abstract class BookkeepingDatabase : RoomDatabase() { } } + private val MIGRATION_4_5 = object : Migration(4, 5) { + override fun migrate(db: SupportSQLiteDatabase) { + // 创建设置表 + db.execSQL(""" + CREATE TABLE IF NOT EXISTS settings ( + id INTEGER PRIMARY KEY NOT NULL DEFAULT 1, + monthStartDay INTEGER NOT NULL DEFAULT 1, + themeMode TEXT NOT NULL DEFAULT 'FOLLOW_SYSTEM', + autoBackupEnabled INTEGER NOT NULL DEFAULT 0, + autoBackupInterval INTEGER NOT NULL DEFAULT 7, + lastBackupTime INTEGER NOT NULL DEFAULT 0 + ) + """) + + // 插入默认设置 + db.execSQL(""" + INSERT OR IGNORE INTO settings (id, monthStartDay, themeMode, autoBackupEnabled, autoBackupInterval, lastBackupTime) + VALUES (1, 1, 'FOLLOW_SYSTEM', 0, 7, 0) + """) + } + } + @Volatile private var INSTANCE: BookkeepingDatabase? = null @@ -134,7 +158,7 @@ abstract class BookkeepingDatabase : RoomDatabase() { BookkeepingDatabase::class.java, "bookkeeping_database" ) - .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4) + .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5) .addCallback(object : Callback() { override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db) @@ -143,6 +167,11 @@ abstract class BookkeepingDatabase : RoomDatabase() { try { val database = getDatabase(context) + // 初始化默认设置 + database.settingsDao().apply { + updateSettings(Settings()) + } + // 初始化默认成员 database.memberDao().apply { if (getMemberCount() == 0) { diff --git a/app/src/main/java/com/yovinchen/bookkeeping/data/SettingsDao.kt b/app/src/main/java/com/yovinchen/bookkeeping/data/SettingsDao.kt new file mode 100644 index 0000000..42edcca --- /dev/null +++ b/app/src/main/java/com/yovinchen/bookkeeping/data/SettingsDao.kt @@ -0,0 +1,32 @@ +package com.yovinchen.bookkeeping.data + +import androidx.room.* +import com.yovinchen.bookkeeping.model.Settings +import kotlinx.coroutines.flow.Flow + +@Dao +interface SettingsDao { + @Query("SELECT * FROM settings WHERE id = 1") + fun getSettings(): Flow + + @Query("SELECT * FROM settings WHERE id = 1") + suspend fun getSettingsOnce(): Settings? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun updateSettings(settings: Settings) + + @Query("UPDATE settings SET monthStartDay = :day WHERE id = 1") + suspend fun updateMonthStartDay(day: Int) + + @Query("UPDATE settings SET themeMode = :mode WHERE id = 1") + suspend fun updateThemeMode(mode: String) + + @Query("UPDATE settings SET autoBackupEnabled = :enabled WHERE id = 1") + suspend fun updateAutoBackupEnabled(enabled: Boolean) + + @Query("UPDATE settings SET autoBackupInterval = :interval WHERE id = 1") + suspend fun updateAutoBackupInterval(interval: Int) + + @Query("UPDATE settings SET lastBackupTime = :time WHERE id = 1") + suspend fun updateLastBackupTime(time: Long) +} \ No newline at end of file diff --git a/app/src/main/java/com/yovinchen/bookkeeping/data/SettingsRepository.kt b/app/src/main/java/com/yovinchen/bookkeeping/data/SettingsRepository.kt new file mode 100644 index 0000000..481bec6 --- /dev/null +++ b/app/src/main/java/com/yovinchen/bookkeeping/data/SettingsRepository.kt @@ -0,0 +1,45 @@ +package com.yovinchen.bookkeeping.data + +import com.yovinchen.bookkeeping.model.Settings +import kotlinx.coroutines.flow.Flow + +class SettingsRepository(private val settingsDao: SettingsDao) { + + fun getSettings(): Flow = settingsDao.getSettings() + + suspend fun getSettingsOnce(): Settings { + return settingsDao.getSettingsOnce() ?: Settings() + } + + suspend fun updateSettings(settings: Settings) { + settingsDao.updateSettings(settings) + } + + suspend fun updateMonthStartDay(day: Int) { + // 确保日期在有效范围内 (1-28) + val validDay = day.coerceIn(1, 28) + settingsDao.updateMonthStartDay(validDay) + } + + suspend fun updateThemeMode(mode: String) { + settingsDao.updateThemeMode(mode) + } + + suspend fun updateAutoBackupEnabled(enabled: Boolean) { + settingsDao.updateAutoBackupEnabled(enabled) + } + + suspend fun updateAutoBackupInterval(interval: Int) { + settingsDao.updateAutoBackupInterval(interval) + } + + suspend fun updateLastBackupTime(time: Long) { + settingsDao.updateLastBackupTime(time) + } + + suspend fun ensureSettingsExist() { + if (settingsDao.getSettingsOnce() == null) { + settingsDao.updateSettings(Settings()) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/yovinchen/bookkeeping/model/Settings.kt b/app/src/main/java/com/yovinchen/bookkeeping/model/Settings.kt new file mode 100644 index 0000000..5041e19 --- /dev/null +++ b/app/src/main/java/com/yovinchen/bookkeeping/model/Settings.kt @@ -0,0 +1,14 @@ +package com.yovinchen.bookkeeping.model + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "settings") +data class Settings( + @PrimaryKey val id: Int = 1, + val monthStartDay: Int = 1, // 月度开始日期,1-28,默认为1号 + val themeMode: String = "FOLLOW_SYSTEM", // 主题模式:FOLLOW_SYSTEM, LIGHT, DARK + val autoBackupEnabled: Boolean = false, // 自动备份开关 + val autoBackupInterval: Int = 7, // 自动备份间隔(天) + val lastBackupTime: Long = 0L // 上次备份时间 +) \ No newline at end of file diff --git a/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/SettingsScreen.kt b/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/SettingsScreen.kt index a015d78..054ecf4 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/SettingsScreen.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/SettingsScreen.kt @@ -4,13 +4,19 @@ import android.content.Context import android.widget.Toast import androidx.activity.ComponentActivity import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.yovinchen.bookkeeping.model.ThemeMode @@ -36,7 +42,10 @@ fun SettingsScreen( val categories by viewModel.categories.collectAsState() val selectedType by viewModel.selectedCategoryType.collectAsState() val members by memberViewModel.allMembers.collectAsState(initial = emptyList()) + val monthStartDay by viewModel.monthStartDay.collectAsState() val context = LocalContext.current + + var showMonthStartDayDialog by remember { mutableStateOf(false) } Column(modifier = Modifier.fillMaxSize()) { // 成员管理设置项 @@ -81,6 +90,15 @@ fun SettingsScreen( }, modifier = Modifier.clickable { showThemeDialog = true } ) + + HorizontalDivider() + + // 月度开始日期设置项 + ListItem( + headlineContent = { Text("月度开始日期") }, + supportingContent = { Text("每月从${monthStartDay}号开始计算") }, + modifier = Modifier.clickable { showMonthStartDayDialog = true } + ) if (showThemeDialog) { AlertDialog( @@ -144,6 +162,76 @@ fun SettingsScreen( } ) } + + // 月度开始日期对话框 + if (showMonthStartDayDialog) { + AlertDialog( + onDismissRequest = { showMonthStartDayDialog = false }, + title = { Text("选择月度开始日期") }, + text = { + Column { + Text("选择每月记账的开始日期(1-28号)") + Spacer(modifier = Modifier.height(16.dp)) + + // 日期选择器 + val days = (1..28).toList() + LazyVerticalGrid( + columns = GridCells.Fixed(7), + modifier = Modifier.fillMaxWidth().height(280.dp), + contentPadding = PaddingValues(4.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(days) { day -> + Surface( + onClick = { + viewModel.setMonthStartDay(day) + showMonthStartDayDialog = false + }, + shape = RoundedCornerShape(8.dp), + color = if (day == monthStartDay) { + MaterialTheme.colorScheme.primaryContainer + } else { + MaterialTheme.colorScheme.surface + }, + border = BorderStroke( + width = 1.dp, + color = if (day == monthStartDay) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.outline + } + ), + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxSize() + ) { + Text( + text = day.toString(), + style = MaterialTheme.typography.bodyMedium, + color = if (day == monthStartDay) { + MaterialTheme.colorScheme.onPrimaryContainer + } else { + MaterialTheme.colorScheme.onSurface + } + ) + } + } + } + } + } + }, + confirmButton = { + TextButton(onClick = { showMonthStartDayDialog = false }) { + Text("取消") + } + } + ) + } // 备份对话框 if (showBackupDialog) { diff --git a/app/src/main/java/com/yovinchen/bookkeeping/utils/DateUtils.kt b/app/src/main/java/com/yovinchen/bookkeeping/utils/DateUtils.kt new file mode 100644 index 0000000..a525ad6 --- /dev/null +++ b/app/src/main/java/com/yovinchen/bookkeeping/utils/DateUtils.kt @@ -0,0 +1,85 @@ +package com.yovinchen.bookkeeping.utils + +import java.time.LocalDate +import java.time.YearMonth +import java.time.ZoneId +import java.util.Date + +object DateUtils { + + /** + * 根据月度开始日期计算给定日期所属的记账月份 + * @param date 要判断的日期 + * @param monthStartDay 月度开始日期(1-28) + * @return 该日期所属的记账月份 + */ + fun getAccountingMonth(date: Date, monthStartDay: Int): YearMonth { + val localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() + return getAccountingMonth(localDate, monthStartDay) + } + + /** + * 根据月度开始日期计算给定日期所属的记账月份 + * @param date 要判断的日期 + * @param monthStartDay 月度开始日期(1-28) + * @return 该日期所属的记账月份 + */ + fun getAccountingMonth(date: LocalDate, monthStartDay: Int): YearMonth { + val dayOfMonth = date.dayOfMonth + + return if (dayOfMonth >= monthStartDay) { + // 当前日期大于等于开始日期,属于当前月 + YearMonth.from(date) + } else { + // 当前日期小于开始日期,属于上个月 + YearMonth.from(date.minusMonths(1)) + } + } + + /** + * 获取记账月份的开始日期 + * @param yearMonth 记账月份 + * @param monthStartDay 月度开始日期(1-28) + * @return 该记账月份的开始日期 + */ + fun getMonthStartDate(yearMonth: YearMonth, monthStartDay: Int): LocalDate { + return yearMonth.atDay(monthStartDay) + } + + /** + * 获取记账月份的结束日期 + * @param yearMonth 记账月份 + * @param monthStartDay 月度开始日期(1-28) + * @return 该记账月份的结束日期 + */ + fun getMonthEndDate(yearMonth: YearMonth, monthStartDay: Int): LocalDate { + val nextMonth = yearMonth.plusMonths(1) + return nextMonth.atDay(monthStartDay).minusDays(1) + } + + /** + * 检查日期是否在指定的记账月份内 + * @param date 要检查的日期 + * @param yearMonth 记账月份 + * @param monthStartDay 月度开始日期(1-28) + * @return 是否在该记账月份内 + */ + fun isInAccountingMonth(date: Date, yearMonth: YearMonth, monthStartDay: Int): Boolean { + val localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() + return isInAccountingMonth(localDate, yearMonth, monthStartDay) + } + + /** + * 检查日期是否在指定的记账月份内 + * @param date 要检查的日期 + * @param yearMonth 记账月份 + * @param monthStartDay 月度开始日期(1-28) + * @return 是否在该记账月份内 + */ + fun isInAccountingMonth(date: LocalDate, yearMonth: YearMonth, monthStartDay: Int): Boolean { + val startDate = getMonthStartDate(yearMonth, monthStartDay) + val endDate = getMonthEndDate(yearMonth, monthStartDay) + + return date >= startDate && date <= endDate + } +} \ No newline at end of file diff --git a/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/AnalysisViewModel.kt b/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/AnalysisViewModel.kt index eaba8e9..e035ced 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/AnalysisViewModel.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/AnalysisViewModel.kt @@ -4,21 +4,22 @@ import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.yovinchen.bookkeeping.data.BookkeepingDatabase +import com.yovinchen.bookkeeping.data.SettingsRepository import com.yovinchen.bookkeeping.model.AnalysisType import com.yovinchen.bookkeeping.model.BookkeepingRecord import com.yovinchen.bookkeeping.model.CategoryStat import com.yovinchen.bookkeeping.model.MemberStat import com.yovinchen.bookkeeping.model.TransactionType +import com.yovinchen.bookkeeping.utils.DateUtils import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import java.time.LocalDateTime import java.time.YearMonth -import java.time.ZoneId import java.util.* class AnalysisViewModel(application: Application) : AndroidViewModel(application) { private val recordDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao() private val memberDao = BookkeepingDatabase.getDatabase(application).memberDao() + private val settingsRepository = SettingsRepository(BookkeepingDatabase.getDatabase(application).settingsDao()) private val _startMonth = MutableStateFlow(YearMonth.now()) val startMonth: StateFlow = _startMonth.asStateFlow() @@ -38,16 +39,41 @@ class AnalysisViewModel(application: Application) : AndroidViewModel(application private val _records = MutableStateFlow>(emptyList()) val records: StateFlow> = _records.asStateFlow() + // 存储月度开始日期设置 + private val _monthStartDay = MutableStateFlow(1) + val monthStartDay: StateFlow = _monthStartDay.asStateFlow() + init { + // 订阅设置变化,获取月度开始日期 viewModelScope.launch { - combine(startMonth, endMonth, selectedAnalysisType) { start, end, type -> - Triple(start, end, type) - }.collect { (start, end, type) -> - updateStats(start, end, type) + settingsRepository.getSettings().collect { settings -> + _monthStartDay.value = settings?.monthStartDay ?: 1 + } + } + + // 当月度开始日期、起始月份、结束月份或分析类型变化时,更新统计数据 + viewModelScope.launch { + combine( + startMonth, + endMonth, + selectedAnalysisType, + monthStartDay + ) { start, end, type, startDay -> + UpdateParams(start, end, type, startDay) + }.collect { params -> + updateStats(params.start, params.end, params.type, params.startDay) } } } + // 用于传递更新参数的数据类 + private data class UpdateParams( + val start: YearMonth, + val end: YearMonth, + val type: AnalysisType, + val startDay: Int + ) + fun setStartMonth(month: YearMonth) { _startMonth.value = month } @@ -60,16 +86,16 @@ class AnalysisViewModel(application: Application) : AndroidViewModel(application _selectedAnalysisType.value = type } - private suspend fun updateStats(startMonth: YearMonth, endMonth: YearMonth, type: AnalysisType) { + private suspend fun updateStats(startMonth: YearMonth, endMonth: YearMonth, type: AnalysisType, monthStartDay: Int) { val records = recordDao.getAllRecords().first() - // 过滤日期范围内的记录 - val monthRecords = records.filter { - val recordDate = Date(it.date.time) - val localDateTime = LocalDateTime.ofInstant(recordDate.toInstant(), ZoneId.systemDefault()) - val yearMonth = YearMonth.from(localDateTime) - yearMonth.isAfter(startMonth.minusMonths(1)) && - yearMonth.isBefore(endMonth.plusMonths(1)) + // 使用 DateUtils 过滤日期范围内的记录 + val monthRecords = records.filter { record -> + val recordDate = Date(record.date.time) + val accountingMonth = DateUtils.getAccountingMonth(recordDate, monthStartDay) + + // 检查记账月份是否在选定的范围内 + accountingMonth >= startMonth && accountingMonth <= endMonth } // 更新记录数据 diff --git a/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/HomeViewModel.kt b/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/HomeViewModel.kt index daebbf3..53e85bb 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/HomeViewModel.kt @@ -4,10 +4,12 @@ import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.yovinchen.bookkeeping.data.BookkeepingDatabase +import com.yovinchen.bookkeeping.data.SettingsRepository import com.yovinchen.bookkeeping.model.BookkeepingRecord import com.yovinchen.bookkeeping.model.Category import com.yovinchen.bookkeeping.model.Member import com.yovinchen.bookkeeping.model.TransactionType +import com.yovinchen.bookkeeping.utils.DateUtils import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -18,9 +20,26 @@ import java.util.* @OptIn(ExperimentalCoroutinesApi::class) class HomeViewModel(application: Application) : AndroidViewModel(application) { - private val bookkeepingDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao() - private val memberDao = BookkeepingDatabase.getDatabase(application).memberDao() - private val categoryDao = BookkeepingDatabase.getDatabase(application).categoryDao() + private val database = BookkeepingDatabase.getDatabase(application) + private val bookkeepingDao = database.bookkeepingDao() + private val memberDao = database.memberDao() + private val categoryDao = database.categoryDao() + private val settingsRepository = SettingsRepository(database.settingsDao()) + + // 设置相关 + private val _monthStartDay = MutableStateFlow(1) + val monthStartDay: StateFlow = _monthStartDay.asStateFlow() + + init { + viewModelScope.launch { + settingsRepository.ensureSettingsExist() + settingsRepository.getSettings().collect { settings -> + settings?.let { + _monthStartDay.value = it.monthStartDay + } + } + } + } private val _selectedRecordType = MutableStateFlow(null) val selectedRecordType: StateFlow = _selectedRecordType.asStateFlow() @@ -56,17 +75,13 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { allRecords, _selectedRecordType, _selectedMonth, - _selectedMember - ) { records, selectedType, selectedMonth, selectedMember -> + _selectedMember, + _monthStartDay + ) { records, selectedType, selectedMonth, selectedMember, monthStartDay -> records .filter { record -> - val recordDate = record.date.toInstant() - .atZone(ZoneId.systemDefault()) - .toLocalDate() - val recordYearMonth = YearMonth.from(recordDate) - val typeMatches = selectedType?.let { record.type == it } ?: true - val monthMatches = recordYearMonth == selectedMonth + val monthMatches = DateUtils.isInAccountingMonth(record.date, selectedMonth, monthStartDay) val memberMatches = selectedMember?.let { record.memberId == it.id } ?: true monthMatches && memberMatches && typeMatches @@ -90,16 +105,12 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { val totalIncome = combine( allRecords, _selectedMonth, - _selectedMember - ) { records, selectedMonth, selectedMember -> + _selectedMember, + _monthStartDay + ) { records, selectedMonth, selectedMember, monthStartDay -> records .filter { record -> - val recordDate = record.date.toInstant() - .atZone(ZoneId.systemDefault()) - .toLocalDate() - val recordYearMonth = YearMonth.from(recordDate) - - val monthMatches = recordYearMonth == selectedMonth + val monthMatches = DateUtils.isInAccountingMonth(record.date, selectedMonth, monthStartDay) val memberMatches = selectedMember?.let { record.memberId == it.id } ?: true val typeMatches = record.type == TransactionType.INCOME @@ -115,16 +126,12 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { val totalExpense = combine( allRecords, _selectedMonth, - _selectedMember - ) { records, selectedMonth, selectedMember -> + _selectedMember, + _monthStartDay + ) { records, selectedMonth, selectedMember, monthStartDay -> records .filter { record -> - val recordDate = record.date.toInstant() - .atZone(ZoneId.systemDefault()) - .toLocalDate() - val recordYearMonth = YearMonth.from(recordDate) - - val monthMatches = recordYearMonth == selectedMonth + val monthMatches = DateUtils.isInAccountingMonth(record.date, selectedMonth, monthStartDay) val memberMatches = selectedMember?.let { record.memberId == it.id } ?: true val typeMatches = record.type == TransactionType.EXPENSE diff --git a/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/SettingsViewModel.kt b/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/SettingsViewModel.kt index 840a7c4..4fcfa8a 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/SettingsViewModel.kt @@ -9,8 +9,10 @@ import androidx.lifecycle.viewModelScope import com.opencsv.CSVReader import com.opencsv.CSVWriter import com.yovinchen.bookkeeping.data.BookkeepingDatabase +import com.yovinchen.bookkeeping.data.SettingsRepository import com.yovinchen.bookkeeping.model.BookkeepingRecord import com.yovinchen.bookkeeping.model.Category +import com.yovinchen.bookkeeping.model.Settings import com.yovinchen.bookkeeping.model.TransactionType import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -38,8 +40,36 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application private val database = BookkeepingDatabase.getDatabase(application) private val dao = database.bookkeepingDao() private val memberDao = database.memberDao() + private val settingsRepository = SettingsRepository(database.settingsDao()) + + // 设置相关的状态 + val settings: StateFlow = settingsRepository.getSettings() + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = null + ) + private val _isAutoBackupEnabled = MutableStateFlow(false) val isAutoBackupEnabled: StateFlow = _isAutoBackupEnabled.asStateFlow() + + private val _monthStartDay = MutableStateFlow(1) + val monthStartDay: StateFlow = _monthStartDay.asStateFlow() + + init { + viewModelScope.launch { + // 确保设置存在 + settingsRepository.ensureSettingsExist() + + // 监听设置变化 + settings.collect { settings -> + settings?.let { + _isAutoBackupEnabled.value = it.autoBackupEnabled + _monthStartDay.value = it.monthStartDay + } + } + } + } private val _selectedCategoryType = MutableStateFlow(TransactionType.EXPENSE) val selectedCategoryType: StateFlow = _selectedCategoryType.asStateFlow() @@ -85,11 +115,19 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application fun setAutoBackup(enabled: Boolean) { viewModelScope.launch { _isAutoBackupEnabled.value = enabled + settingsRepository.updateAutoBackupEnabled(enabled) if (enabled) { schedulePeriodicBackup() } } } + + fun setMonthStartDay(day: Int) { + viewModelScope.launch { + _monthStartDay.value = day + settingsRepository.updateMonthStartDay(day) + } + } private fun schedulePeriodicBackup() { viewModelScope.launch(Dispatchers.IO) {