parent
a86898011d
commit
439080499b
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"permissions": {
|
|
||||||
"allow": [
|
|
||||||
"Bash(find:*)",
|
|
||||||
"Bash(ls:*)",
|
|
||||||
"Bash(./gradlew:*)",
|
|
||||||
"Bash(git checkout:*)",
|
|
||||||
"Bash(git add:*)"
|
|
||||||
],
|
|
||||||
"deny": []
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="AndroidProjectSystem">
|
|
||||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@ -49,10 +49,6 @@
|
|||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
<option name="previewFile" value="true" />
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
|
||||||
<option name="composableFile" value="true" />
|
|
||||||
<option name="previewFile" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
<option name="previewFile" value="true" />
|
<option name="previewFile" value="true" />
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="EntryPointsManager">
|
<component name="EntryPointsManager">
|
||||||
<list size="1">
|
<list size="1">
|
||||||
|
135
CLAUDE.md
135
CLAUDE.md
@ -1,135 +0,0 @@
|
|||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## 项目概述
|
|
||||||
|
|
||||||
这是一个名为"轻记账"的 Android 记账应用,使用 Kotlin 和 Jetpack Compose 开发,采用 MVVM 架构模式。应用完全离线运行,注重用户隐私保护。
|
|
||||||
|
|
||||||
## 常用开发命令
|
|
||||||
|
|
||||||
### 构建命令
|
|
||||||
```bash
|
|
||||||
# 清理项目
|
|
||||||
./gradlew clean
|
|
||||||
|
|
||||||
# 构建 Debug APK
|
|
||||||
./gradlew assembleDebug
|
|
||||||
|
|
||||||
# 构建 Release APK
|
|
||||||
./gradlew assembleRelease
|
|
||||||
|
|
||||||
# 运行所有构建任务
|
|
||||||
./gradlew build
|
|
||||||
```
|
|
||||||
|
|
||||||
### 测试命令
|
|
||||||
```bash
|
|
||||||
# 运行单元测试
|
|
||||||
./gradlew test
|
|
||||||
|
|
||||||
# 运行仪器测试
|
|
||||||
./gradlew connectedAndroidTest
|
|
||||||
|
|
||||||
# 运行特定模块的测试
|
|
||||||
./gradlew :app:test
|
|
||||||
```
|
|
||||||
|
|
||||||
### 代码检查
|
|
||||||
```bash
|
|
||||||
# 运行 lint 检查
|
|
||||||
./gradlew lint
|
|
||||||
|
|
||||||
# 查看 lint 报告(生成在 app/build/reports/lint-results.html)
|
|
||||||
./gradlew lintDebug
|
|
||||||
```
|
|
||||||
|
|
||||||
## 项目架构
|
|
||||||
|
|
||||||
### 核心架构模式:MVVM + Repository
|
|
||||||
- **View (UI层)**:使用 Jetpack Compose 构建的 UI 组件
|
|
||||||
- **ViewModel**:处理业务逻辑和状态管理
|
|
||||||
- **Model (数据层)**:Room Database + Repository 模式
|
|
||||||
- **依赖注入**:手动依赖注入(未使用 Hilt/Dagger)
|
|
||||||
|
|
||||||
### 主要技术栈
|
|
||||||
- **UI框架**:Jetpack Compose with Material 3
|
|
||||||
- **数据库**:Room 2.6.1
|
|
||||||
- **异步处理**:Kotlin Coroutines + Flow
|
|
||||||
- **导航**:Navigation Compose
|
|
||||||
- **图表**:MPAndroidChart 3.1.0
|
|
||||||
- **文件处理**:OpenCSV 5.7.1, Apache POI 5.2.3
|
|
||||||
|
|
||||||
### 包结构说明
|
|
||||||
```
|
|
||||||
com.yovinchen.bookkeeping/
|
|
||||||
├── data/ # 数据层:数据库、DAO、Repository
|
|
||||||
├── model/ # 数据模型:实体类和数据类
|
|
||||||
├── ui/ # UI 层
|
|
||||||
│ ├── components/ # 可复用的 UI 组件
|
|
||||||
│ ├── dialog/ # 对话框组件
|
|
||||||
│ ├── screen/ # 屏幕页面(HomeScreen, AnalysisScreen等)
|
|
||||||
│ └── theme/ # 主题配置
|
|
||||||
├── utils/ # 工具类
|
|
||||||
└── viewmodel/ # ViewModel 层
|
|
||||||
```
|
|
||||||
|
|
||||||
### 数据流架构
|
|
||||||
1. **UI 层**通过 ViewModel 获取数据和发送事件
|
|
||||||
2. **ViewModel** 通过 Repository 访问数据,使用 StateFlow 管理 UI 状态
|
|
||||||
3. **Repository** 封装数据访问逻辑,统一数据源接口
|
|
||||||
4. **Room Database** 提供本地数据持久化
|
|
||||||
|
|
||||||
### 关键设计决策
|
|
||||||
1. **单 Activity 架构**:整个应用只有一个 MainActivity,所有页面通过 Compose Navigation 管理
|
|
||||||
2. **状态管理**:使用 ViewModel + StateFlow 进行状态管理,确保配置变更时的数据保持
|
|
||||||
3. **主题系统**:支持深色/浅色模式切换和自定义主题色,通过 CompositionLocal 传递主题状态
|
|
||||||
4. **权限最小化**:仅在需要时请求必要权限(如文件读写权限)
|
|
||||||
|
|
||||||
## 开发规范
|
|
||||||
|
|
||||||
### Git 分支管理
|
|
||||||
- `master`:稳定主分支
|
|
||||||
- `develop`:主开发分支
|
|
||||||
- `feature/*`:功能开发分支
|
|
||||||
- `release/*`:版本发布分支
|
|
||||||
- `hotfix/*`:紧急修复分支
|
|
||||||
|
|
||||||
### 提交信息格式
|
|
||||||
使用约定式提交:`<type>: <description>`
|
|
||||||
|
|
||||||
类型包括:feat, fix, docs, style, refactor, perf, test, build, ci, chore
|
|
||||||
|
|
||||||
### 版本管理
|
|
||||||
- 版本号在 `app/build.gradle.kts` 中的 `versionName` 和 `versionCode` 管理
|
|
||||||
- APK 命名格式:`轻记账_${buildType}_v${versionName}_${date}.apk`
|
|
||||||
|
|
||||||
## 关键功能实现
|
|
||||||
|
|
||||||
### 记账记录管理
|
|
||||||
- 数据模型:`BookkeepingRecord` 实体类
|
|
||||||
- 存储:Room Database 自动管理
|
|
||||||
- 展示:按日期分组,支持编辑和删除
|
|
||||||
|
|
||||||
### 成员系统
|
|
||||||
- 成员数据存储在独立表中
|
|
||||||
- 记账记录通过成员 ID 列表关联
|
|
||||||
- 支持成员消费统计和分析
|
|
||||||
|
|
||||||
### 数据导入导出
|
|
||||||
- CSV 导出:使用 OpenCSV 库
|
|
||||||
- Excel 导出:使用 Apache POI 库
|
|
||||||
- 文件选择:通过 SAF (Storage Access Framework)
|
|
||||||
|
|
||||||
### 图表分析
|
|
||||||
- 使用 MPAndroidChart 库
|
|
||||||
- 通过 AndroidView 在 Compose 中集成
|
|
||||||
- 支持饼图、折线图等多种图表类型
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **数据库迁移**:修改数据库结构时必须提供 Migration
|
|
||||||
2. **Compose 预览**:使用 `@Preview` 注解时需要提供默认参数
|
|
||||||
3. **性能优化**:大量数据时注意使用分页或懒加载
|
|
||||||
4. **主题适配**:新增 UI 组件时确保同时支持深色和浅色主题
|
|
||||||
5. **图标资源**:项目包含大量自定义图标,位于 `drawable` 目录
|
|
@ -13,15 +13,14 @@ import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
|||||||
import com.yovinchen.bookkeeping.model.Category
|
import com.yovinchen.bookkeeping.model.Category
|
||||||
import com.yovinchen.bookkeeping.model.Converters
|
import com.yovinchen.bookkeeping.model.Converters
|
||||||
import com.yovinchen.bookkeeping.model.Member
|
import com.yovinchen.bookkeeping.model.Member
|
||||||
import com.yovinchen.bookkeeping.model.Settings
|
|
||||||
import com.yovinchen.bookkeeping.model.TransactionType
|
import com.yovinchen.bookkeeping.model.TransactionType
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [BookkeepingRecord::class, Category::class, Member::class, Settings::class],
|
entities = [BookkeepingRecord::class, Category::class, Member::class],
|
||||||
version = 5,
|
version = 4,
|
||||||
exportSchema = false
|
exportSchema = false
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
@ -29,7 +28,6 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
|||||||
abstract fun bookkeepingDao(): BookkeepingDao
|
abstract fun bookkeepingDao(): BookkeepingDao
|
||||||
abstract fun categoryDao(): CategoryDao
|
abstract fun categoryDao(): CategoryDao
|
||||||
abstract fun memberDao(): MemberDao
|
abstract fun memberDao(): MemberDao
|
||||||
abstract fun settingsDao(): SettingsDao
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "BookkeepingDatabase"
|
private const val TAG = "BookkeepingDatabase"
|
||||||
@ -126,28 +124,6 @@ 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
|
@Volatile
|
||||||
private var INSTANCE: BookkeepingDatabase? = null
|
private var INSTANCE: BookkeepingDatabase? = null
|
||||||
|
|
||||||
@ -158,7 +134,7 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
|||||||
BookkeepingDatabase::class.java,
|
BookkeepingDatabase::class.java,
|
||||||
"bookkeeping_database"
|
"bookkeeping_database"
|
||||||
)
|
)
|
||||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
|
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
|
||||||
.addCallback(object : Callback() {
|
.addCallback(object : Callback() {
|
||||||
override fun onCreate(db: SupportSQLiteDatabase) {
|
override fun onCreate(db: SupportSQLiteDatabase) {
|
||||||
super.onCreate(db)
|
super.onCreate(db)
|
||||||
@ -167,11 +143,6 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
|||||||
try {
|
try {
|
||||||
val database = getDatabase(context)
|
val database = getDatabase(context)
|
||||||
|
|
||||||
// 初始化默认设置
|
|
||||||
database.settingsDao().apply {
|
|
||||||
updateSettings(Settings())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化默认成员
|
// 初始化默认成员
|
||||||
database.memberDao().apply {
|
database.memberDao().apply {
|
||||||
if (getMemberCount() == 0) {
|
if (getMemberCount() == 0) {
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
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,19 +4,13 @@ import android.content.Context
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.foundation.BorderStroke
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
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.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.yovinchen.bookkeeping.model.ThemeMode
|
import com.yovinchen.bookkeeping.model.ThemeMode
|
||||||
@ -42,11 +36,8 @@ fun SettingsScreen(
|
|||||||
val categories by viewModel.categories.collectAsState()
|
val categories by viewModel.categories.collectAsState()
|
||||||
val selectedType by viewModel.selectedCategoryType.collectAsState()
|
val selectedType by viewModel.selectedCategoryType.collectAsState()
|
||||||
val members by memberViewModel.allMembers.collectAsState(initial = emptyList())
|
val members by memberViewModel.allMembers.collectAsState(initial = emptyList())
|
||||||
val monthStartDay by viewModel.monthStartDay.collectAsState()
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
var showMonthStartDayDialog by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
// 成员管理设置项
|
// 成员管理设置项
|
||||||
ListItem(
|
ListItem(
|
||||||
@ -91,15 +82,6 @@ fun SettingsScreen(
|
|||||||
modifier = Modifier.clickable { showThemeDialog = true }
|
modifier = Modifier.clickable { showThemeDialog = true }
|
||||||
)
|
)
|
||||||
|
|
||||||
HorizontalDivider()
|
|
||||||
|
|
||||||
// 月度开始日期设置项
|
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text("月度开始日期") },
|
|
||||||
supportingContent = { Text("每月从${monthStartDay}号开始计算") },
|
|
||||||
modifier = Modifier.clickable { showMonthStartDayDialog = true }
|
|
||||||
)
|
|
||||||
|
|
||||||
if (showThemeDialog) {
|
if (showThemeDialog) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showThemeDialog = false },
|
onDismissRequest = { showThemeDialog = false },
|
||||||
@ -163,76 +145,6 @@ 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) {
|
if (showBackupDialog) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
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,22 +4,21 @@ import android.app.Application
|
|||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
||||||
import com.yovinchen.bookkeeping.data.SettingsRepository
|
|
||||||
import com.yovinchen.bookkeeping.model.AnalysisType
|
import com.yovinchen.bookkeeping.model.AnalysisType
|
||||||
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
||||||
import com.yovinchen.bookkeeping.model.CategoryStat
|
import com.yovinchen.bookkeeping.model.CategoryStat
|
||||||
import com.yovinchen.bookkeeping.model.MemberStat
|
import com.yovinchen.bookkeeping.model.MemberStat
|
||||||
import com.yovinchen.bookkeeping.model.TransactionType
|
import com.yovinchen.bookkeeping.model.TransactionType
|
||||||
import com.yovinchen.bookkeeping.utils.DateUtils
|
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.LocalDateTime
|
||||||
import java.time.YearMonth
|
import java.time.YearMonth
|
||||||
|
import java.time.ZoneId
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class AnalysisViewModel(application: Application) : AndroidViewModel(application) {
|
class AnalysisViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private val recordDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao()
|
private val recordDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao()
|
||||||
private val memberDao = BookkeepingDatabase.getDatabase(application).memberDao()
|
private val memberDao = BookkeepingDatabase.getDatabase(application).memberDao()
|
||||||
private val settingsRepository = SettingsRepository(BookkeepingDatabase.getDatabase(application).settingsDao())
|
|
||||||
|
|
||||||
private val _startMonth = MutableStateFlow(YearMonth.now())
|
private val _startMonth = MutableStateFlow(YearMonth.now())
|
||||||
val startMonth: StateFlow<YearMonth> = _startMonth.asStateFlow()
|
val startMonth: StateFlow<YearMonth> = _startMonth.asStateFlow()
|
||||||
@ -39,41 +38,16 @@ class AnalysisViewModel(application: Application) : AndroidViewModel(application
|
|||||||
private val _records = MutableStateFlow<List<BookkeepingRecord>>(emptyList())
|
private val _records = MutableStateFlow<List<BookkeepingRecord>>(emptyList())
|
||||||
val records: StateFlow<List<BookkeepingRecord>> = _records.asStateFlow()
|
val records: StateFlow<List<BookkeepingRecord>> = _records.asStateFlow()
|
||||||
|
|
||||||
// 存储月度开始日期设置
|
|
||||||
private val _monthStartDay = MutableStateFlow(1)
|
|
||||||
val monthStartDay: StateFlow<Int> = _monthStartDay.asStateFlow()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// 订阅设置变化,获取月度开始日期
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
settingsRepository.getSettings().collect { settings ->
|
combine(startMonth, endMonth, selectedAnalysisType) { start, end, type ->
|
||||||
_monthStartDay.value = settings?.monthStartDay ?: 1
|
Triple(start, end, type)
|
||||||
}
|
}.collect { (start, end, type) ->
|
||||||
}
|
updateStats(start, end, type)
|
||||||
|
|
||||||
// 当月度开始日期、起始月份、结束月份或分析类型变化时,更新统计数据
|
|
||||||
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) {
|
fun setStartMonth(month: YearMonth) {
|
||||||
_startMonth.value = month
|
_startMonth.value = month
|
||||||
}
|
}
|
||||||
@ -86,16 +60,16 @@ class AnalysisViewModel(application: Application) : AndroidViewModel(application
|
|||||||
_selectedAnalysisType.value = type
|
_selectedAnalysisType.value = type
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateStats(startMonth: YearMonth, endMonth: YearMonth, type: AnalysisType, monthStartDay: Int) {
|
private suspend fun updateStats(startMonth: YearMonth, endMonth: YearMonth, type: AnalysisType) {
|
||||||
val records = recordDao.getAllRecords().first()
|
val records = recordDao.getAllRecords().first()
|
||||||
|
|
||||||
// 使用 DateUtils 过滤日期范围内的记录
|
// 过滤日期范围内的记录
|
||||||
val monthRecords = records.filter { record ->
|
val monthRecords = records.filter {
|
||||||
val recordDate = Date(record.date.time)
|
val recordDate = Date(it.date.time)
|
||||||
val accountingMonth = DateUtils.getAccountingMonth(recordDate, monthStartDay)
|
val localDateTime = LocalDateTime.ofInstant(recordDate.toInstant(), ZoneId.systemDefault())
|
||||||
|
val yearMonth = YearMonth.from(localDateTime)
|
||||||
// 检查记账月份是否在选定的范围内
|
yearMonth.isAfter(startMonth.minusMonths(1)) &&
|
||||||
accountingMonth >= startMonth && accountingMonth <= endMonth
|
yearMonth.isBefore(endMonth.plusMonths(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新记录数据
|
// 更新记录数据
|
||||||
|
@ -4,12 +4,10 @@ import android.app.Application
|
|||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
||||||
import com.yovinchen.bookkeeping.data.SettingsRepository
|
|
||||||
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
||||||
import com.yovinchen.bookkeeping.model.Category
|
import com.yovinchen.bookkeeping.model.Category
|
||||||
import com.yovinchen.bookkeeping.model.Member
|
import com.yovinchen.bookkeeping.model.Member
|
||||||
import com.yovinchen.bookkeeping.model.TransactionType
|
import com.yovinchen.bookkeeping.model.TransactionType
|
||||||
import com.yovinchen.bookkeeping.utils.DateUtils
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -20,26 +18,9 @@ import java.util.*
|
|||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private val database = BookkeepingDatabase.getDatabase(application)
|
private val bookkeepingDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao()
|
||||||
private val bookkeepingDao = database.bookkeepingDao()
|
private val memberDao = BookkeepingDatabase.getDatabase(application).memberDao()
|
||||||
private val memberDao = database.memberDao()
|
private val categoryDao = BookkeepingDatabase.getDatabase(application).categoryDao()
|
||||||
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)
|
private val _selectedRecordType = MutableStateFlow<TransactionType?>(null)
|
||||||
val selectedRecordType: StateFlow<TransactionType?> = _selectedRecordType.asStateFlow()
|
val selectedRecordType: StateFlow<TransactionType?> = _selectedRecordType.asStateFlow()
|
||||||
@ -75,13 +56,17 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
allRecords,
|
allRecords,
|
||||||
_selectedRecordType,
|
_selectedRecordType,
|
||||||
_selectedMonth,
|
_selectedMonth,
|
||||||
_selectedMember,
|
_selectedMember
|
||||||
_monthStartDay
|
) { records, selectedType, selectedMonth, selectedMember ->
|
||||||
) { records, selectedType, selectedMonth, selectedMember, monthStartDay ->
|
|
||||||
records
|
records
|
||||||
.filter { record ->
|
.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 typeMatches = selectedType?.let { record.type == it } ?: true
|
||||||
val monthMatches = DateUtils.isInAccountingMonth(record.date, selectedMonth, monthStartDay)
|
val monthMatches = recordYearMonth == selectedMonth
|
||||||
val memberMatches = selectedMember?.let { record.memberId == it.id } ?: true
|
val memberMatches = selectedMember?.let { record.memberId == it.id } ?: true
|
||||||
|
|
||||||
monthMatches && memberMatches && typeMatches
|
monthMatches && memberMatches && typeMatches
|
||||||
@ -105,12 +90,16 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
val totalIncome = combine(
|
val totalIncome = combine(
|
||||||
allRecords,
|
allRecords,
|
||||||
_selectedMonth,
|
_selectedMonth,
|
||||||
_selectedMember,
|
_selectedMember
|
||||||
_monthStartDay
|
) { records, selectedMonth, selectedMember ->
|
||||||
) { records, selectedMonth, selectedMember, monthStartDay ->
|
|
||||||
records
|
records
|
||||||
.filter { record ->
|
.filter { record ->
|
||||||
val monthMatches = DateUtils.isInAccountingMonth(record.date, selectedMonth, monthStartDay)
|
val recordDate = record.date.toInstant()
|
||||||
|
.atZone(ZoneId.systemDefault())
|
||||||
|
.toLocalDate()
|
||||||
|
val recordYearMonth = YearMonth.from(recordDate)
|
||||||
|
|
||||||
|
val monthMatches = recordYearMonth == selectedMonth
|
||||||
val memberMatches = selectedMember?.let { record.memberId == it.id } ?: true
|
val memberMatches = selectedMember?.let { record.memberId == it.id } ?: true
|
||||||
val typeMatches = record.type == TransactionType.INCOME
|
val typeMatches = record.type == TransactionType.INCOME
|
||||||
|
|
||||||
@ -126,12 +115,16 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
val totalExpense = combine(
|
val totalExpense = combine(
|
||||||
allRecords,
|
allRecords,
|
||||||
_selectedMonth,
|
_selectedMonth,
|
||||||
_selectedMember,
|
_selectedMember
|
||||||
_monthStartDay
|
) { records, selectedMonth, selectedMember ->
|
||||||
) { records, selectedMonth, selectedMember, monthStartDay ->
|
|
||||||
records
|
records
|
||||||
.filter { record ->
|
.filter { record ->
|
||||||
val monthMatches = DateUtils.isInAccountingMonth(record.date, selectedMonth, monthStartDay)
|
val recordDate = record.date.toInstant()
|
||||||
|
.atZone(ZoneId.systemDefault())
|
||||||
|
.toLocalDate()
|
||||||
|
val recordYearMonth = YearMonth.from(recordDate)
|
||||||
|
|
||||||
|
val monthMatches = recordYearMonth == selectedMonth
|
||||||
val memberMatches = selectedMember?.let { record.memberId == it.id } ?: true
|
val memberMatches = selectedMember?.let { record.memberId == it.id } ?: true
|
||||||
val typeMatches = record.type == TransactionType.EXPENSE
|
val typeMatches = record.type == TransactionType.EXPENSE
|
||||||
|
|
||||||
|
@ -9,10 +9,8 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.opencsv.CSVReader
|
import com.opencsv.CSVReader
|
||||||
import com.opencsv.CSVWriter
|
import com.opencsv.CSVWriter
|
||||||
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
||||||
import com.yovinchen.bookkeeping.data.SettingsRepository
|
|
||||||
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
||||||
import com.yovinchen.bookkeeping.model.Category
|
import com.yovinchen.bookkeeping.model.Category
|
||||||
import com.yovinchen.bookkeeping.model.Settings
|
|
||||||
import com.yovinchen.bookkeeping.model.TransactionType
|
import com.yovinchen.bookkeeping.model.TransactionType
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
@ -40,37 +38,9 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
private val database = BookkeepingDatabase.getDatabase(application)
|
private val database = BookkeepingDatabase.getDatabase(application)
|
||||||
private val dao = database.bookkeepingDao()
|
private val dao = database.bookkeepingDao()
|
||||||
private val memberDao = database.memberDao()
|
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)
|
private val _isAutoBackupEnabled = MutableStateFlow(false)
|
||||||
val isAutoBackupEnabled: StateFlow<Boolean> = _isAutoBackupEnabled.asStateFlow()
|
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)
|
private val _selectedCategoryType = MutableStateFlow(TransactionType.EXPENSE)
|
||||||
val selectedCategoryType: StateFlow<TransactionType> = _selectedCategoryType.asStateFlow()
|
val selectedCategoryType: StateFlow<TransactionType> = _selectedCategoryType.asStateFlow()
|
||||||
|
|
||||||
@ -115,20 +85,12 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
fun setAutoBackup(enabled: Boolean) {
|
fun setAutoBackup(enabled: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_isAutoBackupEnabled.value = enabled
|
_isAutoBackupEnabled.value = enabled
|
||||||
settingsRepository.updateAutoBackupEnabled(enabled)
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
schedulePeriodicBackup()
|
schedulePeriodicBackup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setMonthStartDay(day: Int) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
_monthStartDay.value = day
|
|
||||||
settingsRepository.updateMonthStartDay(day)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun schedulePeriodicBackup() {
|
private fun schedulePeriodicBackup() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
while (isAutoBackupEnabled.value) {
|
while (isAutoBackupEnabled.value) {
|
||||||
|
Loading…
Reference in New Issue
Block a user