Add long-press copy on App and Core version items

This commit is contained in:
世界
2026-04-19 23:25:45 +08:00
parent ea63fb0f8b
commit 2d7efc04a7
2 changed files with 143 additions and 62 deletions

View File

@@ -9,9 +9,13 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.text.format.Formatter import android.text.format.Formatter
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable 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.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -26,6 +30,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack 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.AdminPanelSettings
import androidx.compose.material.icons.outlined.Autorenew import androidx.compose.material.icons.outlined.Autorenew
import androidx.compose.material.icons.outlined.DeleteForever 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.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton 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.component.UpdateAvailableDialog
import io.nekohasekai.sfa.compose.topbar.OverrideTopBar import io.nekohasekai.sfa.compose.topbar.OverrideTopBar
import io.nekohasekai.sfa.database.Settings import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.ktx.clipboardText
import io.nekohasekai.sfa.update.UpdateCheckException import io.nekohasekai.sfa.update.UpdateCheckException
import io.nekohasekai.sfa.update.UpdateSource import io.nekohasekai.sfa.update.UpdateSource
import io.nekohasekai.sfa.update.UpdateState import io.nekohasekai.sfa.update.UpdateState
@@ -99,7 +107,7 @@ import java.io.File
import java.util.Locale import java.util.Locale
import android.provider.Settings as AndroidSettings import android.provider.Settings as AndroidSettings
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable @Composable
fun AppSettingsScreen(navController: NavController) { fun AppSettingsScreen(navController: NavController) {
OverrideTopBar { OverrideTopBar {
@@ -142,6 +150,7 @@ fun AppSettingsScreen(navController: NavController) {
var downloadJob by remember { mutableStateOf<Job?>(null) } var downloadJob by remember { mutableStateOf<Job?>(null) }
var downloadError by remember { mutableStateOf<String?>(null) } var downloadError by remember { mutableStateOf<String?>(null) }
var showUpdateAvailableDialog by remember { mutableStateOf(false) } var showUpdateAvailableDialog by remember { mutableStateOf(false) }
var showVersionMenu by remember { mutableStateOf(false) }
var notificationEnabled by remember { mutableStateOf(true) } var notificationEnabled by remember { mutableStateOf(true) }
var dynamicNotification by remember { mutableStateOf(Settings.dynamicNotification) } var dynamicNotification by remember { mutableStateOf(Settings.dynamicNotification) }
@@ -433,39 +442,70 @@ fun AppSettingsScreen(navController: NavController) {
), ),
) { ) {
Column { Column {
ListItem( Box {
headlineContent = { ListItem(
Text( headlineContent = {
stringResource(R.string.app_version_title), Text(
style = MaterialTheme.typography.bodyLarge, stringResource(R.string.app_version_title),
) style = MaterialTheme.typography.bodyLarge,
}, )
supportingContent = { },
Text( supportingContent = {
BuildConfig.VERSION_NAME, Text(
style = MaterialTheme.typography.bodyMedium, BuildConfig.VERSION_NAME,
) style = MaterialTheme.typography.bodyMedium,
}, )
leadingContent = { },
Icon( leadingContent = {
imageVector = Icons.Outlined.Info, Icon(
contentDescription = null, imageVector = Icons.Outlined.Info,
tint = MaterialTheme.colorScheme.primary, contentDescription = null,
) tint = MaterialTheme.colorScheme.primary,
}, )
trailingContent = { },
if (hasUpdate) { trailingContent = {
Badge(containerColor = MaterialTheme.colorScheme.primary) { Text("New") } 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( ListItem(
headlineContent = { headlineContent = {

View File

@@ -5,8 +5,11 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable 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.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -18,6 +21,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack 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.DeleteForever
import androidx.compose.material.icons.outlined.FolderOpen import androidx.compose.material.icons.outlined.FolderOpen
import androidx.compose.material.icons.outlined.Info 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.material.icons.outlined.WarningAmber
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@@ -41,6 +47,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -52,11 +59,12 @@ import io.nekohasekai.libbox.Libbox
import io.nekohasekai.sfa.R import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.compose.topbar.OverrideTopBar import io.nekohasekai.sfa.compose.topbar.OverrideTopBar
import io.nekohasekai.sfa.database.Settings import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.ktx.clipboardText
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable @Composable
fun CoreSettingsScreen(navController: NavController) { fun CoreSettingsScreen(navController: NavController) {
OverrideTopBar { OverrideTopBar {
@@ -77,6 +85,7 @@ fun CoreSettingsScreen(navController: NavController) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var dataSize by remember { mutableStateOf("") } var dataSize by remember { mutableStateOf("") }
val version = remember { Libbox.version() } val version = remember { Libbox.version() }
var showVersionMenu by remember { mutableStateOf(false) }
var disableDeprecatedWarnings by remember { mutableStateOf(Settings.disableDeprecatedWarnings) } var disableDeprecatedWarnings by remember { mutableStateOf(Settings.disableDeprecatedWarnings) }
// Calculate data size on launch // Calculate data size on launch
@@ -114,34 +123,66 @@ fun CoreSettingsScreen(navController: NavController) {
) { ) {
Column { Column {
// Version Info // Version Info
ListItem( Box {
headlineContent = { ListItem(
Text( headlineContent = {
stringResource(R.string.core_version_title), Text(
style = MaterialTheme.typography.bodyLarge, stringResource(R.string.core_version_title),
) style = MaterialTheme.typography.bodyLarge,
}, )
supportingContent = { },
Text( supportingContent = {
version, Text(
style = MaterialTheme.typography.bodyMedium, version,
color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(top = 4.dp), color = MaterialTheme.colorScheme.onSurfaceVariant,
) modifier = Modifier.padding(top = 4.dp),
}, )
leadingContent = { },
Icon( leadingContent = {
imageVector = Icons.Outlined.Info, Icon(
contentDescription = null, imageVector = Icons.Outlined.Info,
tint = MaterialTheme.colorScheme.primary, contentDescription = null,
) tint = MaterialTheme.colorScheme.primary,
}, )
modifier = Modifier.clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)), },
colors = modifier = Modifier
ListItemDefaults.colors( .clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp))
containerColor = Color.Transparent, .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 // Data Size
ListItem( ListItem(