feat: 实现月度记账开始日期功能
- 添加 Settings 实体和 DAO 来持久化存储设置 - 创建 SettingsRepository 管理设置数据 - 添加数据库迁移从版本 4 到版本 5 - 在设置界面添加月度开始日期选择器(1-28号) - 创建 DateUtils 工具类处理基于月度开始日期的日期计算 - 更新 HomeViewModel 和 AnalysisViewModel 使用月度开始日期进行统计 - 修复日期选择器中数字显示不完整的问题
This commit is contained in:
parent
4c1aa501e6
commit
f4f03ce0a4
@ -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) {
|
||||
|
@ -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<Settings?>
|
||||
|
||||
@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)
|
||||
}
|
@ -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<Settings?> = 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())
|
||||
}
|
||||
}
|
||||
}
|
@ -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 // 上次备份时间
|
||||
)
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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<YearMonth> = _startMonth.asStateFlow()
|
||||
@ -38,16 +39,41 @@ class AnalysisViewModel(application: Application) : AndroidViewModel(application
|
||||
private val _records = MutableStateFlow<List<BookkeepingRecord>>(emptyList())
|
||||
val records: StateFlow<List<BookkeepingRecord>> = _records.asStateFlow()
|
||||
|
||||
// 存储月度开始日期设置
|
||||
private val _monthStartDay = MutableStateFlow(1)
|
||||
val monthStartDay: StateFlow<Int> = _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
|
||||
}
|
||||
|
||||
// 更新记录数据
|
||||
|
@ -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<Int> = _monthStartDay.asStateFlow()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
settingsRepository.ensureSettingsExist()
|
||||
settingsRepository.getSettings().collect { settings ->
|
||||
settings?.let {
|
||||
_monthStartDay.value = it.monthStartDay
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val _selectedRecordType = MutableStateFlow<TransactionType?>(null)
|
||||
val selectedRecordType: StateFlow<TransactionType?> = _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
|
||||
|
||||
|
@ -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<Settings?> = settingsRepository.getSettings()
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5000),
|
||||
initialValue = null
|
||||
)
|
||||
|
||||
private val _isAutoBackupEnabled = MutableStateFlow(false)
|
||||
val isAutoBackupEnabled: StateFlow<Boolean> = _isAutoBackupEnabled.asStateFlow()
|
||||
|
||||
private val _monthStartDay = MutableStateFlow(1)
|
||||
val monthStartDay: StateFlow<Int> = _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<TransactionType> = _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) {
|
||||
|
Loading…
Reference in New Issue
Block a user