From 2c69cbe77549bcbda7b7fd8af871b7ce4e7d8d98 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 5 Oct 2026 00:39:09 +0700 Subject: [PATCH] android: 2.14.0 - minSdk 23 + adaptive navigation (Stage 3) - minSdkVersion 21 -> 23 (device is Android 17, no other users); jni Application.mk: APP_PLATFORM android-23 + -DHAVE_SIGWAITINFO (the NDK sysroot declares sigwaitinfo since API 23, conflicting with strongSwan's fallback prototype; the system implementation is used now) - replace the bottom NavigationBar with NavigationSuiteScaffold (bottom bar on phones, navigation rail in landscape/on tablets); item colors keep the brand palette via NavigationSuiteItemColors(bar/rail/drawer) - HomeScreen applies statusBarsPadding itself (the adaptive scaffold no longer pads the content area) - predictive back was already enabled (android:enableOnBackInvokedCallback) --- src/frontends/android/app/build.gradle | 14 +-- .../android/ui/compose/HomeScreen.kt | 5 +- .../strongswan/android/ui/compose/ShxApp.kt | 102 ++++++++++++------ .../android/app/src/main/jni/Application.mk | 5 +- 4 files changed, 82 insertions(+), 44 deletions(-) diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle index 5539d2246..a12a2d377 100644 --- a/src/frontends/android/app/build.gradle +++ b/src/frontends/android/app/build.gradle @@ -34,11 +34,11 @@ android { defaultConfig { applicationId "one.shx.strongswan.ext" compileSdk = 36 - minSdkVersion 21 + minSdkVersion 23 targetSdkVersion 36 - versionCode 152 - versionName "2.13.15" + versionCode 153 + versionName "2.14.0" /* Base URL of the update storage, trailing slash included */ buildConfigField "String", "UPDATE_BASE_URL", "\"${updateBaseUrl}\"" @@ -146,15 +146,15 @@ dependencies { implementation 'androidx.work:work-runtime-ktx:2.10.1' implementation 'com.google.android.material:material:1.13.0' - /* BOM 2025.12.01 (compose 1.10) требует minSdk 23 — приложение поддерживает API 21. - 2025.09.01 = первый BOM с material3 1.4.0 (Expressive) на compose 1.9 (minSdk 21). */ + /* BOM 2025.12.01 (compose 1.10, Expressive-тема) — отдельная задача после + стабилизации; 2025.09.01 = material3 1.4.0 на compose 1.9. */ def composeBom = platform('androidx.compose:compose-bom:2025.09.01') implementation composeBom implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.material3:material3' implementation 'androidx.compose.material3:material3-window-size-class' - /* adaptive-navigation-suite требует minSdk 23 (adaptive:1.2.0) — подключить на этапе 3 - вместе с решением о minSdk; сейчас приложение поддерживает API 21 */ + /* adaptive-navigation-suite (NavigationSuiteScaffold): BOM 1.4.0, требует minSdk 23 */ + implementation 'androidx.compose.material3:material3-adaptive-navigation-suite' implementation 'androidx.compose.material:material-icons-extended' implementation 'androidx.activity:activity-compose:1.11.0' implementation 'androidx.navigation:navigation-compose:2.9.4' 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 af992cacb..011f3e0c9 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 @@ -44,6 +44,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed @@ -191,7 +192,9 @@ fun HomeScreen( StatusGlowBackground(color = statusColor, active = connected || connecting) LazyColumn( state = listState, - modifier = Modifier.fillMaxSize(), + /* the adaptive navigation scaffold no longer pads the content area, + * so the status headline offsets itself below the status bar */ + modifier = Modifier.fillMaxSize().statusBarsPadding(), horizontalAlignment = Alignment.CenterHorizontally, ) { 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 b9c868ad8..cf2ed8835 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 @@ -24,10 +24,8 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons @@ -43,13 +41,15 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Checkbox import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItemDefaults -import androidx.compose.material3.Scaffold +import androidx.compose.material3.NavigationDrawerItemDefaults +import androidx.compose.material3.NavigationRailItemDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItemColors +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -57,6 +57,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext @@ -107,17 +108,18 @@ fun ShxApp(viewModel: HomeViewModel = viewModel()) /* the home list keeps its scroll state across tab switches */ val homeListState = rememberLazyListState() - Scaffold( - bottomBar = { - ShxBottomBar(selected = state.tab, onSelect = viewModel::setTab) + /* adaptive navigation: bottom bar on phones, navigation rail in landscape + * and on tablets (Stage 3 of ui-ux-plan). Item colors are built here in a + * composable context - the items lambda itself does not compose. */ + val navItemColors = shxNavigationItemColors() + NavigationSuiteScaffold( + navigationSuiteItems = { + ShxNavigationSuiteItems(selected = state.tab, onSelect = viewModel::setTab, colors = navItemColors) }, containerColor = MaterialTheme.colorScheme.background, - ) { padding -> + ) { Surface( - modifier = Modifier - .fillMaxSize() - .padding(padding) - .consumeWindowInsets(padding), + modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background, ) { AnimatedContent( @@ -206,32 +208,62 @@ private fun ShareProfileDialog(profile: VpnProfile, onDismiss: () -> Unit, onCon ) } +/** Item colors for the adaptive navigation suite, keeping the brand palette. */ @Composable -private fun ShxBottomBar(selected: ShxTab, onSelect: (ShxTab) -> Unit) +private fun shxNavigationItemColors(): NavigationSuiteItemColors { val bg = MaterialTheme.colorScheme.surfaceContainer val indicator = MaterialTheme.colorScheme.primary.copy(alpha = 0.32f).compositeOver(bg) - NavigationBar(containerColor = bg, tonalElevation = 0.dp) { - ShxTab.entries.forEach { tab -> - NavigationBarItem( - selected = tab == selected, - onClick = { onSelect(tab) }, - icon = { - Icon( - imageVector = tab.icon(selected = tab == selected), - contentDescription = null, - ) - }, - label = { Text(stringResource(tab.label())) }, - colors = NavigationBarItemDefaults.colors( - selectedIconColor = MaterialTheme.colorScheme.primary, - unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant, - selectedTextColor = MaterialTheme.colorScheme.primary, - unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant, - indicatorColor = indicator, - ), - ) - } + return NavigationSuiteItemColors( + navigationBarItemColors = NavigationBarItemDefaults.colors( + selectedIconColor = MaterialTheme.colorScheme.primary, + unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant, + selectedTextColor = MaterialTheme.colorScheme.primary, + unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant, + indicatorColor = indicator, + ), + navigationRailItemColors = NavigationRailItemDefaults.colors( + selectedIconColor = MaterialTheme.colorScheme.primary, + unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant, + selectedTextColor = MaterialTheme.colorScheme.primary, + unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant, + indicatorColor = indicator, + ), + navigationDrawerItemColors = NavigationDrawerItemDefaults.colors( + selectedContainerColor = indicator, + unselectedContainerColor = Color.Transparent, + selectedIconColor = MaterialTheme.colorScheme.primary, + unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant, + selectedTextColor = MaterialTheme.colorScheme.primary, + unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant, + ), + ) +} + +/** + * Registers the app tabs as navigation suite items. The scope's item() is a + * plain registration function - only the icon/label lambdas compose - so + * colors are built outside (in a composable context) and passed in. + */ +private fun NavigationSuiteScope.ShxNavigationSuiteItems( + selected: ShxTab, + onSelect: (ShxTab) -> Unit, + colors: NavigationSuiteItemColors, +) +{ + ShxTab.entries.forEach { tab -> + item( + selected = tab == selected, + onClick = { onSelect(tab) }, + icon = { + Icon( + imageVector = tab.icon(selected = tab == selected), + contentDescription = null, + ) + }, + label = { Text(stringResource(tab.label())) }, + colors = colors, + ) } } diff --git a/src/frontends/android/app/src/main/jni/Application.mk b/src/frontends/android/app/src/main/jni/Application.mk index e123e6a6e..74f4eb6a6 100644 --- a/src/frontends/android/app/src/main/jni/Application.mk +++ b/src/frontends/android/app/src/main/jni/Application.mk @@ -1,3 +1,6 @@ -APP_PLATFORM := android-21 +APP_PLATFORM := android-23 APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true APP_ABI := armeabi-v7a arm64-v8a x86_64 +# minSdk 23: the NDK sysroot declares sigwaitinfo() (introduced in API 23), +# so use the system implementation instead of strongSwan's fallback prototype +APP_CFLAGS += -DHAVE_SIGWAITINFO