Красивый UI

This commit is contained in:
2026-05-21 01:10:55 +03:00
parent 184edc0b67
commit 9c38da76d2
19 changed files with 557 additions and 213 deletions

View File

@@ -4,17 +4,15 @@ import android.content.Intent
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.List import androidx.compose.material.icons.automirrored.rounded.List
import androidx.compose.material.icons.rounded.Menu import androidx.compose.material.icons.rounded.Menu
import androidx.compose.material.icons.rounded.Settings import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material.icons.rounded.Sync import androidx.compose.material.icons.rounded.Sync
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -22,16 +20,16 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.navDeepLink import androidx.navigation.navDeepLink
import com.github.nullptroma.wallenc.ui.elements.FloatingWallencNavigationBar
import com.github.nullptroma.wallenc.ui.navigation.NavBarItemData import com.github.nullptroma.wallenc.ui.navigation.NavBarItemData
import com.github.nullptroma.wallenc.ui.navigation.WallencDeepLinks import com.github.nullptroma.wallenc.ui.navigation.WallencDeepLinks
import com.github.nullptroma.wallenc.ui.navigation.matchesWallencDeepLink import com.github.nullptroma.wallenc.ui.navigation.matchesWallencDeepLink
import com.github.nullptroma.wallenc.ui.elements.NavigationBarMarqueeText
import com.github.nullptroma.wallenc.ui.navigation.rememberNavigationState import com.github.nullptroma.wallenc.ui.navigation.rememberNavigationState
import com.github.nullptroma.wallenc.ui.screens.main.MainRoute import com.github.nullptroma.wallenc.ui.screens.main.MainRoute
import com.github.nullptroma.wallenc.ui.screens.main.MainScreen import com.github.nullptroma.wallenc.ui.screens.main.MainScreen
@@ -108,57 +106,52 @@ fun WallencNavRoot(
) )
} }
val navBackStackEntry by navState.navHostController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Scaffold(bottomBar = { Scaffold(
NavigationBar(modifier = Modifier.wrapContentHeight()) { bottomBar = {
val navBackStackEntry by navState.navHostController.currentBackStackEntryAsState() Box(
val currentRoute = navBackStackEntry?.destination?.route modifier = Modifier
topLevelNavBarItems.forEach { .navigationBarsPadding()
val routeClassName = it.key .padding(horizontal = 12.dp)
val navBarItemData = it.value .padding(top = 4.dp, bottom = 6.dp),
NavigationBarItem( ) {
modifier = Modifier.wrapContentHeight(), FloatingWallencNavigationBar(
icon = { items = topLevelNavBarItems,
if (navBarItemData.icon != null) Icon( routes = topLevelRoutes,
navBarItemData.icon, currentRoute = currentRoute,
contentDescription = stringResource(navBarItemData.iconContentDescriptionResourceId), onNavigate = { item ->
) val route = topLevelRoutes[item.screenRouteClass]
}, ?: error("Route ${item.screenRouteClass} not found")
label = { if (currentRoute?.startsWith(item.screenRouteClass) != true) {
NavigationBarMarqueeText( navState.changeTop(route)
text = stringResource(navBarItemData.nameStringResourceId), }
)
}, },
selected = currentRoute?.startsWith(routeClassName) == true,
onClick = {
val route = topLevelRoutes[navBarItemData.screenRouteClass]
if (route == null)
throw NullPointerException("Route $route not found")
if (currentRoute?.startsWith(routeClassName) != true) navState.changeTop(
route
)
}
) )
} }
} },
}) { innerPaddings -> ) { innerPaddings ->
NavHost( NavHost(
navState.navHostController, navState.navHostController,
startDestination = topLevelRoutes[MainRoute::class.qualifiedName]!! startDestination = topLevelRoutes[MainRoute::class.qualifiedName]!!,
modifier = Modifier.padding(innerPaddings),
) { ) {
composable<MainRoute>( composable<MainRoute>(
deepLinks = listOf( deepLinks = listOf(
navDeepLink { uriPattern = WallencDeepLinks.MAIN_URI_PATTERN }, navDeepLink { uriPattern = WallencDeepLinks.MAIN_URI_PATTERN },
), ),
enterTransition = { enterTransition = {
fadeIn(tween(200)) fadeIn(tween(200))
}, exitTransition = { },
fadeOut(tween(200)) exitTransition = {
}) { fadeOut(tween(200))
},
) {
MainScreen( MainScreen(
modifier = Modifier.padding(innerPaddings), modifier = Modifier,
navState = mainNavState, navState = mainNavState,
viewModel = mainViewModel viewModel = mainViewModel,
) )
} }
composable<SettingsRoute>( composable<SettingsRoute>(
@@ -166,23 +159,27 @@ fun WallencNavRoot(
navDeepLink { uriPattern = WallencDeepLinks.SETTINGS_URI_PATTERN }, navDeepLink { uriPattern = WallencDeepLinks.SETTINGS_URI_PATTERN },
), ),
enterTransition = { enterTransition = {
fadeIn(tween(200)) fadeIn(tween(200))
}, exitTransition = { },
fadeOut(tween(200)) exitTransition = {
}) { fadeOut(tween(200))
SettingsScreen(Modifier.padding(innerPaddings), settingsViewModel) },
) {
SettingsScreen(Modifier, settingsViewModel)
} }
composable<StorageSyncRoute>( composable<StorageSyncRoute>(
deepLinks = listOf( deepLinks = listOf(
navDeepLink { uriPattern = WallencDeepLinks.SYNC_URI_PATTERN }, navDeepLink { uriPattern = WallencDeepLinks.SYNC_URI_PATTERN },
), ),
enterTransition = { enterTransition = {
fadeIn(tween(200)) fadeIn(tween(200))
}, exitTransition = { },
fadeOut(tween(200)) exitTransition = {
}) { fadeOut(tween(200))
},
) {
StorageSyncScreen( StorageSyncScreen(
modifier = Modifier.padding(innerPaddings), modifier = Modifier,
viewModel = storageSyncViewModel, viewModel = storageSyncViewModel,
) )
} }
@@ -191,13 +188,13 @@ fun WallencNavRoot(
navDeepLink { uriPattern = WallencDeepLinks.TASKS_URI_PATTERN }, navDeepLink { uriPattern = WallencDeepLinks.TASKS_URI_PATTERN },
), ),
enterTransition = { enterTransition = {
fadeIn(tween(200)) fadeIn(tween(200))
}, exitTransition = { },
fadeOut(tween(200)) exitTransition = {
}) { fadeOut(tween(200))
TaskPipelineScreen( },
modifier = Modifier.padding(innerPaddings) ) {
) TaskPipelineScreen(modifier = Modifier)
} }
} }
} }

View File

@@ -0,0 +1,75 @@
package com.github.nullptroma.wallenc.ui.elements
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.nullptroma.wallenc.ui.R
private const val BackButtonAnimMillis = 200
private val backButtonEnter = fadeIn(tween(BackButtonAnimMillis)) +
slideInVertically(
animationSpec = tween(BackButtonAnimMillis),
initialOffsetY = { fullHeight -> fullHeight / 2 },
)
private val backButtonExit = fadeOut(tween(BackButtonAnimMillis)) +
slideOutVertically(
animationSpec = tween(BackButtonAnimMillis),
targetOffsetY = { fullHeight -> -fullHeight / 2 },
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FloatingBackButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Surface(
onClick = onClick,
modifier = modifier.size(44.dp),
shape = CircleShape,
color = MaterialTheme.colorScheme.surfaceContainerHigh,
shadowElevation = 4.dp,
tonalElevation = 2.dp,
contentColor = MaterialTheme.colorScheme.onSurface,
) {
Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = stringResource(R.string.nav_cd_back),
modifier = Modifier.padding(10.dp),
)
}
}
@Composable
fun AnimatedFloatingBackButton(
visible: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = visible,
modifier = modifier,
enter = backButtonEnter,
exit = backButtonExit,
) {
FloatingBackButton(onClick = onClick)
}
}

View File

@@ -0,0 +1,180 @@
package com.github.nullptroma.wallenc.ui.elements
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
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.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.github.nullptroma.wallenc.ui.navigation.NavBarItemData
/** Вертикальный зазор между вложенной и корневой плавающими панелями навигации. */
val WallencNestedNavBarGap = 2.dp
@Composable
fun FloatingWallencNavigationBar(
items: Map<String, NavBarItemData>,
routes: Map<String, *>,
currentRoute: String?,
onNavigate: (NavBarItemData) -> Unit,
modifier: Modifier = Modifier,
compact: Boolean = false,
) {
val haptic = LocalHapticFeedback.current
val barHeight = if (compact) 48.dp else 56.dp
val barShape = if (compact) RoundedCornerShape(22.dp) else RoundedCornerShape(28.dp)
val barHorizontalPadding = if (compact) 4.dp else 6.dp
val barSurface: @Composable () -> Unit = {
Surface(
modifier = Modifier
.then(
if (compact) {
Modifier
.widthIn(max = 300.dp)
.fillMaxWidth(0.68f)
} else {
Modifier.fillMaxWidth()
},
),
shape = barShape,
color = MaterialTheme.colorScheme.surfaceContainerHigh,
shadowElevation = 6.dp,
tonalElevation = 2.dp,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(barHeight)
.padding(horizontal = barHorizontalPadding, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
items.forEach { (routeClassName, navBarItemData) ->
val iconVector = navBarItemData.icon ?: return@forEach
val selected = currentRoute?.startsWith(routeClassName) == true
val enabled = routes[navBarItemData.screenRouteClass] != null
FloatingNavItem(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
icon = iconVector,
label = stringResource(navBarItemData.nameStringResourceId),
contentDescription = stringResource(navBarItemData.iconContentDescriptionResourceId),
selected = selected,
enabled = enabled,
compact = compact,
onClick = {
if (!selected && enabled) {
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
onNavigate(navBarItemData)
}
},
)
}
}
}
}
if (compact) {
Box(
modifier = modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
barSurface()
}
} else {
Box(modifier = modifier.fillMaxWidth()) {
barSurface()
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun FloatingNavItem(
icon: ImageVector,
label: String,
contentDescription: String,
selected: Boolean,
enabled: Boolean,
compact: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val iconSize = if (compact) 22.dp else 24.dp
val itemPaddingH = if (compact) 6.dp else 8.dp
val itemPaddingV = if (compact) 6.dp else 8.dp
val labelStyle = if (compact) {
MaterialTheme.typography.labelSmall
} else {
MaterialTheme.typography.labelMedium
}
val labelVelocity = if (compact) 24.dp else 28.dp
val itemShape = if (compact) RoundedCornerShape(16.dp) else RoundedCornerShape(20.dp)
val containerColor = if (selected) {
MaterialTheme.colorScheme.secondaryContainer
} else {
MaterialTheme.colorScheme.surfaceContainerHigh
}
val contentColor = if (selected) {
MaterialTheme.colorScheme.onSecondaryContainer
} else {
MaterialTheme.colorScheme.onSurfaceVariant
}
Surface(
onClick = onClick,
enabled = enabled,
modifier = modifier
.padding(horizontal = if (compact) 1.dp else 2.dp)
.semantics {
role = Role.Tab
this.contentDescription = contentDescription
},
shape = itemShape,
color = containerColor,
contentColor = contentColor,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = itemPaddingH, vertical = itemPaddingV),
horizontalArrangement = if (selected) Arrangement.Start else Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.size(iconSize),
)
if (selected) {
NavigationBarMarqueeText(
text = label,
modifier = Modifier
.weight(1f)
.padding(start = if (compact) 4.dp else 6.dp),
style = labelStyle,
velocity = labelVelocity,
)
}
}
}
}

View File

@@ -7,25 +7,33 @@ import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/** /**
* Однострочная подпись таба нижней навигации: при нехватке ширины текст * Однострочная подпись таба: при нехватке ширины текст циклически прокручивается.
* прокручивается (marquee), без переноса последних букв на вторую строку.
*/ */
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun NavigationBarMarqueeText( fun NavigationBarMarqueeText(
text: String, text: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
style: TextStyle = LocalTextStyle.current,
velocity: Dp = 28.dp,
) { ) {
Text( Text(
text = text, text = text,
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.basicMarquee(), .basicMarquee(
style = LocalTextStyle.current, iterations = Int.MAX_VALUE,
repeatDelayMillis = 1_200,
velocity = velocity,
),
style = style,
maxLines = 1, maxLines = 1,
softWrap = false, softWrap = false,
overflow = TextOverflow.Clip, overflow = TextOverflow.Clip,

View File

@@ -0,0 +1,49 @@
package com.github.nullptroma.wallenc.ui.elements
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.FabPosition
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun WallencScreenScaffold(
modifier: Modifier = Modifier,
snackbarHostState: SnackbarHostState? = null,
floatingActionButton: @Composable () -> Unit = {},
content: @Composable (PaddingValues) -> Unit,
) {
Scaffold(
modifier = modifier,
contentWindowInsets = WindowInsets(0.dp),
snackbarHost = {
if (snackbarHostState != null) {
SnackbarHost(snackbarHostState)
}
},
floatingActionButton = floatingActionButton,
floatingActionButtonPosition = FabPosition.End,
content = content,
)
}
@Composable
fun WallencScreenContentPadding(
innerPadding: PaddingValues,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
Box(
modifier = modifier
.padding(innerPadding)
.padding(horizontal = 16.dp, vertical = 12.dp),
) {
content()
}
}

View File

@@ -0,0 +1,25 @@
package com.github.nullptroma.wallenc.ui.navigation
import com.github.nullptroma.wallenc.ui.screens.main.screens.remotes.RemoteVaultsRoute
import com.github.nullptroma.wallenc.ui.screens.main.screens.vault.LocalVaultRoute
import com.github.nullptroma.wallenc.ui.screens.shared.TextEditRoute
private val mainTopLevelRoutePrefixes: Set<String> = setOf(
LocalVaultRoute::class.qualifiedName!!,
RemoteVaultsRoute::class.qualifiedName!!,
)
fun isMainTopLevelRoute(route: String?): Boolean {
if (route == null) return true
return mainTopLevelRoutePrefixes.any { route.startsWith(it) }
}
fun isTextEditDestination(route: String?): Boolean {
val qualified = TextEditRoute::class.qualifiedName ?: return false
return route?.startsWith(qualified) == true
}
fun shouldShowMainFloatingBack(route: String?): Boolean {
if (route == null) return false
return !isMainTopLevelRoute(route)
}

View File

@@ -24,6 +24,10 @@ class NavigationState(
restoreState = true restoreState = true
} }
} }
fun pop(): Boolean = navHostController.popBackStack()
fun canPop(): Boolean = navHostController.previousBackStackEntry != null
} }
@Composable @Composable

View File

@@ -3,34 +3,38 @@ package com.github.nullptroma.wallenc.ui.screens.main
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Cloud import androidx.compose.material.icons.outlined.Cloud
import androidx.compose.material.icons.outlined.Folder import androidx.compose.material.icons.outlined.Folder
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.toRoute import androidx.navigation.toRoute
import com.github.nullptroma.wallenc.ui.elements.NavigationBarMarqueeText
import com.github.nullptroma.wallenc.ui.R import com.github.nullptroma.wallenc.ui.R
import com.github.nullptroma.wallenc.ui.elements.AnimatedFloatingBackButton
import com.github.nullptroma.wallenc.ui.elements.FloatingWallencNavigationBar
import com.github.nullptroma.wallenc.ui.elements.WallencNestedNavBarGap
import com.github.nullptroma.wallenc.ui.navigation.NavBarItemData import com.github.nullptroma.wallenc.ui.navigation.NavBarItemData
import com.github.nullptroma.wallenc.ui.navigation.NavigationState import com.github.nullptroma.wallenc.ui.navigation.NavigationState
import com.github.nullptroma.wallenc.ui.navigation.isMainTopLevelRoute
import com.github.nullptroma.wallenc.ui.navigation.isTextEditDestination
import com.github.nullptroma.wallenc.ui.navigation.shouldShowMainFloatingBack
import com.github.nullptroma.wallenc.ui.navigation.rememberNavigationState import com.github.nullptroma.wallenc.ui.navigation.rememberNavigationState
import com.github.nullptroma.wallenc.ui.screens.main.screens.remotes.RemoteVaultsRoute import com.github.nullptroma.wallenc.ui.screens.main.screens.remotes.RemoteVaultsRoute
import com.github.nullptroma.wallenc.ui.screens.main.screens.remotes.RemoteVaultsScreen import com.github.nullptroma.wallenc.ui.screens.main.screens.remotes.RemoteVaultsScreen
@@ -54,13 +58,8 @@ import com.github.nullptroma.wallenc.ui.screens.main.screens.vault.VaultBrowserS
import com.github.nullptroma.wallenc.ui.screens.shared.TextEditRoute import com.github.nullptroma.wallenc.ui.screens.shared.TextEditRoute
import com.github.nullptroma.wallenc.ui.screens.shared.TextEditScreen import com.github.nullptroma.wallenc.ui.screens.shared.TextEditScreen
private fun isTextEditDestination(route: String?): Boolean {
val q = TextEditRoute::class.qualifiedName ?: return false
return route?.startsWith(q) == true
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@androidx.compose.runtime.Composable @Composable
fun MainScreen( fun MainScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: MainViewModel = hiltViewModel(), viewModel: MainViewModel = hiltViewModel(),
@@ -72,8 +71,12 @@ fun MainScreen(
val remoteVaultsViewModel: RemoteVaultsViewModel = hiltViewModel() val remoteVaultsViewModel: RemoteVaultsViewModel = hiltViewModel()
val childBackStackEntry by navState.navHostController.currentBackStackEntryAsState() val childBackStackEntry by navState.navHostController.currentBackStackEntryAsState()
val showWorkStatusBar = !isTextEditDestination(childBackStackEntry?.destination?.route) val childRoute = childBackStackEntry?.destination?.route
val showWorkStatusBar = !isTextEditDestination(childRoute)
val showMainBottomNav = isMainTopLevelRoute(childRoute)
val showFloatingBack = shouldShowMainFloatingBack(childRoute) && navState.canPop()
val workStatus = mainUi.workStatus val workStatus = mainUi.workStatus
val onBack: () -> Unit = { navState.pop() }
val topLevelNavBarItems = remember { val topLevelNavBarItems = remember {
mapOf( mapOf(
@@ -101,50 +104,37 @@ fun MainScreen(
} }
}, },
bottomBar = { bottomBar = {
Column { if (showMainBottomNav) {
NavigationBar(windowInsets = WindowInsets(0)) { Box(
val navBackStackEntry by navState.navHostController.currentBackStackEntryAsState() modifier = Modifier
val currentRoute = navBackStackEntry?.destination?.route .padding(horizontal = 16.dp)
topLevelNavBarItems.forEach { .padding(top = WallencNestedNavBarGap, bottom = WallencNestedNavBarGap),
val routeClassName = it.key ) {
val navBarItemData = it.value FloatingWallencNavigationBar(
val iconVector = navBarItemData.icon compact = true,
?: error("Main tab requires icon") items = topLevelNavBarItems,
NavigationBarItem( routes = routes,
modifier = Modifier.weight(1f), currentRoute = childRoute,
icon = { onNavigate = { item ->
Icon( val route = routes[item.screenRouteClass]
imageVector = iconVector, ?: error("Route ${item.screenRouteClass} not found")
contentDescription = stringResource(navBarItemData.iconContentDescriptionResourceId), navState.changeTop(route)
) },
}, )
label = {
NavigationBarMarqueeText(
text = stringResource(navBarItemData.nameStringResourceId),
)
},
selected = currentRoute?.startsWith(routeClassName) == true,
onClick = {
val route = routes[navBarItemData.screenRouteClass]
?: throw NullPointerException("Route ${navBarItemData.screenRouteClass} not found")
if (currentRoute?.startsWith(routeClassName) != true) {
navState.changeTop(route)
}
},
)
}
} }
HorizontalDivider()
} }
}, },
) { innerPaddings -> ) { innerPaddings ->
NavHost( Box(
navController = navState.navHostController,
startDestination = routes[LocalVaultRoute::class.qualifiedName]!!,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(innerPaddings), .padding(innerPaddings),
) { ) {
NavHost(
navController = navState.navHostController,
startDestination = routes[LocalVaultRoute::class.qualifiedName]!!,
modifier = Modifier.fillMaxSize(),
) {
composable<LocalVaultRoute>( composable<LocalVaultRoute>(
enterTransition = { fadeIn(tween(200)) }, enterTransition = { fadeIn(tween(200)) },
exitTransition = { fadeOut(tween(200)) }, exitTransition = { fadeOut(tween(200)) },
@@ -242,9 +232,7 @@ fun MainScreen(
), ),
) )
}, },
onDeleted = { onDeleted = { navState.pop() },
navState.navHostController.popBackStack()
},
) )
} }
composable<TextSecretEditRoute>( composable<TextSecretEditRoute>(
@@ -255,7 +243,7 @@ fun MainScreen(
TextSecretEditScreen( TextSecretEditScreen(
onSaved = { savedSecretId -> onSaved = { savedSecretId ->
val editingExisting = route.secretId != null val editingExisting = route.secretId != null
navState.navHostController.popBackStack() navState.pop()
if (!editingExisting) { if (!editingExisting) {
navState.push( navState.push(
TextSecretDetailsRoute( TextSecretDetailsRoute(
@@ -269,8 +257,18 @@ fun MainScreen(
} }
composable<TextEditRoute> { composable<TextEditRoute> {
val route: TextEditRoute = it.toRoute() val route: TextEditRoute = it.toRoute()
TextEditScreen(route.text) TextEditScreen(text = route.text)
} }
}
AnimatedFloatingBackButton(
visible = showFloatingBack,
onClick = onBack,
modifier = Modifier
.zIndex(1f)
.align(Alignment.BottomStart)
.navigationBarsPadding()
.padding(start = 12.dp, bottom = 12.dp),
)
} }
} }
} }

View File

@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Arrangement
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.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -16,7 +15,6 @@ import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -27,6 +25,8 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.github.nullptroma.wallenc.ui.R import com.github.nullptroma.wallenc.ui.R
import com.github.nullptroma.wallenc.ui.elements.WallencScreenContentPadding
import com.github.nullptroma.wallenc.ui.elements.WallencScreenScaffold
import com.github.nullptroma.wallenc.ui.resources.resolveText import com.github.nullptroma.wallenc.ui.resources.resolveText
@Composable @Composable
@@ -38,15 +38,10 @@ fun StorageHomeScreen(
) { ) {
val uiState by viewModel.state.collectAsStateWithLifecycle() val uiState by viewModel.state.collectAsStateWithLifecycle()
Scaffold( WallencScreenScaffold(modifier = modifier) { innerPadding ->
modifier = modifier, WallencScreenContentPadding(innerPadding) {
contentWindowInsets = WindowInsets(0.dp),
) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
if (uiState.isLoading) { if (uiState.isLoading) {
@@ -110,6 +105,7 @@ fun StorageHomeScreen(
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
) )
} }
}
} }
} }

View File

@@ -4,7 +4,6 @@ import android.content.ClipData
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
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.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -19,7 +18,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -33,6 +31,8 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.github.nullptroma.wallenc.ui.R import com.github.nullptroma.wallenc.ui.R
import com.github.nullptroma.wallenc.ui.elements.WallencScreenContentPadding
import com.github.nullptroma.wallenc.ui.elements.WallencScreenScaffold
import com.github.nullptroma.wallenc.ui.resources.resolveText import com.github.nullptroma.wallenc.ui.resources.resolveText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -48,15 +48,10 @@ fun TextSecretDetailsScreen(
val clipboard = LocalClipboard.current val clipboard = LocalClipboard.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
Scaffold( WallencScreenScaffold(modifier = modifier) { innerPadding ->
modifier = modifier, WallencScreenContentPadding(innerPadding) {
contentWindowInsets = WindowInsets(0.dp),
) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp), verticalArrangement = Arrangement.spacedBy(10.dp),
) { ) {
uiState.errorNotification?.let { notification -> uiState.errorNotification?.let { notification ->
@@ -144,5 +139,6 @@ fun TextSecretDetailsScreen(
Text(stringResource(R.string.remove)) Text(stringResource(R.string.remove))
} }
} }
}
} }
} }

View File

@@ -3,7 +3,6 @@ package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.secrets
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
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.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -16,7 +15,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -33,6 +31,8 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretEntryRecord import com.github.nullptroma.wallenc.domain.datatypes.TextSecretEntryRecord
import com.github.nullptroma.wallenc.ui.R import com.github.nullptroma.wallenc.ui.R
import com.github.nullptroma.wallenc.ui.elements.WallencScreenContentPadding
import com.github.nullptroma.wallenc.ui.elements.WallencScreenScaffold
import com.github.nullptroma.wallenc.ui.resources.resolveText import com.github.nullptroma.wallenc.ui.resources.resolveText
@Composable @Composable
@@ -58,15 +58,10 @@ fun TextSecretEditScreen(
} }
} }
Scaffold( WallencScreenScaffold(modifier = modifier) { innerPadding ->
modifier = modifier, WallencScreenContentPadding(innerPadding) {
contentWindowInsets = WindowInsets(0.dp),
) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp), verticalArrangement = Arrangement.spacedBy(10.dp),
) { ) {
Text( Text(
@@ -155,5 +150,6 @@ fun TextSecretEditScreen(
} }
} }
} }
}
} }
} }

View File

@@ -1,7 +1,6 @@
package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.secrets package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.secrets
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@@ -10,7 +9,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -21,6 +19,8 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretRecord import com.github.nullptroma.wallenc.domain.datatypes.TextSecretRecord
import com.github.nullptroma.wallenc.ui.R import com.github.nullptroma.wallenc.ui.R
import com.github.nullptroma.wallenc.ui.elements.WallencScreenContentPadding
import com.github.nullptroma.wallenc.ui.elements.WallencScreenScaffold
@Composable @Composable
fun TextSecretsScreen( fun TextSecretsScreen(
@@ -31,9 +31,8 @@ fun TextSecretsScreen(
) { ) {
val uiState by viewModel.state.collectAsStateWithLifecycle() val uiState by viewModel.state.collectAsStateWithLifecycle()
Scaffold( WallencScreenScaffold(
modifier = modifier, modifier = modifier,
contentWindowInsets = WindowInsets(0.dp),
floatingActionButton = { floatingActionButton = {
FloatingActionButton( FloatingActionButton(
onClick = { onClick = {
@@ -47,11 +46,10 @@ fun TextSecretsScreen(
} }
}, },
) { innerPadding -> ) { innerPadding ->
WallencScreenContentPadding(innerPadding) {
TextSecretsScreenContent( TextSecretsScreenContent(
uiState = uiState, uiState = uiState,
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize()
.padding(innerPadding),
) { ) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) { LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {
items(uiState.items) { secret -> items(uiState.items) { secret ->
@@ -63,5 +61,6 @@ fun TextSecretsScreen(
} }
} }
} }
}
} }
} }

View File

@@ -79,6 +79,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.github.nullptroma.wallenc.domain.datatypes.TwoFaTokenRecord import com.github.nullptroma.wallenc.domain.datatypes.TwoFaTokenRecord
import com.github.nullptroma.wallenc.ui.R import com.github.nullptroma.wallenc.ui.R
import com.github.nullptroma.wallenc.ui.elements.QrScannerDialog import com.github.nullptroma.wallenc.ui.elements.QrScannerDialog
import com.github.nullptroma.wallenc.ui.elements.WallencScreenContentPadding
import com.github.nullptroma.wallenc.ui.elements.WallencScreenScaffold
import com.github.nullptroma.wallenc.usecases.TwoFaCodeState import com.github.nullptroma.wallenc.usecases.TwoFaCodeState
import com.github.nullptroma.wallenc.usecases.buildTwoFaCodeState import com.github.nullptroma.wallenc.usecases.buildTwoFaCodeState
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -103,9 +105,8 @@ fun TwoFaTokensScreen(
var editingToken by remember { mutableStateOf<TwoFaTokenRecord?>(null) } var editingToken by remember { mutableStateOf<TwoFaTokenRecord?>(null) }
var creating by remember { mutableStateOf(false) } var creating by remember { mutableStateOf(false) }
Scaffold( WallencScreenScaffold(
modifier = modifier, modifier = modifier,
contentWindowInsets = WindowInsets(0.dp),
floatingActionButton = { floatingActionButton = {
FloatingActionButton( FloatingActionButton(
onClick = { onClick = {
@@ -119,11 +120,10 @@ fun TwoFaTokensScreen(
} }
}, },
) { innerPadding -> ) { innerPadding ->
WallencScreenContentPadding(innerPadding) {
TwoFaTokensScreenContent( TwoFaTokensScreenContent(
uiState = uiState, uiState = uiState,
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize()
.padding(innerPadding),
) { ) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) { LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {
items(uiState.items) { item -> items(uiState.items) { item ->
@@ -244,6 +244,7 @@ fun TwoFaTokensScreen(
} }
} }
} }
}
} }
} }

View File

@@ -27,9 +27,7 @@ fun TwoFaTokensScreenContent(
tokenList: @Composable () -> Unit = {}, tokenList: @Composable () -> Unit = {},
) { ) {
Column( Column(
modifier = modifier modifier = modifier.fillMaxSize(),
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
if (uiState.isLoading) { if (uiState.isLoading) {

View File

@@ -80,32 +80,29 @@ fun VaultBrowserScreen(
val showEmptyState = uiState.storagesList.isEmpty() && !uiState.storagesRefreshing val showEmptyState = uiState.storagesList.isEmpty() && !uiState.storagesRefreshing
val isUuidBusy: (UUID) -> Boolean = { uuid -> uuid in uiState.busyStorageUuids } val isUuidBusy: (UUID) -> Boolean = { uuid -> uuid in uiState.busyStorageUuids }
Box { val addFab: @Composable () -> Unit = {
Scaffold( FloatingActionButton(
modifier = modifier, onClick = {
contentWindowInsets = WindowInsets(0.dp), if (fabEnabled && !fabBusy) {
floatingActionButton = { viewModel.createStorage()
FloatingActionButton(
onClick = {
if (fabEnabled && !fabBusy) {
viewModel.createStorage()
}
},
modifier = Modifier.alpha(if (fabEnabled && !fabBusy) 1f else 0.38f),
) {
Icon(
Icons.Filled.Add,
contentDescription = stringResource(
when {
!fabEnabled -> R.string.vault_fab_add_storage_disabled_cd
fabBusy -> R.string.vault_fab_add_storage_busy_cd
else -> R.string.vault_fab_add_storage_cd
},
),
)
} }
}, },
) { innerPadding -> modifier = Modifier.alpha(if (fabEnabled && !fabBusy) 1f else 0.38f),
) {
Icon(
Icons.Filled.Add,
contentDescription = stringResource(
when {
!fabEnabled -> R.string.vault_fab_add_storage_disabled_cd
fabBusy -> R.string.vault_fab_add_storage_busy_cd
else -> R.string.vault_fab_add_storage_cd
},
),
)
}
}
val vaultContent: @Composable (androidx.compose.foundation.layout.PaddingValues) -> Unit = { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier
.padding(innerPadding) .padding(innerPadding)
@@ -178,7 +175,14 @@ fun VaultBrowserScreen(
} }
} }
} }
} }
Box(modifier = modifier) {
Scaffold(
contentWindowInsets = WindowInsets(0.dp),
floatingActionButton = addFab,
content = vaultContent,
)
if (showFullscreenLoader) { if (showFullscreenLoader) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {

View File

@@ -1,19 +1,25 @@
package com.github.nullptroma.wallenc.ui.screens.shared package com.github.nullptroma.wallenc.ui.screens.shared
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.nullptroma.wallenc.ui.R import com.github.nullptroma.wallenc.ui.R
import com.github.nullptroma.wallenc.ui.elements.WallencScreenContentPadding
import com.github.nullptroma.wallenc.ui.elements.WallencScreenScaffold
@Composable @Composable
fun TextEditScreen(text: String) { fun TextEditScreen(
Text( text: String,
text = stringResource(R.string.text_edit_screen_placeholder, text), modifier: Modifier = Modifier,
style = MaterialTheme.typography.bodyLarge, ) {
modifier = Modifier.padding(16.dp), WallencScreenScaffold(modifier = modifier) { innerPadding ->
) WallencScreenContentPadding(innerPadding) {
Text(
text = stringResource(R.string.text_edit_screen_placeholder, text),
style = MaterialTheme.typography.bodyLarge,
)
}
}
} }

View File

@@ -1,6 +1,7 @@
package com.github.nullptroma.wallenc.ui.screens.sync package com.github.nullptroma.wallenc.ui.screens.sync
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.WindowInsets import androidx.compose.foundation.layout.WindowInsets
@@ -39,6 +40,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.foundation.layout.navigationBarsPadding
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
@@ -46,6 +48,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.github.nullptroma.wallenc.ui.R import com.github.nullptroma.wallenc.ui.R
import com.github.nullptroma.wallenc.ui.elements.AnimatedFloatingBackButton
import com.github.nullptroma.wallenc.ui.resources.UserNotification import com.github.nullptroma.wallenc.ui.resources.UserNotification
import com.github.nullptroma.wallenc.domain.interfaces.StorageSyncGroupEncryptionKind import com.github.nullptroma.wallenc.domain.interfaces.StorageSyncGroupEncryptionKind
import java.util.UUID import java.util.UUID
@@ -441,28 +444,21 @@ private fun StoragePickerScreen(
contentWindowInsets = WindowInsets(0.dp), contentWindowInsets = WindowInsets(0.dp),
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = { SnackbarHost(snackbarHostState) },
) { inner -> ) { inner ->
Column( Box(
modifier = Modifier modifier = Modifier
.padding(inner) .padding(inner)
.fillMaxSize() .fillMaxSize(),
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
Row( Column(
horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier
verticalAlignment = Alignment.CenterVertically, .fillMaxSize()
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
IconButton(onClick = onBack) {
Icon(
Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = stringResource(R.string.sync_cd_picker_back),
)
}
Text( Text(
text = stringResource(id = R.string.sync_picker_title, groupId), text = stringResource(id = R.string.sync_picker_title, groupId),
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleLarge,
) )
}
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@@ -539,6 +535,15 @@ private fun StoragePickerScreen(
} }
} }
} }
}
AnimatedFloatingBackButton(
visible = true,
onClick = onBack,
modifier = Modifier
.align(Alignment.BottomStart)
.navigationBarsPadding()
.padding(start = 12.dp, bottom = 12.dp),
)
} }
} }
} }

View File

@@ -7,6 +7,7 @@
<string name="nav_label_main">Главная</string> <string name="nav_label_main">Главная</string>
<string name="nav_label_sync">Синхронизация</string> <string name="nav_label_sync">Синхронизация</string>
<string name="nav_label_settings">Настройки</string> <string name="nav_label_settings">Настройки</string>
<string name="nav_cd_back">Назад</string>
<string name="main_work_status_label">Статус:</string> <string name="main_work_status_label">Статус:</string>
<string name="main_status_multiple_tasks">Выполняется задач: %1$d</string> <string name="main_status_multiple_tasks">Выполняется задач: %1$d</string>
<string name="main_status_vault_scanning_storages">Сканирование vault: загрузка списка хранилищ…</string> <string name="main_status_vault_scanning_storages">Сканирование vault: загрузка списка хранилищ…</string>

View File

@@ -7,6 +7,12 @@
<string name="nav_label_main">Home</string> <string name="nav_label_main">Home</string>
<string name="nav_label_sync">Sync</string> <string name="nav_label_sync">Sync</string>
<string name="nav_label_settings">Settings</string> <string name="nav_label_settings">Settings</string>
<string name="nav_cd_back">Go back</string>
<string name="screen_title_remote_vault">Remote vault</string>
<string name="screen_title_storage">Storage</string>
<string name="screen_title_two_fa">2FA tokens</string>
<string name="screen_title_text_secrets">Text secrets</string>
<string name="screen_title_text_edit">Text</string>
<string name="main_work_status_label">Status:</string> <string name="main_work_status_label">Status:</string>
<string name="main_status_multiple_tasks">Running tasks: %1$d</string> <string name="main_status_multiple_tasks">Running tasks: %1$d</string>
<string name="main_status_vault_scanning_storages">Scanning vault: loading storage list…</string> <string name="main_status_vault_scanning_storages">Scanning vault: loading storage list…</string>