diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.kt index 07c9e8dc9..dfe176ba5 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.kt @@ -25,7 +25,7 @@ import android.widget.Toast import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity -import androidx.compose.material3.AlertDialog +import org.strongswan.android.ui.compose.design.ShxAlertDialog import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.getValue @@ -175,7 +175,7 @@ class TrustedCertificateImportActivity : AppCompatActivity() @androidx.compose.runtime.Composable private fun ConfirmImportUi(certificate: X509Certificate) { - AlertDialog( + ShxAlertDialog( onDismissRequest = { finish() }, title = { Text(stringResource(R.string.import_certificate)) }, text = { Text(certificate.getSubjectDN().toString()) }, diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/ConnectivityCheckScreen.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/ConnectivityCheckScreen.kt index 3e5b79e10..2e92bf61c 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/ConnectivityCheckScreen.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/ConnectivityCheckScreen.kt @@ -31,7 +31,7 @@ import androidx.compose.material.icons.rounded.Remove import androidx.compose.material.icons.rounded.Public import androidx.compose.material.icons.rounded.Speed import androidx.compose.material.icons.rounded.Timer -import androidx.compose.material3.AlertDialog +import org.strongswan.android.ui.compose.design.ShxAlertDialog import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -252,7 +252,7 @@ fun ConnectivityCheckScreen() if (showIntervalPicker) { val options = listOf(15, 30, 60, 300) - AlertDialog( + ShxAlertDialog( onDismissRequest = { showIntervalPicker = false }, title = { Text(stringResource(R.string.health_check_interval)) }, text = { diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/HomeScreen.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/HomeScreen.kt index 3d85448be..e6734bd83 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/HomeScreen.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/HomeScreen.kt @@ -71,7 +71,7 @@ import androidx.compose.material.icons.rounded.Share import androidx.compose.material.icons.rounded.SignalCellularAlt import androidx.compose.material.icons.rounded.Edit import androidx.compose.material.icons.rounded.Wifi -import androidx.compose.material3.AlertDialog +import org.strongswan.android.ui.compose.design.ShxAlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @@ -425,7 +425,7 @@ fun HomeScreen( } pendingDelete?.let { profile -> - AlertDialog( + ShxAlertDialog( onDismissRequest = { pendingDelete = null }, title = { Text(stringResource(R.string.delete_profile)) }, text = { Text(profile.name ?: "") }, diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/SettingsScreen.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/SettingsScreen.kt index 690cb43f6..a92841387 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/SettingsScreen.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/SettingsScreen.kt @@ -45,7 +45,7 @@ import androidx.compose.material.icons.rounded.Security import androidx.compose.material.icons.rounded.SystemUpdateAlt import androidx.compose.material.icons.rounded.VerifiedUser import androidx.compose.material.icons.rounded.Wifi -import androidx.compose.material3.AlertDialog +import org.strongswan.android.ui.compose.design.ShxAlertDialog import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.Text @@ -551,7 +551,7 @@ fun SettingsScreen(profiles: List, showTitle: Boolean = true) { val options = listOf(Constants.PREF_DEFAULT_VPN_PROFILE_MRU to context.getString(R.string.pref_default_vpn_profile_mru)) + profiles.map { it.getUUID().toString() to (it.name ?: "") } - AlertDialog( + ShxAlertDialog( onDismissRequest = { showDefaultPicker = false }, title = { Text(stringResource(R.string.pref_default_vpn_profile)) }, text = { @@ -586,7 +586,7 @@ fun SettingsScreen(profiles: List, showTitle: Boolean = true) if (showCrl) { val files = remember(showCrl) { context.fileList().filter { it.startsWith("crl-") } } - AlertDialog( + ShxAlertDialog( onDismissRequest = { showCrl = false }, title = { Text(stringResource(R.string.clear_crl_cache_title)) }, text = { @@ -620,7 +620,7 @@ fun SettingsScreen(profiles: List, showTitle: Boolean = true) { val info = (updateState as UpdateState.Available).info val notes = info.notes["ru"] ?: info.notes["en"] ?: info.notes.values.firstOrNull() ?: "" - AlertDialog( + ShxAlertDialog( onDismissRequest = { showUpdateConfirm = false }, title = { Text(stringResource(R.string.update_confirm_title, info.versionName)) }, text = { if (notes.isNotEmpty()) { Text(notes) } }, @@ -905,7 +905,7 @@ private fun SettingsRoot(onOpen: (SettingsPage) -> Unit, updateState: UpdateStat } if (showKillSwitchDialog) { - AlertDialog( + ShxAlertDialog( onDismissRequest = { showKillSwitchDialog = false }, title = { Text(stringResource(R.string.kill_switch_dialog_title)) }, text = { Text(stringResource(R.string.kill_switch_dialog_text)) }, @@ -974,7 +974,7 @@ private fun KillSwitchRow() } if (showKillSwitchDialog) { - AlertDialog( + ShxAlertDialog( onDismissRequest = { showKillSwitchDialog = false }, title = { Text(stringResource(R.string.kill_switch_dialog_title)) }, text = { Text(stringResource(R.string.kill_switch_dialog_text)) }, diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/ShxApp.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/ShxApp.kt index a4728e75e..653a2f507 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/ShxApp.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/ShxApp.kt @@ -37,7 +37,6 @@ import androidx.compose.material.icons.outlined.AltRoute import androidx.compose.material.icons.outlined.Home import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Storage -import androidx.compose.material3.AlertDialog import androidx.compose.material3.Checkbox import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/TrustedNetworksScreen.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/TrustedNetworksScreen.kt index 24c69da54..75fed137d 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/TrustedNetworksScreen.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/TrustedNetworksScreen.kt @@ -38,7 +38,7 @@ import androidx.compose.material.icons.rounded.Add import androidx.compose.material.icons.rounded.Delete import androidx.compose.material.icons.rounded.Language import androidx.compose.material.icons.rounded.Wifi -import androidx.compose.material3.AlertDialog +import org.strongswan.android.ui.compose.design.ShxAlertDialog import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -302,7 +302,7 @@ fun TrustedNetworksScreen( if (showProfilePicker) { - AlertDialog( + ShxAlertDialog( onDismissRequest = { showProfilePicker = false }, title = { Text(stringResource(R.string.auto_connect_profile)) }, text = { @@ -339,7 +339,7 @@ fun TrustedNetworksScreen( if (showAddSsid) { var ssid by remember { mutableStateOf("") } - AlertDialog( + ShxAlertDialog( onDismissRequest = { showAddSsid = false }, title = { Text(stringResource(R.string.auto_connect_add_network)) }, text = { @@ -372,7 +372,7 @@ fun TrustedNetworksScreen( } pendingDeleteSsid?.let { ssid -> - AlertDialog( + ShxAlertDialog( onDismissRequest = { pendingDeleteSsid = null }, title = { Text(stringResource(R.string.auto_connect_remove_network)) }, text = { Text(ssid) }, diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/VpnDialogs.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/VpnDialogs.kt index f72daf53c..b2de1679c 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/VpnDialogs.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/VpnDialogs.kt @@ -22,7 +22,8 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Visibility import androidx.compose.material.icons.rounded.VisibilityOff -import androidx.compose.material3.AlertDialog +import org.strongswan.android.ui.compose.design.ShxAlertDialog +import org.strongswan.android.ui.compose.design.ShxFormField import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -142,7 +143,7 @@ private fun cancelDialog() @Composable private fun ConfirmationUi(dialog: VpnDialog.Confirmation) { - AlertDialog( + ShxAlertDialog( onDismissRequest = ::cancelDialog, title = { Text(dialog.title) }, text = { Text(stringResource(dialog.message)) }, @@ -189,31 +190,28 @@ private fun LoginUi(dialog: VpnDialog.Login) var password by rememberSaveable { mutableStateOf("") } var showPassword by rememberSaveable { mutableStateOf(false) } - AlertDialog( + ShxAlertDialog( onDismissRequest = ::cancelDialog, title = { Text(stringResource(R.string.login_title)) }, text = { Column(verticalArrangement = Arrangement.spacedBy(ShxSpacing.md)) { - OutlinedTextField( + ShxFormField( + label = stringResource(R.string.login_username), value = username, onValueChange = { username = it }, - label = { Text(stringResource(R.string.login_username)) }, - singleLine = true, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), enabled = false, - modifier = Modifier.fillMaxWidth(), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), ) - OutlinedTextField( + ShxFormField( + label = stringResource(R.string.login_password), value = password, onValueChange = { password = it }, - label = { Text(stringResource(R.string.login_password)) }, - singleLine = true, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Password, imeAction = ImeAction.Done, ), visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(), - trailingIcon = { + trailing = { IconButton(onClick = { showPassword = !showPassword }) { Icon( if (showPassword) Icons.Rounded.Visibility else Icons.Rounded.VisibilityOff, @@ -223,7 +221,6 @@ private fun LoginUi(dialog: VpnDialog.Login) ) } }, - modifier = Modifier.fillMaxWidth(), ) } }, @@ -244,7 +241,7 @@ private fun LoginUi(dialog: VpnDialog.Login) @Composable private fun CertificateRequiredUi(dialog: VpnDialog.CertificateRequired) { - AlertDialog( + ShxAlertDialog( onDismissRequest = ::cancelDialog, title = { Text(stringResource(R.string.certificate_required_title)) }, text = { Text(stringResource(R.string.certificate_required_text)) }, @@ -265,7 +262,7 @@ private fun CertificateRequiredUi(dialog: VpnDialog.CertificateRequired) @Composable private fun PowerWhitelistUi() { - AlertDialog( + ShxAlertDialog( onDismissRequest = ::cancelDialog, title = { Text(stringResource(R.string.power_whitelist_title)) }, text = { Text(stringResource(R.string.power_whitelist_text)) }, @@ -281,7 +278,7 @@ private fun PowerWhitelistUi() @Composable private fun NotSupportedUi(dialog: VpnDialog.NotSupported) { - AlertDialog( + ShxAlertDialog( onDismissRequest = ::cancelDialog, title = { Text(stringResource(R.string.vpn_not_supported_title)) }, text = { Text(stringResource(dialog.message)) }, diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/design/ShxKit.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/design/ShxKit.kt index 9655a05db..96cf668b1 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/design/ShxKit.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/design/ShxKit.kt @@ -757,6 +757,7 @@ fun ShxDialog( val c = LocalShxColors.current androidx.compose.ui.window.Dialog(onDismissRequest = onDismiss) { + DialogScrim() Column( verticalArrangement = Arrangement.spacedBy(14.dp), modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(28.dp)).background(c.background).padding(24.dp), @@ -802,3 +803,75 @@ fun ShxDialog( } } } + +/** + * Drop-in for the Material AlertDialog in the mockup look (as [ShxDialog]): + * 28dp card on the page colour, 22/800 title, 14sp soft text, buttons at the end. + */ +@Composable +fun ShxAlertDialog( + onDismissRequest: () -> Unit, + confirmButton: @Composable () -> Unit, + modifier: Modifier = Modifier, + dismissButton: (@Composable () -> Unit)? = null, + icon: (@Composable () -> Unit)? = null, + title: (@Composable () -> Unit)? = null, + text: (@Composable () -> Unit)? = null, +) +{ + val c = LocalShxColors.current + androidx.compose.ui.window.Dialog(onDismissRequest = onDismissRequest) + { + DialogScrim() + Column( + verticalArrangement = Arrangement.spacedBy(14.dp), + modifier = modifier.fillMaxWidth().clip(RoundedCornerShape(28.dp)).background(c.background).padding(24.dp), + ) + { + icon?.let { + androidx.compose.runtime.CompositionLocalProvider(androidx.compose.material3.LocalContentColor provides c.onAccentContainer) { it() } + } + title?.let { + androidx.compose.runtime.CompositionLocalProvider(androidx.compose.material3.LocalContentColor provides c.text) { + androidx.compose.material3.ProvideTextStyle(ShxType.pageTitle) { it() } + } + } + text?.let { + androidx.compose.runtime.CompositionLocalProvider(androidx.compose.material3.LocalContentColor provides c.textSoft) { + androidx.compose.material3.ProvideTextStyle(ShxType.note.copy(fontSize = 14.sp, lineHeight = 21.sp, fontWeight = FontWeight.Normal)) { + Box(Modifier.weight(1f, fill = false)) { it() } + } + } + } + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(top = 4.dp), + ) + { + androidx.compose.material3.ProvideTextStyle(ShxType.button.copy(fontWeight = FontWeight.Bold)) + { + dismissButton?.invoke() + confirmButton() + } + } + } + } +} + +/** + * The 50% scrim of the mockup dialogs. Set on the dialog window itself: + * hosts with a translucent theme (VpnProfileControlActivity) turn the + * platform dim off. + */ +@Composable +private fun DialogScrim() +{ + val view = androidx.compose.ui.platform.LocalView.current + androidx.compose.runtime.SideEffect { + (view.parent as? androidx.compose.ui.window.DialogWindowProvider)?.window?.let { + it.addFlags(android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND) + it.setDimAmount(0.5f) + } + } +}