Красивый UI
This commit is contained in:
@@ -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,43 +106,36 @@ fun WallencNavRoot(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Scaffold(bottomBar = {
|
|
||||||
NavigationBar(modifier = Modifier.wrapContentHeight()) {
|
|
||||||
val navBackStackEntry by navState.navHostController.currentBackStackEntryAsState()
|
val navBackStackEntry by navState.navHostController.currentBackStackEntryAsState()
|
||||||
val currentRoute = navBackStackEntry?.destination?.route
|
val currentRoute = navBackStackEntry?.destination?.route
|
||||||
topLevelNavBarItems.forEach {
|
|
||||||
val routeClassName = it.key
|
Scaffold(
|
||||||
val navBarItemData = it.value
|
bottomBar = {
|
||||||
NavigationBarItem(
|
Box(
|
||||||
modifier = Modifier.wrapContentHeight(),
|
modifier = Modifier
|
||||||
icon = {
|
.navigationBarsPadding()
|
||||||
if (navBarItemData.icon != null) Icon(
|
.padding(horizontal = 12.dp)
|
||||||
navBarItemData.icon,
|
.padding(top = 4.dp, bottom = 6.dp),
|
||||||
contentDescription = stringResource(navBarItemData.iconContentDescriptionResourceId),
|
) {
|
||||||
)
|
FloatingWallencNavigationBar(
|
||||||
|
items = topLevelNavBarItems,
|
||||||
|
routes = topLevelRoutes,
|
||||||
|
currentRoute = currentRoute,
|
||||||
|
onNavigate = { item ->
|
||||||
|
val route = topLevelRoutes[item.screenRouteClass]
|
||||||
|
?: error("Route ${item.screenRouteClass} not found")
|
||||||
|
if (currentRoute?.startsWith(item.screenRouteClass) != true) {
|
||||||
|
navState.changeTop(route)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
label = {
|
|
||||||
NavigationBarMarqueeText(
|
|
||||||
text = stringResource(navBarItemData.nameStringResourceId),
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
selected = currentRoute?.startsWith(routeClassName) == true,
|
) { innerPaddings ->
|
||||||
onClick = {
|
|
||||||
val route = topLevelRoutes[navBarItemData.screenRouteClass]
|
|
||||||
if (route == null)
|
|
||||||
throw NullPointerException("Route $route not found")
|
|
||||||
if (currentRoute?.startsWith(routeClassName) != true) navState.changeTop(
|
|
||||||
route
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) { 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(
|
||||||
@@ -152,13 +143,15 @@ fun WallencNavRoot(
|
|||||||
),
|
),
|
||||||
enterTransition = {
|
enterTransition = {
|
||||||
fadeIn(tween(200))
|
fadeIn(tween(200))
|
||||||
}, exitTransition = {
|
},
|
||||||
|
exitTransition = {
|
||||||
fadeOut(tween(200))
|
fadeOut(tween(200))
|
||||||
}) {
|
},
|
||||||
|
) {
|
||||||
MainScreen(
|
MainScreen(
|
||||||
modifier = Modifier.padding(innerPaddings),
|
modifier = Modifier,
|
||||||
navState = mainNavState,
|
navState = mainNavState,
|
||||||
viewModel = mainViewModel
|
viewModel = mainViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable<SettingsRoute>(
|
composable<SettingsRoute>(
|
||||||
@@ -167,10 +160,12 @@ fun WallencNavRoot(
|
|||||||
),
|
),
|
||||||
enterTransition = {
|
enterTransition = {
|
||||||
fadeIn(tween(200))
|
fadeIn(tween(200))
|
||||||
}, exitTransition = {
|
},
|
||||||
|
exitTransition = {
|
||||||
fadeOut(tween(200))
|
fadeOut(tween(200))
|
||||||
}) {
|
},
|
||||||
SettingsScreen(Modifier.padding(innerPaddings), settingsViewModel)
|
) {
|
||||||
|
SettingsScreen(Modifier, settingsViewModel)
|
||||||
}
|
}
|
||||||
composable<StorageSyncRoute>(
|
composable<StorageSyncRoute>(
|
||||||
deepLinks = listOf(
|
deepLinks = listOf(
|
||||||
@@ -178,11 +173,13 @@ fun WallencNavRoot(
|
|||||||
),
|
),
|
||||||
enterTransition = {
|
enterTransition = {
|
||||||
fadeIn(tween(200))
|
fadeIn(tween(200))
|
||||||
}, exitTransition = {
|
},
|
||||||
|
exitTransition = {
|
||||||
fadeOut(tween(200))
|
fadeOut(tween(200))
|
||||||
}) {
|
},
|
||||||
|
) {
|
||||||
StorageSyncScreen(
|
StorageSyncScreen(
|
||||||
modifier = Modifier.padding(innerPaddings),
|
modifier = Modifier,
|
||||||
viewModel = storageSyncViewModel,
|
viewModel = storageSyncViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -192,12 +189,12 @@ fun WallencNavRoot(
|
|||||||
),
|
),
|
||||||
enterTransition = {
|
enterTransition = {
|
||||||
fadeIn(tween(200))
|
fadeIn(tween(200))
|
||||||
}, exitTransition = {
|
},
|
||||||
|
exitTransition = {
|
||||||
fadeOut(tween(200))
|
fadeOut(tween(200))
|
||||||
}) {
|
},
|
||||||
TaskPipelineScreen(
|
) {
|
||||||
modifier = Modifier.padding(innerPaddings)
|
TaskPipelineScreen(modifier = Modifier)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -24,6 +24,10 @@ class NavigationState(
|
|||||||
restoreState = true
|
restoreState = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pop(): Boolean = navHostController.popBackStack()
|
||||||
|
|
||||||
|
fun canPop(): Boolean = navHostController.previousBackStackEntry != null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -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,49 +104,36 @@ 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),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
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)
|
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)) },
|
||||||
@@ -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),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -111,6 +106,7 @@ fun StorageHomeScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -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 ->
|
||||||
@@ -145,4 +140,5 @@ fun TextSecretDetailsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
@@ -156,4 +151,5 @@ fun TextSecretEditScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ->
|
||||||
@@ -64,4 +62,5 @@ fun TextSecretsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ->
|
||||||
@@ -246,6 +246,7 @@ fun TwoFaTokensScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (creating) {
|
if (creating) {
|
||||||
TwoFaTokenEditDialog(
|
TwoFaTokenEditDialog(
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -80,11 +80,7 @@ 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(
|
|
||||||
modifier = modifier,
|
|
||||||
contentWindowInsets = WindowInsets(0.dp),
|
|
||||||
floatingActionButton = {
|
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (fabEnabled && !fabBusy) {
|
if (fabEnabled && !fabBusy) {
|
||||||
@@ -104,8 +100,9 @@ fun VaultBrowserScreen(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
) { innerPadding ->
|
|
||||||
|
val vaultContent: @Composable (androidx.compose.foundation.layout.PaddingValues) -> Unit = { innerPadding ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
@@ -180,6 +177,13 @@ 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) {
|
||||||
Box(modifier = Modifier.fillMaxSize().alpha(0.6f).background(Color.Black))
|
Box(modifier = Modifier.fillMaxSize().alpha(0.6f).background(Color.Black))
|
||||||
|
|||||||
@@ -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: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
WallencScreenScaffold(modifier = modifier) { innerPadding ->
|
||||||
|
WallencScreenContentPadding(innerPadding) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.text_edit_screen_placeholder, text),
|
text = stringResource(R.string.text_edit_screen_placeholder, text),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
modifier = Modifier.padding(16.dp),
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(16.dp),
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
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(),
|
||||||
@@ -540,6 +536,15 @@ private fun StoragePickerScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AnimatedFloatingBackButton(
|
||||||
|
visible = true,
|
||||||
|
onClick = onBack,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomStart)
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.padding(start = 12.dp, bottom = 12.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user