Proper two-pane layout for Duplicates screen

This commit is contained in:
Artem Chepurnoy 2024-01-27 22:31:10 +02:00
parent 96efcfad65
commit 42dab0318b
No known key found for this signature in database
GPG Key ID: FAC37D0CF674043E
9 changed files with 319 additions and 202 deletions

View File

@ -5,8 +5,12 @@ import com.artemchep.keyguard.common.model.DFilter
import com.artemchep.keyguard.feature.navigation.Route import com.artemchep.keyguard.feature.navigation.Route
data class DuplicatesRoute( data class DuplicatesRoute(
val args: Args = Args(), val args: Args,
) : Route { ) : Route {
companion object {
const val ROUTER_NAME = "duplicates"
}
data class Args( data class Args(
val filter: DFilter? = null, val filter: DFilter? = null,
) )

View File

@ -1,193 +1,24 @@
package com.artemchep.keyguard.feature.duplicates package com.artemchep.keyguard.feature.duplicates
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import com.artemchep.keyguard.feature.duplicates.list.DuplicatesListRoute
import androidx.compose.ui.Modifier import com.artemchep.keyguard.feature.navigation.NavigationRouter
import androidx.compose.ui.input.nestedscroll.nestedScroll import com.artemchep.keyguard.feature.twopane.TwoPaneNavigationContent
import androidx.compose.ui.text.style.TextOverflow
import com.artemchep.keyguard.common.model.fold
import com.artemchep.keyguard.common.model.getOrNull
import com.artemchep.keyguard.feature.EmptySearchView
import com.artemchep.keyguard.feature.home.vault.component.VaultListItem
import com.artemchep.keyguard.feature.navigation.NavigationIcon
import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.ui.DefaultSelection
import com.artemchep.keyguard.ui.DropdownMenuItemFlat
import com.artemchep.keyguard.ui.DropdownMinWidth
import com.artemchep.keyguard.ui.DropdownScopeImpl
import com.artemchep.keyguard.ui.ExpandedIfNotEmpty
import com.artemchep.keyguard.ui.MediumEmphasisAlpha
import com.artemchep.keyguard.ui.ScaffoldLazyColumn
import com.artemchep.keyguard.ui.icons.DropdownIcon
import com.artemchep.keyguard.ui.skeleton.SkeletonItem
import com.artemchep.keyguard.ui.theme.Dimens
import com.artemchep.keyguard.ui.theme.combineAlpha
import com.artemchep.keyguard.ui.toolbar.LargeToolbar
import dev.icerock.moko.resources.compose.stringResource
@OptIn(
ExperimentalMaterial3Api::class,
ExperimentalMaterialApi::class,
ExperimentalFoundationApi::class,
)
@Composable @Composable
fun DuplicatesScreen( fun DuplicatesScreen(
args: DuplicatesRoute.Args, args: DuplicatesRoute.Args,
) { ) {
val loadableState = produceDuplicatesState(args) val initialRoute = remember(args) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() DuplicatesListRoute(
ScaffoldLazyColumn( args = args,
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
topAppBarScrollBehavior = scrollBehavior,
topBar = {
LargeToolbar(
title = {
Column() {
Text(
text = stringResource(Res.strings.watchtower_header_title),
style = MaterialTheme.typography.labelSmall,
color = LocalContentColor.current
.combineAlpha(MediumEmphasisAlpha),
overflow = TextOverflow.Ellipsis,
maxLines = 2,
)
Text(
text = stringResource(Res.strings.watchtower_item_duplicate_items_title),
style = MaterialTheme.typography.titleMedium,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
) )
} }
}, NavigationRouter(
navigationIcon = { id = DuplicatesRoute.ROUTER_NAME,
NavigationIcon() initial = initialRoute,
}, ) { backStack ->
actions = { TwoPaneNavigationContent(backStack)
var isAutofillWindowShowing by remember {
mutableStateOf(false)
}
val normalContentColor = LocalContentColor.current
TextButton(
onClick = {
isAutofillWindowShowing = true
},
) {
Column {
Text(text = "Sensitivity")
val textOrNull =
loadableState.getOrNull()?.sensitivity?.name
?.lowercase()
?.capitalize()
ExpandedIfNotEmpty(
valueOrNull = textOrNull,
) { text ->
Text(
text = text,
style = MaterialTheme.typography.labelSmall,
color = normalContentColor
.combineAlpha(MediumEmphasisAlpha),
)
} }
} }
Spacer(
modifier = Modifier
.width(Dimens.buttonIconPadding),
)
DropdownIcon()
// Inject the dropdown popup to the bottom of the
// content.
val onDismissRequest = {
isAutofillWindowShowing = false
}
DropdownMenu(
modifier = Modifier
.widthIn(min = DropdownMinWidth),
expanded = isAutofillWindowShowing,
onDismissRequest = onDismissRequest,
) {
val scope = DropdownScopeImpl(this, onDismissRequest = onDismissRequest)
loadableState.getOrNull()?.sensitivities.orEmpty()
.forEachIndexed { index, action ->
scope.DropdownMenuItemFlat(
action = action,
)
}
}
}
},
scrollBehavior = scrollBehavior,
)
},
bottomBar = {
val screenState = loadableState.getOrNull()
?: return@ScaffoldLazyColumn
val selectionState = screenState.selectionStateFlow.collectAsState()
DefaultSelection(
state = selectionState.value,
)
},
) {
loadableState.fold(
ifLoading = {
for (i in 0..2) {
item(i) {
SkeletonItem()
}
}
},
ifOk = { state ->
val items = state.items
if (items.isEmpty()) {
item("header.empty") {
NoItemsPlaceholder()
}
}
items(
items = items,
key = { model -> model.id },
) { model ->
VaultListItem(
modifier = Modifier
.animateItemPlacement(),
item = model,
)
}
},
)
}
}
@Composable
private fun NoItemsPlaceholder(
modifier: Modifier = Modifier,
) {
EmptySearchView(
modifier = modifier,
text = {
Text(
text = stringResource(Res.strings.duplicates_empty_label),
)
},
)
}

View File

@ -0,0 +1,14 @@
package com.artemchep.keyguard.feature.duplicates.list
import androidx.compose.runtime.Composable
import com.artemchep.keyguard.feature.duplicates.DuplicatesRoute
import com.artemchep.keyguard.feature.navigation.Route
data class DuplicatesListRoute(
val args: DuplicatesRoute.Args,
) : Route {
@Composable
override fun Content() {
DuplicatesListScreen(args)
}
}

View File

@ -0,0 +1,234 @@
package com.artemchep.keyguard.feature.duplicates.list
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.text.style.TextOverflow
import com.artemchep.keyguard.common.model.fold
import com.artemchep.keyguard.common.model.getOrNull
import com.artemchep.keyguard.feature.EmptySearchView
import com.artemchep.keyguard.feature.duplicates.DuplicatesRoute
import com.artemchep.keyguard.feature.home.vault.component.VaultListItem
import com.artemchep.keyguard.feature.home.vault.screen.VaultViewRoute
import com.artemchep.keyguard.feature.navigation.LocalNavigationEntry
import com.artemchep.keyguard.feature.navigation.LocalNavigationRouter
import com.artemchep.keyguard.feature.navigation.NavigationIcon
import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.ui.Compose
import com.artemchep.keyguard.ui.DefaultSelection
import com.artemchep.keyguard.ui.DropdownMenuItemFlat
import com.artemchep.keyguard.ui.DropdownMinWidth
import com.artemchep.keyguard.ui.DropdownScopeImpl
import com.artemchep.keyguard.ui.ExpandedIfNotEmpty
import com.artemchep.keyguard.ui.MediumEmphasisAlpha
import com.artemchep.keyguard.ui.ScaffoldLazyColumn
import com.artemchep.keyguard.ui.icons.DropdownIcon
import com.artemchep.keyguard.ui.skeleton.SkeletonItem
import com.artemchep.keyguard.ui.theme.Dimens
import com.artemchep.keyguard.ui.theme.combineAlpha
import com.artemchep.keyguard.ui.toolbar.LargeToolbar
import dev.icerock.moko.resources.compose.stringResource
@OptIn(
ExperimentalMaterial3Api::class,
ExperimentalMaterialApi::class,
ExperimentalFoundationApi::class,
)
@Composable
fun DuplicatesListScreen(
args: DuplicatesRoute.Args,
) {
val loadableState = produceDuplicatesListState(args)
val onSelected = loadableState.getOrNull()?.onSelected
Compose {
val screenId = LocalNavigationEntry.current.id
val screenStack = LocalNavigationRouter.current.value
val childBackStackFlow = remember(
screenId,
screenStack,
) {
snapshotFlow {
val backStack = screenStack
.indexOfFirst { it.id == screenId }
// take the next screen
.inc()
// check if in range
.takeIf { it in 1 until screenStack.size }
?.let { index ->
screenStack.subList(
fromIndex = index,
toIndex = screenStack.size,
)
}
.orEmpty()
backStack
}
}
LaunchedEffect(onSelected, childBackStackFlow) {
childBackStackFlow.collect { backStack ->
val firstRoute = backStack.firstOrNull()?.route as? VaultViewRoute?
onSelected?.invoke(firstRoute?.itemId)
}
}
}
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
ScaffoldLazyColumn(
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
topAppBarScrollBehavior = scrollBehavior,
topBar = {
LargeToolbar(
title = {
Column() {
Text(
text = stringResource(Res.strings.watchtower_header_title),
style = MaterialTheme.typography.labelSmall,
color = LocalContentColor.current
.combineAlpha(MediumEmphasisAlpha),
overflow = TextOverflow.Ellipsis,
maxLines = 2,
)
Text(
text = stringResource(Res.strings.watchtower_item_duplicate_items_title),
style = MaterialTheme.typography.titleMedium,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
}
},
navigationIcon = {
NavigationIcon()
},
actions = {
var isAutofillWindowShowing by remember {
mutableStateOf(false)
}
val normalContentColor = LocalContentColor.current
TextButton(
onClick = {
isAutofillWindowShowing = true
},
) {
Column {
Text(text = "Sensitivity")
val textOrNull =
loadableState.getOrNull()?.sensitivity?.name
?.lowercase()
?.capitalize()
ExpandedIfNotEmpty(
valueOrNull = textOrNull,
) { text ->
Text(
text = text,
style = MaterialTheme.typography.labelSmall,
color = normalContentColor
.combineAlpha(MediumEmphasisAlpha),
)
}
}
Spacer(
modifier = Modifier
.width(Dimens.buttonIconPadding),
)
DropdownIcon()
// Inject the dropdown popup to the bottom of the
// content.
val onDismissRequest = {
isAutofillWindowShowing = false
}
DropdownMenu(
modifier = Modifier
.widthIn(min = DropdownMinWidth),
expanded = isAutofillWindowShowing,
onDismissRequest = onDismissRequest,
) {
val scope = DropdownScopeImpl(this, onDismissRequest = onDismissRequest)
loadableState.getOrNull()?.sensitivities.orEmpty()
.forEachIndexed { index, action ->
scope.DropdownMenuItemFlat(
action = action,
)
}
}
}
},
scrollBehavior = scrollBehavior,
)
},
bottomBar = {
val screenState = loadableState.getOrNull()
?: return@ScaffoldLazyColumn
val selectionState = screenState.selectionStateFlow.collectAsState()
DefaultSelection(
state = selectionState.value,
)
},
) {
loadableState.fold(
ifLoading = {
for (i in 0..2) {
item(i) {
SkeletonItem()
}
}
},
ifOk = { state ->
val items = state.items
if (items.isEmpty()) {
item("header.empty") {
NoItemsPlaceholder()
}
}
items(
items = items,
key = { model -> model.id },
) { model ->
VaultListItem(
modifier = Modifier
.animateItemPlacement(),
item = model,
)
}
},
)
}
}
@Composable
private fun NoItemsPlaceholder(
modifier: Modifier = Modifier,
) {
EmptySearchView(
modifier = modifier,
text = {
Text(
text = stringResource(Res.strings.duplicates_empty_label),
)
},
)
}

View File

@ -1,4 +1,4 @@
package com.artemchep.keyguard.feature.duplicates package com.artemchep.keyguard.feature.duplicates.list
import com.artemchep.keyguard.common.usecase.CipherDuplicatesCheck import com.artemchep.keyguard.common.usecase.CipherDuplicatesCheck
import com.artemchep.keyguard.feature.home.vault.model.VaultItem2 import com.artemchep.keyguard.feature.home.vault.model.VaultItem2
@ -6,7 +6,8 @@ import com.artemchep.keyguard.ui.FlatItemAction
import com.artemchep.keyguard.ui.Selection import com.artemchep.keyguard.ui.Selection
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
data class DuplicatesState( data class DuplicatesListState(
val onSelected: (String?) -> Unit,
val items: List<VaultItem2>, val items: List<VaultItem2>,
val sensitivity: CipherDuplicatesCheck.Sensitivity, val sensitivity: CipherDuplicatesCheck.Sensitivity,
val sensitivities: List<FlatItemAction>, val sensitivities: List<FlatItemAction>,

View File

@ -1,6 +1,7 @@
package com.artemchep.keyguard.feature.duplicates package com.artemchep.keyguard.feature.duplicates.list
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.Merge import androidx.compose.material.icons.outlined.Merge
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import arrow.core.partially1 import arrow.core.partially1
@ -29,8 +30,12 @@ import com.artemchep.keyguard.common.util.flow.persistingStateIn
import com.artemchep.keyguard.feature.attachments.SelectableItemState import com.artemchep.keyguard.feature.attachments.SelectableItemState
import com.artemchep.keyguard.feature.attachments.SelectableItemStateRaw import com.artemchep.keyguard.feature.attachments.SelectableItemStateRaw
import com.artemchep.keyguard.feature.confirmation.elevatedaccess.createElevatedAccessDialogIntent import com.artemchep.keyguard.feature.confirmation.elevatedaccess.createElevatedAccessDialogIntent
import com.artemchep.keyguard.feature.duplicates.DuplicatesRoute
import com.artemchep.keyguard.feature.generator.history.mapLatestScoped import com.artemchep.keyguard.feature.generator.history.mapLatestScoped
import com.artemchep.keyguard.feature.generator.wordlist.WordlistsRoute
import com.artemchep.keyguard.feature.home.vault.component.VaultListItem
import com.artemchep.keyguard.feature.home.vault.model.VaultItem2 import com.artemchep.keyguard.feature.home.vault.model.VaultItem2
import com.artemchep.keyguard.feature.home.vault.model.VaultItemIcon
import com.artemchep.keyguard.feature.home.vault.screen.VaultViewRoute import com.artemchep.keyguard.feature.home.vault.screen.VaultViewRoute
import com.artemchep.keyguard.feature.home.vault.screen.toVaultListItem import com.artemchep.keyguard.feature.home.vault.screen.toVaultListItem
import com.artemchep.keyguard.feature.home.vault.screen.verify import com.artemchep.keyguard.feature.home.vault.screen.verify
@ -84,10 +89,10 @@ private data class SelectionData(
) )
@Composable @Composable
fun produceDuplicatesState( fun produceDuplicatesListState(
args: DuplicatesRoute.Args, args: DuplicatesRoute.Args,
) = with(localDI().direct) { ) = with(localDI().direct) {
produceDuplicatesState( produceDuplicatesListState(
directDI = this, directDI = this,
args = args, args = args,
clipboardService = instance(), clipboardService = instance(),
@ -105,7 +110,7 @@ fun produceDuplicatesState(
} }
@Composable @Composable
fun produceDuplicatesState( fun produceDuplicatesListState(
directDI: DirectDI, directDI: DirectDI,
args: DuplicatesRoute.Args, args: DuplicatesRoute.Args,
clipboardService: ClipboardService, clipboardService: ClipboardService,
@ -119,8 +124,8 @@ fun produceDuplicatesState(
getCanWrite: GetCanWrite, getCanWrite: GetCanWrite,
cipherToolbox: CipherToolbox, cipherToolbox: CipherToolbox,
cipherDuplicatesCheck: CipherDuplicatesCheck, cipherDuplicatesCheck: CipherDuplicatesCheck,
): Loadable<DuplicatesState> = produceScreenState( ): Loadable<DuplicatesListState> = produceScreenState(
key = "duplicates", key = "duplicates_list",
initial = Loadable.Loading, initial = Loadable.Loading,
args = arrayOf( args = arrayOf(
getOrganizations, getOrganizations,
@ -132,6 +137,7 @@ fun produceDuplicatesState(
val sensitivitySink = mutablePersistedFlow("sensitivity") { val sensitivitySink = mutablePersistedFlow("sensitivity") {
CipherDuplicatesCheck.Sensitivity.NORMAL CipherDuplicatesCheck.Sensitivity.NORMAL
} }
val itemSink = mutablePersistedFlow("item") { "" }
val selectionHandle = selectionHandle("selection") val selectionHandle = selectionHandle("selection")
val selectionGroupSink = mutablePersistedFlow("selection_group_id") { val selectionGroupSink = mutablePersistedFlow("selection_group_id") {
"" ""
@ -149,10 +155,14 @@ fun produceDuplicatesState(
fun onClickCipher( fun onClickCipher(
cipher: DSecret, cipher: DSecret,
) { ) {
val intent = NavigationIntent.NavigateToRoute( val route = VaultViewRoute(
VaultViewRoute(
itemId = cipher.id, itemId = cipher.id,
accountId = cipher.accountId, accountId = cipher.accountId,
)
val intent = NavigationIntent.Composite(
listOf(
NavigationIntent.PopById(DuplicatesRoute.ROUTER_NAME),
NavigationIntent.NavigateToRoute(route),
), ),
) )
navigate(intent) navigate(intent)
@ -257,7 +267,7 @@ fun produceDuplicatesState(
onLongClick = onLongClick, onLongClick = onLongClick,
) )
} }
val openedStateFlow = flowOf("") val openedStateFlow = itemSink
.map { .map {
val isOpened = it == cipher.id val isOpened = it == cipher.id
VaultItem2.Item.OpenedState(isOpened) VaultItem2.Item.OpenedState(isOpened)
@ -304,7 +314,7 @@ fun produceDuplicatesState(
allItems += VaultItem2.Button( allItems += VaultItem2.Button(
id = "merge." + group.id, id = "merge." + group.id,
title = translate(Res.strings.ciphers_action_merge_title), title = translate(Res.strings.ciphers_action_merge_title),
leading = icon(Icons.Outlined.Merge), leading = icon(Icons.Outlined.Merge, Icons.Outlined.Add),
onClick = { onClick = {
val ciphers = groupedItems val ciphers = groupedItems
.map { it.source } .map { it.source }
@ -329,7 +339,10 @@ fun produceDuplicatesState(
itemsFlow itemsFlow
.combine(sensitivitySink) { items, sensitivity -> .combine(sensitivitySink) { items, sensitivity ->
val state = DuplicatesState( val state = DuplicatesListState(
onSelected = { key ->
itemSink.value = key.orEmpty()
},
items = items, items = items,
sensitivity = sensitivity, sensitivity = sensitivity,
sensitivities = CipherDuplicatesCheck.Sensitivity sensitivities = CipherDuplicatesCheck.Sensitivity

View File

@ -76,6 +76,7 @@ import com.artemchep.keyguard.ui.DisabledEmphasisAlpha
import com.artemchep.keyguard.ui.DropdownMenuItemFlat import com.artemchep.keyguard.ui.DropdownMenuItemFlat
import com.artemchep.keyguard.ui.DropdownMinWidth import com.artemchep.keyguard.ui.DropdownMinWidth
import com.artemchep.keyguard.ui.DropdownScopeImpl import com.artemchep.keyguard.ui.DropdownScopeImpl
import com.artemchep.keyguard.ui.FlatItem
import com.artemchep.keyguard.ui.FlatItemTextContent import com.artemchep.keyguard.ui.FlatItemTextContent
import com.artemchep.keyguard.ui.MediumEmphasisAlpha import com.artemchep.keyguard.ui.MediumEmphasisAlpha
import com.artemchep.keyguard.ui.icons.ChevronIcon import com.artemchep.keyguard.ui.icons.ChevronIcon
@ -149,12 +150,31 @@ fun VaultListItemButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
item: VaultItem2.Button, item: VaultItem2.Button,
) { ) {
VaultViewButtonItem( val contentColor = MaterialTheme.colorScheme.primary
FlatItem(
modifier = modifier, modifier = modifier,
leading = { leading = {
item.leading?.invoke() CompositionLocalProvider(
LocalContentColor provides contentColor,
) {
val leading = item.leading
if (leading != null) {
Row(
modifier = Modifier
.widthIn(min = 36.dp),
horizontalArrangement = Arrangement.Center,
) {
leading.invoke()
}
}
}
}, },
title = {
Text(
text = item.title, text = item.title,
color = contentColor,
)
},
onClick = item.onClick, onClick = item.onClick,
) )
} }

View File

@ -74,7 +74,7 @@ import com.artemchep.keyguard.feature.decorator.ItemDecorator
import com.artemchep.keyguard.feature.decorator.ItemDecoratorDate import com.artemchep.keyguard.feature.decorator.ItemDecoratorDate
import com.artemchep.keyguard.feature.decorator.ItemDecoratorNone import com.artemchep.keyguard.feature.decorator.ItemDecoratorNone
import com.artemchep.keyguard.feature.decorator.ItemDecoratorTitle import com.artemchep.keyguard.feature.decorator.ItemDecoratorTitle
import com.artemchep.keyguard.feature.duplicates.createCipherSelectionFlow import com.artemchep.keyguard.feature.duplicates.list.createCipherSelectionFlow
import com.artemchep.keyguard.feature.generator.history.mapLatestScoped import com.artemchep.keyguard.feature.generator.history.mapLatestScoped
import com.artemchep.keyguard.feature.home.vault.VaultRoute import com.artemchep.keyguard.feature.home.vault.VaultRoute
import com.artemchep.keyguard.feature.home.vault.add.AddRoute import com.artemchep.keyguard.feature.home.vault.add.AddRoute
@ -134,7 +134,6 @@ import org.kodein.di.DirectDI
import org.kodein.di.compose.localDI import org.kodein.di.compose.localDI
import org.kodein.di.direct import org.kodein.di.direct
import org.kodein.di.instance import org.kodein.di.instance
import kotlin.time.ExperimentalTime
import kotlin.time.measureTimedValue import kotlin.time.measureTimedValue
@LeParcelize @LeParcelize

View File

@ -19,6 +19,7 @@ import com.artemchep.keyguard.common.usecase.GetProfiles
import com.artemchep.keyguard.common.util.flow.persistingStateIn import com.artemchep.keyguard.common.util.flow.persistingStateIn
import com.artemchep.keyguard.feature.crashlytics.crashlyticsMap import com.artemchep.keyguard.feature.crashlytics.crashlyticsMap
import com.artemchep.keyguard.feature.duplicates.DuplicatesRoute import com.artemchep.keyguard.feature.duplicates.DuplicatesRoute
import com.artemchep.keyguard.feature.duplicates.list.DuplicatesListRoute
import com.artemchep.keyguard.feature.home.vault.VaultRoute import com.artemchep.keyguard.feature.home.vault.VaultRoute
import com.artemchep.keyguard.feature.home.vault.folders.FoldersRoute import com.artemchep.keyguard.feature.home.vault.folders.FoldersRoute
import com.artemchep.keyguard.feature.home.vault.screen.FilterParams import com.artemchep.keyguard.feature.home.vault.screen.FilterParams