Proper two-pane layout for Duplicates screen
This commit is contained in:
parent
96efcfad65
commit
42dab0318b
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
|
@ -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>,
|
|
@ -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
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue