From 6ae83d7bda1c419884a3fdad5af7813c0d067adb Mon Sep 17 00:00:00 2001 From: lisonge Date: Wed, 6 Mar 2024 22:02:43 +0800 Subject: [PATCH] perf: menu --- .../kotlin/li/songe/gkd/ui/AppItemPage.kt | 190 ++++++++++-------- .../main/kotlin/li/songe/gkd/ui/SubsPage.kt | 75 ++----- .../li/songe/gkd/ui/component/SubsAppCard.kt | 61 +++++- .../li/songe/gkd/ui/home/SubsManagePage.kt | 33 +-- 4 files changed, 182 insertions(+), 177 deletions(-) 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 ac42c262bc..e1485e7a01 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt @@ -3,6 +3,7 @@ package li.songe.gkd.ui import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -13,15 +14,16 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Card +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -49,7 +51,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Dialog import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewModelScope import com.blankj.utilcode.util.ClipboardUtils @@ -62,6 +63,7 @@ import li.songe.gkd.data.RawSubscription import li.songe.gkd.data.SubsConfig import li.songe.gkd.data.stringify import li.songe.gkd.db.DbSet +import li.songe.gkd.ui.component.getDialogResult import li.songe.gkd.ui.destinations.GroupItemPageDestination import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions @@ -86,14 +88,13 @@ fun AppItemPage( val scope = rememberCoroutineScope() val navController = LocalNavController.current val vm = hiltViewModel() - val subsItem by vm.subsItemFlow.collectAsState() + val subsItem = vm.subsItemFlow.collectAsState().value val subsRaw = vm.subsRawFlow.collectAsState().value val subsConfigs by vm.subsConfigsFlow.collectAsState() val categoryConfigs by vm.categoryConfigsFlow.collectAsState() val appRaw by vm.subsAppFlow.collectAsState() val appInfoCache by appInfoCacheFlow.collectAsState() - val subsItemVal = subsItem val groupToCategoryMap = subsRaw?.groupToCategoryMap ?: emptyMap() val (showGroupItem, setShowGroupItem) = remember { @@ -106,9 +107,6 @@ fun AppItemPage( var showAddDlg by remember { mutableStateOf(false) } - val (menuGroupRaw, setMenuGroupRaw) = remember { - mutableStateOf(null) - } val (editGroupRaw, setEditGroupRaw) = remember { mutableStateOf(null) } @@ -213,14 +211,97 @@ fun AppItemPage( } Spacer(modifier = Modifier.width(10.dp)) - IconButton(onClick = { - setMenuGroupRaw(group) - }) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = "more", - ) + var expanded by remember { mutableStateOf(false) } + Box( + modifier = Modifier + .wrapContentSize(Alignment.TopStart) + ) { + IconButton(onClick = { + expanded = true + }) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = "more", + ) + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem( + text = { + Text(text = "复制") + }, + onClick = { + val groupAppText = json.encodeToJson5String( + appRaw.copy( + groups = listOf(group) + ) + ) + ClipboardUtils.copyText(groupAppText) + toast("复制成功") + expanded = false + }, + ) + if (editable) { + DropdownMenuItem( + text = { + Text(text = "编辑") + }, + onClick = { + setEditGroupRaw(group) + expanded = false + }, + ) + } + DropdownMenuItem( + text = { + Text(text = "编辑禁用") + }, + onClick = { + setExcludeGroupRaw(group) + expanded = false + }, + ) + if (editable && subsItem != null && subsRaw != null) { + DropdownMenuItem( + text = { + Text(text = "删除", color = MaterialTheme.colorScheme.error) + }, + onClick = { + vm.viewModelScope.launchTry { + val result = getDialogResult( + "删除规则组", + "确定删除规则组 ${group.name} ?" + ) + if (!result) return@launchTry + val newSubsRaw = subsRaw.copy( + apps = subsRaw.apps + .toMutableList() + .apply { + set( + indexOfFirst { a -> a.id == appRaw.id }, + appRaw.copy( + groups = appRaw.groups + .filter { g -> g.key != group.key } + ) + ) + } + ) + updateSubscription(newSubsRaw) + DbSet.subsItemDao.update(subsItem.copy(mtime = System.currentTimeMillis())) + DbSet.subsConfigDao.delete( + subsItem.id, appRaw.id, group.key + ) + toast("删除成功") + } + expanded = false + }, + ) + } + } } + Spacer(modifier = Modifier.width(10.dp)) val groupEnable = getGroupRawEnable( @@ -285,80 +366,11 @@ fun AppItemPage( Text(text = "查看图片") } } - TextButton(onClick = { - val groupAppText = json.encodeToJson5String( - appRaw.copy( - groups = listOf(showGroupItemVal) - ) - ) - ClipboardUtils.copyText(groupAppText) - toast("复制成功") - }) { - Text(text = "复制规则组") - } } }) } - if (menuGroupRaw != null && subsItemVal != null) { - Dialog(onDismissRequest = { setMenuGroupRaw(null) }) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - shape = RoundedCornerShape(16.dp), - ) { - Text(text = "编辑禁用", modifier = Modifier - .clickable { - setExcludeGroupRaw(menuGroupRaw) - setMenuGroupRaw(null) - } - .padding(16.dp) - .fillMaxWidth()) - if (editable) { - Text(text = "编辑规则组", modifier = Modifier - .clickable { - setEditGroupRaw(menuGroupRaw) - setMenuGroupRaw(null) - } - .padding(16.dp) - .fillMaxWidth()) - Text(text = "删除规则组", modifier = Modifier - .clickable { - vm.viewModelScope.launchTry(Dispatchers.IO) { - subsRaw ?: return@launchTry - val newSubsRaw = subsRaw.copy( - apps = subsRaw.apps - .toMutableList() - .apply { - set( - indexOfFirst { a -> a.id == appRaw.id }, - appRaw.copy( - groups = appRaw.groups - .filter { g -> g.key != menuGroupRaw.key } - ) - ) - } - ) - updateSubscription(newSubsRaw) - DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis())) - DbSet.subsConfigDao.delete( - subsItemVal.id, appRaw.id, menuGroupRaw.key - ) - toast("删除成功") - setMenuGroupRaw(null) - } - } - .padding(16.dp) - .fillMaxWidth(), - color = MaterialTheme.colorScheme.error - ) - } - } - } - } - - if (editGroupRaw != null && subsItemVal != null) { + if (editGroupRaw != null && subsItem != null) { var source by remember { mutableStateOf(json.encodeToJson5String(editGroupRaw)) } @@ -416,7 +428,7 @@ fun AppItemPage( }) vm.viewModelScope.launchTry(Dispatchers.IO) { updateSubscription(newSubsRaw) - DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis())) + DbSet.subsItemDao.update(subsItem.copy(mtime = System.currentTimeMillis())) toast("更新成功") } }, enabled = source.isNotEmpty()) { @@ -426,7 +438,7 @@ fun AppItemPage( ) } - if (excludeGroupRaw != null && subsItemVal != null) { + if (excludeGroupRaw != null && subsItem != null) { var source by remember { mutableStateOf( ExcludeData.parse(subsConfigs.find { s -> s.groupKey == excludeGroupRaw.key }?.exclude) @@ -482,7 +494,7 @@ fun AppItemPage( ) } - if (showAddDlg && subsItemVal != null) { + if (showAddDlg && subsItem != null) { var source by remember { mutableStateOf("") } @@ -548,7 +560,7 @@ fun AppItemPage( } }) vm.viewModelScope.launchTry(Dispatchers.IO) { - DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis())) + DbSet.subsItemDao.update(subsItem.copy(mtime = System.currentTimeMillis())) updateSubscription(newSubsRaw) showAddDlg = false toast("添加成功") 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 ca014019ac..681dbb0531 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt @@ -1,6 +1,5 @@ package li.songe.gkd.ui -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -11,7 +10,6 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.Sort @@ -19,7 +17,6 @@ import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Search import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Card import androidx.compose.material3.Checkbox import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -27,7 +24,6 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold @@ -52,19 +48,17 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewModelScope -import com.blankj.utilcode.util.ClipboardUtils import com.blankj.utilcode.util.LogUtils import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootNavGraph -import kotlinx.coroutines.Dispatchers import li.songe.gkd.data.RawSubscription import li.songe.gkd.data.SubsConfig import li.songe.gkd.db.DbSet import li.songe.gkd.ui.component.AppBarTextField import li.songe.gkd.ui.component.SubsAppCard +import li.songe.gkd.ui.component.getDialogResult import li.songe.gkd.ui.destinations.AppItemPageDestination import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions @@ -89,7 +83,7 @@ fun SubsPage( val navController = LocalNavController.current val vm = hiltViewModel() - val subsItem by vm.subsItemFlow.collectAsState() + val subsItem = vm.subsItemFlow.collectAsState().value val appAndConfigs by vm.filterAppAndConfigsFlow.collectAsState() val searchStr by vm.searchStrFlow.collectAsState() val appInfoCache by appInfoCacheFlow.collectAsState() @@ -103,9 +97,6 @@ fun SubsPage( mutableStateOf(false) } - var menuRawApp by remember { - mutableStateOf(null) - } var editRawApp by remember { mutableStateOf(null) } @@ -259,8 +250,20 @@ fun SubsPage( DbSet.subsConfigDao.insert(newItem) }, showMenu = editable, - onMenuClick = { - menuRawApp = appRaw + onDelClick = { + vm.viewModelScope.launchTry { + val result = getDialogResult( + "删除规则组", + "确定删除 ${appInfoCache[appRaw.id]?.name ?: appRaw.name ?: appRaw.id} 下所有规则组?" + ) + if (!result) return@launchTry + if (subsRaw != null && subsItem != null) { + updateSubscription(subsRaw.copy(apps = subsRaw.apps.filter { a -> a.id != appRaw.id })) + DbSet.subsItemDao.update(subsItem.copy(mtime = System.currentTimeMillis())) + DbSet.subsConfigDao.delete(subsItem.id, appRaw.id) + toast("删除成功") + } + } }) } item { @@ -278,9 +281,8 @@ fun SubsPage( } } - val subsItemVal = subsItem - if (showAddDlg && subsRaw != null && subsItemVal != null) { + if (showAddDlg && subsRaw != null && subsItem != null) { var source by remember { mutableStateOf("") } @@ -351,7 +353,7 @@ fun SubsPage( apps = newApps, version = subsRaw.version + 1 ) ) - DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis())) + DbSet.subsItemDao.update(subsItem.copy(mtime = System.currentTimeMillis())) showAddDlg = false toast("添加成功") } @@ -366,7 +368,7 @@ fun SubsPage( } val editAppRawVal = editRawApp - if (editAppRawVal != null && subsItemVal != null && subsRaw != null) { + if (editAppRawVal != null && subsItem != null && subsRaw != null) { var source by remember { mutableStateOf(json.encodeToJson5String(editAppRawVal)) } @@ -395,7 +397,7 @@ fun SubsPage( }, version = subsRaw.version + 1 ) ) - DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis())) + DbSet.subsItemDao.update(subsItem.copy(mtime = System.currentTimeMillis())) editRawApp = null toast("更新成功") } @@ -412,41 +414,4 @@ fun SubsPage( } }) } - - - val menuAppRawVal = menuRawApp - if (menuAppRawVal != null && subsItemVal != null && subsRaw != null) { - Dialog(onDismissRequest = { menuRawApp = null }) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - shape = RoundedCornerShape(16.dp), - ) { - Text(text = "复制", modifier = Modifier - .clickable { - ClipboardUtils.copyText( - json.encodeToJson5String(menuAppRawVal) - ) - toast("复制成功") - menuRawApp = null - } - .fillMaxWidth() - .padding(16.dp)) - Text(text = "删除", modifier = Modifier - .clickable { - // 也许需要二次确认 - vm.viewModelScope.launchTry(Dispatchers.IO) { - updateSubscription(subsRaw.copy(apps = subsRaw.apps.filter { a -> a.id != menuAppRawVal.id })) - DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis())) - DbSet.subsConfigDao.delete(subsItemVal.id, menuAppRawVal.id) - toast("删除成功") - } - menuRawApp = null - } - .fillMaxWidth() - .padding(16.dp), color = MaterialTheme.colorScheme.error) - } - } - } } \ No newline at end of file diff --git a/app/src/main/kotlin/li/songe/gkd/ui/component/SubsAppCard.kt b/app/src/main/kotlin/li/songe/gkd/ui/component/SubsAppCard.kt index bdc2938c60..5825b05d18 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/component/SubsAppCard.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/component/SubsAppCard.kt @@ -3,6 +3,7 @@ package li.songe.gkd.ui.component import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -12,23 +13,35 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Android import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import com.blankj.utilcode.util.ClipboardUtils import com.google.accompanist.drawablepainter.rememberDrawablePainter import li.songe.gkd.data.AppInfo import li.songe.gkd.data.RawSubscription import li.songe.gkd.data.SubsConfig +import li.songe.gkd.util.encodeToJson5String +import li.songe.gkd.util.json +import li.songe.gkd.util.toast @Composable @@ -39,9 +52,10 @@ fun SubsAppCard( enableSize: Int = rawApp.groups.count { g -> g.enable ?: true }, onClick: (() -> Unit)? = null, showMenu: Boolean = false, - onMenuClick: (() -> Unit)? = null, + onDelClick: (() -> Unit)? = null, onValueChange: ((Boolean) -> Unit)? = null, ) { + var expanded by remember { mutableStateOf(false) } Row( modifier = Modifier .height(60.dp) @@ -110,13 +124,44 @@ fun SubsAppCard( Spacer(modifier = Modifier.width(10.dp)) if (showMenu) { - IconButton(onClick = { - onMenuClick?.invoke() - }) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = "more", - ) + Box( + modifier = Modifier + .wrapContentSize(Alignment.TopStart) + ) { + IconButton(onClick = { + expanded = true + }) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = "more", + ) + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem( + text = { + Text(text = "复制") + }, + onClick = { + ClipboardUtils.copyText( + json.encodeToJson5String(rawApp) + ) + toast("复制成功") + expanded = false + }, + ) + DropdownMenuItem( + text = { + Text(text = "删除", color = MaterialTheme.colorScheme.error) + }, + onClick = { + onDelClick?.invoke() + expanded = false + }, + ) + } } Spacer(modifier = Modifier.width(10.dp)) } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/home/SubsManagePage.kt b/app/src/main/kotlin/li/songe/gkd/ui/home/SubsManagePage.kt index 4affb532eb..5060a477f0 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/home/SubsManagePage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/home/SubsManagePage.kt @@ -40,7 +40,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -63,7 +62,6 @@ import li.songe.gkd.ui.destinations.GlobalRulePageDestination import li.songe.gkd.ui.destinations.SubsPageDestination import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.isSafeUrl -import li.songe.gkd.util.launchAsFn import li.songe.gkd.util.launchTry import li.songe.gkd.util.navigate import li.songe.gkd.util.openUri @@ -82,7 +80,6 @@ val subsNav = BottomNavItem( @Composable fun useSubsManagePage(): ScaffoldExt { val context = LocalContext.current - val scope = rememberCoroutineScope() val navController = LocalNavController.current val vm = hiltViewModel() @@ -93,7 +90,6 @@ fun useSubsManagePage(): ScaffoldExt { mutableStateOf(subItems) } - var deleteSubItem: SubsItem? by remember { mutableStateOf(null) } var menuSubItem: SubsItem? by remember { mutableStateOf(null) } var showAddLinkDialog by remember { mutableStateOf(false) } @@ -181,8 +177,15 @@ fun useSubsManagePage(): ScaffoldExt { if (menuSubItemVal.id != -2L) { Text(text = "删除订阅", modifier = Modifier .clickable { - deleteSubItem = menuSubItemVal menuSubItem = null + vm.viewModelScope.launchTry { + val result = getDialogResult( + "删除订阅", + "是否删除订阅 ${subsIdToRaw[menuSubItemVal.id]?.name} ?", + ) + if (!result) return@launchTry + menuSubItemVal.removeAssets() + } } .fillMaxWidth() .padding(16.dp), color = MaterialTheme.colorScheme.error) @@ -191,26 +194,6 @@ fun useSubsManagePage(): ScaffoldExt { } } - deleteSubItem?.let { deleteSubItemVal -> - AlertDialog(onDismissRequest = { deleteSubItem = null }, - title = { Text(text = "是否删除 ${subsIdToRaw[deleteSubItemVal.id]?.name}?") }, - confirmButton = { - TextButton(onClick = scope.launchAsFn { - deleteSubItem = null - deleteSubItemVal.removeAssets() - }) { - Text(text = "是", color = MaterialTheme.colorScheme.error) - } - }, - dismissButton = { - TextButton(onClick = { - deleteSubItem = null - }) { - Text(text = "否") - } - }) - } - LaunchedEffect(showAddLinkDialog) { if (!showAddLinkDialog) { link = ""