feat: Show Watchtower in the account detail
This commit is contained in:
parent
40f603c4d2
commit
f7a36a5b1c
|
@ -15,6 +15,7 @@ import androidx.compose.material.icons.outlined.HideSource
|
|||
import androidx.compose.material.icons.outlined.Keyboard
|
||||
import androidx.compose.material.icons.outlined.Login
|
||||
import androidx.compose.material.icons.outlined.Logout
|
||||
import androidx.compose.material.icons.outlined.Security
|
||||
import androidx.compose.material.icons.outlined.Send
|
||||
import androidx.compose.material.icons.outlined.VerifiedUser
|
||||
import androidx.compose.material.icons.outlined.VisibilityOff
|
||||
|
@ -83,6 +84,7 @@ import com.artemchep.keyguard.feature.navigation.state.RememberStateFlowScope
|
|||
import com.artemchep.keyguard.feature.navigation.state.copy
|
||||
import com.artemchep.keyguard.feature.navigation.state.produceScreenState
|
||||
import com.artemchep.keyguard.feature.send.SendRoute
|
||||
import com.artemchep.keyguard.feature.watchtower.WatchtowerRoute
|
||||
import com.artemchep.keyguard.provider.bitwarden.ServerEnv
|
||||
import com.artemchep.keyguard.res.Res
|
||||
import com.artemchep.keyguard.ui.FlatItemAction
|
||||
|
@ -670,6 +672,33 @@ private fun buildItemsFlow(
|
|||
},
|
||||
)
|
||||
emit(ff3)
|
||||
val watchtowerSectionItem = VaultViewItem.Section(
|
||||
id = "watchtower.section",
|
||||
)
|
||||
emit(watchtowerSectionItem)
|
||||
val watchtowerItem = VaultViewItem.Action(
|
||||
id = "watchtower",
|
||||
title = scope.translate(Res.strings.watchtower_header_title),
|
||||
leading = {
|
||||
Icon(Icons.Outlined.Security, null)
|
||||
},
|
||||
trailing = {
|
||||
ChevronIcon()
|
||||
},
|
||||
onClick = {
|
||||
val route = WatchtowerRoute(
|
||||
args = WatchtowerRoute.Args(
|
||||
filter = DFilter.ById(
|
||||
id = accountId.id,
|
||||
what = DFilter.ById.What.ACCOUNT,
|
||||
)
|
||||
),
|
||||
)
|
||||
val intent = NavigationIntent.NavigateToRoute(route)
|
||||
scope.navigate(intent)
|
||||
},
|
||||
)
|
||||
emit(watchtowerItem)
|
||||
val ff4 = VaultViewItem.Section(
|
||||
id = "section",
|
||||
text = scope.translate(Res.strings.security),
|
||||
|
|
|
@ -139,6 +139,8 @@ private val vaultRoute = VaultRoute()
|
|||
|
||||
private val sendsRoute = SendRoute()
|
||||
|
||||
private val watchtowerRoute = WatchtowerRoute()
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
defaultRoute: Route = vaultRoute,
|
||||
|
@ -170,7 +172,7 @@ fun HomeScreen(
|
|||
label = TextHolder.Res(Res.strings.home_generator_label),
|
||||
),
|
||||
Rail(
|
||||
route = WatchtowerRoute,
|
||||
route = watchtowerRoute,
|
||||
icon = Icons.Outlined.Security,
|
||||
iconSelected = Icons.Filled.Security,
|
||||
label = TextHolder.Res(Res.strings.home_watchtower_label),
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
package com.artemchep.keyguard.feature.watchtower
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.artemchep.keyguard.common.model.DFilter
|
||||
import com.artemchep.keyguard.feature.navigation.Route
|
||||
|
||||
object WatchtowerRoute : Route {
|
||||
data class WatchtowerRoute(
|
||||
val args: Args = Args(),
|
||||
) : Route {
|
||||
data class Args(
|
||||
val filter: DFilter? = null,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
WatchtowerScreen()
|
||||
WatchtowerScreen(
|
||||
args = args,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,21 +20,16 @@ import androidx.compose.foundation.layout.Row
|
|||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.exclude
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.onConsumedWindowInsetsChanged
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.union
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
@ -46,7 +41,6 @@ import androidx.compose.material.icons.outlined.Delete
|
|||
import androidx.compose.material.icons.outlined.ErrorOutline
|
||||
import androidx.compose.material.icons.outlined.FolderOff
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.outlined.Key
|
||||
import androidx.compose.material.icons.outlined.Recycling
|
||||
import androidx.compose.material.icons.outlined.ShortText
|
||||
import androidx.compose.material.icons.outlined.Timer
|
||||
|
@ -58,7 +52,6 @@ import androidx.compose.material3.LocalContentColor
|
|||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
|
@ -69,7 +62,6 @@ import androidx.compose.runtime.State
|
|||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
|
@ -77,14 +69,11 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.graphics.isSpecified
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.semantics.Role
|
||||
|
@ -104,21 +93,15 @@ import com.artemchep.keyguard.feature.navigation.NavigationIcon
|
|||
import com.artemchep.keyguard.feature.search.filter.FilterButton
|
||||
import com.artemchep.keyguard.feature.search.filter.FilterScreen
|
||||
import com.artemchep.keyguard.feature.twopane.TwoPaneScreen
|
||||
import com.artemchep.keyguard.platform.leIme
|
||||
import com.artemchep.keyguard.platform.leNavigationBars
|
||||
import com.artemchep.keyguard.res.Res
|
||||
import com.artemchep.keyguard.ui.Ah
|
||||
import com.artemchep.keyguard.ui.DefaultEmphasisAlpha
|
||||
import com.artemchep.keyguard.ui.DisabledEmphasisAlpha
|
||||
import com.artemchep.keyguard.ui.ExpandedIfNotEmpty
|
||||
import com.artemchep.keyguard.ui.FlatItem
|
||||
import com.artemchep.keyguard.ui.GridLayout
|
||||
import com.artemchep.keyguard.ui.MediumEmphasisAlpha
|
||||
import com.artemchep.keyguard.ui.OptionsButton
|
||||
import com.artemchep.keyguard.ui.animatedNumberText
|
||||
import com.artemchep.keyguard.ui.grid.preferredGridWidth
|
||||
import com.artemchep.keyguard.ui.icons.ChevronIcon
|
||||
import com.artemchep.keyguard.ui.icons.IconSmallBox
|
||||
import com.artemchep.keyguard.ui.icons.KeyguardTwoFa
|
||||
import com.artemchep.keyguard.ui.icons.KeyguardWebsite
|
||||
import com.artemchep.keyguard.ui.poweredby.PoweredBy2factorauth
|
||||
|
@ -126,8 +109,6 @@ import com.artemchep.keyguard.ui.poweredby.PoweredByHaveibeenpwned
|
|||
import com.artemchep.keyguard.ui.poweredby.PoweredByPasskeys
|
||||
import com.artemchep.keyguard.ui.scaffoldContentWindowInsets
|
||||
import com.artemchep.keyguard.ui.shimmer.shimmer
|
||||
import com.artemchep.keyguard.ui.skeleton.SkeletonItemPilled
|
||||
import com.artemchep.keyguard.ui.skeleton.SkeletonSection
|
||||
import com.artemchep.keyguard.ui.skeleton.SkeletonText
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceColor
|
||||
import com.artemchep.keyguard.ui.theme.Dimens
|
||||
|
@ -144,10 +125,14 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun WatchtowerScreen() {
|
||||
fun WatchtowerScreen(
|
||||
args: WatchtowerRoute.Args,
|
||||
) {
|
||||
RequestAppReviewEffect()
|
||||
|
||||
val state = produceWatchtowerState()
|
||||
val state = produceWatchtowerState(
|
||||
args = args,
|
||||
)
|
||||
WatchtowerScreen(
|
||||
state = state,
|
||||
)
|
||||
|
|
|
@ -55,9 +55,11 @@ import org.kodein.di.instance
|
|||
|
||||
@Composable
|
||||
fun produceWatchtowerState(
|
||||
args: WatchtowerRoute.Args,
|
||||
) = with(localDI().direct) {
|
||||
produceWatchtowerState(
|
||||
directDI = this,
|
||||
args = args,
|
||||
getCiphers = instance(),
|
||||
getAccounts = instance(),
|
||||
getProfiles = instance(),
|
||||
|
@ -81,6 +83,7 @@ private class WatchtowerUiException(
|
|||
@Composable
|
||||
fun produceWatchtowerState(
|
||||
directDI: DirectDI,
|
||||
args: WatchtowerRoute.Args,
|
||||
getCiphers: GetCiphers,
|
||||
getAccounts: GetAccounts,
|
||||
getProfiles: GetProfiles,
|
||||
|
@ -105,8 +108,17 @@ fun produceWatchtowerState(
|
|||
val ciphersRawFlow = filterHiddenProfiles(
|
||||
getProfiles = getProfiles,
|
||||
getCiphers = getCiphers,
|
||||
filter = null,
|
||||
filter = args.filter,
|
||||
)
|
||||
.map { ciphers ->
|
||||
if (args.filter != null) {
|
||||
val predicate = args.filter.prepare(directDI, ciphers)
|
||||
ciphers
|
||||
.filter { predicate(it) }
|
||||
} else {
|
||||
ciphers
|
||||
}
|
||||
}
|
||||
val ciphersFlow = ciphersRawFlow
|
||||
.map { secrets ->
|
||||
secrets
|
||||
|
@ -117,6 +129,15 @@ fun produceWatchtowerState(
|
|||
.map { folders ->
|
||||
folders
|
||||
.filter { folder -> !folder.deleted }
|
||||
.let { list ->
|
||||
if (args.filter != null) {
|
||||
val predicate = args.filter.prepareFolders(directDI, list)
|
||||
list
|
||||
.filter { predicate(it) }
|
||||
} else {
|
||||
list
|
||||
}
|
||||
}
|
||||
}
|
||||
.shareIn(screenScope, SharingStarted.WhileSubscribed(), replay = 1)
|
||||
|
||||
|
@ -184,7 +205,7 @@ fun produceWatchtowerState(
|
|||
ciphersFlow = ciphersFlow,
|
||||
)
|
||||
val filteredTrashedCiphersFlow = filteredCiphers(
|
||||
ciphersFlow = getCiphers()
|
||||
ciphersFlow = ciphersRawFlow
|
||||
.map { secrets ->
|
||||
secrets
|
||||
.filter { secret -> secret.deleted }
|
||||
|
@ -301,9 +322,10 @@ fun produceWatchtowerState(
|
|||
title = translate(score.formatH2()),
|
||||
subtitle = translate(Res.strings.watchtower_header_title),
|
||||
filter = DFilter.And(
|
||||
filters = listOf(
|
||||
filters = listOfNotNull(
|
||||
DFilter.ByPasswordStrength(score),
|
||||
filter,
|
||||
args.filter,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -377,9 +399,10 @@ fun produceWatchtowerState(
|
|||
title = translate(Res.strings.watchtower_item_pwned_passwords_title),
|
||||
subtitle = translate(Res.strings.watchtower_header_title),
|
||||
filter = DFilter.And(
|
||||
filters = listOf(
|
||||
filters = listOfNotNull(
|
||||
DFilter.ByPasswordPwned,
|
||||
filter,
|
||||
args.filter,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -419,9 +442,10 @@ fun produceWatchtowerState(
|
|||
title = translate(Res.strings.watchtower_item_unsecure_websites_title),
|
||||
subtitle = translate(Res.strings.watchtower_header_title),
|
||||
filter = DFilter.And(
|
||||
filters = listOf(
|
||||
filters = listOfNotNull(
|
||||
DFilter.ByUnsecureWebsites,
|
||||
filter,
|
||||
args.filter,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -461,9 +485,10 @@ fun produceWatchtowerState(
|
|||
title = translate(Res.strings.watchtower_item_duplicate_websites_title),
|
||||
subtitle = translate(Res.strings.watchtower_header_title),
|
||||
filter = DFilter.And(
|
||||
filters = listOf(
|
||||
filters = listOfNotNull(
|
||||
DFilter.ByDuplicateWebsites,
|
||||
filter,
|
||||
args.filter,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -503,9 +528,10 @@ fun produceWatchtowerState(
|
|||
title = translate(Res.strings.watchtower_item_inactive_2fa_title),
|
||||
subtitle = translate(Res.strings.watchtower_header_title),
|
||||
filter = DFilter.And(
|
||||
filters = listOf(
|
||||
filters = listOfNotNull(
|
||||
DFilter.ByTfaWebsites,
|
||||
filter,
|
||||
args.filter,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -545,9 +571,10 @@ fun produceWatchtowerState(
|
|||
title = translate(Res.strings.watchtower_item_inactive_passkey_title),
|
||||
subtitle = translate(Res.strings.watchtower_header_title),
|
||||
filter = DFilter.And(
|
||||
filters = listOf(
|
||||
filters = listOfNotNull(
|
||||
DFilter.ByPasskeyWebsites,
|
||||
filter,
|
||||
args.filter,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -587,9 +614,10 @@ fun produceWatchtowerState(
|
|||
title = translate(Res.strings.watchtower_item_reused_passwords_title),
|
||||
subtitle = translate(Res.strings.watchtower_header_title),
|
||||
filter = DFilter.And(
|
||||
filters = listOf(
|
||||
filters = listOfNotNull(
|
||||
DFilter.ByPasswordDuplicates,
|
||||
filter,
|
||||
args.filter,
|
||||
),
|
||||
),
|
||||
sort = PasswordSort,
|
||||
|
@ -630,9 +658,10 @@ fun produceWatchtowerState(
|
|||
title = translate(Res.strings.watchtower_item_vulnerable_accounts_title),
|
||||
subtitle = translate(Res.strings.watchtower_header_title),
|
||||
filter = DFilter.And(
|
||||
filters = listOf(
|
||||
filters = listOfNotNull(
|
||||
DFilter.ByWebsitePwned,
|
||||
filter,
|
||||
args.filter,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -738,9 +767,10 @@ fun produceWatchtowerState(
|
|||
title = translate(Res.strings.watchtower_item_incomplete_items_title),
|
||||
subtitle = translate(Res.strings.watchtower_header_title),
|
||||
filter = DFilter.And(
|
||||
filters = listOf(
|
||||
filters = listOfNotNull(
|
||||
DFilter.ByIncomplete,
|
||||
filter,
|
||||
args.filter,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -780,9 +810,10 @@ fun produceWatchtowerState(
|
|||
title = translate(Res.strings.watchtower_item_expiring_items_title),
|
||||
subtitle = translate(Res.strings.watchtower_header_title),
|
||||
filter = DFilter.And(
|
||||
filters = listOf(
|
||||
filters = listOfNotNull(
|
||||
DFilter.ByExpiring,
|
||||
filter,
|
||||
args.filter,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -821,7 +852,12 @@ fun produceWatchtowerState(
|
|||
VaultRoute.watchtower(
|
||||
title = translate(Res.strings.watchtower_item_trashed_items_title),
|
||||
subtitle = translate(Res.strings.watchtower_header_title),
|
||||
filter = filter,
|
||||
filter = DFilter.And(
|
||||
filters = listOfNotNull(
|
||||
filter,
|
||||
args.filter,
|
||||
),
|
||||
),
|
||||
trash = true,
|
||||
),
|
||||
)
|
||||
|
@ -857,7 +893,12 @@ fun produceWatchtowerState(
|
|||
) {
|
||||
val route = FoldersRoute(
|
||||
args = FoldersRoute.Args(
|
||||
filter = filter,
|
||||
filter = DFilter.And(
|
||||
filters = listOfNotNull(
|
||||
filter,
|
||||
args.filter,
|
||||
),
|
||||
),
|
||||
empty = true,
|
||||
),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue