From 2d7efc04a7b078035562cd408b7ddc87d83f6d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 19 Apr 2026 23:25:45 +0800 Subject: [PATCH] Add long-press copy on App and Core version items --- .../screen/settings/AppSettingsScreen.kt | 106 ++++++++++++------ .../screen/settings/CoreSettingsScreen.kt | 99 +++++++++++----- 2 files changed, 143 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt b/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt index 3a71f46..9c94f63 100644 --- a/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt +++ b/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt @@ -9,9 +9,13 @@ import android.net.Uri import android.os.Build import android.text.format.Formatter import android.util.Log +import android.widget.Toast import androidx.appcompat.app.AppCompatDelegate +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -26,6 +30,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.outlined.AdminPanelSettings import androidx.compose.material.icons.outlined.Autorenew import androidx.compose.material.icons.outlined.DeleteForever @@ -44,6 +49,8 @@ import androidx.compose.material3.Badge import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -83,6 +90,7 @@ import io.nekohasekai.sfa.R import io.nekohasekai.sfa.compose.component.UpdateAvailableDialog import io.nekohasekai.sfa.compose.topbar.OverrideTopBar import io.nekohasekai.sfa.database.Settings +import io.nekohasekai.sfa.ktx.clipboardText import io.nekohasekai.sfa.update.UpdateCheckException import io.nekohasekai.sfa.update.UpdateSource import io.nekohasekai.sfa.update.UpdateState @@ -99,7 +107,7 @@ import java.io.File import java.util.Locale import android.provider.Settings as AndroidSettings -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun AppSettingsScreen(navController: NavController) { OverrideTopBar { @@ -142,6 +150,7 @@ fun AppSettingsScreen(navController: NavController) { var downloadJob by remember { mutableStateOf(null) } var downloadError by remember { mutableStateOf(null) } var showUpdateAvailableDialog by remember { mutableStateOf(false) } + var showVersionMenu by remember { mutableStateOf(false) } var notificationEnabled by remember { mutableStateOf(true) } var dynamicNotification by remember { mutableStateOf(Settings.dynamicNotification) } @@ -433,39 +442,70 @@ fun AppSettingsScreen(navController: NavController) { ), ) { Column { - ListItem( - headlineContent = { - Text( - stringResource(R.string.app_version_title), - style = MaterialTheme.typography.bodyLarge, - ) - }, - supportingContent = { - Text( - BuildConfig.VERSION_NAME, - style = MaterialTheme.typography.bodyMedium, - ) - }, - leadingContent = { - Icon( - imageVector = Icons.Outlined.Info, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - ) - }, - trailingContent = { - if (hasUpdate) { - Badge(containerColor = MaterialTheme.colorScheme.primary) { Text("New") } + Box { + ListItem( + headlineContent = { + Text( + stringResource(R.string.app_version_title), + style = MaterialTheme.typography.bodyLarge, + ) + }, + supportingContent = { + Text( + BuildConfig.VERSION_NAME, + style = MaterialTheme.typography.bodyMedium, + ) + }, + leadingContent = { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + }, + trailingContent = { + if (hasUpdate) { + Badge(containerColor = MaterialTheme.colorScheme.primary) { Text("New") } + } + }, + modifier = + Modifier + .clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)) + .combinedClickable( + onClick = {}, + onLongClick = { showVersionMenu = true }, + ), + colors = + ListItemDefaults.colors( + containerColor = Color.Transparent, + ), + ) + Box(modifier = Modifier.align(Alignment.BottomEnd)) { + DropdownMenu( + expanded = showVersionMenu, + onDismissRequest = { showVersionMenu = false }, + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.per_app_proxy_action_copy)) }, + leadingIcon = { + Icon( + imageVector = Icons.Filled.ContentCopy, + contentDescription = null, + ) + }, + onClick = { + clipboardText = BuildConfig.VERSION_NAME + Toast.makeText( + context, + R.string.copied_to_clipboard, + Toast.LENGTH_SHORT, + ).show() + showVersionMenu = false + }, + ) } - }, - modifier = - Modifier - .clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)), - colors = - ListItemDefaults.colors( - containerColor = Color.Transparent, - ), - ) + } + } ListItem( headlineContent = { diff --git a/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/CoreSettingsScreen.kt b/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/CoreSettingsScreen.kt index c23400c..ff44ad8 100644 --- a/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/CoreSettingsScreen.kt +++ b/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/CoreSettingsScreen.kt @@ -5,8 +5,11 @@ import android.content.Context import android.content.Intent import android.provider.DocumentsContract import android.widget.Toast +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -18,6 +21,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.outlined.DeleteForever import androidx.compose.material.icons.outlined.FolderOpen import androidx.compose.material.icons.outlined.Info @@ -25,6 +29,8 @@ import androidx.compose.material.icons.outlined.Storage import androidx.compose.material.icons.outlined.WarningAmber import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -41,6 +47,7 @@ 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 import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color @@ -52,11 +59,12 @@ import io.nekohasekai.libbox.Libbox import io.nekohasekai.sfa.R import io.nekohasekai.sfa.compose.topbar.OverrideTopBar import io.nekohasekai.sfa.database.Settings +import io.nekohasekai.sfa.ktx.clipboardText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun CoreSettingsScreen(navController: NavController) { OverrideTopBar { @@ -77,6 +85,7 @@ fun CoreSettingsScreen(navController: NavController) { val scope = rememberCoroutineScope() var dataSize by remember { mutableStateOf("") } val version = remember { Libbox.version() } + var showVersionMenu by remember { mutableStateOf(false) } var disableDeprecatedWarnings by remember { mutableStateOf(Settings.disableDeprecatedWarnings) } // Calculate data size on launch @@ -114,34 +123,66 @@ fun CoreSettingsScreen(navController: NavController) { ) { Column { // Version Info - ListItem( - headlineContent = { - Text( - stringResource(R.string.core_version_title), - style = MaterialTheme.typography.bodyLarge, - ) - }, - supportingContent = { - Text( - version, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(top = 4.dp), - ) - }, - leadingContent = { - Icon( - imageVector = Icons.Outlined.Info, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - ) - }, - modifier = Modifier.clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)), - colors = - ListItemDefaults.colors( - containerColor = Color.Transparent, - ), - ) + Box { + ListItem( + headlineContent = { + Text( + stringResource(R.string.core_version_title), + style = MaterialTheme.typography.bodyLarge, + ) + }, + supportingContent = { + Text( + version, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 4.dp), + ) + }, + leadingContent = { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + }, + modifier = Modifier + .clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)) + .combinedClickable( + onClick = {}, + onLongClick = { showVersionMenu = true }, + ), + colors = + ListItemDefaults.colors( + containerColor = Color.Transparent, + ), + ) + Box(modifier = Modifier.align(Alignment.BottomEnd)) { + DropdownMenu( + expanded = showVersionMenu, + onDismissRequest = { showVersionMenu = false }, + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.per_app_proxy_action_copy)) }, + leadingIcon = { + Icon( + imageVector = Icons.Filled.ContentCopy, + contentDescription = null, + ) + }, + onClick = { + clipboardText = version + Toast.makeText( + context, + R.string.copied_to_clipboard, + Toast.LENGTH_SHORT, + ).show() + showVersionMenu = false + }, + ) + } + } + } // Data Size ListItem(