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)
This commit is contained in:
Denozordec committed 2026-10-05 00:39:09 +07:00
1 parent 285d14e996
commit 2c69cbe775
4 files changed
+71 -33

No files matched your search

+7 -7
View File
@@ -34,11 +34,11 @@ android {
defaultConfig { defaultConfig {
applicationId "one.shx.strongswan.ext" applicationId "one.shx.strongswan.ext"
compileSdk = 36 compileSdk = 36
minSdkVersion 21 minSdkVersion 23
targetSdkVersion 36 targetSdkVersion 36
versionCode 152 versionCode 153
versionName "2.13.15" versionName "2.14.0"
/* Base URL of the update storage, trailing slash included */ /* Base URL of the update storage, trailing slash included */
buildConfigField "String", "UPDATE_BASE_URL", "\"${updateBaseUrl}\"" buildConfigField "String", "UPDATE_BASE_URL", "\"${updateBaseUrl}\""
@@ -146,15 +146,15 @@ dependencies {
implementation 'androidx.work:work-runtime-ktx:2.10.1' implementation 'androidx.work:work-runtime-ktx:2.10.1'
implementation 'com.google.android.material:material:1.13.0' implementation 'com.google.android.material:material:1.13.0'
/* BOM 2025.12.01 (compose 1.10) требует minSdk 23 — приложение поддерживает API 21. /* BOM 2025.12.01 (compose 1.10, Expressive-тема) — отдельная задача после
2025.09.01 = первый BOM с material3 1.4.0 (Expressive) на compose 1.9 (minSdk 21). */ стабилизации; 2025.09.01 = material3 1.4.0 на compose 1.9. */
def composeBom = platform('androidx.compose:compose-bom:2025.09.01') def composeBom = platform('androidx.compose:compose-bom:2025.09.01')
implementation composeBom implementation composeBom
implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.material3:material3' implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.material3:material3-window-size-class' implementation 'androidx.compose.material3:material3-window-size-class'
/* adaptive-navigation-suite требует minSdk 23 (adaptive:1.2.0) — подключить на этапе 3 /* adaptive-navigation-suite (NavigationSuiteScaffold): BOM 1.4.0, требует minSdk 23 */
вместе с решением о minSdk; сейчас приложение поддерживает API 21 */ implementation 'androidx.compose.material3:material3-adaptive-navigation-suite'
implementation 'androidx.compose.material:material-icons-extended' implementation 'androidx.compose.material:material-icons-extended'
implementation 'androidx.activity:activity-compose:1.11.0' implementation 'androidx.activity:activity-compose:1.11.0'
implementation 'androidx.navigation:navigation-compose:2.9.4' implementation 'androidx.navigation:navigation-compose:2.9.4'
@@ -44,6 +44,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
@@ -191,7 +192,9 @@ fun HomeScreen(
StatusGlowBackground(color = statusColor, active = connected || connecting) StatusGlowBackground(color = statusColor, active = connected || connecting)
LazyColumn( LazyColumn(
state = listState, 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, horizontalAlignment = Alignment.CenterHorizontally,
) )
{ {
@@ -24,10 +24,8 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -43,13 +41,15 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults 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.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton 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.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -57,6 +57,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext 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 */ /* the home list keeps its scroll state across tab switches */
val homeListState = rememberLazyListState() val homeListState = rememberLazyListState()
Scaffold( /* adaptive navigation: bottom bar on phones, navigation rail in landscape
bottomBar = { * and on tablets (Stage 3 of ui-ux-plan). Item colors are built here in a
ShxBottomBar(selected = state.tab, onSelect = viewModel::setTab) * 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, containerColor = MaterialTheme.colorScheme.background,
) { padding -> ) {
Surface( Surface(
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize()
.padding(padding)
.consumeWindowInsets(padding),
color = MaterialTheme.colorScheme.background, color = MaterialTheme.colorScheme.background,
) { ) {
AnimatedContent( AnimatedContent(
@@ -206,14 +208,51 @@ private fun ShareProfileDialog(profile: VpnProfile, onDismiss: () -> Unit, onCon
) )
} }
/** Item colors for the adaptive navigation suite, keeping the brand palette. */
@Composable @Composable
private fun ShxBottomBar(selected: ShxTab, onSelect: (ShxTab) -> Unit) private fun shxNavigationItemColors(): NavigationSuiteItemColors
{ {
val bg = MaterialTheme.colorScheme.surfaceContainer val bg = MaterialTheme.colorScheme.surfaceContainer
val indicator = MaterialTheme.colorScheme.primary.copy(alpha = 0.32f).compositeOver(bg) val indicator = MaterialTheme.colorScheme.primary.copy(alpha = 0.32f).compositeOver(bg)
NavigationBar(containerColor = bg, tonalElevation = 0.dp) { 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 -> ShxTab.entries.forEach { tab ->
NavigationBarItem( item(
selected = tab == selected, selected = tab == selected,
onClick = { onSelect(tab) }, onClick = { onSelect(tab) },
icon = { icon = {
@@ -223,17 +262,10 @@ private fun ShxBottomBar(selected: ShxTab, onSelect: (ShxTab) -> Unit)
) )
}, },
label = { Text(stringResource(tab.label())) }, label = { Text(stringResource(tab.label())) },
colors = NavigationBarItemDefaults.colors( colors = colors,
selectedIconColor = MaterialTheme.colorScheme.primary,
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
selectedTextColor = MaterialTheme.colorScheme.primary,
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
indicatorColor = indicator,
),
) )
} }
} }
}
private fun ShxTab.icon(selected: Boolean): ImageVector = when (this) private fun ShxTab.icon(selected: Boolean): ImageVector = when (this)
{ {
@@ -1,3 +1,6 @@
APP_PLATFORM := android-21 APP_PLATFORM := android-23
APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true
APP_ABI := armeabi-v7a arm64-v8a x86_64 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