feat: 升级到 v1.3.0 版本

- 完成图标美化计划
- 增加分类和成员图标支持
- 更新 README.md 文档
This commit is contained in:
yovinchen 2024-12-16 23:40:17 +08:00
parent 84d5b6c672
commit 9a0ed2ec7c
53 changed files with 665 additions and 406 deletions

View File

@ -46,6 +46,18 @@
- [x] 成员消费分析
- [x] 自定义统计周期
### 2.1 图标美化计划 (进行中 🎨)
- [ ] 食品类图标 (餐饮、零食、饮料等)
- [ ] 交通类图标 (公交、打车、加油等)
- [ ] 购物类图标 (超市、数码、服装等)
- [ ] 居住类图标 (房租、水电、物业等)
- [ ] 医疗类图标 (药品、诊疗、保健等)
- [ ] 娱乐类图标 (游戏、电影、旅游等)
- [ ] 学习类图标 (书籍、课程、文具等)
- [ ] 其他类图标 (礼物、捐赠、其他等)
- [ ] 收入类图标 (工资、奖金、理财等)
- [ ] 成员图标 (家人、朋友、同事等)
### 3. 数据管理 (进行中 🚀)
- [ ] 导出 CSV/Excel 功能
- [ ] 数据迁移工具

View File

@ -16,8 +16,8 @@ android {
applicationId = "com.yovinchen.bookkeeping"
minSdk = 26
targetSdk = 34
versionCode = 5
versionName = "1.2.4"
versionCode = 6
versionName = "1.3.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View File

@ -8,6 +8,7 @@ import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.yovinchen.bookkeeping.R
import com.yovinchen.bookkeeping.model.BookkeepingRecord
import com.yovinchen.bookkeeping.model.Category
import com.yovinchen.bookkeeping.model.Converters
@ -19,7 +20,7 @@ import kotlinx.coroutines.launch
@Database(
entities = [BookkeepingRecord::class, Category::class, Member::class],
version = 3,
version = 4,
exportSchema = false
)
@TypeConverters(Converters::class)
@ -38,14 +39,15 @@ abstract class BookkeepingDatabase : RoomDatabase() {
CREATE TABLE IF NOT EXISTS members (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT ''
description TEXT NOT NULL DEFAULT '',
icon INTEGER NOT NULL DEFAULT 0
)
""")
// 插入默认成员
db.execSQL("""
INSERT INTO members (name, description)
VALUES ('自己', '默认成员')
INSERT INTO members (name, description, icon)
VALUES ('自己', '默认成员', ${R.drawable.ic_member_boy_24dp})
""")
// 修改记账记录表添加成员ID字段
@ -96,14 +98,15 @@ abstract class BookkeepingDatabase : RoomDatabase() {
CREATE TABLE IF NOT EXISTS categories_new (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
name TEXT NOT NULL,
type TEXT NOT NULL
type TEXT NOT NULL,
icon INTEGER NOT NULL DEFAULT 0
)
""")
// 复制分类数据
db.execSQL("""
INSERT INTO categories_new (id, name, type)
SELECT id, name, type FROM categories
INSERT INTO categories_new (id, name, type, icon)
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
private var INSTANCE: BookkeepingDatabase? = null
@ -124,7 +134,7 @@ abstract class BookkeepingDatabase : RoomDatabase() {
BookkeepingDatabase::class.java,
"bookkeeping_database"
)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
@ -136,40 +146,52 @@ abstract class BookkeepingDatabase : RoomDatabase() {
// 初始化默认成员
database.memberDao().apply {
if (getMemberCount() == 0) {
insertMember(Member(name = "自己", description = "默认成员"))
insertMember(Member(name = "老婆", description = "默认成员"))
insertMember(Member(name = "老公", description = "默认成员"))
insertMember(Member(name = "家庭", description = "默认成员"))
insertMember(Member(name = "儿子", description = "默认成员"))
insertMember(Member(name = "女儿", description = "默认成员"))
insertMember(Member(name = "爸爸", description = "默认成员"))
insertMember(Member(name = "妈妈", description = "默认成员"))
insertMember(Member(name = "爷爷", description = "默认成员"))
insertMember(Member(name = "奶奶", description = "默认成员"))
insertMember(Member(name = "外公", description = "默认成员"))
insertMember(Member(name = "外婆", description = "默认成员"))
insertMember(Member(name = "其他人", description = "默认成员"))
insertMember(Member(name = "自己", description = "默认成员", icon = R.drawable.ic_member_boy_24dp))
insertMember(Member(name = "老婆", description = "默认成员", icon = R.drawable.ic_member_girl_24dp))
insertMember(Member(name = "老公", description = "默认成员", icon = R.drawable.ic_member_boy_24dp))
insertMember(Member(name = "家庭", description = "默认成员", icon = R.drawable.ic_member_family_24dp))
insertMember(Member(name = "儿子", description = "默认成员", icon = R.drawable.ic_member_baby_boy_24dp))
insertMember(Member(name = "女儿", description = "默认成员", icon = R.drawable.ic_member_baby_girl_24dp))
insertMember(Member(name = "爸爸", description = "默认成员", icon = R.drawable.ic_member_father_24dp))
insertMember(Member(name = "妈妈", description = "默认成员", icon = R.drawable.ic_member_mother_24dp))
insertMember(Member(name = "爷爷", description = "默认成员", icon = R.drawable.ic_member_grandfather_24dp))
insertMember(Member(name = "奶奶", description = "默认成员", icon = R.drawable.ic_member_grandmother_24dp))
insertMember(Member(name = "外公", description = "默认成员", icon = R.drawable.ic_member_grandfather_24dp))
insertMember(Member(name = "外婆", description = "默认成员", icon = R.drawable.ic_member_grandmother_24dp))
insertMember(Member(name = "其他人", description = "默认成员", icon = R.drawable.ic_member_boy_24dp))
}
}
// 初始化默认分类
// 初始化默认分类
database.categoryDao().apply {
// 支出分类
insertCategory(Category(name = "餐饮", type = TransactionType.EXPENSE))
insertCategory(Category(name = "交通", type = TransactionType.EXPENSE))
insertCategory(Category(name = "购物", type = TransactionType.EXPENSE))
insertCategory(Category(name = "娱乐", type = TransactionType.EXPENSE))
insertCategory(Category(name = "居住", type = TransactionType.EXPENSE))
insertCategory(Category(name = "医疗", type = TransactionType.EXPENSE))
insertCategory(Category(name = "教育", type = TransactionType.EXPENSE))
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, icon = R.drawable.ic_category_taxi_24dp))
insertCategory(Category(name = "购物", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_supermarket_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_hotel_24dp))
insertCategory(Category(name = "医疗", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_medicine_24dp))
insertCategory(Category(name = "教育", type = TransactionType.EXPENSE, icon = R.drawable.ic_category_training_24dp))
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))
insertCategory(Category(name = "投资", type = TransactionType.INCOME))
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, icon = R.drawable.ic_category_gift_24dp))
insertCategory(Category(name = "投资", type = TransactionType.INCOME, icon = R.drawable.ic_category_digital_24dp))
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")
} catch (e: Exception) {

View File

@ -8,5 +8,6 @@ data class Category(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val name: String,
val type: TransactionType
val type: TransactionType,
val icon: Int? = null
)

View File

@ -8,5 +8,6 @@ data class Member(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val name: String,
val description: String = "" // 可选的描述信息
val description: String = "", // 可选的描述信息
val icon: Int? = null // 新增icon字段可为空
)

View File

@ -6,10 +6,14 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
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.model.BookkeepingRecord
import com.yovinchen.bookkeeping.model.Member
import com.yovinchen.bookkeeping.model.TransactionType
import com.yovinchen.bookkeeping.utils.IconManager
import java.text.SimpleDateFormat
import java.util.*
@ -24,6 +28,7 @@ fun RecordItem(
var showDeleteDialog by remember { mutableStateOf(false) }
val timeFormat = remember { SimpleDateFormat("HH:mm", Locale.getDefault()) }
val member = members.find { it.id == record.memberId }
val categoryIcon = IconManager.getCategoryIconVector(record.category)
Card(
modifier = modifier
@ -36,9 +41,20 @@ fun RecordItem(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 左侧分类图标
if (categoryIcon != null) {
Icon(
imageVector = categoryIcon,
contentDescription = record.category,
modifier = Modifier.size(24.dp),
tint = Color.Unspecified
)
}
// 中间内容区域
Column(
modifier = Modifier.weight(1f)
) {
@ -64,7 +80,7 @@ fun RecordItem(
)
}
// 金额显示
// 右侧金额显示
Text(
text = String.format("%.2f", record.amount),
style = MaterialTheme.typography.titleMedium,

View File

@ -1,38 +1,23 @@
package com.yovinchen.bookkeeping.ui.dialog
import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
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.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
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.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
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.model.Category
import com.yovinchen.bookkeeping.model.TransactionType
import com.yovinchen.bookkeeping.utils.IconManager
private const val TAG = "CategoryManagementDialog"
@ -41,233 +26,217 @@ private const val TAG = "CategoryManagementDialog"
fun CategoryManagementDialog(
onDismiss: () -> Unit,
categories: List<Category>,
onAddCategory: (String, TransactionType) -> Unit,
onAddCategory: (String, TransactionType, Int?) -> Unit,
onDeleteCategory: (Category) -> Unit,
onUpdateCategory: (Category, String) -> Unit,
onUpdateCategory: (Category, String, Int?) -> Unit,
selectedType: TransactionType,
onTypeChange: (TransactionType) -> Unit
) {
var newCategoryName by remember { mutableStateOf("") }
var showDialog by remember { mutableStateOf(true) }
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 }
var showAddDialog by remember { mutableStateOf(false) }
var editingCategory by remember { mutableStateOf<Category?>(null) }
Log.d(TAG, "Dialog state - showDialog: $showDialog, showDeleteDialog: $showDeleteDialog")
Log.d(TAG, "Selected category: ${selectedCategory?.name}")
if (showDialog) {
AlertDialog(
onDismissRequest = {
Log.d(TAG, "Main dialog dismiss requested")
showDialog = false
onDismiss()
},
title = { Text("类别管理") },
text = {
Column(
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("类别管理") },
text = {
Column {
// 类型选择器
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.padding(bottom = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// 类型选择
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
TransactionType.values().forEach { type ->
FilterChip(
selected = selectedType == TransactionType.EXPENSE,
onClick = {
Log.d(TAG, "Switching to EXPENSE type")
onTypeChange(TransactionType.EXPENSE)
},
label = { Text("支出") }
)
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 = ""
}
selected = type == selectedType,
onClick = { onTypeChange(type) },
label = {
Text(when (type) {
TransactionType.EXPENSE -> "支出"
TransactionType.INCOME -> "收入"
})
}
) {
Icon(Icons.Default.Add, contentDescription = "添加类别")
}
)
}
}
Spacer(modifier = Modifier.height(16.dp))
// 类别列表
LazyColumn(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(filteredCategories) { category ->
// 类别列表
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
items(categories.filter { it.type == selectedType }) { category ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.clickable { editingCategory = category },
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Text(
text = category.name,
modifier = Modifier
.weight(1f)
.clickable {
selectedCategory = category
editingCategoryName = category.name
showEditDialog = true
}
)
IconButton(
onClick = {
Log.d(TAG, "Selected category for deletion: ${category.name}")
selectedCategory = category
showDeleteDialog = true
// 显示类别图标
if (category.icon != null) {
Icon(
imageVector = ImageVector.vectorResource(id = category.icon),
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = Color.Unspecified
)
} else {
IconManager.getCategoryIconVector(category.name)?.let { icon ->
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.size(24.dp),
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(
onClick = {
Log.d(TAG, "Main dialog confirmed")
showDialog = false
onDismiss()
}
// 添加类别按钮
Button(
onClick = { showAddDialog = true },
modifier = Modifier
.fillMaxWidth()
.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) {
AlertDialog(
onDismissRequest = {
Log.d(TAG, "Delete dialog dismissed")
showDeleteDialog = false
selectedCategory = null
},
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 (showAddDialog) {
CategoryEditDialog(
onDismiss = { showAddDialog = false },
onConfirm = { name, iconResId ->
onAddCategory(name, selectedType, iconResId)
showAddDialog = false
}
)
}
// 编辑类别对话框
if (showEditDialog && selectedCategory != null) {
AlertDialog(
onDismissRequest = {
showEditDialog = false
selectedCategory = null
editingCategoryName = ""
editingCategory?.let { category ->
CategoryEditDialog(
onDismiss = { editingCategory = null },
onConfirm = { name, iconResId ->
onUpdateCategory(category, name, iconResId)
editingCategory = null
},
title = { Text("编辑类别") },
text = {
OutlinedTextField(
value = editingCategoryName,
onValueChange = { editingCategoryName = it },
label = { Text("类别名称") }
)
},
confirmButton = {
TextButton(
onClick = {
if (editingCategoryName.isNotBlank()) {
selectedCategory?.let { category ->
onUpdateCategory(category, editingCategoryName)
}
}
showEditDialog = false
selectedCategory = null
editingCategoryName = ""
}
) {
Text("确定")
}
},
dismissButton = {
TextButton(
onClick = {
showEditDialog = false
selectedCategory = null
editingCategoryName = ""
}
) {
Text("取消")
}
}
initialName = category.name,
initialIcon = category.icon
)
}
}
@Composable
private fun CategoryEditDialog(
onDismiss: () -> Unit,
onConfirm: (String, Int?) -> Unit,
initialName: String = "",
initialIcon: Int? = null
) {
var name by remember { mutableStateOf(initialName) }
var selectedIcon by remember { mutableStateOf(initialIcon) }
var showIconPicker by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(if (initialName.isEmpty()) "添加类别" else "编辑类别") },
text = {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("名称") },
modifier = Modifier.fillMaxWidth()
)
// 图标选择按钮
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 = "选择类别图标"
)
}
}

View File

@ -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("取消")
}
}
)
}

View File

@ -7,20 +7,25 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
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.model.Member
import com.yovinchen.bookkeeping.utils.IconManager
@Composable
fun MemberManagementDialog(
onDismiss: () -> Unit,
members: List<Member>,
onAddMember: (String, String) -> Unit,
onAddMember: (String, String, Int?) -> Unit,
onDeleteMember: (Member) -> Unit,
onUpdateMember: (Member, String, String) -> Unit
onUpdateMember: (Member, String, String, Int?) -> Unit
) {
var showAddDialog by remember { mutableStateOf(false) }
var selectedMember by remember { mutableStateOf<Member?>(null) }
@ -43,41 +48,65 @@ fun MemberManagementDialog(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
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(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
// 显示成员图标
if (member.icon != null) {
Icon(
imageVector = ImageVector.vectorResource(member.icon),
contentDescription = null,
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 {
IconButton(onClick = { selectedMember = member }) {
Icon(Icons.Default.Edit, "编辑成员")
Icon(Icons.Default.Edit, contentDescription = "编辑")
}
IconButton(onClick = { onDeleteMember(member) }) {
Icon(Icons.Default.Delete, "删除成员")
Icon(Icons.Default.Delete, contentDescription = "删除")
}
}
}
if (members.indexOf(member) < members.size - 1) {
HorizontalDivider()
}
}
}
Button(
onClick = { showAddDialog = true },
modifier = Modifier
.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))
Text("添加成员")
}
@ -85,32 +114,31 @@ fun MemberManagementDialog(
},
confirmButton = {
TextButton(onClick = onDismiss) {
Text("关闭")
Text("完成")
}
}
)
// 添加成员对话框
if (showAddDialog) {
MemberEditDialog(
onDismiss = { showAddDialog = false },
onConfirm = { name, description ->
onAddMember(name, description)
onConfirm = { name, description, iconResId ->
onAddMember(name, description, iconResId)
showAddDialog = false
}
)
}
// 编辑成员对话框
selectedMember?.let { member ->
MemberEditDialog(
onDismiss = { selectedMember = null },
onConfirm = { name, description ->
onUpdateMember(member, name, description)
onConfirm = { name, description, iconResId ->
onUpdateMember(member, name, description, iconResId)
selectedMember = null
},
initialName = member.name,
initialDescription = member.description
initialDescription = member.description,
initialIcon = member.icon
)
}
}
@ -118,45 +146,63 @@ fun MemberManagementDialog(
@Composable
private fun MemberEditDialog(
onDismiss: () -> Unit,
onConfirm: (String, String) -> Unit,
onConfirm: (String, String, Int?) -> Unit,
initialName: String = "",
initialDescription: String = ""
initialDescription: String = "",
initialIcon: Int? = null
) {
var name by remember { mutableStateOf(initialName) }
var description by remember { mutableStateOf(initialDescription) }
var selectedIcon by remember { mutableStateOf(initialIcon) }
var showIconPicker by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(if (initialName.isEmpty()) "添加成员" else "编辑成员") },
text = {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("成员名称") },
label = { Text("名称") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = description,
onValueChange = { description = it },
label = { Text("描述(可选)") },
label = { Text("描述") },
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 = {
TextButton(
onClick = {
if (name.isNotBlank()) {
onConfirm(name.trim(), description.trim())
onConfirm(name, description, selectedIcon)
}
},
enabled = name.isNotBlank()
}
) {
Text("确定")
}
@ -167,4 +213,17 @@ private fun MemberEditDialog(
}
}
)
if (showIconPicker) {
IconPickerDialog(
onDismiss = { showIconPicker = false },
onIconSelected = {
selectedIcon = it
showIconPicker = false
},
selectedIcon = selectedIcon,
isMemberIcon = true,
title = "选择成员图标"
)
}
}

View File

@ -1,20 +1,13 @@
package com.yovinchen.bookkeeping.ui.navigation
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
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.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
@ -22,20 +15,38 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.yovinchen.bookkeeping.R
import com.yovinchen.bookkeeping.model.AnalysisType
import com.yovinchen.bookkeeping.model.ThemeMode
import com.yovinchen.bookkeeping.ui.screen.*
import androidx.compose.material3.*
import androidx.compose.ui.unit.dp
import java.time.YearMonth
import java.time.format.DateTimeFormatter
sealed class Screen(
val route: String,
val title: String,
val icon: ImageVector? = null
val iconResId: Int? = null
) {
object Home : Screen("home", "记账", Icons.AutoMirrored.Filled.List)
object Analysis : Screen("analysis", "分析", Icons.Default.Analytics)
object Settings : Screen("settings", "设置", Icons.Default.Settings)
@Composable
fun icon(): ImageVector? = iconResId?.let { ImageVector.vectorResource(it) }
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(
"category_detail/{category}/{startMonth}/{endMonth}",
"分类详情"
@ -75,18 +86,33 @@ fun MainNavigation(
onThemeChange: (ThemeMode) -> Unit
) {
val navController = rememberNavController()
val items = listOf(
Screen.Home,
Screen.Analysis,
Screen.Settings
)
Scaffold(
bottomBar = {
NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Screen.bottomNavigationItems().forEach { screen ->
items.forEach { screen ->
val selected = currentRoute == screen.route
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) },
selected = currentRoute == screen.route,
selected = selected,
onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id) {

View File

@ -152,9 +152,13 @@ fun SettingsScreen(
CategoryManagementDialog(
onDismiss = { showCategoryDialog = false },
categories = categories,
onAddCategory = viewModel::addCategory,
onAddCategory = { name, type, iconResId ->
viewModel.addCategory(name, type, iconResId)
},
onDeleteCategory = viewModel::deleteCategory,
onUpdateCategory = viewModel::updateCategory,
onUpdateCategory = { category, newName, iconResId ->
viewModel.updateCategory(category, newName, iconResId)
},
selectedType = selectedType,
onTypeChange = viewModel::setSelectedCategoryType
)
@ -165,10 +169,16 @@ fun SettingsScreen(
MemberManagementDialog(
onDismiss = { showMemberDialog = false },
members = members,
onAddMember = memberViewModel::addMember,
onAddMember = { name, description, iconResId ->
memberViewModel.addMember(name, description, iconResId)
},
onDeleteMember = memberViewModel::deleteMember,
onUpdateMember = { member, name, description ->
memberViewModel.updateMember(member.copy(name = name, description = description))
onUpdateMember = { member, name, description, iconResId ->
memberViewModel.updateMember(member.copy(
name = name,
description = description,
icon = iconResId
))
}
)
}

View File

@ -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()
}
}

View File

@ -1,7 +1,6 @@
package com.yovinchen.bookkeeping.viewmodel
import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
@ -19,7 +18,6 @@ import java.util.*
@OptIn(ExperimentalCoroutinesApi::class)
class HomeViewModel(application: Application) : AndroidViewModel(application) {
private val TAG = "HomeViewModel"
private val bookkeepingDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao()
private val memberDao = BookkeepingDatabase.getDatabase(application).memberDao()
private val categoryDao = BookkeepingDatabase.getDatabase(application).categoryDao()

View File

@ -13,9 +13,9 @@ class MemberViewModel(application: Application) : AndroidViewModel(application)
val allMembers: Flow<List<Member>> = memberDao.getAllMembers()
fun addMember(name: String, description: String = "") {
fun addMember(name: String, description: String = "", iconResId: Int? = null) {
viewModelScope.launch {
val member = Member(name = name, description = description)
val member = Member(name = name, description = description, icon = iconResId)
memberDao.insertMember(member)
}
}

View File

@ -32,9 +32,9 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
_selectedCategoryType.value = type
}
fun addCategory(name: String, type: TransactionType) {
fun addCategory(name: String, type: TransactionType, iconResId: Int?) {
viewModelScope.launch {
val category = Category(name = name, type = type)
val category = Category(name = name, type = type, icon = iconResId)
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 {
val updatedCategory = category.copy(name = newName)
val updatedCategory = category.copy(name = newName, icon = iconResId)
dao.updateCategory(updatedCategory)
// 更新所有使用该类别的记录
dao.updateRecordCategories(category.name, newName)

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path

View File

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path