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.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<Job?>(null) }
var downloadError by remember { mutableStateOf<String?>(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 = {

View File

@@ -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(