android: help screen as in the Final 2026 mockup; log opens directly from settings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8cd704ed08
commit
739068ccb9
4 files changed
+165
-29
No files matched your search
+115
@@ -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))
|
||||
}
|
||||
}
|
||||
+8
-29
@@ -404,35 +404,11 @@ fun SettingsScreen(profiles: List<VpnProfile>, 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),
|
||||
|
||||
@@ -1036,4 +1036,25 @@
|
||||
<string name="log_send_report">Отправить отчёт</string>
|
||||
<string name="log_copy">Скопировать журнал</string>
|
||||
<string name="log_copied">Журнал скопирован</string>
|
||||
<string name="settings_help">Помощь</string>
|
||||
<string name="settings_help_sub">Частые вопросы, о приложении</string>
|
||||
<string name="help_check">Проверить подключение</string>
|
||||
<string name="help_write">Написать в поддержку</string>
|
||||
<string name="help_faq">Частые вопросы</string>
|
||||
<string name="help_about">О приложении</string>
|
||||
<string name="help_build">сборка %1$d · на основе strongSwan · GPLv2</string>
|
||||
<string name="help_updates">Проверить обновления</string>
|
||||
<string name="help_licenses">Лицензии открытого ПО</string>
|
||||
<string name="help_source">Исходный код strongSwan</string>
|
||||
<string name="help_for_support">Для поддержки</string>
|
||||
<string name="faq_q1">Банк или Госуслуги не открываются</string>
|
||||
<string name="faq_a1">Многие российские сервисы не пускают зарубежные адреса. Откройте «Не открывается?» на главной и выберите приложение — мы пустим его напрямую, мимо VPN.</string>
|
||||
<string name="faq_q2">Где взять профиль .sswan?</string>
|
||||
<string name="faq_a2">Его выдаёт администратор вашего VPN: файлом, ссылкой или QR-кодом. Можно отсканировать QR с экрана друга.</string>
|
||||
<string name="faq_q3">VPN сам отключается</string>
|
||||
<string name="faq_a3">Скорее всего, Android экономит батарею. Откройте Настройки → Стабильная работа и снимите ограничения.</string>
|
||||
<string name="faq_q4">Стабильный или Гибкий режим?</string>
|
||||
<string name="faq_a4">Стабильный надёжнее: новые адреса применяются после переподключения. Гибкий обновляет маршруты на лету, но пока в бете.</string>
|
||||
<string name="faq_q5">Видит ли SHX мои сайты?</string>
|
||||
<string name="faq_a5">Нет. С доступом к статистике приложение считает только объём трафика по приложениям; сайты и содержимое не сохраняются и никуда не отправляются.</string>
|
||||
</resources>
|
||||
@@ -1022,4 +1022,25 @@
|
||||
<string name="log_send_report">Send report</string>
|
||||
<string name="log_copy">Copy log</string>
|
||||
<string name="log_copied">Log copied</string>
|
||||
<string name="settings_help">Help</string>
|
||||
<string name="settings_help_sub">FAQ, about the app</string>
|
||||
<string name="help_check">Check the connection</string>
|
||||
<string name="help_write">Contact support</string>
|
||||
<string name="help_faq">Frequent questions</string>
|
||||
<string name="help_about">About the app</string>
|
||||
<string name="help_build">build %1$d · based on strongSwan · GPLv2</string>
|
||||
<string name="help_updates">Check for updates</string>
|
||||
<string name="help_licenses">Open source licenses</string>
|
||||
<string name="help_source">strongSwan source code</string>
|
||||
<string name="help_for_support">For support</string>
|
||||
<string name="faq_q1">A bank or Gosuslugi does not open</string>
|
||||
<string name="faq_a1">Many Russian services reject foreign addresses. Tap «Not loading?» on Home and pick the app: it will go direct, past the VPN.</string>
|
||||
<string name="faq_q2">Where do I get an .sswan profile?</string>
|
||||
<string name="faq_a2">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.</string>
|
||||
<string name="faq_q3">The VPN turns itself off</string>
|
||||
<string name="faq_a3">Most likely Android is saving battery. Open Settings → Stable work and lift the restrictions.</string>
|
||||
<string name="faq_q4">Stable or Flexible mode?</string>
|
||||
<string name="faq_a4">Stable is more reliable: new addresses apply after a reconnect. Flexible updates routes on the fly, but is still in beta.</string>
|
||||
<string name="faq_q5">Does SHX see my sites?</string>
|
||||
<string name="faq_a5">No. With usage access the app only counts traffic per app; sites and content are not stored or sent anywhere.</string>
|
||||
</resources>
|
||||
Reference in new issue
Block a user