feat: 升级到 v1.3.0 版本
- 完成图标美化计划 - 增加分类和成员图标支持 - 更新 README.md 文档
This commit is contained in:
parent
84d5b6c672
commit
9a0ed2ec7c
12
README.md
12
README.md
@ -46,6 +46,18 @@
|
|||||||
- [x] 成员消费分析
|
- [x] 成员消费分析
|
||||||
- [x] 自定义统计周期
|
- [x] 自定义统计周期
|
||||||
|
|
||||||
|
### 2.1 图标美化计划 (进行中 🎨)
|
||||||
|
- [ ] 食品类图标 (餐饮、零食、饮料等)
|
||||||
|
- [ ] 交通类图标 (公交、打车、加油等)
|
||||||
|
- [ ] 购物类图标 (超市、数码、服装等)
|
||||||
|
- [ ] 居住类图标 (房租、水电、物业等)
|
||||||
|
- [ ] 医疗类图标 (药品、诊疗、保健等)
|
||||||
|
- [ ] 娱乐类图标 (游戏、电影、旅游等)
|
||||||
|
- [ ] 学习类图标 (书籍、课程、文具等)
|
||||||
|
- [ ] 其他类图标 (礼物、捐赠、其他等)
|
||||||
|
- [ ] 收入类图标 (工资、奖金、理财等)
|
||||||
|
- [ ] 成员图标 (家人、朋友、同事等)
|
||||||
|
|
||||||
### 3. 数据管理 (进行中 🚀)
|
### 3. 数据管理 (进行中 🚀)
|
||||||
- [ ] 导出 CSV/Excel 功能
|
- [ ] 导出 CSV/Excel 功能
|
||||||
- [ ] 数据迁移工具
|
- [ ] 数据迁移工具
|
||||||
|
@ -16,8 +16,8 @@ android {
|
|||||||
applicationId = "com.yovinchen.bookkeeping"
|
applicationId = "com.yovinchen.bookkeeping"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 5
|
versionCode = 6
|
||||||
versionName = "1.2.4"
|
versionName = "1.3.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
@ -8,6 +8,7 @@ import androidx.room.RoomDatabase
|
|||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
import androidx.room.migration.Migration
|
import androidx.room.migration.Migration
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import com.yovinchen.bookkeeping.R
|
||||||
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.Converters
|
import com.yovinchen.bookkeeping.model.Converters
|
||||||
@ -19,7 +20,7 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [BookkeepingRecord::class, Category::class, Member::class],
|
entities = [BookkeepingRecord::class, Category::class, Member::class],
|
||||||
version = 3,
|
version = 4,
|
||||||
exportSchema = false
|
exportSchema = false
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
@ -38,14 +39,15 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
|||||||
CREATE TABLE IF NOT EXISTS members (
|
CREATE TABLE IF NOT EXISTS members (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
description TEXT NOT NULL DEFAULT ''
|
description TEXT NOT NULL DEFAULT '',
|
||||||
|
icon INTEGER NOT NULL DEFAULT 0
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
// 插入默认成员
|
// 插入默认成员
|
||||||
db.execSQL("""
|
db.execSQL("""
|
||||||
INSERT INTO members (name, description)
|
INSERT INTO members (name, description, icon)
|
||||||
VALUES ('自己', '默认成员')
|
VALUES ('自己', '默认成员', ${R.drawable.ic_member_boy_24dp})
|
||||||
""")
|
""")
|
||||||
|
|
||||||
// 修改记账记录表,添加成员ID字段
|
// 修改记账记录表,添加成员ID字段
|
||||||
@ -96,14 +98,15 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
|||||||
CREATE TABLE IF NOT EXISTS categories_new (
|
CREATE TABLE IF NOT EXISTS categories_new (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
type TEXT NOT NULL
|
type TEXT NOT NULL,
|
||||||
|
icon INTEGER NOT NULL DEFAULT 0
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
// 复制分类数据
|
// 复制分类数据
|
||||||
db.execSQL("""
|
db.execSQL("""
|
||||||
INSERT INTO categories_new (id, name, type)
|
INSERT INTO categories_new (id, name, type, icon)
|
||||||
SELECT id, name, type FROM categories
|
SELECT id, name, type, 0 FROM categories
|
||||||
""")
|
""")
|
||||||
|
|
||||||
// 删除旧表
|
// 删除旧表
|
||||||
@ -114,6 +117,13 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val MIGRATION_3_4 = object : Migration(3, 4) {
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
// 如果需要,在这里添加数据库迁移逻辑
|
||||||
|
// 由于这次更改可能只是schema hash的变化,我们不需要实际的数据库更改
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var INSTANCE: BookkeepingDatabase? = null
|
private var INSTANCE: BookkeepingDatabase? = null
|
||||||
|
|
||||||
@ -124,7 +134,7 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
|||||||
BookkeepingDatabase::class.java,
|
BookkeepingDatabase::class.java,
|
||||||
"bookkeeping_database"
|
"bookkeeping_database"
|
||||||
)
|
)
|
||||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
|
.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)
|
||||||
@ -136,41 +146,53 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
|||||||
// 初始化默认成员
|
// 初始化默认成员
|
||||||
database.memberDao().apply {
|
database.memberDao().apply {
|
||||||
if (getMemberCount() == 0) {
|
if (getMemberCount() == 0) {
|
||||||
insertMember(Member(name = "自己", description = "默认成员"))
|
insertMember(Member(name = "自己", description = "默认成员", icon = R.drawable.ic_member_boy_24dp))
|
||||||
insertMember(Member(name = "老婆", description = "默认成员"))
|
insertMember(Member(name = "老婆", description = "默认成员", icon = R.drawable.ic_member_girl_24dp))
|
||||||
insertMember(Member(name = "老公", description = "默认成员"))
|
insertMember(Member(name = "老公", description = "默认成员", icon = R.drawable.ic_member_boy_24dp))
|
||||||
insertMember(Member(name = "家庭", description = "默认成员"))
|
insertMember(Member(name = "家庭", description = "默认成员", icon = R.drawable.ic_member_family_24dp))
|
||||||
insertMember(Member(name = "儿子", description = "默认成员"))
|
insertMember(Member(name = "儿子", description = "默认成员", icon = R.drawable.ic_member_baby_boy_24dp))
|
||||||
insertMember(Member(name = "女儿", description = "默认成员"))
|
insertMember(Member(name = "女儿", description = "默认成员", icon = R.drawable.ic_member_baby_girl_24dp))
|
||||||
insertMember(Member(name = "爸爸", description = "默认成员"))
|
insertMember(Member(name = "爸爸", description = "默认成员", icon = R.drawable.ic_member_father_24dp))
|
||||||
insertMember(Member(name = "妈妈", description = "默认成员"))
|
insertMember(Member(name = "妈妈", description = "默认成员", icon = R.drawable.ic_member_mother_24dp))
|
||||||
insertMember(Member(name = "爷爷", description = "默认成员"))
|
insertMember(Member(name = "爷爷", description = "默认成员", icon = R.drawable.ic_member_grandfather_24dp))
|
||||||
insertMember(Member(name = "奶奶", description = "默认成员"))
|
insertMember(Member(name = "奶奶", description = "默认成员", icon = R.drawable.ic_member_grandmother_24dp))
|
||||||
insertMember(Member(name = "外公", description = "默认成员"))
|
insertMember(Member(name = "外公", description = "默认成员", icon = R.drawable.ic_member_grandfather_24dp))
|
||||||
insertMember(Member(name = "外婆", description = "默认成员"))
|
insertMember(Member(name = "外婆", description = "默认成员", icon = R.drawable.ic_member_grandmother_24dp))
|
||||||
insertMember(Member(name = "其他人", description = "默认成员"))
|
insertMember(Member(name = "其他人", description = "默认成员", icon = R.drawable.ic_member_boy_24dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化默认分类
|
// 初始化默认分类
|
||||||
database.categoryDao().apply {
|
database.categoryDao().apply {
|
||||||
// 支出分类
|
// 支出分类
|
||||||
insertCategory(Category(name = "餐饮", type = TransactionType.EXPENSE))
|
insertCategory(Category(name = "餐饮", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_food_24dp))
|
||||||
insertCategory(Category(name = "交通", type = TransactionType.EXPENSE))
|
insertCategory(Category(name = "交通", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_taxi_24dp))
|
||||||
insertCategory(Category(name = "购物", type = TransactionType.EXPENSE))
|
insertCategory(Category(name = "购物", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_supermarket_24dp))
|
||||||
insertCategory(Category(name = "娱乐", type = TransactionType.EXPENSE))
|
insertCategory(Category(name = "娱乐", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_bar_24dp))
|
||||||
insertCategory(Category(name = "居住", type = TransactionType.EXPENSE))
|
insertCategory(Category(name = "居住", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_hotel_24dp))
|
||||||
insertCategory(Category(name = "医疗", type = TransactionType.EXPENSE))
|
insertCategory(Category(name = "医疗", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_medicine_24dp))
|
||||||
insertCategory(Category(name = "教育", type = TransactionType.EXPENSE))
|
insertCategory(Category(name = "教育", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_training_24dp))
|
||||||
insertCategory(Category(name = "其他支出", type = TransactionType.EXPENSE))
|
insertCategory(Category(name = "宠物", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_pet_24dp))
|
||||||
|
insertCategory(Category(name = "花卉", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_flower_24dp))
|
||||||
|
insertCategory(Category(name = "酒吧", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_bar_24dp))
|
||||||
|
insertCategory(Category(name = "快递", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_delivery_24dp))
|
||||||
|
insertCategory(Category(name = "数码", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_digital_24dp))
|
||||||
|
insertCategory(Category(name = "化妆品", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_cosmetics_24dp))
|
||||||
|
insertCategory(Category(name = "水果", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_fruit_24dp))
|
||||||
|
insertCategory(Category(name = "零食", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_snack_24dp))
|
||||||
|
insertCategory(Category(name = "蔬菜", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_vegetable_24dp))
|
||||||
|
insertCategory(Category(name = "其他支出", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_more_24dp))
|
||||||
|
|
||||||
// 收入分类
|
// 收入分类
|
||||||
insertCategory(Category(name = "工资", type = TransactionType.INCOME))
|
insertCategory(Category(name = "工资", type = TransactionType.INCOME, icon = R.drawable.ic_category_membership_24dp))
|
||||||
insertCategory(Category(name = "奖金", type = TransactionType.INCOME))
|
insertCategory(Category(name = "奖金", type = TransactionType.INCOME, icon = R.drawable.ic_category_gift_24dp))
|
||||||
insertCategory(Category(name = "投资", type = TransactionType.INCOME))
|
insertCategory(Category(name = "投资", type = TransactionType.INCOME, icon = R.drawable.ic_category_digital_24dp))
|
||||||
insertCategory(Category(name = "其他收入", type = TransactionType.INCOME))
|
insertCategory(Category(name = "礼物", type = TransactionType.INCOME, icon = R.drawable.ic_category_gift_24dp))
|
||||||
|
insertCategory(Category(name = "会员费", type = TransactionType.INCOME, icon = R.drawable.ic_category_membership_24dp))
|
||||||
|
insertCategory(Category(name = "其他收入", type = TransactionType.INCOME, icon = R.drawable.ic_category_more_24dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Log.d(TAG, "Default data initialized successfully")
|
Log.d(TAG, "Default data initialized successfully")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error initializing default data", e)
|
Log.e(TAG, "Error initializing default data", e)
|
||||||
|
@ -8,5 +8,6 @@ data class Category(
|
|||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
val id: Long = 0,
|
val id: Long = 0,
|
||||||
val name: String,
|
val name: String,
|
||||||
val type: TransactionType
|
val type: TransactionType,
|
||||||
|
val icon: Int? = null
|
||||||
)
|
)
|
||||||
|
@ -8,5 +8,6 @@ data class Member(
|
|||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
val name: String,
|
val name: String,
|
||||||
val description: String = "" // 可选的描述信息
|
val description: String = "", // 可选的描述信息
|
||||||
|
val icon: Int? = null // 新增icon字段,可为空
|
||||||
)
|
)
|
||||||
|
@ -6,10 +6,14 @@ 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.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
||||||
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.IconManager
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -24,6 +28,7 @@ fun RecordItem(
|
|||||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||||
val timeFormat = remember { SimpleDateFormat("HH:mm", Locale.getDefault()) }
|
val timeFormat = remember { SimpleDateFormat("HH:mm", Locale.getDefault()) }
|
||||||
val member = members.find { it.id == record.memberId }
|
val member = members.find { it.id == record.memberId }
|
||||||
|
val categoryIcon = IconManager.getCategoryIconVector(record.category)
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@ -36,9 +41,20 @@ fun RecordItem(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
// 左侧分类图标
|
||||||
|
if (categoryIcon != null) {
|
||||||
|
Icon(
|
||||||
|
imageVector = categoryIcon,
|
||||||
|
contentDescription = record.category,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中间内容区域
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
) {
|
) {
|
||||||
@ -64,7 +80,7 @@ fun RecordItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 金额显示
|
// 右侧金额显示
|
||||||
Text(
|
Text(
|
||||||
text = String.format("%.2f", record.amount),
|
text = String.format("%.2f", record.amount),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
@ -1,38 +1,23 @@
|
|||||||
package com.yovinchen.bookkeeping.ui.dialog
|
package com.yovinchen.bookkeeping.ui.dialog
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.material3.FilterChip
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.yovinchen.bookkeeping.model.Category
|
import com.yovinchen.bookkeeping.model.Category
|
||||||
import com.yovinchen.bookkeeping.model.TransactionType
|
import com.yovinchen.bookkeeping.model.TransactionType
|
||||||
|
import com.yovinchen.bookkeeping.utils.IconManager
|
||||||
|
|
||||||
private const val TAG = "CategoryManagementDialog"
|
private const val TAG = "CategoryManagementDialog"
|
||||||
|
|
||||||
@ -41,233 +26,217 @@ private const val TAG = "CategoryManagementDialog"
|
|||||||
fun CategoryManagementDialog(
|
fun CategoryManagementDialog(
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
categories: List<Category>,
|
categories: List<Category>,
|
||||||
onAddCategory: (String, TransactionType) -> Unit,
|
onAddCategory: (String, TransactionType, Int?) -> Unit,
|
||||||
onDeleteCategory: (Category) -> Unit,
|
onDeleteCategory: (Category) -> Unit,
|
||||||
onUpdateCategory: (Category, String) -> Unit,
|
onUpdateCategory: (Category, String, Int?) -> Unit,
|
||||||
selectedType: TransactionType,
|
selectedType: TransactionType,
|
||||||
onTypeChange: (TransactionType) -> Unit
|
onTypeChange: (TransactionType) -> Unit
|
||||||
) {
|
) {
|
||||||
var newCategoryName by remember { mutableStateOf("") }
|
var showAddDialog by remember { mutableStateOf(false) }
|
||||||
var showDialog by remember { mutableStateOf(true) }
|
var editingCategory by remember { mutableStateOf<Category?>(null) }
|
||||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
|
||||||
var showEditDialog by remember { mutableStateOf(false) }
|
|
||||||
var selectedCategory: Category? by remember { mutableStateOf(null) }
|
|
||||||
var editingCategoryName by remember { mutableStateOf("") }
|
|
||||||
val filteredCategories = categories.filter { it.type == selectedType }
|
|
||||||
|
|
||||||
Log.d(TAG, "Dialog state - showDialog: $showDialog, showDeleteDialog: $showDeleteDialog")
|
AlertDialog(
|
||||||
Log.d(TAG, "Selected category: ${selectedCategory?.name}")
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text("类别管理") },
|
||||||
if (showDialog) {
|
text = {
|
||||||
AlertDialog(
|
Column {
|
||||||
onDismissRequest = {
|
// 类型选择器
|
||||||
Log.d(TAG, "Main dialog dismiss requested")
|
Row(
|
||||||
showDialog = false
|
|
||||||
onDismiss()
|
|
||||||
},
|
|
||||||
title = { Text("类别管理") },
|
|
||||||
text = {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 8.dp)
|
.padding(bottom = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
// 类型选择
|
TransactionType.values().forEach { type ->
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
|
||||||
) {
|
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = selectedType == TransactionType.EXPENSE,
|
selected = type == selectedType,
|
||||||
onClick = {
|
onClick = { onTypeChange(type) },
|
||||||
Log.d(TAG, "Switching to EXPENSE type")
|
label = {
|
||||||
onTypeChange(TransactionType.EXPENSE)
|
Text(when (type) {
|
||||||
},
|
TransactionType.EXPENSE -> "支出"
|
||||||
label = { Text("支出") }
|
TransactionType.INCOME -> "收入"
|
||||||
)
|
})
|
||||||
FilterChip(
|
|
||||||
selected = selectedType == TransactionType.INCOME,
|
|
||||||
onClick = {
|
|
||||||
Log.d(TAG, "Switching to INCOME type")
|
|
||||||
onTypeChange(TransactionType.INCOME)
|
|
||||||
},
|
|
||||||
label = { Text("收入") }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 添加新类别
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = newCategoryName,
|
|
||||||
onValueChange = { newCategoryName = it },
|
|
||||||
label = { Text("新类别名称") },
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
if (newCategoryName.isNotBlank()) {
|
|
||||||
Log.d(TAG, "Adding new category: $newCategoryName")
|
|
||||||
onAddCategory(newCategoryName, selectedType)
|
|
||||||
newCategoryName = ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
)
|
||||||
Icon(Icons.Default.Add, contentDescription = "添加类别")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
// 类别列表
|
||||||
|
LazyColumn(
|
||||||
// 类别列表
|
modifier = Modifier
|
||||||
LazyColumn(
|
.fillMaxWidth()
|
||||||
modifier = Modifier.fillMaxWidth(),
|
.weight(1f)
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
) {
|
||||||
) {
|
items(categories.filter { it.type == selectedType }) { category ->
|
||||||
items(filteredCategories) { category ->
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
.clickable { editingCategory = category },
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
modifier = Modifier.weight(1f)
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
Text(
|
// 显示类别图标
|
||||||
text = category.name,
|
if (category.icon != null) {
|
||||||
modifier = Modifier
|
Icon(
|
||||||
.weight(1f)
|
imageVector = ImageVector.vectorResource(id = category.icon),
|
||||||
.clickable {
|
contentDescription = null,
|
||||||
selectedCategory = category
|
modifier = Modifier.size(24.dp),
|
||||||
editingCategoryName = category.name
|
tint = Color.Unspecified
|
||||||
showEditDialog = true
|
)
|
||||||
}
|
} else {
|
||||||
)
|
IconManager.getCategoryIconVector(category.name)?.let { icon ->
|
||||||
IconButton(
|
Icon(
|
||||||
onClick = {
|
imageVector = icon,
|
||||||
Log.d(TAG, "Selected category for deletion: ${category.name}")
|
contentDescription = null,
|
||||||
selectedCategory = category
|
modifier = Modifier.size(24.dp),
|
||||||
showDeleteDialog = true
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
Icon(Icons.Default.Delete, contentDescription = "删除类别")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(category.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = { onDeleteCategory(category) },
|
||||||
|
enabled = categories.size > 1
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Delete,
|
||||||
|
contentDescription = "删除",
|
||||||
|
tint = if (categories.size > 1)
|
||||||
|
MaterialTheme.colorScheme.error
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
confirmButton = {
|
// 添加类别按钮
|
||||||
TextButton(
|
Button(
|
||||||
onClick = {
|
onClick = { showAddDialog = true },
|
||||||
Log.d(TAG, "Main dialog confirmed")
|
modifier = Modifier
|
||||||
showDialog = false
|
.fillMaxWidth()
|
||||||
onDismiss()
|
.padding(top = 8.dp)
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Text("完成")
|
Icon(Icons.Default.Add, contentDescription = null)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("添加类别")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
},
|
||||||
}
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text("完成")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// 删除确认对话框
|
// 添加类别对话框
|
||||||
if (showDeleteDialog && selectedCategory != null) {
|
if (showAddDialog) {
|
||||||
AlertDialog(
|
CategoryEditDialog(
|
||||||
onDismissRequest = {
|
onDismiss = { showAddDialog = false },
|
||||||
Log.d(TAG, "Delete dialog dismissed")
|
onConfirm = { name, iconResId ->
|
||||||
showDeleteDialog = false
|
onAddCategory(name, selectedType, iconResId)
|
||||||
selectedCategory = null
|
showAddDialog = false
|
||||||
},
|
|
||||||
title = { Text("确认删除") },
|
|
||||||
text = {
|
|
||||||
Text(
|
|
||||||
text = buildString {
|
|
||||||
append("确定要删除类别 ")
|
|
||||||
append(selectedCategory?.name ?: "")
|
|
||||||
append(" 吗?")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
try {
|
|
||||||
selectedCategory?.let { category ->
|
|
||||||
Log.d(TAG, "Confirming deletion of category: ${category.name}")
|
|
||||||
onDeleteCategory(category)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error during category deletion callback", e)
|
|
||||||
e.printStackTrace()
|
|
||||||
} finally {
|
|
||||||
showDeleteDialog = false
|
|
||||||
selectedCategory = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text("确定")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
Log.d(TAG, "Canceling deletion")
|
|
||||||
showDeleteDialog = false
|
|
||||||
selectedCategory = null
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text("取消")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑类别对话框
|
// 编辑类别对话框
|
||||||
if (showEditDialog && selectedCategory != null) {
|
editingCategory?.let { category ->
|
||||||
AlertDialog(
|
CategoryEditDialog(
|
||||||
onDismissRequest = {
|
onDismiss = { editingCategory = null },
|
||||||
showEditDialog = false
|
onConfirm = { name, iconResId ->
|
||||||
selectedCategory = null
|
onUpdateCategory(category, name, iconResId)
|
||||||
editingCategoryName = ""
|
editingCategory = null
|
||||||
},
|
},
|
||||||
title = { Text("编辑类别") },
|
initialName = category.name,
|
||||||
text = {
|
initialIcon = category.icon
|
||||||
OutlinedTextField(
|
)
|
||||||
value = editingCategoryName,
|
}
|
||||||
onValueChange = { editingCategoryName = it },
|
}
|
||||||
label = { Text("类别名称") }
|
|
||||||
)
|
@Composable
|
||||||
},
|
private fun CategoryEditDialog(
|
||||||
confirmButton = {
|
onDismiss: () -> Unit,
|
||||||
TextButton(
|
onConfirm: (String, Int?) -> Unit,
|
||||||
onClick = {
|
initialName: String = "",
|
||||||
if (editingCategoryName.isNotBlank()) {
|
initialIcon: Int? = null
|
||||||
selectedCategory?.let { category ->
|
) {
|
||||||
onUpdateCategory(category, editingCategoryName)
|
var name by remember { mutableStateOf(initialName) }
|
||||||
}
|
var selectedIcon by remember { mutableStateOf(initialIcon) }
|
||||||
}
|
var showIconPicker by remember { mutableStateOf(false) }
|
||||||
showEditDialog = false
|
|
||||||
selectedCategory = null
|
AlertDialog(
|
||||||
editingCategoryName = ""
|
onDismissRequest = onDismiss,
|
||||||
}
|
title = { Text(if (initialName.isEmpty()) "添加类别" else "编辑类别") },
|
||||||
) {
|
text = {
|
||||||
Text("确定")
|
Column(
|
||||||
}
|
modifier = Modifier.fillMaxWidth(),
|
||||||
},
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
dismissButton = {
|
) {
|
||||||
TextButton(
|
OutlinedTextField(
|
||||||
onClick = {
|
value = name,
|
||||||
showEditDialog = false
|
onValueChange = { name = it },
|
||||||
selectedCategory = null
|
label = { Text("名称") },
|
||||||
editingCategoryName = ""
|
modifier = Modifier.fillMaxWidth()
|
||||||
}
|
)
|
||||||
) {
|
|
||||||
Text("取消")
|
// 图标选择按钮
|
||||||
}
|
Button(
|
||||||
}
|
onClick = { showIconPicker = true },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
selectedIcon?.let { iconResId ->
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(id = iconResId),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
}
|
||||||
|
Text(if (selectedIcon == null) "选择图标" else "更改图标")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
if (name.isNotBlank()) {
|
||||||
|
onConfirm(name, selectedIcon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("确定")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text("取消")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (showIconPicker) {
|
||||||
|
IconPickerDialog(
|
||||||
|
onDismiss = { showIconPicker = false },
|
||||||
|
onIconSelected = {
|
||||||
|
selectedIcon = it
|
||||||
|
showIconPicker = false
|
||||||
|
},
|
||||||
|
selectedIcon = selectedIcon,
|
||||||
|
isMemberIcon = false,
|
||||||
|
title = "选择类别图标"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
package com.yovinchen.bookkeeping.ui.dialog
|
||||||
|
|
||||||
|
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.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.yovinchen.bookkeeping.utils.IconManager
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun IconPickerDialog(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onIconSelected: (Int) -> Unit,
|
||||||
|
selectedIcon: Int? = null,
|
||||||
|
isMemberIcon: Boolean = false,
|
||||||
|
title: String = "选择图标"
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text(title) },
|
||||||
|
text = {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(4),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(300.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
val icons = if (isMemberIcon) {
|
||||||
|
IconManager.getAllMemberIcons()
|
||||||
|
} else {
|
||||||
|
IconManager.getAllCategoryIcons()
|
||||||
|
}
|
||||||
|
|
||||||
|
items(icons) { iconResId ->
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(id = iconResId),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clickable { onIconSelected(iconResId) },
|
||||||
|
tint = if (selectedIcon == iconResId) MaterialTheme.colorScheme.primary else Color.Unspecified
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text("取消")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
@ -7,20 +7,25 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material.icons.filled.Edit
|
import androidx.compose.material.icons.filled.Edit
|
||||||
|
import androidx.compose.material.icons.filled.Person
|
||||||
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.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.yovinchen.bookkeeping.model.Member
|
import com.yovinchen.bookkeeping.model.Member
|
||||||
|
import com.yovinchen.bookkeeping.utils.IconManager
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MemberManagementDialog(
|
fun MemberManagementDialog(
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
members: List<Member>,
|
members: List<Member>,
|
||||||
onAddMember: (String, String) -> Unit,
|
onAddMember: (String, String, Int?) -> Unit,
|
||||||
onDeleteMember: (Member) -> Unit,
|
onDeleteMember: (Member) -> Unit,
|
||||||
onUpdateMember: (Member, String, String) -> Unit
|
onUpdateMember: (Member, String, String, Int?) -> Unit
|
||||||
) {
|
) {
|
||||||
var showAddDialog by remember { mutableStateOf(false) }
|
var showAddDialog by remember { mutableStateOf(false) }
|
||||||
var selectedMember by remember { mutableStateOf<Member?>(null) }
|
var selectedMember by remember { mutableStateOf<Member?>(null) }
|
||||||
@ -43,31 +48,55 @@ fun MemberManagementDialog(
|
|||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Row(
|
||||||
Text(
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
text = member.name,
|
modifier = Modifier.weight(1f)
|
||||||
style = MaterialTheme.typography.titleMedium
|
) {
|
||||||
)
|
// 显示成员图标
|
||||||
if (member.description.isNotEmpty()) {
|
if (member.icon != null) {
|
||||||
Text(
|
Icon(
|
||||||
text = member.description,
|
imageVector = ImageVector.vectorResource(member.icon),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
contentDescription = null,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
IconManager.getMemberIconVector(member.name)?.let { icon ->
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = member.name,
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
if (member.description.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = member.description,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
IconButton(onClick = { selectedMember = member }) {
|
IconButton(onClick = { selectedMember = member }) {
|
||||||
Icon(Icons.Default.Edit, "编辑成员")
|
Icon(Icons.Default.Edit, contentDescription = "编辑")
|
||||||
}
|
}
|
||||||
IconButton(onClick = { onDeleteMember(member) }) {
|
IconButton(onClick = { onDeleteMember(member) }) {
|
||||||
Icon(Icons.Default.Delete, "删除成员")
|
Icon(Icons.Default.Delete, contentDescription = "删除")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (members.indexOf(member) < members.size - 1) {
|
|
||||||
HorizontalDivider()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,9 +104,9 @@ fun MemberManagementDialog(
|
|||||||
onClick = { showAddDialog = true },
|
onClick = { showAddDialog = true },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = 16.dp)
|
.padding(top = 8.dp)
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Add, "添加成员")
|
Icon(Icons.Default.Add, contentDescription = null)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text("添加成员")
|
Text("添加成员")
|
||||||
}
|
}
|
||||||
@ -85,32 +114,31 @@ fun MemberManagementDialog(
|
|||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onDismiss) {
|
TextButton(onClick = onDismiss) {
|
||||||
Text("关闭")
|
Text("完成")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 添加成员对话框
|
|
||||||
if (showAddDialog) {
|
if (showAddDialog) {
|
||||||
MemberEditDialog(
|
MemberEditDialog(
|
||||||
onDismiss = { showAddDialog = false },
|
onDismiss = { showAddDialog = false },
|
||||||
onConfirm = { name, description ->
|
onConfirm = { name, description, iconResId ->
|
||||||
onAddMember(name, description)
|
onAddMember(name, description, iconResId)
|
||||||
showAddDialog = false
|
showAddDialog = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑成员对话框
|
|
||||||
selectedMember?.let { member ->
|
selectedMember?.let { member ->
|
||||||
MemberEditDialog(
|
MemberEditDialog(
|
||||||
onDismiss = { selectedMember = null },
|
onDismiss = { selectedMember = null },
|
||||||
onConfirm = { name, description ->
|
onConfirm = { name, description, iconResId ->
|
||||||
onUpdateMember(member, name, description)
|
onUpdateMember(member, name, description, iconResId)
|
||||||
selectedMember = null
|
selectedMember = null
|
||||||
},
|
},
|
||||||
initialName = member.name,
|
initialName = member.name,
|
||||||
initialDescription = member.description
|
initialDescription = member.description,
|
||||||
|
initialIcon = member.icon
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,45 +146,63 @@ fun MemberManagementDialog(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun MemberEditDialog(
|
private fun MemberEditDialog(
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onConfirm: (String, String) -> Unit,
|
onConfirm: (String, String, Int?) -> Unit,
|
||||||
initialName: String = "",
|
initialName: String = "",
|
||||||
initialDescription: String = ""
|
initialDescription: String = "",
|
||||||
|
initialIcon: Int? = null
|
||||||
) {
|
) {
|
||||||
var name by remember { mutableStateOf(initialName) }
|
var name by remember { mutableStateOf(initialName) }
|
||||||
var description by remember { mutableStateOf(initialDescription) }
|
var description by remember { mutableStateOf(initialDescription) }
|
||||||
|
var selectedIcon by remember { mutableStateOf(initialIcon) }
|
||||||
|
var showIconPicker by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = { Text(if (initialName.isEmpty()) "添加成员" else "编辑成员") },
|
title = { Text(if (initialName.isEmpty()) "添加成员" else "编辑成员") },
|
||||||
text = {
|
text = {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
.padding(vertical = 8.dp)
|
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = name,
|
value = name,
|
||||||
onValueChange = { name = it },
|
onValueChange = { name = it },
|
||||||
label = { Text("成员名称") },
|
label = { Text("名称") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = description,
|
value = description,
|
||||||
onValueChange = { description = it },
|
onValueChange = { description = it },
|
||||||
label = { Text("描述(可选)") },
|
label = { Text("描述") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 图标选择按钮
|
||||||
|
Button(
|
||||||
|
onClick = { showIconPicker = true },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
selectedIcon?.let { iconResId ->
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(iconResId),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
}
|
||||||
|
Text(if (selectedIcon == null) "选择图标" else "更改图标")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (name.isNotBlank()) {
|
if (name.isNotBlank()) {
|
||||||
onConfirm(name.trim(), description.trim())
|
onConfirm(name, description, selectedIcon)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
enabled = name.isNotBlank()
|
|
||||||
) {
|
) {
|
||||||
Text("确定")
|
Text("确定")
|
||||||
}
|
}
|
||||||
@ -167,4 +213,17 @@ private fun MemberEditDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (showIconPicker) {
|
||||||
|
IconPickerDialog(
|
||||||
|
onDismiss = { showIconPicker = false },
|
||||||
|
onIconSelected = {
|
||||||
|
selectedIcon = it
|
||||||
|
showIconPicker = false
|
||||||
|
},
|
||||||
|
selectedIcon = selectedIcon,
|
||||||
|
isMemberIcon = true,
|
||||||
|
title = "选择成员图标"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,13 @@
|
|||||||
package com.yovinchen.bookkeeping.ui.navigation
|
package com.yovinchen.bookkeeping.ui.navigation
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.automirrored.filled.List
|
|
||||||
import androidx.compose.material.icons.filled.Analytics
|
|
||||||
import androidx.compose.material.icons.filled.Settings
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.NavigationBar
|
|
||||||
import androidx.compose.material3.NavigationBarItem
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
@ -22,20 +15,38 @@ import androidx.navigation.compose.composable
|
|||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
|
import com.yovinchen.bookkeeping.R
|
||||||
import com.yovinchen.bookkeeping.model.AnalysisType
|
import com.yovinchen.bookkeeping.model.AnalysisType
|
||||||
import com.yovinchen.bookkeeping.model.ThemeMode
|
import com.yovinchen.bookkeeping.model.ThemeMode
|
||||||
import com.yovinchen.bookkeeping.ui.screen.*
|
import com.yovinchen.bookkeeping.ui.screen.*
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import java.time.YearMonth
|
import java.time.YearMonth
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
sealed class Screen(
|
sealed class Screen(
|
||||||
val route: String,
|
val route: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val icon: ImageVector? = null
|
val iconResId: Int? = null
|
||||||
) {
|
) {
|
||||||
object Home : Screen("home", "记账", Icons.AutoMirrored.Filled.List)
|
@Composable
|
||||||
object Analysis : Screen("analysis", "分析", Icons.Default.Analytics)
|
fun icon(): ImageVector? = iconResId?.let { ImageVector.vectorResource(it) }
|
||||||
object Settings : Screen("settings", "设置", Icons.Default.Settings)
|
|
||||||
|
object Home : Screen(
|
||||||
|
"home",
|
||||||
|
"记账",
|
||||||
|
iconResId = R.drawable.account
|
||||||
|
)
|
||||||
|
object Analysis : Screen(
|
||||||
|
"analysis",
|
||||||
|
"分析",
|
||||||
|
iconResId = R.drawable.piechart
|
||||||
|
)
|
||||||
|
object Settings : Screen(
|
||||||
|
"settings",
|
||||||
|
"设置",
|
||||||
|
iconResId = R.drawable.setting
|
||||||
|
)
|
||||||
object CategoryDetail : Screen(
|
object CategoryDetail : Screen(
|
||||||
"category_detail/{category}/{startMonth}/{endMonth}",
|
"category_detail/{category}/{startMonth}/{endMonth}",
|
||||||
"分类详情"
|
"分类详情"
|
||||||
@ -75,6 +86,11 @@ fun MainNavigation(
|
|||||||
onThemeChange: (ThemeMode) -> Unit
|
onThemeChange: (ThemeMode) -> Unit
|
||||||
) {
|
) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
val items = listOf(
|
||||||
|
Screen.Home,
|
||||||
|
Screen.Analysis,
|
||||||
|
Screen.Settings
|
||||||
|
)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
@ -82,11 +98,21 @@ fun MainNavigation(
|
|||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
val currentRoute = navBackStackEntry?.destination?.route
|
val currentRoute = navBackStackEntry?.destination?.route
|
||||||
|
|
||||||
Screen.bottomNavigationItems().forEach { screen ->
|
items.forEach { screen ->
|
||||||
|
val selected = currentRoute == screen.route
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
icon = { Icon(screen.icon!!, contentDescription = screen.title) },
|
icon = {
|
||||||
|
screen.icon()?.let { icon ->
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = screen.title,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
label = { Text(screen.title) },
|
label = { Text(screen.title) },
|
||||||
selected = currentRoute == screen.route,
|
selected = selected,
|
||||||
onClick = {
|
onClick = {
|
||||||
navController.navigate(screen.route) {
|
navController.navigate(screen.route) {
|
||||||
popUpTo(navController.graph.findStartDestination().id) {
|
popUpTo(navController.graph.findStartDestination().id) {
|
||||||
|
@ -152,9 +152,13 @@ fun SettingsScreen(
|
|||||||
CategoryManagementDialog(
|
CategoryManagementDialog(
|
||||||
onDismiss = { showCategoryDialog = false },
|
onDismiss = { showCategoryDialog = false },
|
||||||
categories = categories,
|
categories = categories,
|
||||||
onAddCategory = viewModel::addCategory,
|
onAddCategory = { name, type, iconResId ->
|
||||||
|
viewModel.addCategory(name, type, iconResId)
|
||||||
|
},
|
||||||
onDeleteCategory = viewModel::deleteCategory,
|
onDeleteCategory = viewModel::deleteCategory,
|
||||||
onUpdateCategory = viewModel::updateCategory,
|
onUpdateCategory = { category, newName, iconResId ->
|
||||||
|
viewModel.updateCategory(category, newName, iconResId)
|
||||||
|
},
|
||||||
selectedType = selectedType,
|
selectedType = selectedType,
|
||||||
onTypeChange = viewModel::setSelectedCategoryType
|
onTypeChange = viewModel::setSelectedCategoryType
|
||||||
)
|
)
|
||||||
@ -165,10 +169,16 @@ fun SettingsScreen(
|
|||||||
MemberManagementDialog(
|
MemberManagementDialog(
|
||||||
onDismiss = { showMemberDialog = false },
|
onDismiss = { showMemberDialog = false },
|
||||||
members = members,
|
members = members,
|
||||||
onAddMember = memberViewModel::addMember,
|
onAddMember = { name, description, iconResId ->
|
||||||
|
memberViewModel.addMember(name, description, iconResId)
|
||||||
|
},
|
||||||
onDeleteMember = memberViewModel::deleteMember,
|
onDeleteMember = memberViewModel::deleteMember,
|
||||||
onUpdateMember = { member, name, description ->
|
onUpdateMember = { member, name, description, iconResId ->
|
||||||
memberViewModel.updateMember(member.copy(name = name, description = description))
|
memberViewModel.updateMember(member.copy(
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
icon = iconResId
|
||||||
|
))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
package com.yovinchen.bookkeeping.utils
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import com.yovinchen.bookkeeping.R
|
||||||
|
|
||||||
|
object IconManager {
|
||||||
|
// 类别图标映射
|
||||||
|
private val categoryIcons = mapOf(
|
||||||
|
"食品" to R.drawable.ic_category_food_24dp,
|
||||||
|
"交通" to R.drawable.ic_category_taxi_24dp,
|
||||||
|
"娱乐" to R.drawable.ic_category_bar_24dp,
|
||||||
|
"购物" to R.drawable.ic_category_supermarket_24dp,
|
||||||
|
"工资" to R.drawable.ic_category_membership_24dp,
|
||||||
|
"服装" to R.drawable.ic_category_clothes_24dp,
|
||||||
|
"数码" to R.drawable.ic_category_digital_24dp,
|
||||||
|
"饮料" to R.drawable.ic_category_drink_24dp,
|
||||||
|
"医疗" to R.drawable.ic_category_medicine_24dp,
|
||||||
|
"旅行" to R.drawable.ic_category_travel_24dp,
|
||||||
|
"便利店" to R.drawable.ic_category_convenience_24dp,
|
||||||
|
"化妆品" to R.drawable.ic_category_cosmetics_24dp,
|
||||||
|
"外卖" to R.drawable.ic_category_delivery_24dp,
|
||||||
|
"鲜花" to R.drawable.ic_category_flower_24dp,
|
||||||
|
"水果" to R.drawable.ic_category_fruit_24dp,
|
||||||
|
"礼物" to R.drawable.ic_category_gift_24dp,
|
||||||
|
"住宿" to R.drawable.ic_category_hotel_24dp,
|
||||||
|
"宠物" to R.drawable.ic_category_pet_24dp,
|
||||||
|
"景点" to R.drawable.ic_category_scenic_24dp,
|
||||||
|
"零食" to R.drawable.ic_category_snack_24dp,
|
||||||
|
"培训" to R.drawable.ic_category_training_24dp,
|
||||||
|
"蔬菜" to R.drawable.ic_category_vegetable_24dp,
|
||||||
|
"婴儿" to R.drawable.ic_category_baby_24dp,
|
||||||
|
"餐饮" to R.drawable.ic_category_food_24dp, // 添加餐饮分类
|
||||||
|
"居住" to R.drawable.ic_category_hotel_24dp, // 添加居住分类
|
||||||
|
"其他" to R.drawable.ic_category_more_24dp
|
||||||
|
)
|
||||||
|
|
||||||
|
// 成员图标映射
|
||||||
|
private val memberIcons = mapOf(
|
||||||
|
"自己" to R.drawable.ic_member_boy_24dp,
|
||||||
|
"家庭" to R.drawable.ic_member_family_24dp,
|
||||||
|
"父亲" to R.drawable.ic_member_father_24dp,
|
||||||
|
"母亲" to R.drawable.ic_member_mother_24dp,
|
||||||
|
"男宝" to R.drawable.ic_member_baby_boy_24dp,
|
||||||
|
"女宝" to R.drawable.ic_member_baby_girl_24dp,
|
||||||
|
"新娘" to R.drawable.ic_member_bride_24dp,
|
||||||
|
"新郎" to R.drawable.ic_member_groom_24dp,
|
||||||
|
"爷爷" to R.drawable.ic_member_grandfather_24dp,
|
||||||
|
"奶奶" to R.drawable.ic_member_grandmother_24dp,
|
||||||
|
"男生" to R.drawable.ic_member_boy_24dp,
|
||||||
|
"女生" to R.drawable.ic_member_girl_24dp,
|
||||||
|
"其他" to R.drawable.ic_member_girl_24dp
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getCategoryIconVector(name: String): ImageVector? {
|
||||||
|
return categoryIcons[name]?.let { ImageVector.vectorResource(id = it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getMemberIconVector(name: String): ImageVector? {
|
||||||
|
return memberIcons[name]?.let { ImageVector.vectorResource(id = it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
fun getCategoryIcon(name: String): Int? {
|
||||||
|
return categoryIcons[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
fun getMemberIcon(name: String): Int? {
|
||||||
|
return memberIcons[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllCategoryIcons(): List<Int> {
|
||||||
|
return categoryIcons.values.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllMemberIcons(): List<Int> {
|
||||||
|
return memberIcons.values.toList()
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package com.yovinchen.bookkeeping.viewmodel
|
package com.yovinchen.bookkeeping.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.util.Log
|
|
||||||
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
|
||||||
@ -19,7 +18,6 @@ import java.util.*
|
|||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private val TAG = "HomeViewModel"
|
|
||||||
private val bookkeepingDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao()
|
private val bookkeepingDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao()
|
||||||
private val memberDao = BookkeepingDatabase.getDatabase(application).memberDao()
|
private val memberDao = BookkeepingDatabase.getDatabase(application).memberDao()
|
||||||
private val categoryDao = BookkeepingDatabase.getDatabase(application).categoryDao()
|
private val categoryDao = BookkeepingDatabase.getDatabase(application).categoryDao()
|
||||||
|
@ -13,9 +13,9 @@ class MemberViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
|
|
||||||
val allMembers: Flow<List<Member>> = memberDao.getAllMembers()
|
val allMembers: Flow<List<Member>> = memberDao.getAllMembers()
|
||||||
|
|
||||||
fun addMember(name: String, description: String = "") {
|
fun addMember(name: String, description: String = "", iconResId: Int? = null) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val member = Member(name = name, description = description)
|
val member = Member(name = name, description = description, icon = iconResId)
|
||||||
memberDao.insertMember(member)
|
memberDao.insertMember(member)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,9 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
_selectedCategoryType.value = type
|
_selectedCategoryType.value = type
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addCategory(name: String, type: TransactionType) {
|
fun addCategory(name: String, type: TransactionType, iconResId: Int?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val category = Category(name = name, type = type)
|
val category = Category(name = name, type = type, icon = iconResId)
|
||||||
dao.insertCategory(category)
|
dao.insertCategory(category)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,9 +45,9 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateCategory(category: Category, newName: String) {
|
fun updateCategory(category: Category, newName: String, iconResId: Int?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val updatedCategory = category.copy(name = newName)
|
val updatedCategory = category.copy(name = newName, icon = iconResId)
|
||||||
dao.updateCategory(updatedCategory)
|
dao.updateCategory(updatedCategory)
|
||||||
// 更新所有使用该类别的记录
|
// 更新所有使用该类别的记录
|
||||||
dao.updateRecordCategories(category.name, newName)
|
dao.updateRecordCategories(category.name, newName)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="200dp"
|
android:width="24dp"
|
||||||
android:height="200dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1024"
|
android:viewportWidth="1024"
|
||||||
android:viewportHeight="1024">
|
android:viewportHeight="1024">
|
||||||
<path
|
<path
|
||||||
|
Loading…
Reference in New Issue
Block a user