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
+82 -44

No files matched your search

+7 -7
View File
@@ -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'
@@ -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,
)
{
@@ -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,
)
}
}
@@ -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