perf: 优化设置页面的性能

This commit is contained in:
Super12138 2025-06-30 13:10:48 +08:00
parent 711d8a19c5
commit 5c25a4657d
10 changed files with 259 additions and 181 deletions

View file

@ -34,7 +34,7 @@ android {
applicationId = "cn.super12138.todo"
minSdk = 24
targetSdk = 36
versionCode = 730
versionCode = 733
versionName = "2.1.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

View file

@ -224,8 +224,6 @@ fun TodoEditorPage(
}
}
}
//Spacer(Modifier.size(20.dp))
}
}

View file

@ -1,11 +1,9 @@
package cn.super12138.todo.ui.pages.settings
import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Balance
import androidx.compose.material.icons.outlined.Numbers
@ -50,61 +48,68 @@ fun SettingsAbout(
val context = LocalContext.current
val uriHandler = LocalUriHandler.current
Column(
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.verticalScroll(rememberScrollState())
) {
var clickCount by remember { mutableIntStateOf(0) }
var lastClickTime by remember { mutableLongStateOf(0L) }
item {
var clickCount by remember { mutableIntStateOf(0) }
var lastClickTime by remember { mutableLongStateOf(0L) }
LaunchedEffect(clickCount) {
if (clickCount > 0) {
lastClickTime = System.currentTimeMillis()
val currentClickTime = lastClickTime
delay(300L)
LaunchedEffect(clickCount) {
if (clickCount > 0) {
lastClickTime = System.currentTimeMillis()
val currentClickTime = lastClickTime
delay(300L)
if (currentClickTime == lastClickTime) {
clickCount = 0
}
}
}
SettingsItem(
leadingIcon = Icons.Outlined.Numbers,
title = stringResource(R.string.pref_app_version),
description = SystemUtils.getAppVersion(context),
onClick = {
clickCount++
if (clickCount == 5) {
if ((System.currentTimeMillis() % 2) == 0.toLong()) {
Toast.makeText(context, "🍨", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "", Toast.LENGTH_SHORT).show()
if (currentClickTime == lastClickTime) {
clickCount = 0
}
clickCount = 0
}
}
)
SettingsItem(
leadingIcon = Icons.Outlined.Person4,
title = stringResource(R.string.pref_developer),
description = stringResource(R.string.developer_name),
onClick = { uriHandler.openUri(Constants.DEVELOPER_GITHUB) }
)
SettingsItem(
leadingIcon = GitHubIcon,
title = stringResource(R.string.pref_view_on_github),
description = stringResource(R.string.pref_view_on_github_desc),
onClick = { uriHandler.openUri(Constants.GITHUB_REPO) }
)
SettingsItem(
leadingIcon = Icons.Outlined.Balance,
title = stringResource(R.string.pref_licence),
description = stringResource(R.string.pref_licence_desc),
onClick = toLicencePage
)
SettingsItem(
leadingIcon = Icons.Outlined.Numbers,
title = stringResource(R.string.pref_app_version),
description = SystemUtils.getAppVersion(context),
onClick = {
clickCount++
if (clickCount == 5) {
if ((System.currentTimeMillis() % 2) == 0.toLong()) {
Toast.makeText(context, "🍨", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "", Toast.LENGTH_SHORT).show()
}
clickCount = 0
}
}
)
}
item {
SettingsItem(
leadingIcon = Icons.Outlined.Person4,
title = stringResource(R.string.pref_developer),
description = stringResource(R.string.developer_name),
onClick = { uriHandler.openUri(Constants.DEVELOPER_GITHUB) }
)
}
item {
SettingsItem(
leadingIcon = GitHubIcon,
title = stringResource(R.string.pref_view_on_github),
description = stringResource(R.string.pref_view_on_github_desc),
onClick = { uriHandler.openUri(Constants.GITHUB_REPO) }
)
}
item {
SettingsItem(
leadingIcon = Icons.Outlined.Balance,
title = stringResource(R.string.pref_licence),
description = stringResource(R.string.pref_licence_desc),
onClick = toLicencePage
)
}
}
}
}

View file

@ -4,11 +4,9 @@ import android.content.Context
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FileDownload
import androidx.compose.material.icons.outlined.FileUpload
@ -110,30 +108,34 @@ fun SettingsData(
snackbarHost = { SnackbarHost(snackbarHostState) },
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { innerPadding ->
Column(
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.verticalScroll(rememberScrollState())
) {
SettingsItem(
leadingIcon = Icons.Outlined.FileDownload,
title = stringResource(R.string.pref_backup),
description = stringResource(R.string.pref_backup_desc),
onClick = {
backupLauncher.launch("Todo-backup-${SystemUtils.getTime()}.zip")
}
)
SettingsItem(
leadingIcon = Icons.Outlined.FileUpload,
title = stringResource(R.string.pref_restore),
description = stringResource(R.string.pref_restore_desc),
onClick = {
restoreLauncher.launch(arrayOf("application/zip"))
}
)
item {
SettingsItem(
leadingIcon = Icons.Outlined.FileDownload,
title = stringResource(R.string.pref_backup),
description = stringResource(R.string.pref_backup_desc),
onClick = {
backupLauncher.launch("Todo-backup-${SystemUtils.getTime()}.zip")
}
)
}
item {
SettingsItem(
leadingIcon = Icons.Outlined.FileUpload,
title = stringResource(R.string.pref_restore),
description = stringResource(R.string.pref_restore_desc),
onClick = {
restoreLauncher.launch(arrayOf("application/zip"))
}
)
}
}
}
ConfirmDialog(
visible = showRestoreDialog,
icon = Icons.Outlined.RestartAlt,

View file

@ -1,10 +1,8 @@
package cn.super12138.todo.ui.pages.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Sort
import androidx.compose.material.icons.outlined.Checklist
@ -59,43 +57,54 @@ fun SettingsInterface(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { innerPadding ->
Column(
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.verticalScroll(rememberScrollState())
) {
SettingsCategory(stringResource(R.string.pref_category_todo_list))
SwitchSettingsItem(
checked = showCompleted,
leadingIcon = Icons.Outlined.Checklist,
title = stringResource(R.string.pref_show_completed),
description = stringResource(R.string.pref_show_completed_desc),
onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } },
)
SettingsItem(
leadingIcon = Icons.AutoMirrored.Outlined.Sort,
title = stringResource(R.string.pref_sorting_method),
description = SortingMethod.fromId(sortingMethod).getDisplayName(context),
onClick = { showSortingMethodDialog = true }
)
item {
SettingsCategory(stringResource(R.string.pref_category_todo_list))
}
item {
SwitchSettingsItem(
checked = showCompleted,
leadingIcon = Icons.Outlined.Checklist,
title = stringResource(R.string.pref_show_completed),
description = stringResource(R.string.pref_show_completed_desc),
onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } },
)
}
item {
SettingsItem(
leadingIcon = Icons.AutoMirrored.Outlined.Sort,
title = stringResource(R.string.pref_sorting_method),
description = SortingMethod.fromId(sortingMethod).getDisplayName(context),
onClick = { showSortingMethodDialog = true }
)
}
SettingsCategory(stringResource(R.string.pref_category_global))
SwitchSettingsItem(
checked = secureMode,
leadingIcon = Icons.Outlined.Shield,
title = stringResource(R.string.pref_secure_mode),
description = stringResource(R.string.pref_secure_mode_desc),
onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } }
)
SwitchSettingsItem(
checked = hapticFeedback,
leadingIcon = Icons.Outlined.Vibration,
title = stringResource(R.string.pref_haptic_feedback),
description = stringResource(R.string.pref_haptic_feedback_desc),
onCheckedChange = { scope.launch { DataStoreManager.setHapticFeedback(it) } }
)
SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info))
item {
SettingsCategory(stringResource(R.string.pref_category_global))
}
item {
SwitchSettingsItem(
checked = secureMode,
leadingIcon = Icons.Outlined.Shield,
title = stringResource(R.string.pref_secure_mode),
description = stringResource(R.string.pref_secure_mode_desc),
onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } }
)
}
item {
SwitchSettingsItem(
checked = hapticFeedback,
leadingIcon = Icons.Outlined.Vibration,
title = stringResource(R.string.pref_haptic_feedback),
description = stringResource(R.string.pref_haptic_feedback_desc),
onCheckedChange = { scope.launch { DataStoreManager.setHapticFeedback(it) } }
)
SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info))
}
}
}

View file

@ -1,10 +1,8 @@
package cn.super12138.todo.ui.pages.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ColorLens
import androidx.compose.material.icons.outlined.Dns
@ -14,6 +12,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
@ -33,38 +32,46 @@ fun SettingsMain(
LargeTopAppBarScaffold(
title = stringResource(R.string.page_settings),
scrollBehavior = scrollBehavior,
onBack = onNavigateUp
onBack = onNavigateUp,
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) { innerPadding ->
Column(
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.verticalScroll(rememberScrollState())
) {
SettingsItem(
leadingIcon = Icons.Outlined.ColorLens,
title = stringResource(R.string.pref_appearance),
description = stringResource(R.string.pref_appearance_desc),
onClick = toAppearancePage
)
SettingsItem(
leadingIcon = Icons.Outlined.ViewComfy,
title = stringResource(R.string.pref_interface_interaction),
description = stringResource(R.string.pref_interface_interaction_desc),
onClick = toInterfacePage
)
SettingsItem(
leadingIcon = Icons.Outlined.Dns,
title = stringResource(R.string.pref_data),
description = stringResource(R.string.pref_data_desc),
onClick = toDataPage
)
SettingsItem(
leadingIcon = Icons.Outlined.Info,
title = stringResource(R.string.pref_about),
description = stringResource(R.string.pref_about_desc),
onClick = toAboutPage
)
item {
SettingsItem(
leadingIcon = Icons.Outlined.ColorLens,
title = stringResource(R.string.pref_appearance),
description = stringResource(R.string.pref_appearance_desc),
onClick = toAppearancePage
)
}
item {
SettingsItem(
leadingIcon = Icons.Outlined.ViewComfy,
title = stringResource(R.string.pref_interface_interaction),
description = stringResource(R.string.pref_interface_interaction_desc),
onClick = toInterfacePage
)
}
item {
SettingsItem(
leadingIcon = Icons.Outlined.Dns,
title = stringResource(R.string.pref_data),
description = stringResource(R.string.pref_data_desc),
onClick = toDataPage
)
}
item {
SettingsItem(
leadingIcon = Icons.Outlined.Info,
title = stringResource(R.string.pref_about),
description = stringResource(R.string.pref_about_desc),
onClick = toAboutPage
)
}
}
}
}

View file

@ -9,6 +9,8 @@ import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@ -82,6 +84,55 @@ fun RowSettingsItem(
}
}
@Composable
fun LazyRowSettingsItem(
modifier: Modifier = Modifier,
leadingIcon: (@Composable () -> Unit)? = null,
title: String,
description: String? = null,
trailingContent: (@Composable () -> Unit)? = null,
shape: Shape = MaterialTheme.shapes.large,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
fadedEdgeWidth: Dp,
maskColor: Color = MaterialTheme.colorScheme.background,
content: LazyListScope.() -> Unit
) {
MoreContentSettingsItem(
leadingIcon = leadingIcon,
title = title,
description = description,
trailingContent = trailingContent,
shape = shape,
modifier = modifier
) {
LazyRow(
modifier = Modifier
.fillMaxWidth()
// 渲染到离屏缓冲区是为了确保边缘淡出的 alpha 效果仅应用于文本本身,而不影响该可组合项下方绘制的内容(例如窗口背景)。
.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
.drawWithContent {
// 需要调用 drawContent因为它用于将内容绘制到布局中的接收器作用域允许内容穿插在其他画布操作之间绘制。
// 如果未调用 drawContent则不会绘制该布局的内容。
drawContent()
drawFadedEdge(
edgeWidth = fadedEdgeWidth,
maskColor = maskColor,
leftEdge = true
)
drawFadedEdge(
edgeWidth = fadedEdgeWidth,
maskColor = maskColor,
leftEdge = false
)
},
horizontalArrangement = horizontalArrangement,
verticalAlignment = verticalAlignment,
content = content
)
}
}
@Composable
fun MoreContentSettingsItem(
modifier: Modifier = Modifier,

View file

@ -33,9 +33,7 @@ fun SwitchSettingsItem(
modifier = Modifier.padding(start = TodoDefaults.settingsItemHorizontalPadding / 2)
)
},
onClick = {
onCheckedChange(!checked)
},
onClick = { onCheckedChange(!checked) },
modifier = modifier
)
}

View file

@ -9,7 +9,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cn.super12138.todo.R
import cn.super12138.todo.ui.pages.settings.components.RowSettingsItem
import cn.super12138.todo.ui.pages.settings.components.LazyRowSettingsItem
import cn.super12138.todo.ui.theme.DarkMode
@Composable
@ -20,38 +20,44 @@ fun DarkModePicker(
) {
val context = LocalContext.current
val isInDarkTheme = isSystemInDarkTheme()
RowSettingsItem(
LazyRowSettingsItem(
title = stringResource(R.string.pref_dark_mode),
description = stringResource(R.string.pref_dark_mode_desc),
horizontalArrangement = Arrangement.spacedBy(5.dp),
fadedEdgeWidth = 8.dp,
modifier = modifier
) {
DarkModeItem(
icon = DarkMode.FollowSystem.icon,
name = DarkMode.FollowSystem.getDisplayName(context),
contentColor = if (isInDarkTheme) Color.White else Color.Black,
containerColor = if (isInDarkTheme) Color.Black else Color.White,
selected = currentDarkMode == DarkMode.FollowSystem,
onSelect = { onDarkModeChange(DarkMode.FollowSystem) }
)
item {
DarkModeItem(
icon = DarkMode.FollowSystem.icon,
name = DarkMode.FollowSystem.getDisplayName(context),
contentColor = if (isInDarkTheme) Color.White else Color.Black,
containerColor = if (isInDarkTheme) Color.Black else Color.White,
selected = currentDarkMode == DarkMode.FollowSystem,
onSelect = { onDarkModeChange(DarkMode.FollowSystem) }
)
}
DarkModeItem(
icon = DarkMode.Light.icon,
name = DarkMode.Light.getDisplayName(context),
contentColor = Color.Black,
containerColor = Color.White,
selected = currentDarkMode == DarkMode.Light,
onSelect = { onDarkModeChange(DarkMode.Light) }
)
item {
DarkModeItem(
icon = DarkMode.Light.icon,
name = DarkMode.Light.getDisplayName(context),
contentColor = Color.Black,
containerColor = Color.White,
selected = currentDarkMode == DarkMode.Light,
onSelect = { onDarkModeChange(DarkMode.Light) }
)
}
DarkModeItem(
icon = DarkMode.Dark.icon,
name = DarkMode.Dark.getDisplayName(context),
contentColor = Color.White,
containerColor = Color.Black,
selected = currentDarkMode == DarkMode.Dark,
onSelect = { onDarkModeChange(DarkMode.Dark) }
)
item {
DarkModeItem(
icon = DarkMode.Dark.icon,
name = DarkMode.Dark.getDisplayName(context),
contentColor = Color.White,
containerColor = Color.Black,
selected = currentDarkMode == DarkMode.Dark,
onSelect = { onDarkModeChange(DarkMode.Dark) }
)
}
}
}

View file

@ -8,7 +8,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cn.super12138.todo.R
import cn.super12138.todo.ui.pages.settings.components.RowSettingsItem
import cn.super12138.todo.ui.pages.settings.components.LazyRowSettingsItem
import cn.super12138.todo.ui.theme.ContrastLevel
import cn.super12138.todo.ui.theme.DarkMode
import cn.super12138.todo.ui.theme.PaletteStyle
@ -27,7 +27,7 @@ fun PalettePicker(
PaletteStyle.entries.toList()
}
RowSettingsItem(
LazyRowSettingsItem(
title = stringResource(R.string.pref_palette_style),
description = stringResource(R.string.pref_palette_style_desc),
horizontalArrangement = Arrangement.spacedBy(5.dp),
@ -35,18 +35,20 @@ fun PalettePicker(
modifier = modifier
) {
paletteOptions.forEach { paletteStyle ->
PaletteItem(
isDynamicColor = isDynamicColor,
isDark = when (isDarkMode) {
DarkMode.FollowSystem -> isSystemInDarkTheme()
DarkMode.Light -> false
DarkMode.Dark -> true
},
paletteStyle = paletteStyle,
selected = currentPalette == paletteStyle,
contrastLevel = contrastLevel,
onSelect = { onPaletteChange(paletteStyle) }
)
item {
PaletteItem(
isDynamicColor = isDynamicColor,
isDark = when (isDarkMode) {
DarkMode.FollowSystem -> isSystemInDarkTheme()
DarkMode.Light -> false
DarkMode.Dark -> true
},
paletteStyle = paletteStyle,
selected = currentPalette == paletteStyle,
contrastLevel = contrastLevel,
onSelect = { onPaletteChange(paletteStyle) }
)
}
}
}
}