1.2.4稳定版 #3

Merged
yovinchen merged 40 commits from develop into master 2024-12-05 16:52:26 +08:00
9 changed files with 303 additions and 134 deletions
Showing only changes of commit 3296f6d154 - Show all commits

View File

@ -3,7 +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.CategoryStat
import com.yovinchen.bookkeeping.model.MemberStat
import com.yovinchen.bookkeeping.model.TransactionType
import kotlinx.coroutines.flow.Flow
import java.util.Date
@ -51,15 +51,11 @@ interface BookkeepingDao {
): Flow<List<BookkeepingRecord>>
@Query("""
SELECT m.name as category,
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
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
@ -70,7 +66,7 @@ interface BookkeepingDao {
fun getMemberStatsByCategory(
category: String,
yearMonth: String
): Flow<List<CategoryStat>>
): Flow<List<MemberStat>>
@Query("""
SELECT * FROM bookkeeping_records
@ -81,6 +77,67 @@ interface BookkeepingDao {
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

View File

@ -1,8 +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
)

View File

@ -36,14 +36,30 @@ sealed class Screen(
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}/{yearMonth}", "分类详情") {
fun createRoute(category: String, yearMonth: YearMonth): String {
return "category_detail/$category/${yearMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))}"
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}/{yearMonth}?type={type}", "成员详情") {
fun createRoute(memberName: String, category: String, yearMonth: YearMonth, type: AnalysisType): String {
return "member_detail/$memberName/$category/${yearMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))}?type=${type.name}"
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}"
}
}
@ -94,11 +110,11 @@ fun MainNavigation(
composable(Screen.Analysis.route) {
AnalysisScreen(
onNavigateToCategoryDetail = { category, yearMonth ->
navController.navigate(Screen.CategoryDetail.createRoute(category, yearMonth))
onNavigateToCategoryDetail = { category, startMonth, endMonth ->
navController.navigate(Screen.CategoryDetail.createRoute(category, startMonth, endMonth))
},
onNavigateToMemberDetail = { memberName, yearMonth, analysisType ->
navController.navigate(Screen.MemberDetail.createRoute(memberName, "", yearMonth, analysisType))
onNavigateToMemberDetail = { memberName, startMonth, endMonth, analysisType ->
navController.navigate(Screen.MemberDetail.createRoute(memberName, "", startMonth, endMonth, analysisType))
}
)
}
@ -114,51 +130,68 @@ fun MainNavigation(
route = Screen.CategoryDetail.route,
arguments = listOf(
navArgument("category") { type = NavType.StringType },
navArgument("yearMonth") { type = NavType.StringType }
navArgument("startMonth") { type = NavType.StringType },
navArgument("endMonth") { type = NavType.StringType }
)
) { backStackEntry ->
val category = backStackEntry.arguments?.getString("category") ?: return@composable
val yearMonthStr = backStackEntry.arguments?.getString("yearMonth") ?: return@composable
val yearMonth = YearMonth.parse(yearMonthStr)
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,
yearMonth = yearMonth,
startMonth = startMonth,
endMonth = endMonth,
onNavigateBack = { navController.popBackStack() },
onNavigateToMemberDetail = { memberName ->
navController.navigate(Screen.MemberDetail.createRoute(memberName, category, yearMonth, AnalysisType.EXPENSE))
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("yearMonth") { type = NavType.StringType },
navArgument("type") {
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") ?: return@composable
val category = backStackEntry.arguments?.getString("category") ?: return@composable
val yearMonthStr = backStackEntry.arguments?.getString("yearMonth") ?: return@composable
val yearMonth = YearMonth.parse(yearMonthStr)
val type = backStackEntry.arguments?.getString("type")?.let {
try {
AnalysisType.valueOf(it)
} catch (e: IllegalArgumentException) {
AnalysisType.EXPENSE
}
} ?: AnalysisType.EXPENSE
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,
yearMonth = yearMonth,
category = category,
startMonth = startMonth,
endMonth = endMonth,
analysisType = type,
onNavigateBack = { navController.popBackStack() }
)

View File

@ -48,8 +48,9 @@ enum class ViewMode {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AnalysisScreen(
onNavigateToCategoryDetail: (String, YearMonth) -> Unit,
onNavigateToMemberDetail: (String, YearMonth, AnalysisType) -> Unit
onNavigateToCategoryDetail: (String, YearMonth, YearMonth) -> Unit,
onNavigateToMemberDetail: (String, YearMonth, YearMonth, AnalysisType) -> Unit,
modifier: Modifier = Modifier
) {
val viewModel: AnalysisViewModel = viewModel()
val startMonth by viewModel.startMonth.collectAsState()
@ -61,11 +62,13 @@ fun AnalysisScreen(
var showViewModeMenu by remember { mutableStateOf(false) }
var currentViewMode by rememberSaveable { mutableStateOf(ViewMode.CATEGORY) }
Scaffold { padding ->
Scaffold(
modifier = modifier.fillMaxSize()
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(paddingValues)
) {
// 时间区间选择
DateRangePicker(
@ -151,9 +154,9 @@ fun AnalysisScreen(
.padding(bottom = 16.dp),
onCategoryClick = { category ->
if (currentViewMode == ViewMode.CATEGORY) {
onNavigateToCategoryDetail(category, startMonth)
onNavigateToCategoryDetail(category, startMonth, endMonth)
} else {
onNavigateToMemberDetail(category, startMonth, selectedAnalysisType)
onNavigateToMemberDetail(category, startMonth, endMonth, selectedAnalysisType)
}
}
)
@ -169,9 +172,9 @@ fun AnalysisScreen(
stat = stat,
onClick = {
if (currentViewMode == ViewMode.CATEGORY && category != null) {
onNavigateToCategoryDetail(category, startMonth)
onNavigateToCategoryDetail(category, startMonth, endMonth)
} else if (currentViewMode == ViewMode.MEMBER && member != null) {
onNavigateToMemberDetail(member, startMonth, selectedAnalysisType)
onNavigateToMemberDetail(member, startMonth, endMonth, selectedAnalysisType)
}
}
)

View File

@ -1,5 +1,6 @@
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
@ -17,6 +18,7 @@ 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
@ -33,6 +35,7 @@ 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
@ -47,17 +50,20 @@ import java.util.Locale
@Composable
fun CategoryDetailScreen(
category: String,
yearMonth: YearMonth,
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 context = LocalContext.current
val database = remember { BookkeepingDatabase.getDatabase(context) }
val viewModel: CategoryDetailViewModel = viewModel(
factory = CategoryDetailViewModelFactory(database, category, yearMonth)
)
val records by viewModel.records.collectAsState()
val memberStats by viewModel.memberStats.collectAsState()
val total by viewModel.total.collectAsState()
@ -107,16 +113,16 @@ fun CategoryDetailScreen(
}
}
// 第二部分:扇形图
// 第二部分:成员统计
item {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
.padding(16.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
@ -125,19 +131,33 @@ fun CategoryDetailScreen(
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
// 饼状图
CategoryPieChart(
categoryData = memberStats.map { Pair(it.category, it.percentage.toFloat()) },
memberData = emptyList(),
currentViewMode = false,
categoryData = emptyList(),
memberData = memberStats.map { Pair(it.member, it.percentage.toFloat()) },
currentViewMode = true,
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
onCategoryClick = { memberName ->
if (records.isNotEmpty() && records.first().type == TransactionType.EXPENSE) {
onNavigateToMemberDetail(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) }
)
}
}
}
}
}
@ -208,6 +228,29 @@ fun CategoryDetailScreen(
}
}
@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,

View File

@ -44,7 +44,8 @@ import java.util.Locale
@Composable
fun MemberDetailScreen(
memberName: String,
yearMonth: YearMonth,
startMonth: YearMonth,
endMonth: YearMonth,
category: String = "",
analysisType: AnalysisType = AnalysisType.EXPENSE,
onNavigateBack: () -> Unit,
@ -53,8 +54,14 @@ fun MemberDetailScreen(
val records by viewModel.memberRecords.collectAsState(initial = emptyList())
val totalAmount by viewModel.totalAmount.collectAsState(initial = 0.0)
LaunchedEffect(memberName, category, yearMonth, analysisType) {
viewModel.loadMemberRecords(memberName, category, yearMonth, analysisType)
LaunchedEffect(memberName, category, startMonth, endMonth, analysisType) {
viewModel.loadMemberRecords(
memberName = memberName,
category = category,
startMonth = startMonth,
endMonth = endMonth,
analysisType = analysisType
)
}
val groupedRecords = remember(records) {

View File

@ -5,24 +5,25 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
import com.yovinchen.bookkeeping.model.BookkeepingRecord
import com.yovinchen.bookkeeping.model.CategoryStat
import com.yovinchen.bookkeeping.model.MemberStat
import kotlinx.coroutines.flow.*
import java.time.YearMonth
import java.time.format.DateTimeFormatter
import java.time.ZoneId
import java.util.Date
class CategoryDetailViewModel(
private val database: BookkeepingDatabase,
private val category: String,
private val month: YearMonth
private val startMonth: YearMonth,
private val endMonth: YearMonth
) : ViewModel() {
private val recordDao = database.bookkeepingDao()
private val yearMonthStr = month.format(DateTimeFormatter.ofPattern("yyyy-MM"))
private val _records = MutableStateFlow<List<BookkeepingRecord>>(emptyList())
val records: StateFlow<List<BookkeepingRecord>> = _records.asStateFlow()
private val _memberStats = MutableStateFlow<List<CategoryStat>>(emptyList())
val memberStats: StateFlow<List<CategoryStat>> = _memberStats.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 } }
@ -33,22 +34,30 @@ class CategoryDetailViewModel(
)
init {
recordDao.getRecordsByCategory(category)
.onEach { records ->
_records.value = records.filter { record ->
val recordMonth = YearMonth.from(
DateTimeFormatter.ofPattern("yyyy-MM")
.parse(yearMonthStr)
)
YearMonth.from(record.date.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime()) == recordMonth
}
}
.launchIn(viewModelScope)
val startDate = startMonth.atDay(1).atStartOfDay()
.atZone(ZoneId.systemDefault())
.toInstant()
.let { Date.from(it) }
recordDao.getMemberStatsByCategory(category, yearMonthStr)
.onEach { stats ->
_memberStats.value = stats
}
.launchIn(viewModelScope)
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)
}
}

View File

@ -8,12 +8,13 @@ import java.time.YearMonth
class CategoryDetailViewModelFactory(
private val database: BookkeepingDatabase,
private val category: String,
private val month: YearMonth
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, month) as T
return CategoryDetailViewModel(database, category, startMonth, endMonth) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}

View File

@ -7,9 +7,7 @@ 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.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.*
import java.time.YearMonth
import java.time.ZoneId
import java.util.Date
@ -19,47 +17,56 @@ class MemberDetailViewModel(application: Application) : AndroidViewModel(applica
private val recordDao = database.bookkeepingDao()
private val _memberRecords = MutableStateFlow<List<BookkeepingRecord>>(emptyList())
val memberRecords: StateFlow<List<BookkeepingRecord>> = _memberRecords
val memberRecords: StateFlow<List<BookkeepingRecord>> = _memberRecords.asStateFlow()
private val _totalAmount = MutableStateFlow(0.0)
val totalAmount: StateFlow<Double> = _totalAmount
val totalAmount: StateFlow<Double> = _totalAmount.asStateFlow()
fun loadMemberRecords(memberName: String, category: String, yearMonth: YearMonth, analysisType: AnalysisType) {
viewModelScope.launch {
val startDate = yearMonth.atDay(1).atStartOfDay()
.atZone(ZoneId.systemDefault())
.toInstant()
.let { Date.from(it) }
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 = yearMonth.atEndOfMonth().atTime(23, 59, 59)
.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 records = if (category.isEmpty()) {
recordDao.getRecordsByMember(
memberName = memberName,
startDate = startDate,
endDate = endDate,
transactionType = transactionType
)
} else {
recordDao.getRecordsByMemberAndCategory(
memberName = memberName,
category = category,
startDate = startDate,
endDate = endDate,
transactionType = transactionType
)
}
_memberRecords.value = records
_totalAmount.value = records.sumOf { it.amount }
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)
}
}