From 889c643d9920af20d7d00ea66a04ac48abfd8d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E5=88=BA=E8=9E=88?= Date: Mon, 13 Jan 2025 21:25:36 +0800 Subject: [PATCH] feat: add ActionLog for App/Subs --- .../kotlin/li/songe/gkd/data/ActionLog.kt | 12 +++ .../kotlin/li/songe/gkd/ui/ActionLogPage.kt | 83 ++++++++++++++----- .../kotlin/li/songe/gkd/ui/ActionLogVm.kt | 20 +++-- .../kotlin/li/songe/gkd/ui/AppConfigPage.kt | 11 +++ .../kotlin/li/songe/gkd/ui/AppItemPage.kt | 2 +- .../kotlin/li/songe/gkd/ui/CategoryPage.kt | 2 +- .../li/songe/gkd/ui/GlobalRuleExcludePage.kt | 2 +- .../kotlin/li/songe/gkd/ui/GlobalRulePage.kt | 2 +- .../main/kotlin/li/songe/gkd/ui/SubsPage.kt | 2 +- .../li/songe/gkd/ui/component/SubsSheet.kt | 10 +++ .../li/songe/gkd/ui/component/TowLineText.kt | 24 ++++-- .../li/songe/gkd/ui/home/ControlPage.kt | 2 +- .../kotlin/li/songe/gkd/util/SubsState.kt | 1 - 13 files changed, 130 insertions(+), 43 deletions(-) diff --git a/app/src/main/kotlin/li/songe/gkd/data/ActionLog.kt b/app/src/main/kotlin/li/songe/gkd/data/ActionLog.kt index 4a0347fd76..123bd27294 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/ActionLog.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/ActionLog.kt @@ -60,12 +60,24 @@ data class ActionLog( @Query("DELETE FROM action_log") suspend fun deleteAll() + @Query("DELETE FROM action_log WHERE subs_id=:subsId") + suspend fun deleteSubsAll(subsId: Long) + + @Query("DELETE FROM action_log WHERE app_id=:appId") + suspend fun deleteAppAll(appId: String) + @Query("SELECT * FROM action_log ORDER BY id DESC LIMIT 1000") fun query(): Flow> @Query("SELECT * FROM action_log ORDER BY id DESC ") fun pagingSource(): PagingSource + @Query("SELECT * FROM action_log WHERE subs_id=:subsId ORDER BY id DESC ") + fun pagingSubsSource(subsId: Long): PagingSource + + @Query("SELECT * FROM action_log WHERE app_id=:appId ORDER BY id DESC ") + fun pagingAppSource(appId: String): PagingSource + @Query("SELECT COUNT(*) FROM action_log") fun count(): Flow diff --git a/app/src/main/kotlin/li/songe/gkd/ui/ActionLogPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/ActionLogPage.kt index d78493c111..0f2b6e4f7d 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/ActionLogPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/ActionLogPage.kt @@ -71,6 +71,7 @@ import li.songe.gkd.ui.component.EmptyText import li.songe.gkd.ui.component.FixedTimeText import li.songe.gkd.ui.component.LocalNumberCharWidth import li.songe.gkd.ui.component.StartEllipsisText +import li.songe.gkd.ui.component.TowLineText import li.songe.gkd.ui.component.measureNumberTextWidth import li.songe.gkd.ui.component.waitResult import li.songe.gkd.ui.style.EmptyHeight @@ -87,12 +88,14 @@ import li.songe.gkd.util.toast @Destination(style = ProfileTransitions::class) @Composable -fun ActionLogPage() { +fun ActionLogPage( + subsId: Long? = null, + appId: String? = null, +) { val context = LocalContext.current as MainActivity val mainVm = context.mainVm val navController = LocalNavController.current val vm = viewModel() - val actionLogCount by vm.actionLogCountFlow.collectAsState() val actionDataItems = vm.pagingDataFlow.collectAsLazyPagingItems() val subsIdToRaw by subsIdToRawFlow.collectAsState() @@ -134,16 +137,46 @@ fun ActionLogPage() { ) } }, - title = { Text(text = "触发记录") }, + title = { + val title = "触发记录" + if (subsId != null) { + TowLineText( + title = title, + subtitle = subsIdToRaw[subsId]?.name ?: subsId.toString() + ) + } else if (appId != null) { + TowLineText( + title = title, + subtitle = appId, + showApp = true, + ) + } else { + Text(text = title) + } + }, actions = { - if (actionLogCount > 0) { + if (actionDataItems.itemCount > 0) { IconButton(onClick = throttle(fn = mainVm.viewModelScope.launchAsFn { + val text = if (subsId != null) { + "确定删除当前订阅所有触发记录?" + } else if (appId != null) { + "确定删除当前应用所有触发记录?" + } else { + "确定删除所有触发记录?" + } mainVm.dialogFlow.waitResult( title = "删除记录", - text = "确定删除所有触发记录?", + text = text, error = true, ) - DbSet.actionLogDao.deleteAll() + if (subsId != null) { + DbSet.actionLogDao.deleteSubsAll(subsId) + } else if (appId != null) { + DbSet.actionLogDao.deleteAppAll(appId) + } else { + DbSet.actionLogDao.deleteAll() + } + })) { Icon( imageVector = Icons.Outlined.Delete, @@ -172,13 +205,15 @@ fun ActionLogPage() { lastItem = lastItem, onClick = { previewActionLog = item.t0 - } + }, + subsId = subsId, + appId = appId, ) } } item { Spacer(modifier = Modifier.height(EmptyHeight)) - if (actionLogCount == 0 && actionDataItems.loadState.refresh !is LoadState.Loading) { + if (actionDataItems.itemCount == 0 && actionDataItems.loadState.refresh !is LoadState.Loading) { EmptyText(text = "暂无记录") } } @@ -330,7 +365,9 @@ private fun ActionLogCard( i: Int, item: Tuple3, lastItem: Tuple3?, - onClick: () -> Unit + onClick: () -> Unit, + subsId: Long?, + appId: String?, ) { val context = LocalContext.current as MainActivity val (actionLog, group, rule) = item @@ -348,7 +385,7 @@ private fun ActionLogCard( top = verticalPadding ) ) { - if (isDiffApp) { + if (isDiffApp && appId == null) { AppNameText(appId = actionLog.appId) } Row( @@ -357,7 +394,9 @@ private fun ActionLogCard( .fillMaxWidth() .height(IntrinsicSize.Min) ) { - Spacer(modifier = Modifier.width(2.dp)) + if (appId == null) { + Spacer(modifier = Modifier.width(2.dp)) + } Spacer( modifier = Modifier .fillMaxHeight() @@ -386,16 +425,18 @@ private fun ActionLogCard( color = LocalContentColor.current.copy(alpha = 0.5f), ) } - Text( - text = subscription?.name ?: "id=${actionLog.subsId}", - modifier = Modifier.clickable(onClick = throttle { - if (subsItemsFlow.value.any { it.id == actionLog.subsId }) { - context.mainVm.sheetSubsIdFlow.value = actionLog.subsId - } else { - toast("订阅不存在") - } - }) - ) + if (subsId == null) { + Text( + text = subscription?.name ?: "id=${actionLog.subsId}", + modifier = Modifier.clickable(onClick = throttle { + if (subsItemsFlow.value.any { it.id == actionLog.subsId }) { + context.mainVm.sheetSubsIdFlow.value = actionLog.subsId + } else { + toast("订阅不存在") + } + }) + ) + } Row( modifier = Modifier.fillMaxWidth() ) { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/ActionLogVm.kt b/app/src/main/kotlin/li/songe/gkd/ui/ActionLogVm.kt index ca59b1420a..25c2b673a6 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/ActionLogVm.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/ActionLogVm.kt @@ -1,22 +1,31 @@ package li.songe.gkd.ui +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.cachedIn import androidx.paging.map -import kotlinx.coroutines.flow.SharingStarted +import com.ramcosta.composedestinations.generated.destinations.ActionLogPageDestination import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.stateIn import li.songe.gkd.data.SubsConfig import li.songe.gkd.data.Tuple3 import li.songe.gkd.db.DbSet import li.songe.gkd.util.subsIdToRawFlow -class ActionLogVm : ViewModel() { +class ActionLogVm(stateHandle: SavedStateHandle) : ViewModel() { + private val args = ActionLogPageDestination.argsFrom(stateHandle) - val pagingDataFlow = Pager(PagingConfig(pageSize = 100)) { DbSet.actionLogDao.pagingSource() } + val pagingDataFlow = Pager(PagingConfig(pageSize = 100)) { + if (args.subsId != null) { + DbSet.actionLogDao.pagingSubsSource(subsId = args.subsId) + } else if (args.appId != null) { + DbSet.actionLogDao.pagingAppSource(appId = args.appId) + } else { + DbSet.actionLogDao.pagingSource() + } + } .flow .combine(subsIdToRawFlow) { pagingData, subsIdToRaw -> pagingData.map { c -> @@ -38,7 +47,4 @@ class ActionLogVm : ViewModel() { } .cachedIn(viewModelScope) - val actionLogCountFlow = - DbSet.actionLogDao.count().stateIn(viewModelScope, SharingStarted.Eagerly, 0) - } \ No newline at end of file diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AppConfigPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/AppConfigPage.kt index 2ff4a9e418..a728ca228d 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AppConfigPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AppConfigPage.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.Sort +import androidx.compose.material.icons.filled.History import androidx.compose.material.icons.outlined.Edit import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -50,6 +51,7 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph +import com.ramcosta.composedestinations.generated.destinations.ActionLogPageDestination import com.ramcosta.composedestinations.generated.destinations.AppItemPageDestination import com.ramcosta.composedestinations.generated.destinations.GlobalRulePageDestination import com.ramcosta.composedestinations.utils.toDestinationsNavigator @@ -118,6 +120,15 @@ fun AppConfigPage(appId: String) { appId = appId ) }, actions = { + IconButton(onClick = throttle { + navController.toDestinationsNavigator() + .navigate(ActionLogPageDestination(appId = appId)) + }) { + Icon( + imageVector = Icons.Default.History, + contentDescription = null, + ) + } IconButton(onClick = { expanded = true }) { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt index 1748f472c9..1ec983ca28 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt @@ -137,7 +137,7 @@ fun AppItemPage( }, title = { TowLineText( title = subsRaw?.name ?: subsItemId.toString(), - subTitle = appInfoCache[appId]?.name ?: appRaw.name ?: appId + subtitle = appInfoCache[appId]?.name ?: appRaw.name ?: appId ) }, actions = {}) }, floatingActionButton = { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/CategoryPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/CategoryPage.kt index f9ae64f5ed..54bb5733b8 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/CategoryPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/CategoryPage.kt @@ -105,7 +105,7 @@ fun CategoryPage(subsItemId: Long) { }, title = { TowLineText( title = subsRaw?.name ?: subsItemId.toString(), - subTitle = "规则类别" + subtitle = "规则类别" ) }, actions = { IconButton(onClick = throttle { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludePage.kt b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludePage.kt index fecb33537e..32d9cfa450 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludePage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludePage.kt @@ -163,7 +163,7 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) { } else { TowLineText( title = rawSubs?.name ?: subsItemId.toString(), - subTitle = (group?.name ?: groupKey.toString()) + subtitle = (group?.name ?: groupKey.toString()) ) } }, actions = { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRulePage.kt b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRulePage.kt index 4ae5c8ce3b..b4c860973f 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRulePage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRulePage.kt @@ -125,7 +125,7 @@ fun GlobalRulePage(subsItemId: Long, focusGroupKey: Int? = null) { }, title = { TowLineText( title = rawSubs?.name ?: subsItemId.toString(), - subTitle = "全局规则" + subtitle = "全局规则" ) }) }, diff --git a/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt index a87654131e..e9fb0b18fb 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt @@ -169,7 +169,7 @@ fun SubsPage( } else { TowLineText( title = subsRaw?.name ?: subsItemId.toString(), - subTitle = "应用规则", + subtitle = "应用规则", ) } }, actions = { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/component/SubsSheet.kt b/app/src/main/kotlin/li/songe/gkd/ui/component/SubsSheet.kt index 8ec1052815..defc5cb29a 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/component/SubsSheet.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/component/SubsSheet.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.automirrored.filled.OpenInNew +import androidx.compose.material.icons.filled.History import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Edit @@ -42,6 +43,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.ramcosta.composedestinations.generated.destinations.ActionLogPageDestination import com.ramcosta.composedestinations.generated.destinations.CategoryPageDestination import com.ramcosta.composedestinations.generated.destinations.CategoryPageDestination.invoke import com.ramcosta.composedestinations.generated.destinations.GlobalRulePageDestination @@ -416,6 +418,14 @@ fun SubsSheet( modifier = childModifier, horizontalArrangement = Arrangement.End ) { + IconButton(onClick = throttle { + setSubsId(null) + sheetSubsIdFlow.value = null + navController.toDestinationsNavigator() + .navigate(ActionLogPageDestination(subsId = subsItem.id)) + }) { + Icon(imageVector = Icons.Default.History, contentDescription = null) + } if (subscription != null || !subsItem.isLocal) { IconButton(onClick = throttle { context.mainVm.showShareDataIdsFlow.value = setOf(subsItem.id) diff --git a/app/src/main/kotlin/li/songe/gkd/ui/component/TowLineText.kt b/app/src/main/kotlin/li/songe/gkd/ui/component/TowLineText.kt index dae4addeb5..aad70d3ef9 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/component/TowLineText.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/component/TowLineText.kt @@ -1,15 +1,18 @@ package li.songe.gkd.ui.component import androidx.compose.foundation.layout.Column +import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.text.style.TextOverflow @Composable fun TowLineText( title: String, - subTitle: String + subtitle: String, + showApp: Boolean = false, ) { Column { Text( @@ -18,11 +21,16 @@ fun TowLineText( overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleMedium ) - Text( - text = subTitle, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleSmall - ) + CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.titleSmall) { + if (showApp) { + AppNameText(appId = subtitle) + } else { + Text( + text = subtitle, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/li/songe/gkd/ui/home/ControlPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/home/ControlPage.kt index cc6d8920ec..b8d8551e65 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/home/ControlPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/home/ControlPage.kt @@ -229,7 +229,7 @@ fun useControlPage(): ScaffoldExt { imageVector = Icons.Default.History, onClick = { navController.toDestinationsNavigator() - .navigate(ActionLogPageDestination) + .navigate(ActionLogPageDestination()) } ) diff --git a/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt b/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt index 24e8c857fa..9a8a7edf5c 100644 --- a/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt +++ b/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.withContext -import kotlinx.serialization.encodeToString import li.songe.gkd.appScope import li.songe.gkd.data.AppRule import li.songe.gkd.data.CategoryConfig