From 739068ccb924f6924f3bb67829623103b9ca1990 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 12 Oct 2026 10:43:54 +0700 Subject: [PATCH] android: help screen as in the Final 2026 mockup; log opens directly from settings Co-Authored-By: Claude Opus 5.5 --- .../android/ui/compose/SettingsPages.kt | 115 ++++++++++++++++++ .../android/ui/compose/SettingsScreen.kt | 37 ++---- .../app/src/main/res/values-ru/strings.xml | 21 ++++ .../app/src/main/res/values/strings.xml | 21 ++++ 4 files changed, 165 insertions(+), 29 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/SettingsPages.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/SettingsPages.kt index 249872d7e..95442a82b 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/SettingsPages.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/SettingsPages.kt @@ -20,8 +20,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -38,6 +40,7 @@ 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.draw.rotate import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -300,3 +303,115 @@ fun UpdateCard(state: UpdateState, installed: String, onCheck: () -> Unit, onIns Text(stringResource(R.string.update_card_footer), style = ShxType.meta.copy(fontWeight = FontWeight.Normal), color = c.textSub, modifier = Modifier.padding(horizontal = 6.dp)) } } + +/** + * «Help» (F_Help): two big buttons (check the connection, contact support), + * the frequent questions as an accordion, and «About the app». + */ +@Composable +internal fun HelpPage(onUpdates: () -> Unit) +{ + val c = LocalShxColors.current + val context = LocalContext.current + val info = remember { runCatching { context.packageManager.getPackageInfo(context.packageName, 0) }.getOrNull() } + val build = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) info?.longVersionCode?.toInt() ?: 0 else @Suppress("DEPRECATION") (info?.versionCode ?: 0) + fun open(url: String) = runCatching { context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) } + Column(Modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) + { + Row(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.height(androidx.compose.foundation.layout.IntrinsicSize.Min)) + { + HelpTile(ShxIcons.Pulse, stringResource(R.string.help_check), c.accent, Color.White, Modifier.weight(1f).fillMaxHeight()) { + context.startActivity(Intent(context, org.strongswan.android.ui.DiagnosticsActivity::class.java)) + } + HelpTile(ShxIcons.Doc, stringResource(R.string.help_write), c.accentContainer, c.onAccentContainer, Modifier.weight(1f).fillMaxHeight()) { + context.startActivity(Intent(context, org.strongswan.android.ui.LogActivity::class.java)) + } + } + ShxSectionHeader(stringResource(R.string.help_faq).uppercase(), Modifier.padding(start = 6.dp, end = 6.dp, top = 6.dp)) + val faq = listOf( + R.string.faq_q1 to R.string.faq_a1, R.string.faq_q2 to R.string.faq_a2, R.string.faq_q3 to R.string.faq_a3, + R.string.faq_q4 to R.string.faq_a4, R.string.faq_q5 to R.string.faq_a5, + ) + var open by remember { mutableStateOf(0) } + ShxPanel() + { + faq.forEachIndexed { i, (q, a) -> + val expanded = open == i + Column(Modifier.fillMaxWidth()) + { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp), + modifier = Modifier.fillMaxWidth().heightIn(min = 58.dp) + .clickable(role = Role.Button) { open = if (expanded) -1 else i }.padding(horizontal = 16.dp, vertical = 10.dp), + ) + { + Text(stringResource(q), style = ShxType.rowTitle.copy(fontSize = 15.sp), color = c.text, modifier = Modifier.weight(1f)) + Icon( + ShxIcons.Chevron, contentDescription = null, tint = c.textSub, + modifier = Modifier.size(18.dp).rotate(if (expanded) 270f else 90f), + ) + } + if (expanded) + { + Text( + stringResource(a), style = ShxType.note.copy(fontSize = 14.sp, lineHeight = 21.sp, fontWeight = FontWeight.Normal), color = c.textSoft, + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + ) + } + if (i < faq.lastIndex) + { + Box(Modifier.fillMaxWidth().height(1.dp).background(c.tile)) + } + } + } + } + ShxSectionHeader(stringResource(R.string.help_about).uppercase(), Modifier.padding(start = 6.dp, end = 6.dp, top = 6.dp)) + ShxPanel() + { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(14.dp), modifier = Modifier.padding(16.dp)) + { + Box(contentAlignment = Alignment.Center, modifier = Modifier.size(52.dp).clip(RoundedCornerShape(15.dp)).background(c.accent)) + { + Icon(painterResource(R.drawable.ic_widget_logo_accent), contentDescription = null, tint = Color.Unspecified, modifier = Modifier.size(32.dp)) + } + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) + { + Text("SHX VPN " + (info?.versionName ?: ""), style = ShxType.cardHeading, color = c.text) + Text(stringResource(R.string.help_build, build), style = ShxType.meta.copy(fontWeight = FontWeight.Normal), color = c.textSub) + } + } + HelpLink(stringResource(R.string.help_updates), onUpdates) + HelpLink(stringResource(R.string.help_licenses)) { open("https://www.gnu.org/licenses/old-licenses/gpl-2.0.html") } + HelpLink(stringResource(R.string.help_source)) { open("https://github.com/strongswan/strongswan") } + } + } +} + +@Composable +private fun HelpTile(icon: androidx.compose.ui.graphics.vector.ImageVector, text: String, bg: Color, fg: Color, modifier: Modifier, onClick: () -> Unit) +{ + Column( + verticalArrangement = Arrangement.spacedBy(10.dp), + modifier = modifier.heightIn(min = 84.dp).clip(RoundedCornerShape(22.dp)).background(bg).clickable(role = Role.Button, onClick = onClick).padding(14.dp), + ) + { + Icon(icon, contentDescription = null, tint = fg, modifier = Modifier.size(22.dp)) + Text(text, style = ShxType.body.copy(fontWeight = FontWeight.ExtraBold), color = fg, maxLines = 2) + } +} + +@Composable +private fun HelpLink(text: String, onClick: () -> Unit) +{ + val c = LocalShxColors.current + Box(Modifier.fillMaxWidth().height(1.dp).background(c.tile)) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().height(52.dp).clickable(role = Role.Button, onClick = onClick).padding(horizontal = 16.dp), + ) + { + Text(text, style = ShxType.body.copy(fontSize = 15.sp), color = c.text, modifier = Modifier.weight(1f)) + Icon(ShxIcons.Chevron, contentDescription = null, tint = c.textSub, modifier = Modifier.size(18.dp)) + } +} 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 5b3d62731..9053e153a 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 @@ -404,35 +404,11 @@ fun SettingsScreen(profiles: List, showTitle: Boolean = true) } } - if (page == SettingsPage.SUPPORT) + if (page == SettingsPage.HELP) { - if (managed.isAllowProfileImport) + HelpPage(onUpdates = { pageState = SettingsPage.UPDATES }) + ShxSection(title = stringResource(R.string.help_for_support)) { - ShxSection(title = stringResource(R.string.settings_section_profiles)) - { - ShxSettingRow( - icon = Icons.Rounded.FileDownload, - title = stringResource(R.string.profile_import), - onClick = { - context.startActivity(Intent(context, VpnProfileImportActivity::class.java)) - }, - ) - } - } - } - - if (page == SettingsPage.SUPPORT) - { - ShxSection(title = stringResource(R.string.settings_section_support)) - { - ShxSettingRow( - icon = Icons.Rounded.Description, - title = stringResource(R.string.show_log), - onClick = { - context.startActivity(Intent(context, LogActivity::class.java)) - }, - ) - ShxSectionDivider() var extendedDiag by remember { mutableStateOf(prefs.getBoolean(Constants.PREF_EXTENDED_DIAG, false)) } @@ -744,8 +720,8 @@ enum class SettingsPage(val titleRes: Int) STABILITY(R.string.settings_stability), SECURITY(R.string.settings_section_security), UPDATES(R.string.settings_section_updates), - SUPPORT(R.string.settings_section_support), KILLSWITCH(R.string.settings_killswitch), + HELP(R.string.settings_help), } /** One row of the settings list (F_Settings): 62dp, 36dp grey icon well, 15/700 title, 12 sub. */ @@ -832,7 +808,10 @@ private fun SettingsRoot(onOpen: (SettingsPage) -> Unit, updateState: UpdateStat ) { onOpen(SettingsPage.APPEARANCE) }, ), stringResource(R.string.settings_section_support) to listOf( - SettingsEntry(stringResource(R.string.settings_log), stringResource(R.string.settings_log_sub), ShxIcons.Doc) { onOpen(SettingsPage.SUPPORT) }, + SettingsEntry(stringResource(R.string.settings_help), stringResource(R.string.settings_help_sub), ShxIcons.Help) { onOpen(SettingsPage.HELP) }, + SettingsEntry(stringResource(R.string.settings_log), stringResource(R.string.settings_log_sub), ShxIcons.Doc) { + context.startActivity(Intent(context, LogActivity::class.java)) + }, SettingsEntry( stringResource(R.string.trusted_certs_title), pluralStringResource(R.plurals.settings_certs_n, certs, certs), diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml index eab6055ea..dce5084d8 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -1036,4 +1036,25 @@ Отправить отчёт Скопировать журнал Журнал скопирован + Помощь + Частые вопросы, о приложении + Проверить подключение + Написать в поддержку + Частые вопросы + О приложении + сборка %1$d · на основе strongSwan · GPLv2 + Проверить обновления + Лицензии открытого ПО + Исходный код strongSwan + Для поддержки + Банк или Госуслуги не открываются + Многие российские сервисы не пускают зарубежные адреса. Откройте «Не открывается?» на главной и выберите приложение — мы пустим его напрямую, мимо VPN. + Где взять профиль .sswan? + Его выдаёт администратор вашего VPN: файлом, ссылкой или QR-кодом. Можно отсканировать QR с экрана друга. + VPN сам отключается + Скорее всего, Android экономит батарею. Откройте Настройки → Стабильная работа и снимите ограничения. + Стабильный или Гибкий режим? + Стабильный надёжнее: новые адреса применяются после переподключения. Гибкий обновляет маршруты на лету, но пока в бете. + Видит ли SHX мои сайты? + Нет. С доступом к статистике приложение считает только объём трафика по приложениям; сайты и содержимое не сохраняются и никуда не отправляются. diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml index 1fc85ad4e..b75abb1ae 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -1022,4 +1022,25 @@ Send report Copy log Log copied + Help + FAQ, about the app + Check the connection + Contact support + Frequent questions + About the app + build %1$d · based on strongSwan · GPLv2 + Check for updates + Open source licenses + strongSwan source code + For support + A bank or Gosuslugi does not open + Many Russian services reject foreign addresses. Tap «Not loading?» on Home and pick the app: it will go direct, past the VPN. + Where do I get an .sswan profile? + Your VPN admin gives it out: as a file, a link or a QR code. You can also scan a QR from a friend\'s screen. + The VPN turns itself off + Most likely Android is saving battery. Open Settings → Stable work and lift the restrictions. + Stable or Flexible mode? + Stable is more reliable: new addresses apply after a reconnect. Flexible updates routes on the fly, but is still in beta. + Does SHX see my sites? + No. With usage access the app only counts traffic per app; sites and content are not stored or sent anywhere.