refactor(templates): move config templates from a list card to a top-bar action (#561)
The templates card sat at the top of the scrolling config list, pushing the actual settings down. Templates apply to the config as a whole, so they belong with the editor's fixed actions: replace the card with a top-bar icon button (WtaScreen actions) that opens a dropdown — save as template, tap-to-apply list (scrollable when long), and manage. Dialogs and store semantics unchanged; docs wording updated to point at the top-bar button. Co-authored-by: shiaho <222622008+shiaho777@users.noreply.github.com>
This commit is contained in:
+61
-68
@@ -3,13 +3,15 @@ package com.webtoapp.ui.screens
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.DriveFileRenameOutline
|
||||
import androidx.compose.material.icons.outlined.EditNote
|
||||
import androidx.compose.material.icons.outlined.LibraryAddCheck
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -18,98 +20,89 @@ import com.webtoapp.core.i18n.Strings
|
||||
import com.webtoapp.data.model.WebViewConfig
|
||||
import com.webtoapp.data.repository.ConfigTemplateStore
|
||||
import com.webtoapp.ui.components.PremiumTextField
|
||||
import com.webtoapp.ui.design.*
|
||||
|
||||
/**
|
||||
* Save / apply named snapshots of the common config (WebViewConfig). Applying replaces
|
||||
* the whole WebViewConfig — a template is a full snapshot, so the result is exactly
|
||||
* what was saved, never a mix of old and new values.
|
||||
* Top-bar action for common-config templates: save the current WebViewConfig as a named
|
||||
* snapshot, apply one to the app (a template is a full snapshot — applying replaces the
|
||||
* whole WebViewConfig), and manage saved templates. Lives in the editor's fixed top bar
|
||||
* instead of the scrolling config list, since it applies to the config as a whole.
|
||||
*/
|
||||
@Composable
|
||||
fun ConfigTemplateCard(
|
||||
fun ConfigTemplateMenuButton(
|
||||
config: WebViewConfig,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onApplyConfig: (WebViewConfig) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var templates by remember { mutableStateOf(ConfigTemplateStore.list(context)) }
|
||||
var refresh by remember { mutableIntStateOf(0) }
|
||||
LaunchedEffect(refresh) {
|
||||
LaunchedEffect(refresh, templates.isEmpty()) {
|
||||
// reload after any mutation; also opportunistically refresh when the menu opens
|
||||
if (refresh > 0) templates = ConfigTemplateStore.list(context)
|
||||
}
|
||||
|
||||
var menuOpen by remember { mutableStateOf(false) }
|
||||
var saveDialogOpen by remember { mutableStateOf(false) }
|
||||
var manageOpen by remember { mutableStateOf(false) }
|
||||
var renameTarget by remember { mutableStateOf<ConfigTemplateStore.ConfigTemplate?>(null) }
|
||||
var renameText by remember { mutableStateOf("") }
|
||||
var toast by remember { mutableStateOf<String?>(null) }
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
LaunchedEffect(toast) {
|
||||
toast?.let {
|
||||
snackbarHostState.showSnackbar(it)
|
||||
toast = null
|
||||
}
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
fun notify(message: String) {
|
||||
scope.launch { snackbarHostState.showSnackbar(message) }
|
||||
}
|
||||
|
||||
Box {
|
||||
WtaSettingCard {
|
||||
Column {
|
||||
WtaChoiceRow(
|
||||
title = Strings.configTemplates,
|
||||
subtitle = Strings.configTemplatesDesc,
|
||||
icon = Icons.Outlined.LibraryAddCheck,
|
||||
value = "",
|
||||
isExpanded = templates.isNotEmpty(),
|
||||
onClick = { manageOpen = true }
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = WtaSpacing.RowHorizontal, vertical = WtaSpacing.ContentGap),
|
||||
horizontalArrangement = Arrangement.spacedBy(WtaSpacing.ContentGap)
|
||||
) {
|
||||
WtaButton(
|
||||
onClick = { saveDialogOpen = true },
|
||||
text = Strings.templateSaveAs,
|
||||
modifier = Modifier.weight(1f),
|
||||
variant = WtaButtonVariant.Outlined,
|
||||
leadingIcon = Icons.Outlined.EditNote
|
||||
)
|
||||
IconButton(onClick = {
|
||||
templates = ConfigTemplateStore.list(context)
|
||||
menuOpen = true
|
||||
}) {
|
||||
Icon(
|
||||
Icons.Outlined.LibraryAddCheck,
|
||||
contentDescription = Strings.configTemplates
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = menuOpen,
|
||||
onDismissRequest = { menuOpen = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(Strings.templateSaveAs) },
|
||||
onClick = {
|
||||
menuOpen = false
|
||||
saveDialogOpen = true
|
||||
}
|
||||
|
||||
if (templates.isNotEmpty()) {
|
||||
Column(modifier = Modifier.padding(horizontal = WtaSpacing.RowHorizontal)) {
|
||||
Text(
|
||||
Strings.templateApplyHint,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Spacer(modifier = Modifier.height(WtaSpacing.ContentGap))
|
||||
templates.forEach { template ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
onApplyConfig(template.webViewConfig)
|
||||
toast = Strings.templateApplied(template.name)
|
||||
}) {
|
||||
Text(template.name, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
)
|
||||
if (templates.isNotEmpty()) {
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
Text(
|
||||
Strings.templateApplyHint,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 2.dp)
|
||||
)
|
||||
Column(modifier = Modifier.heightIn(max = 280.dp).verticalScroll(rememberScrollState())) {
|
||||
templates.forEach { template ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(template.name) },
|
||||
onClick = {
|
||||
menuOpen = false
|
||||
onApplyConfig(template.webViewConfig)
|
||||
notify(Strings.templateApplied(template.name))
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(WtaSpacing.ContentGap))
|
||||
TextButton(onClick = { manageOpen = true }) {
|
||||
Text(Strings.templateManage)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
DropdownMenuItem(
|
||||
text = { Text(Strings.templateManage) },
|
||||
onClick = {
|
||||
menuOpen = false
|
||||
manageOpen = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
SnackbarHost(
|
||||
hostState = snackbarHostState,
|
||||
modifier = Modifier.align(Alignment.BottomCenter)
|
||||
)
|
||||
}
|
||||
|
||||
if (saveDialogOpen) {
|
||||
@@ -131,7 +124,7 @@ fun ConfigTemplateCard(
|
||||
onClick = {
|
||||
if (ConfigTemplateStore.save(context, name, config)) {
|
||||
refresh++
|
||||
toast = Strings.templateSaved(name.trim())
|
||||
notify(Strings.templateSaved(name.trim()))
|
||||
}
|
||||
saveDialogOpen = false
|
||||
}
|
||||
@@ -148,6 +148,15 @@ fun CreateAppScreen(
|
||||
onBack = {
|
||||
if (hasUnsavedChanges) showDiscardDialog = true else onBack()
|
||||
},
|
||||
actions = {
|
||||
ConfigTemplateMenuButton(
|
||||
config = editState.webViewConfig,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onApplyConfig = { templateConfig ->
|
||||
viewModel.updateEditState { copy(webViewConfig = templateConfig) }
|
||||
}
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
CreateAppBottomBar(
|
||||
canPreview = canPreview,
|
||||
@@ -189,15 +198,6 @@ fun CreateAppScreen(
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
ConfigTemplateCard(
|
||||
config = editState.webViewConfig,
|
||||
onApplyConfig = { templateConfig ->
|
||||
viewModel.updateEditState { copy(webViewConfig = templateConfig) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
if (editState.appType == AppType.WEB) {
|
||||
PwaAnalysisSection(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Save the whole common config as a named template once, then apply it to any app in one tap — so a heavily customized browser setup never has to be re-configured app by app.
|
||||
|
||||
**Where:** the **Config Templates** card at the top of the [Edit Common Config](/guide/app-actions/edit-common-config/) editor.
|
||||
**Where:** the **Config Templates** button (library icon) in the top bar of the [Edit Common Config](/guide/app-actions/edit-common-config/) editor.
|
||||
|
||||
## What a template contains
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
把整套通用配置保存为命名模板,之后任何应用一键套用 —— 精心调好的浏览器配置不用再逐个应用重新设置。
|
||||
|
||||
**位置:**[编辑通用配置](/zh/guide/app-actions/edit-common-config/)编辑器顶部的**配置模板**卡片。
|
||||
**位置:**[编辑通用配置](/zh/guide/app-actions/edit-common-config/)编辑器顶栏的**配置模板**按钮(书库图标)。
|
||||
|
||||
## 模板包含什么
|
||||
|
||||
|
||||
Reference in New Issue
Block a user