1.2.4稳定版 #3
@ -1,5 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EntryPointsManager">
|
||||
<list size="1">
|
||||
<item index="0" class="java.lang.String" itemvalue="androidx.compose.runtime.Composable" />
|
||||
</list>
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
|
15
README.md
@ -25,7 +25,7 @@
|
||||
|
||||
## 🗺 开发路线图
|
||||
|
||||
### 1. 基础记账 (已完成 ✨)
|
||||
### 0. 基础记账 (已完成 ✨)
|
||||
- [x] 收入/支出记录管理
|
||||
- [x] 分类管理系统
|
||||
- [x] 自定义日期选择器
|
||||
@ -33,41 +33,40 @@
|
||||
- [x] 深色/浅色主题切换
|
||||
- [x] 主题色自定义
|
||||
|
||||
### 2. 成员系统 (已完成 🎉)
|
||||
### 1. 成员系统 (已完成 🎉)
|
||||
- [x] 成员添加/编辑/删除
|
||||
- [x] 记账时选择相关成员
|
||||
- [x] 主页账单修改相关成员
|
||||
- [x] 成员消费统计
|
||||
|
||||
### 3. 数据分析 (进行中 🚀)
|
||||
### 2. 图表分析 (进行中 🚀)
|
||||
- [ ] 支出/收入趋势图表
|
||||
- [ ] 分类占比饼图
|
||||
- [ ] 月度/年度报表
|
||||
- [ ] 成员消费分析
|
||||
- [ ] 自定义统计周期
|
||||
|
||||
### 4. 数据管理 (计划中 📝)
|
||||
### 3. 数据管理 (计划中 📝)
|
||||
- [ ] 导出 CSV/Excel 功能
|
||||
- [ ] 云端备份支持
|
||||
- [ ] 数据迁移工具
|
||||
- [ ] 定期自动备份
|
||||
- [ ] 备份加密功能
|
||||
|
||||
### 5. 预算管理 (计划中 💡)
|
||||
### 4. 预算管理 (计划中 💡)
|
||||
- [ ] 月度预算设置
|
||||
- [ ] 预算超支提醒
|
||||
- [ ] 分类预算管理
|
||||
- [ ] 成员预算管理
|
||||
- [ ] 预算分析报告
|
||||
|
||||
### 6. 体验优化 (持续进行 🔄)
|
||||
### 5. 体验优化 (持续进行 🔄)
|
||||
- [x] 深色模式支持
|
||||
- [ ] 手势操作优化
|
||||
- [ ] 快速记账小组件
|
||||
- [ ] 多语言支持
|
||||
- [ ] 自定义主题
|
||||
|
||||
### 7. 性能提升 (持续进行 ⚡️)
|
||||
### 6. 性能提升 (持续进行 ⚡️)
|
||||
- [ ] 大数据量处理优化
|
||||
- [ ] 启动速度优化
|
||||
- [ ] 内存使用优化
|
||||
|
@ -16,8 +16,8 @@ android {
|
||||
applicationId = "com.yovinchen.bookkeeping"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "1.0.0"
|
||||
versionCode = 4
|
||||
versionName = "1.2.2"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
@ -89,6 +89,7 @@ dependencies {
|
||||
implementation(libs.androidx.room.common)
|
||||
implementation(libs.androidx.navigation.common.ktx)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.vision.internal.vkp)
|
||||
|
||||
// Room
|
||||
val roomVersion = "2.6.1"
|
||||
@ -96,6 +97,9 @@ dependencies {
|
||||
implementation("androidx.room:room-ktx:$roomVersion")
|
||||
ksp("androidx.room:room-compiler:$roomVersion")
|
||||
|
||||
// 图表库
|
||||
implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")
|
||||
implementation("androidx.compose.material:material-icons-extended:1.4.3")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
|
@ -3,6 +3,7 @@ package com.yovinchen.bookkeeping.data
|
||||
import androidx.room.*
|
||||
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
||||
import com.yovinchen.bookkeeping.model.Category
|
||||
import com.yovinchen.bookkeeping.model.MemberStat
|
||||
import com.yovinchen.bookkeeping.model.TransactionType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.util.Date
|
||||
@ -27,6 +28,116 @@ interface BookkeepingDao {
|
||||
@Query("SELECT SUM(amount) FROM bookkeeping_records WHERE type = :type AND (memberId = :memberId OR memberId IS NULL)")
|
||||
fun getTotalAmountByType(type: TransactionType, memberId: Int? = null): Flow<Double?>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM bookkeeping_records
|
||||
WHERE category = :category
|
||||
AND strftime('%Y-%m', datetime(date/1000, 'unixepoch')) = :yearMonth
|
||||
ORDER BY date DESC
|
||||
""")
|
||||
fun getRecordsByCategoryAndMonth(
|
||||
category: String,
|
||||
yearMonth: String
|
||||
): Flow<List<BookkeepingRecord>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM bookkeeping_records
|
||||
WHERE memberId IN (SELECT id FROM members WHERE name = :memberName)
|
||||
AND strftime('%Y-%m', datetime(date/1000, 'unixepoch')) = :yearMonth
|
||||
ORDER BY date DESC
|
||||
""")
|
||||
fun getRecordsByMemberAndMonth(
|
||||
memberName: String,
|
||||
yearMonth: String
|
||||
): Flow<List<BookkeepingRecord>>
|
||||
|
||||
@Query("""
|
||||
SELECT
|
||||
m.name as member,
|
||||
SUM(r.amount) as amount,
|
||||
COUNT(*) as count,
|
||||
(SUM(r.amount) * 100.0 / (SELECT SUM(amount) FROM bookkeeping_records WHERE category = :category AND strftime('%Y-%m', datetime(date/1000, 'unixepoch')) = :yearMonth)) as percentage
|
||||
FROM bookkeeping_records r
|
||||
JOIN members m ON r.memberId = m.id
|
||||
WHERE r.category = :category
|
||||
AND strftime('%Y-%m', datetime(r.date/1000, 'unixepoch')) = :yearMonth
|
||||
GROUP BY m.name
|
||||
ORDER BY amount DESC
|
||||
""")
|
||||
fun getMemberStatsByCategory(
|
||||
category: String,
|
||||
yearMonth: String
|
||||
): Flow<List<MemberStat>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM bookkeeping_records
|
||||
WHERE category = :category
|
||||
ORDER BY date DESC
|
||||
""")
|
||||
fun getRecordsByCategory(
|
||||
category: String
|
||||
): Flow<List<BookkeepingRecord>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM bookkeeping_records
|
||||
WHERE category = :category
|
||||
AND date BETWEEN :startDate AND :endDate
|
||||
ORDER BY date DESC
|
||||
""")
|
||||
fun getRecordsByCategoryAndDateRange(
|
||||
category: String,
|
||||
startDate: Date,
|
||||
endDate: Date
|
||||
): Flow<List<BookkeepingRecord>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM bookkeeping_records
|
||||
WHERE memberId IN (SELECT id FROM members WHERE name = :memberName)
|
||||
AND date BETWEEN :startDate AND :endDate
|
||||
AND (:transactionType IS NULL OR type = :transactionType)
|
||||
ORDER BY date DESC
|
||||
""")
|
||||
fun getRecordsByMemberAndDateRange(
|
||||
memberName: String,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
transactionType: TransactionType?
|
||||
): Flow<List<BookkeepingRecord>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM bookkeeping_records
|
||||
WHERE memberId IN (SELECT id FROM members WHERE name = :memberName)
|
||||
AND category = :category
|
||||
AND date BETWEEN :startDate AND :endDate
|
||||
AND (:transactionType IS NULL OR type = :transactionType)
|
||||
ORDER BY date DESC
|
||||
""")
|
||||
fun getRecordsByMemberCategoryAndDateRange(
|
||||
memberName: String,
|
||||
category: String,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
transactionType: TransactionType?
|
||||
): Flow<List<BookkeepingRecord>>
|
||||
|
||||
@Query("""
|
||||
SELECT
|
||||
m.name as member,
|
||||
SUM(r.amount) as amount,
|
||||
COUNT(*) as count,
|
||||
(SUM(r.amount) * 100.0 / (SELECT SUM(amount) FROM bookkeeping_records WHERE category = :category AND date BETWEEN :startDate AND :endDate)) as percentage
|
||||
FROM bookkeeping_records r
|
||||
JOIN members m ON r.memberId = m.id
|
||||
WHERE r.category = :category
|
||||
AND r.date BETWEEN :startDate AND :endDate
|
||||
GROUP BY m.name
|
||||
ORDER BY amount DESC
|
||||
""")
|
||||
fun getMemberStatsByCategoryAndDateRange(
|
||||
category: String,
|
||||
startDate: Date,
|
||||
endDate: Date
|
||||
): Flow<List<MemberStat>>
|
||||
|
||||
@Insert
|
||||
suspend fun insertRecord(record: BookkeepingRecord): Long
|
||||
|
||||
@ -53,4 +164,50 @@ interface BookkeepingDao {
|
||||
|
||||
@Query("UPDATE bookkeeping_records SET category = :newName WHERE category = :oldName")
|
||||
suspend fun updateRecordCategories(oldName: String, newName: String)
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM bookkeeping_records
|
||||
WHERE memberId IN (SELECT id FROM members WHERE name = :memberName)
|
||||
AND date BETWEEN :startDate AND :endDate
|
||||
AND (
|
||||
:transactionType IS NULL
|
||||
OR type = (
|
||||
CASE :transactionType
|
||||
WHEN 'INCOME' THEN 'INCOME'
|
||||
WHEN 'EXPENSE' THEN 'EXPENSE'
|
||||
END
|
||||
)
|
||||
)
|
||||
ORDER BY date DESC
|
||||
""")
|
||||
suspend fun getRecordsByMember(
|
||||
memberName: String,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
transactionType: TransactionType?
|
||||
): List<BookkeepingRecord>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM bookkeeping_records
|
||||
WHERE memberId IN (SELECT id FROM members WHERE name = :memberName)
|
||||
AND category = :category
|
||||
AND date BETWEEN :startDate AND :endDate
|
||||
AND (
|
||||
:transactionType IS NULL
|
||||
OR type = (
|
||||
CASE :transactionType
|
||||
WHEN 'INCOME' THEN 'INCOME'
|
||||
WHEN 'EXPENSE' THEN 'EXPENSE'
|
||||
END
|
||||
)
|
||||
)
|
||||
ORDER BY date DESC
|
||||
""")
|
||||
suspend fun getRecordsByMemberAndCategory(
|
||||
memberName: String,
|
||||
category: String,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
transactionType: TransactionType?
|
||||
): List<BookkeepingRecord>
|
||||
}
|
||||
|
@ -32,9 +32,9 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
||||
private const val TAG = "BookkeepingDatabase"
|
||||
|
||||
private val MIGRATION_1_2 = object : Migration(1, 2) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
// 创建成员表
|
||||
database.execSQL("""
|
||||
db.execSQL("""
|
||||
CREATE TABLE IF NOT EXISTS members (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
@ -43,20 +43,20 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
||||
""")
|
||||
|
||||
// 插入默认成员
|
||||
database.execSQL("""
|
||||
db.execSQL("""
|
||||
INSERT INTO members (name, description)
|
||||
VALUES ('自己', '默认成员')
|
||||
""")
|
||||
|
||||
// 修改记账记录表,添加成员ID字段
|
||||
database.execSQL("""
|
||||
db.execSQL("""
|
||||
ALTER TABLE bookkeeping_records
|
||||
ADD COLUMN memberId INTEGER DEFAULT NULL
|
||||
REFERENCES members(id) ON DELETE SET NULL
|
||||
""")
|
||||
|
||||
// 更新现有记录,将其关联到默认成员
|
||||
database.execSQL("""
|
||||
db.execSQL("""
|
||||
UPDATE bookkeeping_records
|
||||
SET memberId = (SELECT id FROM members WHERE name = '我自己')
|
||||
""")
|
||||
@ -64,9 +64,9 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
||||
}
|
||||
|
||||
private val MIGRATION_2_3 = object : Migration(2, 3) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
// 重新创建记账记录表
|
||||
database.execSQL("""
|
||||
db.execSQL("""
|
||||
CREATE TABLE IF NOT EXISTS bookkeeping_records_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
amount REAL NOT NULL,
|
||||
@ -80,19 +80,19 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
||||
""")
|
||||
|
||||
// 复制数据
|
||||
database.execSQL("""
|
||||
db.execSQL("""
|
||||
INSERT INTO bookkeeping_records_new (id, amount, type, category, description, date, memberId)
|
||||
SELECT id, amount, type, category, description, date, memberId FROM bookkeeping_records
|
||||
""")
|
||||
|
||||
// 删除旧表
|
||||
database.execSQL("DROP TABLE bookkeeping_records")
|
||||
db.execSQL("DROP TABLE bookkeeping_records")
|
||||
|
||||
// 重命名新表
|
||||
database.execSQL("ALTER TABLE bookkeeping_records_new RENAME TO bookkeeping_records")
|
||||
db.execSQL("ALTER TABLE bookkeeping_records_new RENAME TO bookkeeping_records")
|
||||
|
||||
// 重新创建分类表
|
||||
database.execSQL("""
|
||||
db.execSQL("""
|
||||
CREATE TABLE IF NOT EXISTS categories_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
@ -101,16 +101,16 @@ abstract class BookkeepingDatabase : RoomDatabase() {
|
||||
""")
|
||||
|
||||
// 复制分类数据
|
||||
database.execSQL("""
|
||||
db.execSQL("""
|
||||
INSERT INTO categories_new (id, name, type)
|
||||
SELECT id, name, type FROM categories
|
||||
""")
|
||||
|
||||
// 删除旧表
|
||||
database.execSQL("DROP TABLE categories")
|
||||
db.execSQL("DROP TABLE categories")
|
||||
|
||||
// 重命名新表
|
||||
database.execSQL("ALTER TABLE categories_new RENAME TO categories")
|
||||
db.execSQL("ALTER TABLE categories_new RENAME TO categories")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package com.yovinchen.bookkeeping.data
|
||||
import androidx.room.TypeConverter
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
|
||||
class Converters {
|
||||
private val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME
|
||||
@ -18,4 +19,14 @@ class Converters {
|
||||
fun dateToTimestamp(date: LocalDateTime?): String? {
|
||||
return date?.format(formatter)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromDate(value: Date?): String? {
|
||||
return value?.time?.toString()
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun toDate(timestamp: String?): Date? {
|
||||
return timestamp?.let { Date(it.toLong()) }
|
||||
}
|
||||
}
|
||||
|
@ -12,5 +12,6 @@ data class Record(
|
||||
val category: String,
|
||||
val description: String,
|
||||
val dateTime: LocalDateTime = LocalDateTime.now(),
|
||||
val isExpense: Boolean = true
|
||||
val isExpense: Boolean = true,
|
||||
val member: String = "Default"
|
||||
)
|
||||
|
@ -0,0 +1,7 @@
|
||||
package com.yovinchen.bookkeeping.model
|
||||
|
||||
enum class AnalysisType {
|
||||
EXPENSE,
|
||||
INCOME,
|
||||
TREND
|
||||
}
|
@ -2,6 +2,7 @@ package com.yovinchen.bookkeeping.model
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverter
|
||||
import androidx.room.TypeConverters
|
||||
@ -43,6 +44,9 @@ class Converters {
|
||||
childColumns = ["memberId"],
|
||||
onDelete = ForeignKey.SET_NULL
|
||||
)
|
||||
],
|
||||
indices = [
|
||||
Index(value = ["memberId"])
|
||||
]
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
@ -0,0 +1,8 @@
|
||||
package com.yovinchen.bookkeeping.model
|
||||
|
||||
data class CategoryStat(
|
||||
val category: String,
|
||||
val amount: Double,
|
||||
val count: Int = 0,
|
||||
val percentage: Double = 0.0
|
||||
)
|
@ -0,0 +1,17 @@
|
||||
package com.yovinchen.bookkeeping.model
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
|
||||
data class MemberStat(
|
||||
@ColumnInfo(name = "member")
|
||||
val member: String,
|
||||
|
||||
@ColumnInfo(name = "amount")
|
||||
val amount: Double,
|
||||
|
||||
@ColumnInfo(name = "count")
|
||||
val count: Int,
|
||||
|
||||
@ColumnInfo(name = "percentage")
|
||||
val percentage: Double = 0.0
|
||||
)
|
@ -0,0 +1,82 @@
|
||||
package com.yovinchen.bookkeeping.ui.components
|
||||
|
||||
import android.graphics.Color as AndroidColor
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.github.mikephil.charting.charts.PieChart
|
||||
import com.github.mikephil.charting.data.Entry
|
||||
import com.github.mikephil.charting.data.PieData
|
||||
import com.github.mikephil.charting.data.PieDataSet
|
||||
import com.github.mikephil.charting.data.PieEntry
|
||||
import com.github.mikephil.charting.formatter.PercentFormatter
|
||||
import com.github.mikephil.charting.highlight.Highlight
|
||||
import com.github.mikephil.charting.listener.OnChartValueSelectedListener
|
||||
import com.github.mikephil.charting.utils.ColorTemplate
|
||||
|
||||
@Composable
|
||||
fun CategoryPieChart(
|
||||
categoryData: List<Pair<String, Float>>,
|
||||
memberData: List<Pair<String, Float>>,
|
||||
currentViewMode: Boolean = false, // false 为分类视图,true 为成员视图
|
||||
modifier: Modifier = Modifier,
|
||||
onCategoryClick: (String) -> Unit = {}
|
||||
) {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface.toArgb()
|
||||
val data = if (currentViewMode) memberData else categoryData
|
||||
|
||||
AndroidView(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(300.dp),
|
||||
factory = { context ->
|
||||
PieChart(context).apply {
|
||||
description.isEnabled = false
|
||||
setUsePercentValues(true)
|
||||
setDrawEntryLabels(true)
|
||||
legend.isEnabled = false
|
||||
isDrawHoleEnabled = true
|
||||
holeRadius = 40f
|
||||
setHoleColor(AndroidColor.TRANSPARENT)
|
||||
setTransparentCircleRadius(45f)
|
||||
setEntryLabelColor(textColor)
|
||||
setEntryLabelTextSize(12f)
|
||||
setCenterTextColor(textColor)
|
||||
|
||||
setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
|
||||
override fun onValueSelected(e: Entry?, h: Highlight?) {
|
||||
e?.let {
|
||||
if (it is PieEntry) {
|
||||
onCategoryClick(it.label ?: return)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNothingSelected() {}
|
||||
})
|
||||
}
|
||||
},
|
||||
update = { chart ->
|
||||
val entries = data.map { (label, amount) ->
|
||||
PieEntry(amount, label)
|
||||
}
|
||||
|
||||
val dataSet = PieDataSet(entries, "").apply {
|
||||
colors = ColorTemplate.MATERIAL_COLORS.toList()
|
||||
valueTextSize = 14f
|
||||
valueFormatter = PercentFormatter(chart)
|
||||
valueTextColor = textColor
|
||||
setDrawValues(true)
|
||||
}
|
||||
|
||||
val pieData = PieData(dataSet)
|
||||
chart.data = pieData
|
||||
chart.invalidate()
|
||||
}
|
||||
)
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.yovinchen.bookkeeping.ui.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.yovinchen.bookkeeping.model.CategoryStat
|
||||
import com.yovinchen.bookkeeping.model.MemberStat
|
||||
import java.text.NumberFormat
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
fun CategoryStatItem(
|
||||
stat: Any,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val name = when (stat) {
|
||||
is CategoryStat -> stat.category
|
||||
is MemberStat -> stat.member
|
||||
else -> return
|
||||
}
|
||||
|
||||
val amount = when (stat) {
|
||||
is CategoryStat -> stat.amount
|
||||
is MemberStat -> stat.amount
|
||||
else -> return
|
||||
}
|
||||
|
||||
val count = when (stat) {
|
||||
is CategoryStat -> stat.count
|
||||
is MemberStat -> stat.count
|
||||
else -> return
|
||||
}
|
||||
|
||||
val percentage = when (stat) {
|
||||
is CategoryStat -> stat.percentage
|
||||
is MemberStat -> stat.percentage
|
||||
else -> return
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp)
|
||||
.clickable(onClick = onClick),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = name,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
Text(
|
||||
text = "${count}笔 · ${String.format("%.1f%%", percentage)}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = NumberFormat.getCurrencyInstance(Locale.CHINA).format(amount),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.yovinchen.bookkeeping.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import java.time.YearMonth
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DateRangePicker(
|
||||
startMonth: YearMonth,
|
||||
endMonth: YearMonth,
|
||||
onStartMonthSelected: (YearMonth) -> Unit,
|
||||
onEndMonthSelected: (YearMonth) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
var showStartMonthPicker by remember { mutableStateOf(false) }
|
||||
var showEndMonthPicker by remember { mutableStateOf(false) }
|
||||
val formatter = DateTimeFormatter.ofPattern("yyyy年MM月")
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Button(onClick = { showStartMonthPicker = true }) {
|
||||
Text(startMonth.format(formatter))
|
||||
}
|
||||
Text("至")
|
||||
Button(onClick = { showEndMonthPicker = true }) {
|
||||
Text(endMonth.format(formatter))
|
||||
}
|
||||
}
|
||||
|
||||
if (showStartMonthPicker) {
|
||||
MonthYearPicker(
|
||||
selectedMonth = startMonth,
|
||||
onMonthSelected = {
|
||||
onStartMonthSelected(it)
|
||||
showStartMonthPicker = false
|
||||
},
|
||||
onDismiss = { showStartMonthPicker = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showEndMonthPicker) {
|
||||
MonthYearPicker(
|
||||
selectedMonth = endMonth,
|
||||
onMonthSelected = {
|
||||
onEndMonthSelected(it)
|
||||
showEndMonthPicker = false
|
||||
},
|
||||
onDismiss = { showEndMonthPicker = false }
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package com.yovinchen.bookkeeping.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import java.time.YearMonth
|
||||
|
||||
@Composable
|
||||
fun MonthYearPicker(
|
||||
selectedMonth: YearMonth,
|
||||
onMonthSelected: (YearMonth) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
var year by remember { mutableStateOf(selectedMonth.year) }
|
||||
var month by remember { mutableStateOf(selectedMonth.monthValue) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("选择月份") },
|
||||
text = {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// 年份选择
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text("年份:")
|
||||
OutlinedButton(
|
||||
onClick = { year-- }
|
||||
) {
|
||||
Text("-")
|
||||
}
|
||||
Text(year.toString())
|
||||
OutlinedButton(
|
||||
onClick = { year++ }
|
||||
) {
|
||||
Text("+")
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// 月份选择
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text("月份:")
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
if (month > 1) month--
|
||||
}
|
||||
) {
|
||||
Text("-")
|
||||
}
|
||||
Text(month.toString())
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
if (month < 12) month++
|
||||
}
|
||||
) {
|
||||
Text("+")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onMonthSelected(YearMonth.of(year, month))
|
||||
}
|
||||
) {
|
||||
Text("确定")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("取消")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
@ -52,10 +52,8 @@ fun RecordItem(
|
||||
Text(
|
||||
text = buildString {
|
||||
append(timeFormat.format(record.date))
|
||||
if (member != null && member.name != "自己") {
|
||||
append(" | ")
|
||||
append(member.name)
|
||||
}
|
||||
append(" | ")
|
||||
append(member?.name ?: "自己")
|
||||
if (record.description.isNotEmpty()) {
|
||||
append(" | ")
|
||||
append(record.description)
|
||||
|
@ -2,40 +2,70 @@ package com.yovinchen.bookkeeping.ui.navigation
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.NavigationBarItemDefaults
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import com.yovinchen.bookkeeping.model.AnalysisType
|
||||
import com.yovinchen.bookkeeping.model.ThemeMode
|
||||
import com.yovinchen.bookkeeping.ui.screen.HomeScreen
|
||||
import com.yovinchen.bookkeeping.ui.screen.SettingsScreen
|
||||
import com.yovinchen.bookkeeping.ui.screen.*
|
||||
import java.time.YearMonth
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
sealed class Screen(val route: String, val icon: @Composable () -> Unit, val label: String) {
|
||||
object Home : Screen(
|
||||
route = "home",
|
||||
icon = { Icon(Icons.Default.Home, contentDescription = "主页") },
|
||||
label = "主页"
|
||||
)
|
||||
object Settings : Screen(
|
||||
route = "settings",
|
||||
icon = { Icon(Icons.Default.Settings, contentDescription = "设置") },
|
||||
label = "设置"
|
||||
)
|
||||
sealed class Screen(
|
||||
val route: String,
|
||||
val title: String,
|
||||
val icon: ImageVector? = null
|
||||
) {
|
||||
object Home : Screen("home", "记账", Icons.AutoMirrored.Filled.List)
|
||||
object Analysis : Screen("analysis", "分析", Icons.Default.Analytics)
|
||||
object Settings : Screen("settings", "设置", Icons.Default.Settings)
|
||||
object CategoryDetail : Screen(
|
||||
"category_detail/{category}/{startMonth}/{endMonth}",
|
||||
"分类详情"
|
||||
) {
|
||||
fun createRoute(
|
||||
category: String,
|
||||
startMonth: YearMonth,
|
||||
endMonth: YearMonth
|
||||
): String {
|
||||
return "category_detail/$category/${startMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))}/${endMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))}"
|
||||
}
|
||||
}
|
||||
object MemberDetail : Screen(
|
||||
"member_detail/{memberName}/{category}/{startMonth}/{endMonth}?type={type}",
|
||||
"成员详情"
|
||||
) {
|
||||
fun createRoute(
|
||||
memberName: String,
|
||||
category: String,
|
||||
startMonth: YearMonth,
|
||||
endMonth: YearMonth,
|
||||
type: AnalysisType
|
||||
): String {
|
||||
return "member_detail/$memberName/$category/${startMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))}/${endMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))}?type=${type.name}"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun bottomNavigationItems() = listOf(Home, Analysis, Settings)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -45,22 +75,18 @@ fun MainNavigation(
|
||||
onThemeChange: (ThemeMode) -> Unit
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
val items = listOf(Screen.Home, Screen.Settings)
|
||||
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
NavigationBar(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
) {
|
||||
NavigationBar {
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentDestination = navBackStackEntry?.destination
|
||||
val currentRoute = navBackStackEntry?.destination?.route
|
||||
|
||||
items.forEach { screen ->
|
||||
Screen.bottomNavigationItems().forEach { screen ->
|
||||
NavigationBarItem(
|
||||
icon = screen.icon,
|
||||
label = { Text(screen.label) },
|
||||
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
|
||||
icon = { Icon(screen.icon!!, contentDescription = screen.title) },
|
||||
label = { Text(screen.title) },
|
||||
selected = currentRoute == screen.route,
|
||||
onClick = {
|
||||
navController.navigate(screen.route) {
|
||||
popUpTo(navController.graph.findStartDestination().id) {
|
||||
@ -69,33 +95,107 @@ fun MainNavigation(
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
selectedIconColor = MaterialTheme.colorScheme.primary,
|
||||
selectedTextColor = MaterialTheme.colorScheme.primary,
|
||||
unselectedIconColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
|
||||
unselectedTextColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
|
||||
indicatorColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
) { innerPadding ->
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Screen.Home.route,
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
composable(Screen.Home.route) {
|
||||
HomeScreen()
|
||||
composable(Screen.Home.route) { HomeScreen() }
|
||||
|
||||
composable(Screen.Analysis.route) {
|
||||
AnalysisScreen(
|
||||
onNavigateToCategoryDetail = { category, startMonth, endMonth ->
|
||||
navController.navigate(Screen.CategoryDetail.createRoute(category, startMonth, endMonth))
|
||||
},
|
||||
onNavigateToMemberDetail = { memberName, startMonth, endMonth, analysisType ->
|
||||
navController.navigate(Screen.MemberDetail.createRoute(memberName, "", startMonth, endMonth, analysisType))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
composable(Screen.Settings.route) {
|
||||
SettingsScreen(
|
||||
currentTheme = currentTheme,
|
||||
onThemeChange = onThemeChange
|
||||
)
|
||||
}
|
||||
|
||||
composable(
|
||||
route = Screen.CategoryDetail.route,
|
||||
arguments = listOf(
|
||||
navArgument("category") { type = NavType.StringType },
|
||||
navArgument("startMonth") { type = NavType.StringType },
|
||||
navArgument("endMonth") { type = NavType.StringType }
|
||||
)
|
||||
) { backStackEntry ->
|
||||
val category = backStackEntry.arguments?.getString("category") ?: ""
|
||||
val startMonth = YearMonth.parse(
|
||||
backStackEntry.arguments?.getString("startMonth") ?: "",
|
||||
DateTimeFormatter.ofPattern("yyyy-MM")
|
||||
)
|
||||
val endMonth = YearMonth.parse(
|
||||
backStackEntry.arguments?.getString("endMonth") ?: "",
|
||||
DateTimeFormatter.ofPattern("yyyy-MM")
|
||||
)
|
||||
CategoryDetailScreen(
|
||||
category = category,
|
||||
startMonth = startMonth,
|
||||
endMonth = endMonth,
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToMemberDetail = { memberName ->
|
||||
navController.navigate(
|
||||
Screen.MemberDetail.createRoute(
|
||||
memberName = memberName,
|
||||
category = category,
|
||||
startMonth = startMonth,
|
||||
endMonth = endMonth,
|
||||
type = AnalysisType.EXPENSE
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable(
|
||||
route = Screen.MemberDetail.route,
|
||||
arguments = listOf(
|
||||
navArgument("memberName") { type = NavType.StringType },
|
||||
navArgument("category") { type = NavType.StringType },
|
||||
navArgument("startMonth") { type = NavType.StringType },
|
||||
navArgument("endMonth") { type = NavType.StringType },
|
||||
navArgument("type") {
|
||||
type = NavType.StringType
|
||||
defaultValue = AnalysisType.EXPENSE.name
|
||||
}
|
||||
)
|
||||
) { backStackEntry ->
|
||||
val memberName = backStackEntry.arguments?.getString("memberName") ?: ""
|
||||
val category = backStackEntry.arguments?.getString("category") ?: ""
|
||||
val startMonth = YearMonth.parse(
|
||||
backStackEntry.arguments?.getString("startMonth") ?: "",
|
||||
DateTimeFormatter.ofPattern("yyyy-MM")
|
||||
)
|
||||
val endMonth = YearMonth.parse(
|
||||
backStackEntry.arguments?.getString("endMonth") ?: "",
|
||||
DateTimeFormatter.ofPattern("yyyy-MM")
|
||||
)
|
||||
val type = AnalysisType.valueOf(
|
||||
backStackEntry.arguments?.getString("type") ?: AnalysisType.EXPENSE.name
|
||||
)
|
||||
MemberDetailScreen(
|
||||
memberName = memberName,
|
||||
category = category,
|
||||
startMonth = startMonth,
|
||||
endMonth = endMonth,
|
||||
analysisType = type,
|
||||
onNavigateBack = { navController.popBackStack() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,185 @@
|
||||
package com.yovinchen.bookkeeping.ui.screen
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.yovinchen.bookkeeping.model.AnalysisType
|
||||
import com.yovinchen.bookkeeping.model.CategoryStat
|
||||
import com.yovinchen.bookkeeping.model.MemberStat
|
||||
import com.yovinchen.bookkeeping.ui.components.CategoryPieChart
|
||||
import com.yovinchen.bookkeeping.ui.components.CategoryStatItem
|
||||
import com.yovinchen.bookkeeping.ui.components.DateRangePicker
|
||||
import com.yovinchen.bookkeeping.viewmodel.AnalysisViewModel
|
||||
import java.time.YearMonth
|
||||
|
||||
enum class ViewMode {
|
||||
CATEGORY, MEMBER
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AnalysisScreen(
|
||||
onNavigateToCategoryDetail: (String, YearMonth, YearMonth) -> Unit,
|
||||
onNavigateToMemberDetail: (String, YearMonth, YearMonth, AnalysisType) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val viewModel: AnalysisViewModel = viewModel()
|
||||
val startMonth by viewModel.startMonth.collectAsState()
|
||||
val endMonth by viewModel.endMonth.collectAsState()
|
||||
val selectedAnalysisType by viewModel.selectedAnalysisType.collectAsState()
|
||||
val categoryStats by viewModel.categoryStats.collectAsState()
|
||||
val memberStats by viewModel.memberStats.collectAsState()
|
||||
|
||||
var showViewModeMenu by remember { mutableStateOf(false) }
|
||||
var currentViewMode by rememberSaveable { mutableStateOf(ViewMode.CATEGORY) }
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.fillMaxSize()
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
// 时间区间选择
|
||||
DateRangePicker(
|
||||
startMonth = startMonth,
|
||||
endMonth = endMonth,
|
||||
onStartMonthSelected = viewModel::setStartMonth,
|
||||
onEndMonthSelected = viewModel::setEndMonth
|
||||
)
|
||||
|
||||
// 分析类型和视图模式选择行
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 分类/成员切换下拉菜单
|
||||
Box {
|
||||
Button(
|
||||
onClick = { showViewModeMenu = true }
|
||||
) {
|
||||
Text(if (currentViewMode == ViewMode.CATEGORY) "分类" else "成员")
|
||||
Icon(Icons.Default.ArrowDropDown, "切换视图")
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = showViewModeMenu,
|
||||
onDismissRequest = { showViewModeMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("分类") },
|
||||
onClick = {
|
||||
currentViewMode = ViewMode.CATEGORY
|
||||
showViewModeMenu = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("成员") },
|
||||
onClick = {
|
||||
currentViewMode = ViewMode.MEMBER
|
||||
showViewModeMenu = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 类型切换
|
||||
Row {
|
||||
AnalysisType.entries.forEach { type ->
|
||||
FilterChip(
|
||||
selected = selectedAnalysisType == type,
|
||||
onClick = { viewModel.setAnalysisType(type) },
|
||||
label = {
|
||||
Text(
|
||||
when (type) {
|
||||
AnalysisType.EXPENSE -> "支出"
|
||||
AnalysisType.INCOME -> "收入"
|
||||
AnalysisType.TREND -> "趋势"
|
||||
}
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用LazyColumn包含饼图和列表
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(16.dp)
|
||||
) {
|
||||
// 添加饼图作为第一个项目
|
||||
if (selectedAnalysisType != AnalysisType.TREND) {
|
||||
item {
|
||||
CategoryPieChart(
|
||||
categoryData = categoryStats.map { Pair(it.category, it.percentage.toFloat()) },
|
||||
memberData = memberStats.map { Pair(it.member, it.percentage.toFloat()) },
|
||||
currentViewMode = currentViewMode == ViewMode.MEMBER,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp)
|
||||
.padding(bottom = 16.dp),
|
||||
onCategoryClick = { category ->
|
||||
if (currentViewMode == ViewMode.CATEGORY) {
|
||||
onNavigateToCategoryDetail(category, startMonth, endMonth)
|
||||
} else {
|
||||
onNavigateToMemberDetail(category, startMonth, endMonth, selectedAnalysisType)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加统计列表项目
|
||||
items(if (currentViewMode == ViewMode.CATEGORY) categoryStats else memberStats) { stat ->
|
||||
val category = if (stat is CategoryStat) stat.category else null
|
||||
val member = if (stat is MemberStat) stat.member else null
|
||||
|
||||
CategoryStatItem(
|
||||
stat = stat,
|
||||
onClick = {
|
||||
if (currentViewMode == ViewMode.CATEGORY && category != null) {
|
||||
onNavigateToCategoryDetail(category, startMonth, endMonth)
|
||||
} else if (currentViewMode == ViewMode.MEMBER && member != null) {
|
||||
onNavigateToMemberDetail(member, startMonth, endMonth, selectedAnalysisType)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,291 @@
|
||||
package com.yovinchen.bookkeeping.ui.screen
|
||||
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
||||
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
||||
import com.yovinchen.bookkeeping.model.MemberStat
|
||||
import com.yovinchen.bookkeeping.model.TransactionType
|
||||
import com.yovinchen.bookkeeping.ui.components.CategoryPieChart
|
||||
import com.yovinchen.bookkeeping.ui.components.RecordItem
|
||||
import com.yovinchen.bookkeeping.viewmodel.CategoryDetailViewModel
|
||||
import com.yovinchen.bookkeeping.viewmodel.CategoryDetailViewModelFactory
|
||||
import java.text.NumberFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.YearMonth
|
||||
import java.util.Locale
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CategoryDetailScreen(
|
||||
category: String,
|
||||
startMonth: YearMonth,
|
||||
endMonth: YearMonth,
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToMemberDetail: (String) -> Unit,
|
||||
viewModel: CategoryDetailViewModel = viewModel(
|
||||
factory = CategoryDetailViewModelFactory(
|
||||
database = BookkeepingDatabase.getDatabase(LocalContext.current),
|
||||
category = category,
|
||||
startMonth = startMonth,
|
||||
endMonth = endMonth
|
||||
)
|
||||
),
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val records by viewModel.records.collectAsState()
|
||||
val memberStats by viewModel.memberStats.collectAsState()
|
||||
val total by viewModel.total.collectAsState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(category) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "返回")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// 第一部分:总支出
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = if (records.isNotEmpty() && records.first().type == TransactionType.INCOME) "总收入" else "总支出",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = NumberFormat.getCurrencyInstance(Locale.CHINA).format(total),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 第二部分:成员统计
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "成员分布",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
// 饼状图
|
||||
CategoryPieChart(
|
||||
categoryData = emptyList(),
|
||||
memberData = memberStats.map { Pair(it.member, it.percentage.toFloat()) },
|
||||
currentViewMode = true,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp),
|
||||
onCategoryClick = { memberName ->
|
||||
onNavigateToMemberDetail(memberName)
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
HorizontalDivider()
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// 成员列表
|
||||
Column {
|
||||
memberStats.forEach { stat ->
|
||||
MemberStatItem(
|
||||
stat = stat,
|
||||
onClick = { onNavigateToMemberDetail(stat.member) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 第三部分:详细信息
|
||||
item {
|
||||
Text(
|
||||
text = "详细记录",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// 按日期分组的记录列表
|
||||
val groupedRecords = records.groupBy { record ->
|
||||
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(record.date)
|
||||
}.toSortedMap(compareByDescending { it })
|
||||
|
||||
groupedRecords.forEach { (date, dayRecords) ->
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// 日期标题和总金额
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = date,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(
|
||||
text = NumberFormat.getCurrencyInstance(Locale.CHINA)
|
||||
.format(dayRecords.sumOf { it.amount }),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// 当天的记录列表
|
||||
dayRecords.forEach { record ->
|
||||
RecordItem(record = record)
|
||||
if (record != dayRecords.last()) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
color = MaterialTheme.colorScheme.outlineVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MemberStatItem(
|
||||
stat: MemberStat,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = { Text(stat.member) },
|
||||
supportingContent = {
|
||||
Text(
|
||||
buildString {
|
||||
append("金额: ¥%.2f".format(stat.amount))
|
||||
append(" | ")
|
||||
append("次数: ${stat.count}")
|
||||
append(" | ")
|
||||
append("占比: %.1f%%".format(stat.percentage))
|
||||
}
|
||||
)
|
||||
},
|
||||
modifier = modifier.clickable(onClick = onClick)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RecordItem(
|
||||
record: BookkeepingRecord,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = record.memberId.toString(), // 暂时显示 memberId,后续可以通过 MemberDao 获取成员名称
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
if (record.description.isNotBlank()) {
|
||||
Text(
|
||||
text = record.description,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = SimpleDateFormat("HH:mm", Locale.getDefault()).format(record.date),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = NumberFormat.getCurrencyInstance(Locale.CHINA).format(record.amount),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
package com.yovinchen.bookkeeping.ui.screen
|
||||
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.yovinchen.bookkeeping.data.Record
|
||||
import com.yovinchen.bookkeeping.model.AnalysisType
|
||||
import com.yovinchen.bookkeeping.model.TransactionType
|
||||
import com.yovinchen.bookkeeping.ui.components.RecordItem
|
||||
import com.yovinchen.bookkeeping.viewmodel.MemberDetailViewModel
|
||||
import java.text.NumberFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.YearMonth
|
||||
import java.util.Locale
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MemberDetailScreen(
|
||||
memberName: String,
|
||||
startMonth: YearMonth,
|
||||
endMonth: YearMonth,
|
||||
category: String = "",
|
||||
analysisType: AnalysisType = AnalysisType.EXPENSE,
|
||||
onNavigateBack: () -> Unit,
|
||||
viewModel: MemberDetailViewModel = viewModel()
|
||||
) {
|
||||
val records by viewModel.memberRecords.collectAsState(initial = emptyList())
|
||||
val totalAmount by viewModel.totalAmount.collectAsState(initial = 0.0)
|
||||
|
||||
LaunchedEffect(memberName, category, startMonth, endMonth, analysisType) {
|
||||
viewModel.loadMemberRecords(
|
||||
memberName = memberName,
|
||||
category = category,
|
||||
startMonth = startMonth,
|
||||
endMonth = endMonth,
|
||||
analysisType = analysisType
|
||||
)
|
||||
}
|
||||
|
||||
val groupedRecords = remember(records) {
|
||||
records.groupBy { record ->
|
||||
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(record.date)
|
||||
}.toSortedMap(reverseOrder())
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(memberName)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, "返回")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
) {
|
||||
// 第一层:总金额卡片
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = if (records.isNotEmpty() && records.first().type == TransactionType.INCOME) "总收入" else "总支出",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = NumberFormat.getCurrencyInstance(Locale.CHINA)
|
||||
.format(totalAmount),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 第二层:按日期分组的记录列表
|
||||
groupedRecords.forEach { (date, dayRecords) ->
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = date,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Text(
|
||||
text = NumberFormat.getCurrencyInstance(Locale.CHINA)
|
||||
.format(dayRecords.sumOf { it.amount }),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
dayRecords.forEach { record ->
|
||||
RecordItem(record = record)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RecordItem(record: Record) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column {
|
||||
if (record.description.isNotBlank()) {
|
||||
Text(
|
||||
text = record.description,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = SimpleDateFormat("HH:mm", Locale.getDefault())
|
||||
.format(record.dateTime),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = NumberFormat.getCurrencyInstance(Locale.CHINA)
|
||||
.format(record.amount),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package com.yovinchen.bookkeeping.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
||||
import com.yovinchen.bookkeeping.model.AnalysisType
|
||||
import com.yovinchen.bookkeeping.model.CategoryStat
|
||||
import com.yovinchen.bookkeeping.model.MemberStat
|
||||
import com.yovinchen.bookkeeping.model.TransactionType
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.LocalDateTime
|
||||
import java.time.YearMonth
|
||||
import java.time.ZoneId
|
||||
import java.util.*
|
||||
|
||||
class AnalysisViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val recordDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao()
|
||||
private val memberDao = BookkeepingDatabase.getDatabase(application).memberDao()
|
||||
|
||||
private val _startMonth = MutableStateFlow(YearMonth.now())
|
||||
val startMonth: StateFlow<YearMonth> = _startMonth.asStateFlow()
|
||||
|
||||
private val _endMonth = MutableStateFlow(YearMonth.now())
|
||||
val endMonth: StateFlow<YearMonth> = _endMonth.asStateFlow()
|
||||
|
||||
private val _selectedAnalysisType = MutableStateFlow(AnalysisType.EXPENSE)
|
||||
val selectedAnalysisType: StateFlow<AnalysisType> = _selectedAnalysisType.asStateFlow()
|
||||
|
||||
private val _categoryStats = MutableStateFlow<List<CategoryStat>>(emptyList())
|
||||
val categoryStats: StateFlow<List<CategoryStat>> = _categoryStats.asStateFlow()
|
||||
|
||||
private val _memberStats = MutableStateFlow<List<MemberStat>>(emptyList())
|
||||
val memberStats: StateFlow<List<MemberStat>> = _memberStats.asStateFlow()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
combine(startMonth, endMonth, selectedAnalysisType) { start, end, type ->
|
||||
Triple(start, end, type)
|
||||
}.collect { (start, end, type) ->
|
||||
updateStats(start, end, type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setStartMonth(month: YearMonth) {
|
||||
_startMonth.value = month
|
||||
}
|
||||
|
||||
fun setEndMonth(month: YearMonth) {
|
||||
_endMonth.value = month
|
||||
}
|
||||
|
||||
fun setAnalysisType(type: AnalysisType) {
|
||||
_selectedAnalysisType.value = type
|
||||
}
|
||||
|
||||
private suspend fun updateStats(startMonth: YearMonth, endMonth: YearMonth, type: AnalysisType) {
|
||||
val records = recordDao.getAllRecords().first()
|
||||
val monthRecords = records.filter {
|
||||
val recordDate = Date(it.date.time)
|
||||
val localDateTime = LocalDateTime.ofInstant(recordDate.toInstant(), ZoneId.systemDefault())
|
||||
val yearMonth = YearMonth.from(localDateTime)
|
||||
yearMonth.isAfter(startMonth.minusMonths(1)) &&
|
||||
yearMonth.isBefore(endMonth.plusMonths(1)) &&
|
||||
it.type == when(type) {
|
||||
AnalysisType.EXPENSE -> TransactionType.EXPENSE
|
||||
AnalysisType.INCOME -> TransactionType.INCOME
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
// 按分类统计
|
||||
val categoryMap = monthRecords.groupBy { it.category }
|
||||
val categoryStats = categoryMap.map { (category, records) ->
|
||||
CategoryStat(
|
||||
category = category,
|
||||
amount = records.sumOf { it.amount },
|
||||
count = records.size
|
||||
)
|
||||
}.sortedByDescending { it.amount }
|
||||
|
||||
// 计算分类总额和百分比
|
||||
val categoryTotal = categoryStats.sumOf { it.amount }
|
||||
val categoryStatsWithPercentage = categoryStats.map {
|
||||
it.copy(percentage = if (categoryTotal > 0) it.amount / categoryTotal * 100 else 0.0)
|
||||
}
|
||||
|
||||
// 按成员统计
|
||||
val members = memberDao.getAllMembers().first()
|
||||
val memberMap = monthRecords.groupBy { record ->
|
||||
members.find { it.id == record.memberId }?.name ?: "未分配"
|
||||
}
|
||||
|
||||
val memberStats = memberMap.map { (memberName, records) ->
|
||||
MemberStat(
|
||||
member = memberName,
|
||||
amount = records.sumOf { it.amount },
|
||||
count = records.size
|
||||
)
|
||||
}.sortedByDescending { it.amount }
|
||||
|
||||
// 计算成员总额和百分比
|
||||
val memberTotal = memberStats.sumOf { it.amount }
|
||||
val memberStatsWithPercentage = memberStats.map {
|
||||
it.copy(percentage = if (memberTotal > 0) it.amount / memberTotal * 100 else 0.0)
|
||||
}
|
||||
|
||||
_categoryStats.value = categoryStatsWithPercentage
|
||||
_memberStats.value = memberStatsWithPercentage
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package com.yovinchen.bookkeeping.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
||||
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
||||
import com.yovinchen.bookkeeping.model.MemberStat
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import java.time.YearMonth
|
||||
import java.time.ZoneId
|
||||
import java.util.Date
|
||||
|
||||
class CategoryDetailViewModel(
|
||||
database: BookkeepingDatabase,
|
||||
category: String,
|
||||
startMonth: YearMonth,
|
||||
endMonth: YearMonth
|
||||
) : ViewModel() {
|
||||
private val recordDao = database.bookkeepingDao()
|
||||
|
||||
private val _records = MutableStateFlow<List<BookkeepingRecord>>(emptyList())
|
||||
val records: StateFlow<List<BookkeepingRecord>> = _records.asStateFlow()
|
||||
|
||||
private val _memberStats = MutableStateFlow<List<MemberStat>>(emptyList())
|
||||
val memberStats: StateFlow<List<MemberStat>> = _memberStats.asStateFlow()
|
||||
|
||||
val total: StateFlow<Double> = records
|
||||
.map { records -> records.sumOf { it.amount } }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5000),
|
||||
initialValue = 0.0
|
||||
)
|
||||
|
||||
init {
|
||||
val startDate = startMonth.atDay(1).atStartOfDay()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
.let { Date.from(it) }
|
||||
|
||||
val endDate = endMonth.atEndOfMonth().atTime(23, 59, 59)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
.let { Date.from(it) }
|
||||
|
||||
recordDao.getRecordsByCategoryAndDateRange(
|
||||
category = category,
|
||||
startDate = startDate,
|
||||
endDate = endDate
|
||||
)
|
||||
.onEach { records -> _records.value = records }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
recordDao.getMemberStatsByCategoryAndDateRange(
|
||||
category = category,
|
||||
startDate = startDate,
|
||||
endDate = endDate
|
||||
)
|
||||
.onEach { stats -> _memberStats.value = stats }
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.yovinchen.bookkeeping.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
||||
import java.time.YearMonth
|
||||
|
||||
class CategoryDetailViewModelFactory(
|
||||
private val database: BookkeepingDatabase,
|
||||
private val category: String,
|
||||
private val startMonth: YearMonth,
|
||||
private val endMonth: YearMonth
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
if (modelClass.isAssignableFrom(CategoryDetailViewModel::class.java)) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return CategoryDetailViewModel(database, category, startMonth, endMonth) as T
|
||||
}
|
||||
throw IllegalArgumentException("Unknown ViewModel class")
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.yovinchen.bookkeeping.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
||||
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
||||
import com.yovinchen.bookkeeping.model.AnalysisType
|
||||
import com.yovinchen.bookkeeping.model.TransactionType
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.time.YearMonth
|
||||
import java.time.ZoneId
|
||||
import java.util.Date
|
||||
|
||||
class MemberDetailViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val database = BookkeepingDatabase.getDatabase(application)
|
||||
private val recordDao = database.bookkeepingDao()
|
||||
|
||||
private val _memberRecords = MutableStateFlow<List<BookkeepingRecord>>(emptyList())
|
||||
val memberRecords: StateFlow<List<BookkeepingRecord>> = _memberRecords.asStateFlow()
|
||||
|
||||
private val _totalAmount = MutableStateFlow(0.0)
|
||||
val totalAmount: StateFlow<Double> = _totalAmount.asStateFlow()
|
||||
|
||||
fun loadMemberRecords(
|
||||
memberName: String,
|
||||
category: String,
|
||||
startMonth: YearMonth,
|
||||
endMonth: YearMonth,
|
||||
analysisType: AnalysisType
|
||||
) {
|
||||
val startDate = startMonth.atDay(1).atStartOfDay()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
.let { Date.from(it) }
|
||||
|
||||
val endDate = endMonth.atEndOfMonth().atTime(23, 59, 59)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
.let { Date.from(it) }
|
||||
|
||||
val transactionType = when (analysisType) {
|
||||
AnalysisType.INCOME -> TransactionType.INCOME
|
||||
AnalysisType.EXPENSE -> TransactionType.EXPENSE
|
||||
else -> null
|
||||
}
|
||||
|
||||
val recordsFlow = if (category.isEmpty()) {
|
||||
recordDao.getRecordsByMemberAndDateRange(
|
||||
memberName = memberName,
|
||||
startDate = startDate,
|
||||
endDate = endDate,
|
||||
transactionType = transactionType
|
||||
)
|
||||
} else {
|
||||
recordDao.getRecordsByMemberCategoryAndDateRange(
|
||||
memberName = memberName,
|
||||
category = category,
|
||||
startDate = startDate,
|
||||
endDate = endDate,
|
||||
transactionType = transactionType
|
||||
)
|
||||
}
|
||||
|
||||
recordsFlow
|
||||
.onEach { records ->
|
||||
_memberRecords.value = records
|
||||
_totalAmount.value = records.sumOf { it.amount }
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 8.0 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733368452865" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2083" data-darkreader-inline-fill="" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M259.69 193.89h456.53c8.52 0 15.44 6.92 15.44 15.44v521.43H202.21v-479.4c0-31.72 25.75-57.47 57.47-57.47z" fill="#57B7F9" p-id="2084" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #0a5b92;"></path><path d="M259.69 665.92h471.97v125.74H259.69c-31.72 0-57.47-25.75-57.47-57.47V723.4c0-31.72 25.75-57.47 57.47-57.47z" fill="#33A1F2" p-id="2085" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #1067a6;"></path><path d="M267.14 701.58h453.9c0.67 0 1.21 0.54 1.21 1.21v52.1c0 0.67-0.54 1.21-1.21 1.21h-453.9c-15.04 0-27.26-12.21-27.26-27.26 0-15.04 12.21-27.26 27.26-27.26z" fill="#A1DCFF" p-id="2086" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #343839;"></path><path d="M262.27 193.66v472.26s-55.01 10.41-55.01 38.17V239.34s11.15-33.61 55.01-45.68z" fill="#33A1F2" p-id="2087" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #1067a6;"></path><path d="M515.07 801.66H265.09c-40.18 0-72.87-32.69-72.87-72.87s32.69-72.87 72.87-72.87h417.4c5.52 0 10 4.48 10 10s-4.48 10-10 10H265.08c-29.15 0-52.87 23.72-52.87 52.87s23.72 52.87 52.87 52.87h249.98c5.52 0 10 4.48 10 10s-4.48 10-10 10z" fill="#333333" p-id="2088" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #272a2b;"></path><path d="M202.21 741.89c-5.52 0-10-4.48-10-10V257.88c0-40.18 32.69-72.87 72.87-72.87h451.96c13.57 0 24.61 11.04 24.61 24.61v336.04c0 5.52-4.48 10-10 10s-10-4.48-10-10V209.63c0-2.54-2.07-4.61-4.61-4.61H265.08c-29.15 0-52.87 23.72-52.87 52.87V731.9c0 5.52-4.48 10-10 10z" fill="#333333" p-id="2089" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #272a2b;"></path><path d="M610.28 253.75h-58.59v154.39l58.59-28.83V253.75z" fill="#FFAC3B" p-id="2090" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ffb148;"></path><path d="M610.28 253.75h58.6v154.39l-58.6-28.83V253.75z" fill="#FFD44A" p-id="2091" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ffd753;"></path><path d="M789.78 646.03l-43.7-43.7L568.5 779.9l13.7 73.71 207.58-207.58z" fill="#FFAC3B" p-id="2092" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #a86205;"></path><path d="M789.78 646.03l43.7 43.7L655.9 867.31l-73.7-13.7 207.58-207.58z" fill="#FFD44A" p-id="2093" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #785c04;"></path><path d="M668.88 418.14c-1.51 0-3.02-0.34-4.41-1.03l-54.18-26.66-54.18 26.66a10.002 10.002 0 0 1-14.42-8.97V253.75c0-5.52 4.48-10 10-10h117.19c5.52 0 10 4.48 10 10v154.39a10 10 0 0 1-4.72 8.49c-1.61 1-3.45 1.51-5.28 1.51z m-58.59-48.83c1.51 0 3.02 0.34 4.42 1.03l44.18 21.74V263.75H561.7v128.33l44.18-21.74c1.39-0.69 2.9-1.03 4.42-1.03z" fill="#333333" p-id="2094" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #cbc5bc;"></path><path d="M813.81 573.291l48.7 48.698c10.677 10.678 10.684 28.023 0 38.708l-29.034 29.033-87.406-87.405 29.034-29.034c10.677-10.677 28.022-10.684 38.707 0z" fill="#F76C69" p-id="2095" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f56d68;"></path><path d="M640.86 876.79c-0.51 0-1.02 0-1.54-0.03l-48.58-1.59c-16.35-0.54-29.58-13.76-30.11-30.11l-1.59-48.58c-0.43-13.08 4.63-25.89 13.88-35.14L765.4 568.86c13.44-13.44 35.31-13.44 48.75 0l52.79 52.79c13.44 13.44 13.44 35.31 0 48.75L674.46 862.88c-8.89 8.89-21.06 13.91-33.6 13.91z m148.91-298.03c-3.71 0-7.41 1.41-10.23 4.23L587.06 775.47a27.575 27.575 0 0 0-8.04 20.34l1.59 48.58c0.19 5.85 4.93 10.59 10.78 10.78l48.58 1.59c7.57 0.24 14.99-2.68 20.34-8.04l192.48-192.48c5.64-5.64 5.64-14.82 0-20.47L800 582.98a14.43 14.43 0 0 0-10.23-4.23z" fill="#333333" p-id="2096" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #272a2b;"></path><path d="M799.92 666.17c-2.56 0-5.12-0.98-7.07-2.93l-48.74-48.74c-3.91-3.91-3.91-10.24 0-14.14s10.24-3.91 14.14 0l48.74 48.74c3.91 3.91 3.91 10.24 0 14.14a9.973 9.973 0 0 1-7.07 2.93z" fill="#333333" p-id="2097" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #cbc5bc;"></path></svg>
|
After Width: | Height: | Size: 4.3 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733368445240" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1583" data-darkreader-inline-fill="" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M788.15 816.26H255.01c-49.14 0-88.98-39.84-88.98-88.98V475.71c0-49.14 39.84-88.98 88.98-88.98h155.68c155.29 0 301.1 74.66 391.87 200.65l0.26 0.37a276.537 276.537 0 0 1 52.17 161.66c0 36.92-29.93 66.85-66.85 66.85z" fill="#57B7F9" p-id="1584" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #0a5b92;"></path><path d="M506.75 396.43s0 88.27 87.97 163.08c74.13 63.04 230.31 59.19 230.31 59.19S700.77 413.67 506.75 396.42z" fill="#D4EFFF" p-id="1585" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #252729;"></path><path d="M817.24 628.74h-0.06c-15.5-0.1-56.38-1.2-101.09-9.37-56.13-10.26-99.15-27.83-127.85-52.24-50.61-43.04-73-90.57-82.87-122.87-1.61-5.28 1.36-10.87 6.64-12.49 5.28-1.61 10.87 1.36 12.49 6.64 9.09 29.74 29.77 73.57 76.7 113.48 25.99 22.1 65.86 38.19 118.49 47.8 43.16 7.88 82.64 8.95 97.62 9.05 5.52 0.03 9.97 4.54 9.94 10.06-0.03 5.5-4.51 9.94-10 9.94z" fill="#333333" p-id="1586" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #272a2b;"></path><path d="M384.59 618.75H177.34V501.3h104.04c42.46 0 82.33 20.42 107.15 54.87l0.07 0.1a75.653 75.653 0 0 1 14.27 44.2c0 10.09-8.18 18.28-18.28 18.28z" fill="#D4EFFF" p-id="1587" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #252729;"></path><path d="M165.5 723.68h688.29s11.23 92.58-65.64 92.58H247.82s-82.32-20.45-82.32-92.58z" fill="#33A1F2" p-id="1588" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #1067a6;"></path><path d="M788.15 826.26H255.01c-54.58 0-98.98-44.4-98.98-98.98v-30.54c0-5.52 4.48-10 10-10s10 4.48 10 10v30.54c0 43.55 35.43 78.98 78.98 78.98h533.14c31.34 0 56.85-25.5 56.85-56.85 0-56.27-17.39-110.15-50.28-155.81l-0.26-0.37c-43.62-60.54-101.77-110.51-168.19-144.52-66.41-34.01-140.96-51.98-215.57-51.98H255.01c-43.55 0-78.98 35.43-78.98 78.98v142.76c0 5.52-4.48 10-10 10s-10-4.48-10-10V475.71c0-54.58 44.4-98.98 98.98-98.98h155.68c77.77 0 155.46 18.73 224.69 54.18s129.84 87.53 175.3 150.63l0.26 0.37C846.3 630.99 865 688.92 865 749.41c0 42.37-34.47 76.85-76.85 76.85z" fill="#333333" p-id="1589" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #272a2b;"></path><path d="M754.79 733.68H170.87c-5.52 0-10-4.48-10-10s4.48-10 10-10h583.92c5.52 0 10 4.48 10 10s-4.48 10-10 10z" fill="#333333" p-id="1590" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #272a2b;"></path><path d="M638.22 278.13l120.21-93.43c1.6-1.25 3.66-1.8 5.68-1.57 14.18 1.57 26.44 1.95 41.48 5.19 2.43 0.52 3.51 3.38 2 5.35L717.5 310.78l-79.28-32.64z" fill="#A1DCFF" p-id="1591" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #90d5fb;"></path><path d="M717.5 311.77l62 27.6c2.06 0.94 4.45 0.84 6.43-0.27l65.04-36.36a9.275 9.275 0 0 1 7.6-0.66l16.74 5.87a7.736 7.736 0 0 1 3.33 12.31l-52.47 61.75c-15.4 18.36-48.05 9.04-48.05 9.04s-251.8-69.94-251.83-69.95c-45.45-13.7-51.3-23.32-51.3-37.31 0-18.65 37.31-23.32 37.31-23.32 8.61-4.28 27.72-14.13 54.54-4.93l71.38 23.58 79.28 32.64z" fill="#FFF6DB" p-id="1592" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #362902;"></path><path d="M482 271.73L782 369s33.47 8 55.23 0c0 0-21.97 30.11-58.1 21.05l-281.54-79.28s-27.58-10.3-15.58-39.04z" fill="#33A1F2" p-id="1593" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #1067a6;"></path><path d="M797.61 402.42h-0.27c-11.34-0.04-20.57-2.64-20.96-2.75q-251.74-69.93-251.79-69.94c-0.06-0.02-0.12-0.04-0.18-0.05C480.19 316.35 466 304.95 466 282.8c0-24.61 35.1-31.59 44.31-32.99 9.93-4.91 31.47-14.74 60.73-4.73l66.26 21.89 116-90.16c3.61-2.81 8.32-4.13 12.91-3.62 3.88 0.43 7.7 0.78 11.4 1.11 9.67 0.88 19.67 1.78 31.08 4.24 4.53 0.97 8.18 4.15 9.77 8.49 1.58 4.32 0.86 9.08-1.95 12.73l-64.75 84.18c-3.37 4.38-9.65 5.2-14.02 1.83-4.38-3.37-5.2-9.65-1.83-14.02l58.1-75.53c-6.17-0.9-12.08-1.44-18.2-1.99-3.53-0.32-7.18-0.65-10.96-1.06l-119.49 92.87a9.997 9.997 0 0 1-9.27 1.6l-71.38-23.58s-0.07-0.02-0.11-0.04c-22.45-7.7-38.05 0.05-46.43 4.22l-0.42 0.21c-1.01 0.5-2.09 0.83-3.21 0.97-13.19 1.69-28.55 7.91-28.55 13.4 0 6.75 0 14.4 44.08 27.7 5.88 1.63 251.72 69.92 251.72 69.92 0.31 0.09 26.88 7.08 37.71-5.83l50.37-59.29-13.32-4.67-64.75 36.2a17.182 17.182 0 0 1-15.44 0.65l-61.93-27.57c-5.05-2.25-7.32-8.16-5.07-13.2 2.25-5.05 8.16-7.31 13.2-5.07l60.74 27.04 63.78-35.66c4.82-2.7 10.58-3.19 15.79-1.37l16.74 5.87c2.06 0.72 3.94 1.8 5.6 3.21 7.46 6.33 8.37 17.54 2.05 25l-52.47 61.76c-8.18 9.75-21.05 14.93-37.18 14.93z m-13.08-73.2h0.03-0.03z" fill="#333333" p-id="1594" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #272a2b;"></path></svg>
|
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733382768845" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2690" data-darkreader-inline-fill="" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M653 727.5c-2.9 0-5.8-0.8-8.4-2.5-7.1-4.6-9.1-14.1-4.4-21.2 14.3-21.9 48-43.7 69.3-49.6 8.1-2.2 16.6 2.5 18.8 10.7s-2.5 16.6-10.7 18.8c-15.2 4.2-42.1 22-51.8 36.8-2.9 4.5-7.8 7-12.8 7z" fill="#F5C73E" p-id="2691" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f6cc4d;"></path><path d="M635.5 750.3c-4.6 0-9.2-2.1-12.2-6.1-5.1-6.8-3.7-16.3 3-21.4 30.8-23.2 69.1-30.4 92.6-29.5 8.4 0.3 15 7.4 14.7 15.9-0.3 8.4-7.4 15.1-15.9 14.7-19.2-0.7-50.3 6.3-73 23.4-2.8 2-6 3-9.2 3z" fill="#F5C73E" p-id="2692" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f6cc4d;"></path><path d="M629.3 906.1H394.7l-77.9-74c23.4-89.3 83.4-130.1 83.4-130.1h223.7s59.9 40.8 83.4 130.1l-78 74z" fill="#64C4F6" p-id="2693" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #0d608a;"></path><path d="M394.7 827.1v79h-87.2c0-27.6 3.5-57.2 9.3-79h77.9zM716.5 906.1h-87.2v-79h77.9c5.7 21.8 9.3 51.4 9.3 79z" fill="#FFECE3" p-id="2694" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M628.7 700.6c64.7 102.9-28.9 155.3-116.7 155.3s-181.4-52.3-116.7-155.3h233.4z" fill="#99D37A" p-id="2695" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #52762a;"></path><path d="M512 878.5c-56 0-122.2-19.9-149.3-68.9-13.4-24.2-21.9-64.9 13.4-121.1 6.7-10.6 20.6-13.8 31.2-7.1s13.8 20.6 7.1 31.2c-18.9 30.1-23 55.3-12.1 75.1 15 27.2 59.1 45.5 109.6 45.5s94.6-18.3 109.6-45.5c10.9-19.7 6.8-45-12.1-75.1-6.7-10.6-3.5-24.6 7.1-31.2 10.6-6.7 24.6-3.5 31.2 7.1 35.3 56.2 26.8 96.9 13.4 121.1-26.9 49-93.2 68.9-149.1 68.9z" fill="#FCE170" p-id="2696" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #665306;"></path><path d="M249.7 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="2697" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M774.3 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="2698" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M512 459.5m-269.3 0a269.3 269.3 0 1 0 538.6 0 269.3 269.3 0 1 0-538.6 0Z" fill="#FFECE3" p-id="2699" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M509.3 265c-14 3-27.8-5.8-30.8-19.8s5.8-27.7 19.8-30.8c26-5.6 37.7-30.6 17-56.4-8.9-11.2-7.1-27.5 4.1-36.4 11.2-8.9 27.5-7.1 36.4 4.1 44.5 55.7 17 125.4-46.5 139.3z" fill="#47515E" p-id="2700" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #b7afa4;"></path><path d="M560 670.9h-96c-44.1 0-80-32.3-80-72s35.9-72 80-72h2.4l2 1.3c26.4 17.6 60.8 17.6 87.1 0l2-1.3h2.4c44.1 0 80 32.3 80 72s-35.8 72-79.9 72z" fill="#4EAEE0" p-id="2701" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #1f6b93;"></path><path d="M543.3 750.9h-62.7c-22.1 0-40-17.9-40-40 0-1 0-2.1 0.1-3.1l2.9-37.7c2.9-37.7 35.9-65.9 73.6-63 33.6 2.6 60.4 29.3 63 63l2.9 37.7c1.7 22-14.8 41.3-36.8 43-0.9 0-1.9 0.1-3 0.1z m-31.3-128c-27.4 0.1-50.1 21.2-52.3 48.5l-2.9 37.7c-1 13.2 8.9 24.8 22.1 25.8 0.6 0 1.2 0.1 1.9 0.1h62.7c13.3 0 24-10.7 24-24 0-0.6 0-1.2-0.1-1.9l-2.9-37.7c-2.4-27.4-25.1-48.4-52.5-48.5z" fill="#64C4F6" p-id="2702" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #69c4f2;"></path><path d="M512 622.9m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" fill="#64C4F6" p-id="2703" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #69c4f2;"></path><path d="M600 582.9m-8 0a8 8 0 1 0 16 0 8 8 0 1 0-16 0Z" fill="#64C4F6" p-id="2704" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #69c4f2;"></path><path d="M600 614.9m-8 0a8 8 0 1 0 16 0 8 8 0 1 0-16 0Z" fill="#64C4F6" p-id="2705" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #69c4f2;"></path><path d="M424 582.9m-8 0a8 8 0 1 0 16 0 8 8 0 1 0-16 0Z" fill="#64C4F6" p-id="2706" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #69c4f2;"></path><path d="M424 614.9m-8 0a8 8 0 1 0 16 0 8 8 0 1 0-16 0Z" fill="#64C4F6" p-id="2707" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #69c4f2;"></path><path d="M436.3 516.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM587.7 516.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7z" fill="#450064" p-id="2708" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #cbc6bd;"></path></svg>
|
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.6 KiB |
1
app/src/main/res/drawable/member/ic_member_boy_24dp.xml
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733382780211" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3211" data-darkreader-inline-fill="" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M629.3 906.1H394.7l-77.9-74c23.4-89.3 83.4-130.1 83.4-130.1h223.7s59.9 40.8 83.4 130.1l-78 74z" fill="#99D37A" p-id="3212" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #52762a;"></path><path d="M394.7 827.1v79h-87.2c0-27.6 3.5-57.2 9.3-79h77.9zM716.5 906.1h-87.2v-79h77.9c5.7 21.8 9.3 51.4 9.3 79z" fill="#FFECE3" p-id="3213" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M438.4 707.7a73.6 47.4 0 1 0 147.2 0 73.6 47.4 0 1 0-147.2 0Z" fill="#F7DBD0" p-id="3214" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="3215" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="3216" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="3217" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="3218" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M249.7 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="3219" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M774.3 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="3220" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M512 459.5m-269.3 0a269.3 269.3 0 1 0 538.6 0 269.3 269.3 0 1 0-538.6 0Z" fill="#FFECE3" p-id="3221" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M436.3 526.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM587.7 526.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM512 597.7c-16.4 0-31.9-7.1-42.7-19.4-4.6-5.3-4-13.3 1.2-17.9 5.3-4.6 13.3-4 17.9 1.2 6 6.8 14.6 10.8 23.6 10.8s17.6-3.9 23.6-10.7c4.6-5.3 12.6-5.8 17.9-1.2 5.3 4.6 5.8 12.6 1.2 17.9-10.8 12.2-26.3 19.3-42.7 19.3z" fill="#450064" p-id="3222" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #cbc6bd;"></path><path d="M812.6 394c-4.1 0.1-8.3 0.2-12.4 0.2-83.8 0-162.2-23.1-229.1-63.4-69.3 44.5-151.8 70.4-240.4 70.4-40.9 0-80.6-5.5-118.3-15.9 11-87.8 60.1-164.1 130.1-211.9C390.9 140.3 449.3 121 512 121s121 19.4 169.4 52.4c15.7 10.7 30.4 22.9 43.8 36.3 48.4 48.3 80.5 112.7 87.4 184.3z" fill="#5C6A7C" p-id="3223" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #4c5662;"></path><path d="M342.6 328.1c0 10.3-4.2 19.6-11 26.4s-16.1 11-26.4 11H121.9c-20.5 0-37.3-16.8-37.3-37.3 0-10.3 4.2-19.6 11-26.4s16.1-11 26.4-11h183.4c20.4 0 37.2 16.8 37.2 37.3z" fill="#4EAEE0" p-id="3224" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #1f6b93;"></path><path d="M807 287.7C779.9 149.5 658.1 45.2 512 45.2S244.1 149.5 217 287.7c-3.7 18.8-5.6 38.2-5.6 58.1 0 3.6 0.1 7.1 0.2 10.6 0.2 5.1 4.3 9.1 9.4 9.1h582c5.1 0 9.2-4 9.4-9.1 0.1-3.5 0.2-7.1 0.2-10.6 0-19.9-1.9-39.3-5.6-58.1z m-223.9 19c-4.1 4.1-9.7 6.6-15.9 6.6H456.8c-12.4 0-22.5-10.1-22.5-22.5 0-6.2 2.5-11.8 6.6-15.9s9.7-6.6 15.9-6.6h110.4c12.4 0 22.5 10.1 22.5 22.5 0 6.2-2.5 11.8-6.6 15.9z" fill="#64C4F6" p-id="3225" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #0d608a;"></path></svg>
|
After Width: | Height: | Size: 3.8 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733382752696" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2177" data-darkreader-inline-fill="" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M903.5 836.3c0 23.5-0.6 46.7-1.6 69.8H122.1c-1-22.9-1.6-46.3-1.6-69.8C120.5 415.9 295.8 75 512 75s391.5 340.9 391.5 761.3z" fill="#F3F3F3" p-id="2178" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #202123;"></path><path d="M181.8 906c0-414.3 95.2-833.6 265.1-833.6 0 0 19.6-34.4 65.1-34.4s65.1 34.4 65.1 34.4c201.7 0 265.1 419.3 265.1 833.6H181.8z" fill="#DEE3E3" p-id="2179" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #2a2d2e;"></path><path d="M548.6 50.4c-12.6-5.7-26.6-8.9-41.3-8.9-55.5 0-100.4 45-100.4 100.4s45 100.4 100.4 100.4c14.7 0 28.7-3.2 41.3-8.9 34.8-15.8 59.1-50.8 59.1-91.5s-24.2-75.8-59.1-91.5z" fill="#47515E" p-id="2180" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #b7afa4;"></path><path d="M707.3 832.1l-78 74H394.7l-77.9-74c2-7.7 4.3-15 6.8-22 26.5-74 76.6-108.1 76.6-108.1h223.7s50 34.1 76.6 108.1c2.5 7 4.8 14.3 6.8 22z" fill="#FFECE3" p-id="2181" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M707.3 832.1l-78 74H394.7l-77.9-74c2-7.7 4.3-15 6.8-22h376.8c2.6 7 4.9 14.3 6.9 22z" fill="#E96E67" p-id="2182" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #851c16;"></path><path d="M394.7 827.1v79h-87.2c0-27.6 3.5-57.2 9.3-79h77.9zM716.5 906.1h-87.2v-79h77.9c5.7 21.8 9.3 51.4 9.3 79z" fill="#FFECE3" p-id="2183" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M249.7 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="2184" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M774.3 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="2185" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M512 459.5m-269.3 0a269.3 269.3 0 1 0 538.6 0 269.3 269.3 0 1 0-538.6 0Z" fill="#FFECE3" p-id="2186" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M813.6 407.5c-113.6-0.1-218.4-38.2-302.2-102.3-83.5 63.9-187.8 101.9-300.9 102.3 5.7-112.3 72.7-208.3 168.2-255.4 40.2-19.8 85.5-31 133.4-31 47.5 0 92.4 11 132.4 30.5 96 46.8 163.4 143.2 169.1 255.9z" fill="#5C6A7C" p-id="2187" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #4c5662;"></path><path d="M704.9 827.1H319.7c-4.2 0-7-4.4-5.1-8.2l34.5-71.7c0.9-2 2.9-3.2 5.1-3.2h316.3c2.2 0 4.2 1.3 5.1 3.2l34.5 71.7c1.8 3.8-1 8.2-5.2 8.2z" fill="#F7867F" p-id="2188" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #7a110a;"></path><path d="M326.5 558.2a49.8 39.5 0 1 0 99.6 0 49.8 39.5 0 1 0-99.6 0Z" fill="#FFD7DF" p-id="2189" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ffb3c0;"></path><path d="M597.9 558.2a49.8 39.5 0 1 0 99.6 0 49.8 39.5 0 1 0-99.6 0Z" fill="#FFD7DF" p-id="2190" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ffb3c0;"></path><path d="M436.3 526.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM587.7 526.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM512 597.7c-16.4 0-31.9-7.1-42.7-19.4-4.6-5.3-4-13.3 1.2-17.9 5.3-4.6 13.3-4 17.9 1.2 6 6.8 14.6 10.8 23.6 10.8s17.6-3.9 23.6-10.7c4.6-5.3 12.6-5.8 17.9-1.2 5.3 4.6 5.8 12.6 1.2 17.9-10.8 12.2-26.3 19.3-42.7 19.3z" fill="#450064" p-id="2191" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #cbc6bd;"></path><path d="M606.2 94.4l-20.8 54.5H435l-21-55 0.8-0.5c13.2 22 24.6 34.7 33 29.9 14.5-8.1 13.9-25.3 13.5-42.4h0.7c8.3 19 16.7 36.4 27.2 35 18.1-2.3 19-31.3 20.8-57.8h0.8c1.7 26.5 2.7 55.6 20.8 57.8 6.2 0.8 11.6-4.9 16.9-13.5 3.6-6.1 7-13.6 10.5-21.5l0.7 0.1c-0.4 17.1-1 34.3 13.5 42.5 8.4 4.8 19.6-7.7 32.7-29.2l0.3 0.1z" fill="#FCE170" p-id="2192" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #fee26e;"></path><path d="M606.2 94.4c0.7 0.2 1.3 0.3 2.1 0.3 4.4 0 7.9-3.6 7.9-8 0.1-4.4-3.5-8-7.8-8-4.4 0-7.9 3.6-7.9 8 0 3.3 2.1 6.2 5 7.4M414.6 93.3c2.2-1.4 3.6-3.9 3.6-6.8 0-4.5-3.5-8-7.9-8s-7.8 3.7-7.8 8.1c0 4.5 3.5 8 7.9 8 1.2 0 2.5-0.3 3.5-0.9M461.8 81c5-0.6 9-5 9-10.2 0-5.7-4.5-10.3-10.1-10.3-5.5 0-10.1 4.6-10.1 10.3s4.5 10.3 10.1 10.3h0.4M510.6 58.1c6.9-0.2 12.4-5.8 12.4-12.8s-5.7-12.8-12.8-12.8c-7.1 0-12.8 5.7-12.8 12.8 0 6.9 5.5 12.6 12.4 12.8M559.3 81c0.4 0 0.7 0.1 1 0.1 5.5 0 10.1-4.6 10.1-10.3s-4.4-10.3-10.1-10.3c-5.6 0-10.1 4.6-10.1 10.3 0 5 3.6 9.3 8.4 10.1" fill="#FCE170" p-id="2193" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #665306;"></path></svg>
|
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 7.8 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733382737759" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1666" data-darkreader-inline-fill="" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M707.3 832.1L631.5 904l-2.2 2.1H394.7l-2.2-2.1-75.7-71.9c23.4-89.3 83.4-130.1 83.4-130.1h223.7s59.9 40.8 83.4 130.1z" fill="#D79B89" p-id="1667" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #6c3726;"></path><path d="M394.7 827.1v79h-87.2c0-27.6 3.5-57.2 9.3-79h77.9zM716.5 906.1h-87.2v-79h77.9c5.7 21.8 9.3 51.4 9.3 79z" fill="#FFECE3" p-id="1668" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M640.3 819.4c0 29.9-3.1 58.4-8.8 84.6l-2.2 2.1H394.7l-2.2-2.1c-5.7-26.2-8.8-54.8-8.8-84.6 0-42.9 6.5-83 17.7-117.4h58.5c1.8 74.9 24.4 134.2 52.1 134.2 27.7 0 50.3-59.2 52.1-134.2h58.5c11.3 34.4 17.7 74.6 17.7 117.4z" fill="#64C4F6" p-id="1669" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #0d608a;"></path><path d="M517.3 720.1l-26.8 38.6c-9.6 13.9-30 14.4-40.3 1l-46.4-60.4 113.5 20.8z" fill="#FFFFFF" p-id="1670" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ece8e2;"></path><path d="M506.7 720.1l26.8 38.6c9.6 13.9 30 14.4 40.3 1l46.4-60.4-113.5 20.8z" fill="#FFFFFF" p-id="1671" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ece8e2;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="1672" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="1673" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="1674" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="1675" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M249.7 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="1676" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M774.3 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="1677" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M512 459.5m-269.3 0a269.3 269.3 0 1 0 538.6 0 269.3 269.3 0 1 0-538.6 0Z" fill="#FFECE3" p-id="1678" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M812.6 394c-4.1 0.1-8.3 0.2-12.4 0.2-83.8 0-162.2-23.1-229.1-63.4-69.3 44.5-151.8 70.4-240.4 70.4-40.9 0-80.6-5.5-118.3-15.9 11-87.8 60.1-164.1 130.1-211.9C390.9 140.3 449.3 121 512 121s121 19.4 169.4 52.4c15.7 10.7 30.4 22.9 43.8 36.3 48.4 48.3 80.5 112.7 87.4 184.3z" fill="#5C6A7C" p-id="1679" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #4c5662;"></path><path d="M436.3 526.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM587.7 526.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM512 645.3c-16.4 0-31.9-7.1-42.7-19.4-4.6-5.3-4-13.3 1.2-17.9 5.3-4.6 13.3-4 17.9 1.2 6 6.8 14.6 10.8 23.6 10.8s17.6-3.9 23.6-10.7c4.6-5.3 12.6-5.8 17.9-1.2 5.3 4.6 5.8 12.6 1.2 17.9-10.8 12.2-26.3 19.3-42.7 19.3z" fill="#450064" p-id="1680" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #cbc6bd;"></path><path d="M609.6 619H414.4c-5.9 0-10.7-4.8-10.7-10.7 0-28.1 23-51.1 51.1-51.1h114.4c28.1 0 51.1 23 51.1 51.1 0 5.9-4.8 10.7-10.7 10.7z" fill="#5C6A7C" p-id="1681" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #4c5662;"></path></svg>
|
After Width: | Height: | Size: 3.8 KiB |
1
app/src/main/res/drawable/member/ic_member_girl_24dp.xml
Normal file
After Width: | Height: | Size: 5.1 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733382759052" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2347" data-darkreader-inline-fill="" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M707.3 832.1L631.5 904l-2.2 2.1H394.7l-2.2-2.1-75.7-71.9c23.4-89.3 83.4-130.1 83.4-130.1h223.7s59.9 40.8 83.4 130.1z" fill="#64C4F6" p-id="2348" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #0d608a;"></path><path d="M394.7 827.1v79h-87.2c0-27.6 3.5-57.2 9.3-79h77.9zM716.5 906.1h-87.2v-79h77.9c5.7 21.8 9.3 51.4 9.3 79z" fill="#FFECE3" p-id="2349" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M640.3 819.4c0 29.9-3.1 58.4-8.8 84.6l-2.2 2.1H394.7l-2.2-2.1c-5.7-26.2-8.8-54.8-8.8-84.6 0-42.9 6.5-83 17.7-117.4h58.5c1.8 74.9 24.4 134.2 52.1 134.2 27.7 0 50.3-59.2 52.1-134.2h58.5c11.3 34.4 17.7 74.6 17.7 117.4z" fill="#D79B89" p-id="2350" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #6c3726;"></path><path d="M517.3 720.1l-26.8 38.6c-9.6 13.9-30 14.4-40.3 1l-46.4-60.4 113.5 20.8z" fill="#FFFFFF" p-id="2351" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ece8e2;"></path><path d="M506.7 720.1l26.8 38.6c9.6 13.9 30 14.4 40.3 1l46.4-60.4-113.5 20.8z" fill="#FFFFFF" p-id="2352" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ece8e2;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="2353" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="2354" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="2355" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="2356" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M249.7 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="2357" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M774.3 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="2358" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M512 459.5m-269.3 0a269.3 269.3 0 1 0 538.6 0 269.3 269.3 0 1 0-538.6 0Z" fill="#FFECE3" p-id="2359" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M450.9 552.3H328.7c-28.2 0-51.2-23-51.2-51.2v-17.8c0-28.2 23-51.2 51.2-51.2h122.2c28.2 0 51.2 23 51.2 51.2v17.8c0 28.2-23 51.2-51.2 51.2zM328.7 452.1c-17.2 0-31.2 14-31.2 31.2v17.8c0 17.2 14 31.2 31.2 31.2h122.2c17.2 0 31.2-14 31.2-31.2v-17.8c0-17.2-14-31.2-31.2-31.2H328.7zM695.5 552.3H573.3c-28.2 0-51.2-23-51.2-51.2v-17.8c0-28.2 23-51.2 51.2-51.2h122.2c28.2 0 51.2 23 51.2 51.2v17.8c0 28.2-23 51.2-51.2 51.2zM573.3 452.1c-17.2 0-31.2 14-31.2 31.2v17.8c0 17.2 14 31.2 31.2 31.2h122.2c17.2 0 31.2-14 31.2-31.2v-17.8c0-17.2-14-31.2-31.2-31.2H573.3z" fill="#450064" p-id="2360" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #36024d;"></path><path d="M719.6 459.4c-3 0-5.9-1.3-7.9-3.8-3.4-4.3-2.6-10.6 1.7-14l63.6-49.8c4.3-3.4 10.6-2.6 14 1.7 3.4 4.3 2.6 10.6-1.7 14l-63.6 49.8c-1.8 1.4-3.9 2.1-6.1 2.1zM304.6 459.4c-2.2 0-4.3-0.7-6.2-2.1l-63.6-49.8c-4.3-3.4-5.1-9.7-1.7-14 3.4-4.3 9.7-5.1 14-1.7l63.6 49.8c4.3 3.4 5.1 9.7 1.7 14-1.9 2.5-4.8 3.8-7.8 3.8zM532.1 511.1h-40c-5.5 0-10-4.5-10-10s4.5-10 10-10h40c5.5 0 10 4.5 10 10s-4.5 10-10 10z" fill="#450064" p-id="2361" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #36024d;"></path><path d="M242.4 418.4c52.1 2.2 55.6-121.9 90.9-160.4-79.2-46.8-128.2 30.1-137.4 73.5-9.1 43.5 5.6 85.1 46.5 86.9zM781.5 418.4c-52.1 2.2-55.6-121.9-90.9-160.4 79.2-46.8 128.2 30.1 137.4 73.5 9.2 43.5-5.5 85.1-46.5 86.9z" fill="#F3F3F3" p-id="2362" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #202123;"></path><path d="M436.3 526.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM587.7 526.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM512 597.7c-16.4 0-31.9-7.1-42.7-19.4-4.6-5.3-4-13.3 1.2-17.9 5.3-4.6 13.3-4 17.9 1.2 6 6.8 14.6 10.8 23.6 10.8s17.6-3.9 23.6-10.7c4.6-5.3 12.6-5.8 17.9-1.2 5.3 4.6 5.8 12.6 1.2 17.9-10.8 12.2-26.3 19.3-42.7 19.3z" fill="#450064" p-id="2363" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #cbc6bd;"></path></svg>
|
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.4 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733382748360" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2007" data-darkreader-inline-fill="" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M629.3 906.1H394.7l-77.9-74c23.4-89.3 83.4-130.1 83.4-130.1h223.7s59.9 40.8 83.4 130.1l-78 74z" fill="#F3F3F3" p-id="2008" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #202123;"></path><path d="M394.7 827.1v79h-87.2c0-27.6 3.5-57.2 9.3-79h77.9zM716.5 906.1h-87.2v-79h77.9c5.7 21.8 9.3 51.4 9.3 79z" fill="#F3F3F3" p-id="2009" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #202123;"></path><path d="M549.3 735.6L538.5 764l-1.8 4.6-7.4 19.5h-37.6l-20-52.5z" fill="#E96E67" p-id="2010" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e7726a;"></path><path d="M529.3 788.1h-37.6l-20 118h77.6z" fill="#F7867F" p-id="2011" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f48077;"></path><path d="M640.3 819.4c0 29.9-3.1 58.4-8.8 84.6l-2.2 2.1H394.7l-2.2-2.1c-5.7-26.2-8.8-54.8-8.8-84.6 0-42.9 6.5-83 17.7-117.4h58.5c1.8 74.9 24.4 134.2 52.1 134.2 27.7 0 50.3-59.2 52.1-134.2h58.5c11.3 34.4 17.7 74.6 17.7 117.4z" fill="#47515E" p-id="2012" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #3a424a;"></path><path d="M517.3 720.1l-26.8 38.6c-9.6 13.9-30 14.4-40.3 1l-46.4-60.4 113.5 20.8z" fill="#FFFFFF" p-id="2013" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ece8e2;"></path><path d="M506.7 720.1l26.8 38.6c9.6 13.9 30 14.4 40.3 1l46.4-60.4-113.5 20.8z" fill="#FFFFFF" p-id="2014" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ece8e2;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="2015" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="2016" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="2017" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="2018" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M249.7 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="2019" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M774.3 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="2020" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M512 459.5m-269.3 0a269.3 269.3 0 1 0 538.6 0 269.3 269.3 0 1 0-538.6 0Z" fill="#FFECE3" p-id="2021" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M813.6 407.5c-113.6-0.1-218.4-38.2-302.2-102.3-83.5 63.9-187.8 101.9-300.9 102.3 5.7-112.3 72.7-208.3 168.2-255.4 40.2-19.8 85.5-31 133.4-31 47.5 0 92.4 11 132.4 30.5 96 46.8 163.4 143.2 169.1 255.9z" fill="#5C6A7C" p-id="2022" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #4c5662;"></path><path d="M436.3 526.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM587.7 526.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM512 597.7c-16.4 0-31.9-7.1-42.7-19.4-4.6-5.3-4-13.3 1.2-17.9 5.3-4.6 13.3-4 17.9 1.2 6 6.8 14.6 10.8 23.6 10.8s17.6-3.9 23.6-10.7c4.6-5.3 12.6-5.8 17.9-1.2 5.3 4.6 5.8 12.6 1.2 17.9-10.8 12.2-26.3 19.3-42.7 19.3z" fill="#450064" p-id="2023" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #cbc6bd;"></path></svg>
|
After Width: | Height: | Size: 3.8 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733382743744" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1835" data-darkreader-inline-fill="" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M884.7 678.4c-39 0-70.7-31.7-70.7-70.7V422.9c0-9.7-0.5-19.4-1.5-28.9-6.9-71.5-39-136-87.3-184.3-13.4-13.4-28.1-25.6-43.8-36.3C633 140.4 574.7 121 512 121s-121.1 19.3-169.4 52.4c-70 47.8-119.1 124.1-130.1 211.9-1.6 12.3-2.4 24.9-2.4 37.6v184.8c0 39-31.7 70.7-70.7 70.7h-0.1c0 82.1 66.6 148.7 148.7 148.7h448c82.1 0 148.7-66.6 148.7-148.7z" fill="#AD6D5A" p-id="1836" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #855343;"></path><path d="M629.3 906.1H394.7l-77.9-74c23.4-89.3 83.4-130.1 83.4-130.1h223.7s59.9 40.8 83.4 130.1l-78 74z" fill="#F7867F" p-id="1837" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #7a110a;"></path><path d="M394.7 827.1v79h-87.2c0-27.6 3.5-57.2 9.3-79h77.9zM716.5 906.1h-87.2v-79h77.9c5.7 21.8 9.3 51.4 9.3 79z" fill="#FFECE3" p-id="1838" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M517.3 720.1l-26.8 38.6c-9.6 13.9-30 14.4-40.3 1l-46.4-60.4 113.5 20.8z" fill="#FFFFFF" p-id="1839" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ece8e2;"></path><path d="M506.7 720.1l26.8 38.6c9.6 13.9 30 14.4 40.3 1l46.4-60.4-113.5 20.8z" fill="#FFFFFF" p-id="1840" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ece8e2;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="1841" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="1842" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="1843" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M507.3 720l-0.3 0.5-0.3-0.5h0.3z" fill="#EFEAEB" p-id="1844" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #e0dbd5;"></path><path d="M249.7 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="1845" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M774.3 470.5m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z" fill="#F7DBD0" p-id="1846" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #f4c7b4;"></path><path d="M251.5 547.6m-36.9 0a36.9 36.9 0 1 0 73.8 0 36.9 36.9 0 1 0-73.8 0Z" fill="#FCE170" p-id="1847" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #fee26e;"></path><path d="M772.5 547.6m-36.9 0a36.9 36.9 0 1 0 73.8 0 36.9 36.9 0 1 0-73.8 0Z" fill="#FCE170" p-id="1848" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #fee26e;"></path><path d="M512 459.5m-269.3 0a269.3 269.3 0 1 0 538.6 0 269.3 269.3 0 1 0-538.6 0Z" fill="#FFECE3" p-id="1849" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #431702;"></path><path d="M326.5 558.2a49.8 39.5 0 1 0 99.6 0 49.8 39.5 0 1 0-99.6 0Z" fill="#FFD7DF" p-id="1850" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ffb3c0;"></path><path d="M597.9 558.2a49.8 39.5 0 1 0 99.6 0 49.8 39.5 0 1 0-99.6 0Z" fill="#FFD7DF" p-id="1851" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ffb3c0;"></path><path d="M436.3 526.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM587.7 526.3c-7 0-12.7-5.7-12.7-12.7v-10.2c0-7 5.7-12.7 12.7-12.7s12.7 5.7 12.7 12.7v10.2c0 7.1-5.7 12.7-12.7 12.7zM512 597.7c-16.4 0-31.9-7.1-42.7-19.4-4.6-5.3-4-13.3 1.2-17.9 5.3-4.6 13.3-4 17.9 1.2 6 6.8 14.6 10.8 23.6 10.8s17.6-3.9 23.6-10.7c4.6-5.3 12.6-5.8 17.9-1.2 5.3 4.6 5.8 12.6 1.2 17.9-10.8 12.2-26.3 19.3-42.7 19.3z" fill="#450064" p-id="1852" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #cbc6bd;"></path><path d="M812.6 394c-4.1 0.1-8.3 0.2-12.4 0.2-83.8 0-162.2-23.1-229.1-63.4-69.3 44.5-151.8 70.4-240.4 70.4-40.9 0-80.6-5.5-118.3-15.9 11-87.8 60.1-164.1 130.1-211.9C390.9 140.3 449.3 121 512 121s121 19.4 169.4 52.4c15.7 10.7 30.4 22.9 43.8 36.3 48.4 48.3 80.5 112.7 87.4 184.3z" fill="#D79B89" p-id="1853" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #6c3726;"></path></svg>
|
After Width: | Height: | Size: 4.4 KiB |
@ -20,4 +20,6 @@ kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
android.nonTransitiveRClass=true
|
||||
# Kotlin
|
||||
org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home
|
@ -11,6 +11,7 @@ composeBom = "2024.04.01"
|
||||
roomCommon = "2.6.1"
|
||||
navigationCommonKtx = "2.8.4"
|
||||
navigationCompose = "2.8.4"
|
||||
visionInternalVkp = "18.2.3"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@ -30,6 +31,7 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3"
|
||||
androidx-room-common = { group = "androidx.room", name = "room-common", version.ref = "roomCommon" }
|
||||
androidx-navigation-common-ktx = { group = "androidx.navigation", name = "navigation-common-ktx", version.ref = "navigationCommonKtx" }
|
||||
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
||||
vision-internal-vkp = { group = "com.google.mlkit", name = "vision-internal-vkp", version.ref = "visionInternalVkp" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
@ -9,6 +9,7 @@ pluginManagement {
|
||||
}
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
@ -16,6 +17,7 @@ dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
}
|
||||
|
||||
|