feat: Show Watchtower in the account detail

This commit is contained in:
Artem Chepurnoy 2024-02-17 12:08:11 +02:00
parent 40f603c4d2
commit f7a36a5b1c
No known key found for this signature in database
GPG Key ID: FAC37D0CF674043E
5 changed files with 104 additions and 38 deletions

View File

@ -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),

View File

@ -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),

View File

@ -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,
)
}
}

View File

@ -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,
)

View File

@ -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,
),
)