android: server delete confirmation as in the Final 2026 mockup
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f193661e68
commit
eb5a89623b
4 files changed
+100
-1
No files matched your search
+27
-1
@@ -97,6 +97,7 @@ import kotlinx.coroutines.delay
|
|||||||
import org.strongswan.android.logic.connectivity.ConnectivityProbe
|
import org.strongswan.android.logic.connectivity.ConnectivityProbe
|
||||||
import org.strongswan.android.ui.compose.design.ShxButton
|
import org.strongswan.android.ui.compose.design.ShxButton
|
||||||
import org.strongswan.android.ui.compose.design.ShxButtonStyle
|
import org.strongswan.android.ui.compose.design.ShxButtonStyle
|
||||||
|
import org.strongswan.android.ui.compose.design.ShxDialog
|
||||||
import org.strongswan.android.ui.compose.design.ShxIcons
|
import org.strongswan.android.ui.compose.design.ShxIcons
|
||||||
import org.strongswan.android.ui.compose.design.ShxPanel
|
import org.strongswan.android.ui.compose.design.ShxPanel
|
||||||
import org.strongswan.android.ui.compose.design.ShxRow
|
import org.strongswan.android.ui.compose.design.ShxRow
|
||||||
@@ -200,6 +201,7 @@ private fun ProfilesList(
|
|||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var menuFor by remember { mutableStateOf<VpnProfile?>(null) }
|
var menuFor by remember { mutableStateOf<VpnProfile?>(null) }
|
||||||
|
var deleteFor by remember { mutableStateOf<VpnProfile?>(null) }
|
||||||
val activeUuid = state.activeProfile?.getUUID()
|
val activeUuid = state.activeProfile?.getUUID()
|
||||||
val connected = state.vpnState == State.CONNECTED
|
val connected = state.vpnState == State.CONNECTED
|
||||||
val profiles = state.profiles
|
val profiles = state.profiles
|
||||||
@@ -237,6 +239,30 @@ private fun ProfilesList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteFor?.let { profile ->
|
||||||
|
val current = profile.getUUID() == activeUuid && state.vpnState != State.DISABLED
|
||||||
|
ShxDialog(
|
||||||
|
title = stringResource(R.string.server_delete_title, profile.name ?: profile.gateway),
|
||||||
|
paragraphs = listOfNotNull(
|
||||||
|
if (current) stringResource(R.string.server_delete_current) else null,
|
||||||
|
stringResource(R.string.server_delete_restore),
|
||||||
|
),
|
||||||
|
confirm = stringResource(R.string.server_delete_confirm),
|
||||||
|
dismiss = stringResource(android.R.string.cancel),
|
||||||
|
onConfirm = {
|
||||||
|
deleteFor = null
|
||||||
|
if (current)
|
||||||
|
{
|
||||||
|
onDisconnect()
|
||||||
|
}
|
||||||
|
deleteWithUndo(profile)
|
||||||
|
},
|
||||||
|
onDismiss = { deleteFor = null },
|
||||||
|
icon = ShxIcons.Trash,
|
||||||
|
danger = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Box(Modifier.fillMaxSize().background(c.background))
|
Box(Modifier.fillMaxSize().background(c.background))
|
||||||
{
|
{
|
||||||
Column(Modifier.fillMaxSize().statusBarsPadding().verticalScroll(rememberScrollState()))
|
Column(Modifier.fillMaxSize().statusBarsPadding().verticalScroll(rememberScrollState()))
|
||||||
@@ -327,7 +353,7 @@ private fun ProfilesList(
|
|||||||
stringResource(R.string.delete_profile),
|
stringResource(R.string.delete_profile),
|
||||||
{
|
{
|
||||||
menuFor = null
|
menuFor = null
|
||||||
deleteWithUndo(profile)
|
deleteFor = profile
|
||||||
},
|
},
|
||||||
style = ShxButtonStyle.DANGER,
|
style = ShxButtonStyle.DANGER,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|||||||
+65
@@ -737,3 +737,68 @@ fun ShxToggleRow(title: String, checked: Boolean, onChange: (Boolean) -> Unit, s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirmation dialog of the mockup (F_ServerDelete): 28dp card, a 48dp round
|
||||||
|
* icon, 22/800 title, 14sp paragraphs, a text «Cancel» and a filled action.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun ShxDialog(
|
||||||
|
title: String,
|
||||||
|
paragraphs: List<String>,
|
||||||
|
confirm: String,
|
||||||
|
dismiss: String,
|
||||||
|
onConfirm: () -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
danger: Boolean = false,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
val c = LocalShxColors.current
|
||||||
|
androidx.compose.ui.window.Dialog(onDismissRequest = onDismiss)
|
||||||
|
{
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(14.dp),
|
||||||
|
modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(28.dp)).background(c.background).padding(24.dp),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (icon != null)
|
||||||
|
{
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.size(48.dp).clip(CircleShape).background(if (danger) c.errorContainer else c.accentContainer),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Icon(icon, contentDescription = null, tint = if (danger) c.error else c.onAccentContainer, modifier = Modifier.size(24.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(title, style = ShxType.pageTitle, color = c.text)
|
||||||
|
paragraphs.forEach {
|
||||||
|
Text(it, style = ShxType.note.copy(fontSize = 14.sp, lineHeight = 21.sp, fontWeight = FontWeight.Normal), color = c.textSoft)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.height(44.dp).clip(RoundedCornerShape(22.dp))
|
||||||
|
.clickable(role = Role.Button, onClick = onDismiss).padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Text(dismiss, style = ShxType.button.copy(fontWeight = FontWeight.Bold), color = c.accentText)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.height(44.dp).clip(RoundedCornerShape(22.dp))
|
||||||
|
.background(if (danger) c.error else c.accent)
|
||||||
|
.clickable(role = Role.Button, onClick = onConfirm).padding(horizontal = 20.dp),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Text(confirm, style = ShxType.button, color = if (danger) c.background else Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1026,4 +1026,8 @@
|
|||||||
<string name="tile_no_answer">Сервер не отвечает</string>
|
<string name="tile_no_answer">Сервер не отвечает</string>
|
||||||
<string name="tile_auth_error">Ошибка входа</string>
|
<string name="tile_auth_error">Ошибка входа</string>
|
||||||
<string name="tile_problem">Проблема с подключением</string>
|
<string name="tile_problem">Проблема с подключением</string>
|
||||||
|
<string name="server_delete_title">Удалить «%1$s»?</string>
|
||||||
|
<string name="server_delete_current">Это текущий сервер — VPN отключится.</string>
|
||||||
|
<string name="server_delete_restore">Вернуть можно, снова добавив профиль по ссылке, файлу или QR.</string>
|
||||||
|
<string name="server_delete_confirm">Удалить</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1012,4 +1012,8 @@
|
|||||||
<string name="tile_auth_error">Sign-in failed</string>
|
<string name="tile_auth_error">Sign-in failed</string>
|
||||||
<string name="tile_problem">Connection problem</string>
|
<string name="tile_problem">Connection problem</string>
|
||||||
<string name="tile_label" translatable="false">SHX VPN</string>
|
<string name="tile_label" translatable="false">SHX VPN</string>
|
||||||
|
<string name="server_delete_title">Delete «%1$s»?</string>
|
||||||
|
<string name="server_delete_current">This is the current server — the VPN will disconnect.</string>
|
||||||
|
<string name="server_delete_restore">You can get it back by adding the profile again from a link, file or QR.</string>
|
||||||
|
<string name="server_delete_confirm">Delete</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in new issue
Block a user