mirror of
https://github.com/bitwarden/android.git
synced 2025-12-11 04:39:19 -06:00
PM-19591: Initial flight recorder UI (#4970)
This commit is contained in:
parent
1e6f896328
commit
1fecd4af5f
@ -0,0 +1,139 @@
|
|||||||
|
package com.x8bit.bitwarden.ui.platform.components.row
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.mirrorIfRtl
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.badge.NotificationBadge
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.icon.BitwardenIcon
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||||
|
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reusable row with push icon built in.
|
||||||
|
*
|
||||||
|
* @param text The displayable text.
|
||||||
|
* @param onClick The callback when the row is clicked.
|
||||||
|
* @param cardStyle The [CardStyle] to be applied to this row.
|
||||||
|
* @param modifier The modifier for this composable.
|
||||||
|
* @param leadingIcon An optional leading icon.
|
||||||
|
* @param notificationCount The optional notification count to be displayed.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun BitwardenPushRow(
|
||||||
|
text: String,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
cardStyle: CardStyle,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
leadingIcon: IconData? = null,
|
||||||
|
notificationCount: Int = 0,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.defaultMinSize(minHeight = 60.dp)
|
||||||
|
.cardStyle(
|
||||||
|
cardStyle = cardStyle,
|
||||||
|
onClick = onClick,
|
||||||
|
paddingStart = leadingIcon?.let { 12.dp } ?: 16.dp,
|
||||||
|
paddingEnd = 20.dp,
|
||||||
|
paddingTop = 6.dp,
|
||||||
|
paddingBottom = 6.dp,
|
||||||
|
),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.defaultMinSize(minHeight = 48.dp)
|
||||||
|
.weight(weight = 1f),
|
||||||
|
) {
|
||||||
|
leadingIcon?.let {
|
||||||
|
BitwardenIcon(
|
||||||
|
iconData = it,
|
||||||
|
tint = BitwardenTheme.colorScheme.icon.primary,
|
||||||
|
modifier = Modifier.size(size = 24.dp),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(width = 12.dp))
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = BitwardenTheme.typography.bodyLarge,
|
||||||
|
color = BitwardenTheme.colorScheme.text.primary,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
TrailingContent(notificationCount = notificationCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TrailingContent(
|
||||||
|
notificationCount: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = modifier.defaultMinSize(minHeight = 48.dp),
|
||||||
|
) {
|
||||||
|
val notificationBadgeVisible = notificationCount > 0
|
||||||
|
NotificationBadge(
|
||||||
|
notificationCount = notificationCount,
|
||||||
|
isVisible = notificationBadgeVisible,
|
||||||
|
)
|
||||||
|
if (notificationBadgeVisible) {
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
painter = rememberVectorPainter(id = R.drawable.ic_chevron_right),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = BitwardenTheme.colorScheme.icon.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.mirrorIfRtl()
|
||||||
|
.size(size = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun BitwardenPushRow_preview() {
|
||||||
|
BitwardenTheme {
|
||||||
|
Column {
|
||||||
|
BitwardenPushRow(
|
||||||
|
text = "Plain Row",
|
||||||
|
onClick = { },
|
||||||
|
cardStyle = CardStyle.Top(),
|
||||||
|
)
|
||||||
|
BitwardenPushRow(
|
||||||
|
text = "Icon Row",
|
||||||
|
onClick = { },
|
||||||
|
cardStyle = CardStyle.Middle(),
|
||||||
|
leadingIcon = IconData.Local(iconRes = R.drawable.ic_vault),
|
||||||
|
)
|
||||||
|
BitwardenPushRow(
|
||||||
|
text = "Notification Row",
|
||||||
|
onClick = { },
|
||||||
|
cardStyle = CardStyle.Bottom,
|
||||||
|
notificationCount = 3,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -254,7 +254,7 @@ fun BitwardenSwitch(
|
|||||||
cardStyle = cardStyle,
|
cardStyle = cardStyle,
|
||||||
onClick = onCheckedChange?.let { { it(!isChecked) } },
|
onClick = onCheckedChange?.let { { it(!isChecked) } },
|
||||||
clickEnabled = !readOnly && enabled,
|
clickEnabled = !readOnly && enabled,
|
||||||
paddingTop = 12.dp,
|
paddingTop = 6.dp,
|
||||||
paddingBottom = 0.dp,
|
paddingBottom = 0.dp,
|
||||||
)
|
)
|
||||||
.semantics(mergeDescendants = true) {
|
.semantics(mergeDescendants = true) {
|
||||||
@ -264,7 +264,7 @@ fun BitwardenSwitch(
|
|||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.defaultMinSize(minHeight = 36.dp),
|
modifier = Modifier.defaultMinSize(minHeight = 48.dp),
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.width(width = 16.dp))
|
Spacer(modifier = Modifier.width(width = 16.dp))
|
||||||
Row(
|
Row(
|
||||||
@ -329,7 +329,7 @@ fun BitwardenSwitch(
|
|||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
?: Spacer(modifier = Modifier.height(height = cardStyle?.let { 12.dp } ?: 0.dp))
|
?: Spacer(modifier = Modifier.height(height = cardStyle?.let { 6.dp } ?: 0.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,8 @@ fun NavGraphBuilder.settingsGraph(
|
|||||||
onNavigateToPendingRequests: () -> Unit,
|
onNavigateToPendingRequests: () -> Unit,
|
||||||
onNavigateToSetupUnlockScreen: () -> Unit,
|
onNavigateToSetupUnlockScreen: () -> Unit,
|
||||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||||
|
onNavigateToFlightRecorder: () -> Unit,
|
||||||
|
onNavigateToRecordedLogs: () -> Unit,
|
||||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
) {
|
) {
|
||||||
navigation(
|
navigation(
|
||||||
@ -54,7 +56,11 @@ fun NavGraphBuilder.settingsGraph(
|
|||||||
onNavigateToVault = { navController.navigateToVaultSettings() },
|
onNavigateToVault = { navController.navigateToVaultSettings() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
aboutDestination(onNavigateBack = { navController.popBackStack() })
|
aboutDestination(
|
||||||
|
onNavigateBack = { navController.popBackStack() },
|
||||||
|
onNavigateToFlightRecorder = onNavigateToFlightRecorder,
|
||||||
|
onNavigateToRecordedLogs = onNavigateToRecordedLogs,
|
||||||
|
)
|
||||||
accountSecurityDestination(
|
accountSecurityDestination(
|
||||||
onNavigateBack = { navController.popBackStack() },
|
onNavigateBack = { navController.popBackStack() },
|
||||||
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
|
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
|
||||||
|
|||||||
@ -1,51 +1,34 @@
|
|||||||
package com.x8bit.bitwarden.ui.platform.feature.settings
|
package com.x8bit.bitwarden.ui.platform.feature.settings
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
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.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
|
||||||
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.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
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.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
|
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.mirrorIfRtl
|
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.toListItemCardStyle
|
import com.x8bit.bitwarden.ui.platform.base.util.toListItemCardStyle
|
||||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenMediumTopAppBar
|
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenMediumTopAppBar
|
||||||
import com.x8bit.bitwarden.ui.platform.components.badge.NotificationBadge
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenPushRow
|
||||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
|
||||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the settings screen.
|
* Displays the settings screen.
|
||||||
@ -91,8 +74,8 @@ fun SettingsScreen(
|
|||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(height = 12.dp))
|
Spacer(modifier = Modifier.height(height = 12.dp))
|
||||||
Settings.entries.forEachIndexed { index, settingEntry ->
|
Settings.entries.forEachIndexed { index, settingEntry ->
|
||||||
SettingsRow(
|
BitwardenPushRow(
|
||||||
text = settingEntry.text,
|
text = settingEntry.text(),
|
||||||
onClick = remember(viewModel) {
|
onClick = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(SettingsAction.SettingsClick(settingEntry)) }
|
{ viewModel.trySendAction(SettingsAction.SettingsClick(settingEntry)) }
|
||||||
},
|
},
|
||||||
@ -105,7 +88,7 @@ fun SettingsScreen(
|
|||||||
// Start padding, plus icon, plus spacing between text.
|
// Start padding, plus icon, plus spacing between text.
|
||||||
dividerPadding = 48.dp,
|
dividerPadding = 48.dp,
|
||||||
),
|
),
|
||||||
iconVectorResource = settingEntry.vectorIconRes,
|
leadingIcon = IconData.Local(iconRes = settingEntry.vectorIconRes),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.testTag(tag = settingEntry.testTag)
|
.testTag(tag = settingEntry.testTag)
|
||||||
.standardHorizontalMargin()
|
.standardHorizontalMargin()
|
||||||
@ -117,102 +100,3 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SettingsRow(
|
|
||||||
text: Text,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
notificationCount: Int,
|
|
||||||
cardStyle: CardStyle?,
|
|
||||||
@DrawableRes iconVectorResource: Int,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.defaultMinSize(minHeight = 60.dp)
|
|
||||||
.cardStyle(
|
|
||||||
cardStyle = cardStyle,
|
|
||||||
onClick = onClick,
|
|
||||||
paddingStart = 12.dp,
|
|
||||||
paddingEnd = 12.dp,
|
|
||||||
),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = rememberVectorPainter(iconVectorResource),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = BitwardenTheme.colorScheme.icon.primary,
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
)
|
|
||||||
Spacer(Modifier.width(12.dp))
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 16.dp),
|
|
||||||
text = text(),
|
|
||||||
style = BitwardenTheme.typography.bodyLarge,
|
|
||||||
color = BitwardenTheme.colorScheme.text.primary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
TrailingContent(notificationCount = notificationCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun TrailingContent(
|
|
||||||
notificationCount: Int,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = modifier,
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
val notificationBadgeVisible = notificationCount > 0
|
|
||||||
NotificationBadge(
|
|
||||||
notificationCount = notificationCount,
|
|
||||||
isVisible = notificationBadgeVisible,
|
|
||||||
)
|
|
||||||
if (notificationBadgeVisible) {
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
}
|
|
||||||
Icon(
|
|
||||||
painter = rememberVectorPainter(id = R.drawable.ic_chevron_right),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = BitwardenTheme.colorScheme.icon.primary,
|
|
||||||
modifier = Modifier
|
|
||||||
.mirrorIfRtl()
|
|
||||||
.size(size = 16.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Preview(name = "Right-To-Left", locale = "ar")
|
|
||||||
@Composable
|
|
||||||
private fun SettingsRows_preview() {
|
|
||||||
BitwardenTheme {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(BitwardenTheme.colorScheme.background.primary)
|
|
||||||
.padding(16.dp)
|
|
||||||
.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
Settings.entries.forEachIndexed { index, it ->
|
|
||||||
SettingsRow(
|
|
||||||
text = it.text,
|
|
||||||
onClick = { },
|
|
||||||
notificationCount = index % 3,
|
|
||||||
iconVectorResource = it.vectorIconRes,
|
|
||||||
cardStyle = Settings.entries.toListItemCardStyle(
|
|
||||||
index = index,
|
|
||||||
dividerPadding = 48.dp,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -12,11 +12,17 @@ private const val ABOUT_ROUTE = "settings_about"
|
|||||||
*/
|
*/
|
||||||
fun NavGraphBuilder.aboutDestination(
|
fun NavGraphBuilder.aboutDestination(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
|
onNavigateToFlightRecorder: () -> Unit,
|
||||||
|
onNavigateToRecordedLogs: () -> Unit,
|
||||||
) {
|
) {
|
||||||
composableWithPushTransitions(
|
composableWithPushTransitions(
|
||||||
route = ABOUT_ROUTE,
|
route = ABOUT_ROUTE,
|
||||||
) {
|
) {
|
||||||
AboutScreen(onNavigateBack = onNavigateBack)
|
AboutScreen(
|
||||||
|
onNavigateBack = onNavigateBack,
|
||||||
|
onNavigateToFlightRecorder = onNavigateToFlightRecorder,
|
||||||
|
onNavigateToRecordedLogs = onNavigateToRecordedLogs,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.about
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@ -37,7 +38,9 @@ import com.x8bit.bitwarden.ui.platform.base.util.mirrorIfRtl
|
|||||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.TooltipData
|
||||||
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenExternalLinkRow
|
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenExternalLinkRow
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenPushRow
|
||||||
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenTextRow
|
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenTextRow
|
||||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||||
@ -54,17 +57,23 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
|||||||
@Composable
|
@Composable
|
||||||
fun AboutScreen(
|
fun AboutScreen(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
|
onNavigateToFlightRecorder: () -> Unit,
|
||||||
|
onNavigateToRecordedLogs: () -> Unit,
|
||||||
viewModel: AboutViewModel = hiltViewModel(),
|
viewModel: AboutViewModel = hiltViewModel(),
|
||||||
intentManager: IntentManager = LocalIntentManager.current,
|
intentManager: IntentManager = LocalIntentManager.current,
|
||||||
) {
|
) {
|
||||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||||
EventsEffect(viewModel = viewModel) { event ->
|
EventsEffect(viewModel = viewModel) { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
is AboutEvent.NavigateToWebVault -> {
|
is AboutEvent.NavigateToWebVault -> intentManager.launchUri(event.vaultUrl.toUri())
|
||||||
intentManager.launchUri(event.vaultUrl.toUri())
|
|
||||||
}
|
|
||||||
|
|
||||||
AboutEvent.NavigateBack -> onNavigateBack.invoke()
|
AboutEvent.NavigateBack -> onNavigateBack.invoke()
|
||||||
|
AboutEvent.NavigateToFlightRecorder -> onNavigateToFlightRecorder()
|
||||||
|
AboutEvent.NavigateToRecordedLogs -> onNavigateToRecordedLogs()
|
||||||
|
|
||||||
|
AboutEvent.NavigateToFlightRecorderHelp -> {
|
||||||
|
// TODO: PM-19809 Update this URL to be specific to the flight recorder
|
||||||
|
intentManager.launchUri("https://bitwarden.com/help".toUri())
|
||||||
|
}
|
||||||
|
|
||||||
AboutEvent.NavigateToHelpCenter -> {
|
AboutEvent.NavigateToHelpCenter -> {
|
||||||
intentManager.launchUri("https://bitwarden.com/help".toUri())
|
intentManager.launchUri("https://bitwarden.com/help".toUri())
|
||||||
@ -97,7 +106,7 @@ fun AboutScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
ContentColumn(
|
AboutScreenContent(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
onHelpCenterClick = remember(viewModel) {
|
onHelpCenterClick = remember(viewModel) {
|
||||||
@ -112,6 +121,15 @@ fun AboutScreen(
|
|||||||
onSubmitCrashLogsCheckedChange = remember(viewModel) {
|
onSubmitCrashLogsCheckedChange = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(AboutAction.SubmitCrashLogsClick(it)) }
|
{ viewModel.trySendAction(AboutAction.SubmitCrashLogsClick(it)) }
|
||||||
},
|
},
|
||||||
|
onFlightRecorderCheckedChange = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(AboutAction.FlightRecorderCheckedChange(it)) }
|
||||||
|
},
|
||||||
|
onFlightRecorderTooltipClick = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(AboutAction.FlightRecorderTooltipClick) }
|
||||||
|
},
|
||||||
|
onViewRecordedLogsClick = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(AboutAction.ViewRecordedLogsClick) }
|
||||||
|
},
|
||||||
onVersionClick = remember(viewModel) {
|
onVersionClick = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(AboutAction.VersionClick) }
|
{ viewModel.trySendAction(AboutAction.VersionClick) }
|
||||||
},
|
},
|
||||||
@ -124,12 +142,15 @@ fun AboutScreen(
|
|||||||
|
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentColumn(
|
private fun AboutScreenContent(
|
||||||
state: AboutState,
|
state: AboutState,
|
||||||
onHelpCenterClick: () -> Unit,
|
onHelpCenterClick: () -> Unit,
|
||||||
onPrivacyPolicyClick: () -> Unit,
|
onPrivacyPolicyClick: () -> Unit,
|
||||||
onLearnAboutOrgsClick: () -> Unit,
|
onLearnAboutOrgsClick: () -> Unit,
|
||||||
onSubmitCrashLogsCheckedChange: (Boolean) -> Unit,
|
onSubmitCrashLogsCheckedChange: (Boolean) -> Unit,
|
||||||
|
onFlightRecorderCheckedChange: (Boolean) -> Unit,
|
||||||
|
onFlightRecorderTooltipClick: () -> Unit,
|
||||||
|
onViewRecordedLogsClick: () -> Unit,
|
||||||
onVersionClick: () -> Unit,
|
onVersionClick: () -> Unit,
|
||||||
onWebVaultClick: () -> Unit,
|
onWebVaultClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@ -139,19 +160,18 @@ private fun ContentColumn(
|
|||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(height = 12.dp))
|
Spacer(modifier = Modifier.height(height = 12.dp))
|
||||||
if (state.shouldShowCrashLogsButton) {
|
CrashLogsCard(
|
||||||
BitwardenSwitch(
|
isVisible = state.shouldShowCrashLogsButton,
|
||||||
label = stringResource(id = R.string.submit_crash_logs),
|
isEnabled = state.isSubmitCrashLogsEnabled,
|
||||||
contentDescription = stringResource(id = R.string.submit_crash_logs),
|
onSubmitCrashLogsCheckedChange = onSubmitCrashLogsCheckedChange,
|
||||||
isChecked = state.isSubmitCrashLogsEnabled,
|
)
|
||||||
onCheckedChange = onSubmitCrashLogsCheckedChange,
|
FlightRecorderCard(
|
||||||
cardStyle = CardStyle.Top(),
|
isVisible = state.shouldShowFlightRecorder,
|
||||||
modifier = Modifier
|
isFlightRecorderEnabled = state.isFlightRecorderEnabled,
|
||||||
.testTag("SubmitCrashLogsSwitch")
|
onFlightRecorderCheckedChange = onFlightRecorderCheckedChange,
|
||||||
.fillMaxWidth()
|
onFlightRecorderTooltipClick = onFlightRecorderTooltipClick,
|
||||||
.standardHorizontalMargin(),
|
onViewRecordedLogsClick = onViewRecordedLogsClick,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
BitwardenExternalLinkRow(
|
BitwardenExternalLinkRow(
|
||||||
text = stringResource(id = R.string.bitwarden_help_center),
|
text = stringResource(id = R.string.bitwarden_help_center),
|
||||||
onConfirmClick = onHelpCenterClick,
|
onConfirmClick = onHelpCenterClick,
|
||||||
@ -160,11 +180,7 @@ private fun ContentColumn(
|
|||||||
id = R.string.learn_more_about_how_to_use_bitwarden_on_the_help_center,
|
id = R.string.learn_more_about_how_to_use_bitwarden_on_the_help_center,
|
||||||
),
|
),
|
||||||
withDivider = false,
|
withDivider = false,
|
||||||
cardStyle = if (state.shouldShowCrashLogsButton) {
|
cardStyle = CardStyle.Top(),
|
||||||
CardStyle.Middle()
|
|
||||||
} else {
|
|
||||||
CardStyle.Top()
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.standardHorizontalMargin()
|
.standardHorizontalMargin()
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -239,6 +255,62 @@ private fun ContentColumn(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ColumnScope.CrashLogsCard(
|
||||||
|
isVisible: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onSubmitCrashLogsCheckedChange: (Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
if (!isVisible) return
|
||||||
|
BitwardenSwitch(
|
||||||
|
label = stringResource(id = R.string.submit_crash_logs),
|
||||||
|
contentDescription = stringResource(id = R.string.submit_crash_logs),
|
||||||
|
isChecked = isEnabled,
|
||||||
|
onCheckedChange = onSubmitCrashLogsCheckedChange,
|
||||||
|
cardStyle = CardStyle.Full,
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag(tag = "SubmitCrashLogsSwitch")
|
||||||
|
.fillMaxWidth()
|
||||||
|
.standardHorizontalMargin(),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ColumnScope.FlightRecorderCard(
|
||||||
|
isVisible: Boolean,
|
||||||
|
isFlightRecorderEnabled: Boolean,
|
||||||
|
onFlightRecorderCheckedChange: (Boolean) -> Unit,
|
||||||
|
onFlightRecorderTooltipClick: () -> Unit,
|
||||||
|
onViewRecordedLogsClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
if (!isVisible) return
|
||||||
|
BitwardenSwitch(
|
||||||
|
label = stringResource(id = R.string.flight_recorder),
|
||||||
|
isChecked = isFlightRecorderEnabled,
|
||||||
|
onCheckedChange = onFlightRecorderCheckedChange,
|
||||||
|
tooltip = TooltipData(
|
||||||
|
contentDescription = stringResource(id = R.string.flight_recorder_help),
|
||||||
|
onClick = onFlightRecorderTooltipClick,
|
||||||
|
),
|
||||||
|
cardStyle = CardStyle.Top(),
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag(tag = "FlightRecorderSwitch")
|
||||||
|
.fillMaxWidth()
|
||||||
|
.standardHorizontalMargin(),
|
||||||
|
)
|
||||||
|
BitwardenPushRow(
|
||||||
|
text = stringResource(id = R.string.view_recorded_logs),
|
||||||
|
onClick = onViewRecordedLogsClick,
|
||||||
|
cardStyle = CardStyle.Bottom,
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag(tag = "ViewRecordedLogs")
|
||||||
|
.fillMaxWidth()
|
||||||
|
.standardHorizontalMargin(),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CopyRow(
|
private fun CopyRow(
|
||||||
text: Text,
|
text: Text,
|
||||||
@ -263,11 +335,28 @@ private fun CopyRow(
|
|||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun CopyRow_preview() {
|
private fun AboutScreenContent_preview() {
|
||||||
BitwardenTheme {
|
BitwardenTheme {
|
||||||
CopyRow(
|
AboutScreenContent(
|
||||||
text = "Copyable Text".asText(),
|
state = AboutState(
|
||||||
onClick = { },
|
version = "Version: 1.0.0 (1)".asText(),
|
||||||
|
deviceData = "device_data".asText(),
|
||||||
|
ciData = "ci_data".asText(),
|
||||||
|
isSubmitCrashLogsEnabled = false,
|
||||||
|
copyrightInfo = "".asText(),
|
||||||
|
shouldShowCrashLogsButton = true,
|
||||||
|
isFlightRecorderEnabled = true,
|
||||||
|
shouldShowFlightRecorder = true,
|
||||||
|
),
|
||||||
|
onHelpCenterClick = {},
|
||||||
|
onPrivacyPolicyClick = {},
|
||||||
|
onLearnAboutOrgsClick = {},
|
||||||
|
onSubmitCrashLogsCheckedChange = { },
|
||||||
|
onFlightRecorderCheckedChange = { },
|
||||||
|
onFlightRecorderTooltipClick = {},
|
||||||
|
onViewRecordedLogsClick = {},
|
||||||
|
onVersionClick = {},
|
||||||
|
onWebVaultClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,10 @@ import android.os.Parcelable
|
|||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.LogsManager
|
import com.x8bit.bitwarden.data.platform.manager.LogsManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault
|
import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault
|
||||||
import com.x8bit.bitwarden.data.platform.util.ciBuildInfo
|
import com.x8bit.bitwarden.data.platform.util.ciBuildInfo
|
||||||
@ -18,6 +20,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText
|
|||||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
@ -30,10 +33,12 @@ private const val KEY_STATE = "state"
|
|||||||
/**
|
/**
|
||||||
* View model for the about screen.
|
* View model for the about screen.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("TooManyFunctions")
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AboutViewModel @Inject constructor(
|
class AboutViewModel @Inject constructor(
|
||||||
private val savedStateHandle: SavedStateHandle,
|
private val savedStateHandle: SavedStateHandle,
|
||||||
private val clipboardManager: BitwardenClipboardManager,
|
private val clipboardManager: BitwardenClipboardManager,
|
||||||
|
featureFlagManager: FeatureFlagManager,
|
||||||
clock: Clock,
|
clock: Clock,
|
||||||
private val logsManager: LogsManager,
|
private val logsManager: LogsManager,
|
||||||
private val environmentRepository: EnvironmentRepository,
|
private val environmentRepository: EnvironmentRepository,
|
||||||
@ -45,6 +50,8 @@ class AboutViewModel @Inject constructor(
|
|||||||
ciData = ciBuildInfo?.let { "\n$it" }.orEmpty().asText(),
|
ciData = ciBuildInfo?.let { "\n$it" }.orEmpty().asText(),
|
||||||
isSubmitCrashLogsEnabled = logsManager.isEnabled,
|
isSubmitCrashLogsEnabled = logsManager.isEnabled,
|
||||||
shouldShowCrashLogsButton = !isFdroid,
|
shouldShowCrashLogsButton = !isFdroid,
|
||||||
|
isFlightRecorderEnabled = false,
|
||||||
|
shouldShowFlightRecorder = featureFlagManager.getFeatureFlag(FlagKey.FlightRecorder),
|
||||||
copyrightInfo = "© Bitwarden Inc. 2015-${Year.now(clock).value}".asText(),
|
copyrightInfo = "© Bitwarden Inc. 2015-${Year.now(clock).value}".asText(),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
@ -52,6 +59,11 @@ class AboutViewModel @Inject constructor(
|
|||||||
stateFlow
|
stateFlow
|
||||||
.onEach { savedStateHandle[KEY_STATE] = it }
|
.onEach { savedStateHandle[KEY_STATE] = it }
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
featureFlagManager
|
||||||
|
.getFeatureFlagFlow(FlagKey.FlightRecorder)
|
||||||
|
.map { AboutAction.Internal.FlightRecorderReceive(isEnabled = it) }
|
||||||
|
.onEach(::sendAction)
|
||||||
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleAction(action: AboutAction): Unit = when (action) {
|
override fun handleAction(action: AboutAction): Unit = when (action) {
|
||||||
@ -62,6 +74,20 @@ class AboutViewModel @Inject constructor(
|
|||||||
is AboutAction.SubmitCrashLogsClick -> handleSubmitCrashLogsClick(action)
|
is AboutAction.SubmitCrashLogsClick -> handleSubmitCrashLogsClick(action)
|
||||||
AboutAction.VersionClick -> handleVersionClick()
|
AboutAction.VersionClick -> handleVersionClick()
|
||||||
AboutAction.WebVaultClick -> handleWebVaultClick()
|
AboutAction.WebVaultClick -> handleWebVaultClick()
|
||||||
|
is AboutAction.FlightRecorderCheckedChange -> handleFlightRecorderCheckedChange(action)
|
||||||
|
AboutAction.FlightRecorderTooltipClick -> handleFlightRecorderTooltipClick()
|
||||||
|
AboutAction.ViewRecordedLogsClick -> handleViewRecordedLogsClick()
|
||||||
|
is AboutAction.Internal -> handleInternalAction(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleInternalAction(action: AboutAction.Internal) {
|
||||||
|
when (action) {
|
||||||
|
is AboutAction.Internal.FlightRecorderReceive -> handleFlightRecorderReceive(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleFlightRecorderReceive(action: AboutAction.Internal.FlightRecorderReceive) {
|
||||||
|
mutableStateFlow.update { it.copy(shouldShowFlightRecorder = action.isEnabled) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleBackClick() {
|
private fun handleBackClick() {
|
||||||
@ -105,6 +131,22 @@ class AboutViewModel @Inject constructor(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleFlightRecorderCheckedChange(action: AboutAction.FlightRecorderCheckedChange) {
|
||||||
|
if (action.isEnabled) {
|
||||||
|
sendEvent(AboutEvent.NavigateToFlightRecorder)
|
||||||
|
} else {
|
||||||
|
// TODO: PM-19592 Disable the feature.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleFlightRecorderTooltipClick() {
|
||||||
|
sendEvent(AboutEvent.NavigateToFlightRecorderHelp)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleViewRecordedLogsClick() {
|
||||||
|
sendEvent(AboutEvent.NavigateToRecordedLogs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,6 +159,8 @@ data class AboutState(
|
|||||||
val ciData: Text,
|
val ciData: Text,
|
||||||
val isSubmitCrashLogsEnabled: Boolean,
|
val isSubmitCrashLogsEnabled: Boolean,
|
||||||
val shouldShowCrashLogsButton: Boolean,
|
val shouldShowCrashLogsButton: Boolean,
|
||||||
|
val isFlightRecorderEnabled: Boolean,
|
||||||
|
val shouldShowFlightRecorder: Boolean,
|
||||||
val copyrightInfo: Text,
|
val copyrightInfo: Text,
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
@ -129,6 +173,21 @@ sealed class AboutEvent {
|
|||||||
*/
|
*/
|
||||||
data object NavigateBack : AboutEvent()
|
data object NavigateBack : AboutEvent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the flight recorder configuration.
|
||||||
|
*/
|
||||||
|
data object NavigateToFlightRecorder : AboutEvent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the flight recorder help info.
|
||||||
|
*/
|
||||||
|
data object NavigateToFlightRecorderHelp : AboutEvent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the flight recorder log history.
|
||||||
|
*/
|
||||||
|
data object NavigateToRecordedLogs : AboutEvent()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigates to the help center.
|
* Navigates to the help center.
|
||||||
*/
|
*/
|
||||||
@ -190,4 +249,31 @@ sealed class AboutAction {
|
|||||||
* User clicked the web vault row.
|
* User clicked the web vault row.
|
||||||
*/
|
*/
|
||||||
data object WebVaultClick : AboutAction()
|
data object WebVaultClick : AboutAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User clicked the flight recorder check box.
|
||||||
|
*/
|
||||||
|
data class FlightRecorderCheckedChange(
|
||||||
|
val isEnabled: Boolean,
|
||||||
|
) : AboutAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User clicked the flight recorder tooltip.
|
||||||
|
*/
|
||||||
|
data object FlightRecorderTooltipClick : AboutAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User clicked the view recorded logs row.
|
||||||
|
*/
|
||||||
|
data object ViewRecordedLogsClick : AboutAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions for internal use by the ViewModel.
|
||||||
|
*/
|
||||||
|
sealed class Internal : AboutAction() {
|
||||||
|
/**
|
||||||
|
* Indicates that the flight recorder feature has been enabled or disabled.
|
||||||
|
*/
|
||||||
|
data class FlightRecorderReceive(val isEnabled: Boolean) : Internal()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavOptions
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||||
|
|
||||||
|
private const val FLIGHT_RECORDER_ROUTE = "flight_recorder_config"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add flight recorder destination to the nav graph.
|
||||||
|
*/
|
||||||
|
fun NavGraphBuilder.flightRecorderDestination(
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
composableWithPushTransitions(
|
||||||
|
route = FLIGHT_RECORDER_ROUTE,
|
||||||
|
) {
|
||||||
|
FlightRecorderScreen(
|
||||||
|
onNavigateBack = onNavigateBack,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the flight recorder screen.
|
||||||
|
*/
|
||||||
|
fun NavController.navigateToFlightRecorder(navOptions: NavOptions? = null) {
|
||||||
|
navigate(FLIGHT_RECORDER_ROUTE, navOptions)
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder
|
||||||
|
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the flight recorder configuration screen.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun FlightRecorderScreen(
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
viewModel: FlightRecorderViewModel = hiltViewModel(),
|
||||||
|
) {
|
||||||
|
EventsEffect(viewModel) { event ->
|
||||||
|
when (event) {
|
||||||
|
FlightRecorderEvent.NavigateBack -> onNavigateBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
BitwardenScaffold(
|
||||||
|
topBar = {
|
||||||
|
BitwardenTopAppBar(
|
||||||
|
title = stringResource(id = R.string.enable_flight_recorder_title),
|
||||||
|
navigationIcon = rememberVectorPainter(id = R.drawable.ic_close),
|
||||||
|
navigationIconContentDescription = stringResource(id = R.string.close),
|
||||||
|
onNavigationIconClick = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(FlightRecorderAction.BackClick) }
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
) {
|
||||||
|
// TODO: PM-19592 Create the flight recorder UI.
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val KEY_STATE = "state"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View model for the flight recorder configuration screen.
|
||||||
|
*/
|
||||||
|
@HiltViewModel
|
||||||
|
class FlightRecorderViewModel @Inject constructor(
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
) : BaseViewModel<FlightRecorderState, FlightRecorderEvent, FlightRecorderAction>(
|
||||||
|
// We load the state from the savedStateHandle for testing purposes.
|
||||||
|
initialState = savedStateHandle[KEY_STATE] ?: FlightRecorderState,
|
||||||
|
) {
|
||||||
|
override fun handleAction(action: FlightRecorderAction) {
|
||||||
|
when (action) {
|
||||||
|
FlightRecorderAction.BackClick -> handleBackClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleBackClick() {
|
||||||
|
sendEvent(FlightRecorderEvent.NavigateBack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models the UI state for the flight recorder screen.
|
||||||
|
*/
|
||||||
|
data object FlightRecorderState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models events for the flight recorder screen.
|
||||||
|
*/
|
||||||
|
sealed class FlightRecorderEvent {
|
||||||
|
/**
|
||||||
|
* Navigates back.
|
||||||
|
*/
|
||||||
|
data object NavigateBack : FlightRecorderEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models actions for the flight recorder screen.
|
||||||
|
*/
|
||||||
|
sealed class FlightRecorderAction {
|
||||||
|
/**
|
||||||
|
* Indicates that the user clicked the close button.
|
||||||
|
*/
|
||||||
|
data object BackClick : FlightRecorderAction()
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavOptions
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||||
|
|
||||||
|
private const val FLIGHT_RECORDER_RECORDED_LOGS_ROUTE = "flight_recorder_recorded_logs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add recorded logs destination to the nav graph.
|
||||||
|
*/
|
||||||
|
fun NavGraphBuilder.recordedLogsDestination(
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
composableWithPushTransitions(
|
||||||
|
route = FLIGHT_RECORDER_RECORDED_LOGS_ROUTE,
|
||||||
|
) {
|
||||||
|
RecordedLogsScreen(
|
||||||
|
onNavigateBack = onNavigateBack,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the flight recorder recorded logs screen.
|
||||||
|
*/
|
||||||
|
fun NavController.navigateToRecordedLogs(navOptions: NavOptions? = null) {
|
||||||
|
navigate(FLIGHT_RECORDER_RECORDED_LOGS_ROUTE, navOptions)
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs
|
||||||
|
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the flight recorder recorded logs screen.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun RecordedLogsScreen(
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
viewModel: RecordedLogsViewModel = hiltViewModel(),
|
||||||
|
) {
|
||||||
|
EventsEffect(viewModel) { event ->
|
||||||
|
when (event) {
|
||||||
|
RecordedLogsEvent.NavigateBack -> onNavigateBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
BitwardenScaffold(
|
||||||
|
topBar = {
|
||||||
|
BitwardenTopAppBar(
|
||||||
|
title = stringResource(id = R.string.recorded_logs_title),
|
||||||
|
navigationIcon = rememberVectorPainter(id = R.drawable.ic_close),
|
||||||
|
navigationIconContentDescription = stringResource(id = R.string.close),
|
||||||
|
onNavigationIconClick = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(RecordedLogsAction.BackClick) }
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
) {
|
||||||
|
// TODO: PM-19593 Create the flight recorder UI.
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val KEY_STATE = "state"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View model for the flight recorder recorded logs screen.
|
||||||
|
*/
|
||||||
|
@HiltViewModel
|
||||||
|
class RecordedLogsViewModel @Inject constructor(
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
) : BaseViewModel<RecordedLogsState, RecordedLogsEvent, RecordedLogsAction>(
|
||||||
|
// We load the state from the savedStateHandle for testing purposes.
|
||||||
|
initialState = savedStateHandle[KEY_STATE] ?: RecordedLogsState,
|
||||||
|
) {
|
||||||
|
override fun handleAction(action: RecordedLogsAction) {
|
||||||
|
when (action) {
|
||||||
|
RecordedLogsAction.BackClick -> handleBackClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleBackClick() {
|
||||||
|
sendEvent(RecordedLogsEvent.NavigateBack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models the UI state for the recorded logs screen.
|
||||||
|
*/
|
||||||
|
data object RecordedLogsState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models events for the recorded logs screen.
|
||||||
|
*/
|
||||||
|
sealed class RecordedLogsEvent {
|
||||||
|
/**
|
||||||
|
* Navigates back.
|
||||||
|
*/
|
||||||
|
data object NavigateBack : RecordedLogsEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models actions for the recorded logs screen.
|
||||||
|
*/
|
||||||
|
sealed class RecordedLogsAction {
|
||||||
|
/**
|
||||||
|
* Indicates that the user clicked the close button.
|
||||||
|
*/
|
||||||
|
data object BackClick : RecordedLogsAction()
|
||||||
|
}
|
||||||
@ -23,6 +23,10 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.pendingr
|
|||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.pendingrequests.pendingRequestsDestination
|
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.pendingrequests.pendingRequestsDestination
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.exportvault.exportVaultDestination
|
import com.x8bit.bitwarden.ui.platform.feature.settings.exportvault.exportVaultDestination
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.exportvault.navigateToExportVault
|
import com.x8bit.bitwarden.ui.platform.feature.settings.exportvault.navigateToExportVault
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.flightRecorderDestination
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.navigateToFlightRecorder
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.navigateToRecordedLogs
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.recordedLogsDestination
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.addedit.folderAddEditDestination
|
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.addedit.folderAddEditDestination
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.addedit.navigateToFolderAddEdit
|
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.addedit.navigateToFolderAddEdit
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.foldersDestination
|
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.foldersDestination
|
||||||
@ -115,7 +119,11 @@ fun NavGraphBuilder.vaultUnlockedGraph(
|
|||||||
parentFolderName = it,
|
parentFolderName = it,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
onNavigateToFlightRecorder = { navController.navigateToFlightRecorder() },
|
||||||
|
onNavigateToRecordedLogs = { navController.navigateToRecordedLogs() },
|
||||||
)
|
)
|
||||||
|
flightRecorderDestination(onNavigateBack = { navController.popBackStack() })
|
||||||
|
recordedLogsDestination(onNavigateBack = { navController.popBackStack() })
|
||||||
deleteAccountDestination(
|
deleteAccountDestination(
|
||||||
onNavigateBack = { navController.popBackStack() },
|
onNavigateBack = { navController.popBackStack() },
|
||||||
onNavigateToDeleteAccountConfirmation = {
|
onNavigateToDeleteAccountConfirmation = {
|
||||||
|
|||||||
@ -40,6 +40,8 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
|
|||||||
onNavigateToPasswordHistory: () -> Unit,
|
onNavigateToPasswordHistory: () -> Unit,
|
||||||
onNavigateToSetupUnlockScreen: () -> Unit,
|
onNavigateToSetupUnlockScreen: () -> Unit,
|
||||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||||
|
onNavigateToFlightRecorder: () -> Unit,
|
||||||
|
onNavigateToRecordedLogs: () -> Unit,
|
||||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
onNavigateToAddFolderScreen: (selectedFolderName: String?) -> Unit,
|
onNavigateToAddFolderScreen: (selectedFolderName: String?) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -63,6 +65,8 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
|
|||||||
onNavigateToSetupAutoFillScreen = onNavigateToSetupAutoFillScreen,
|
onNavigateToSetupAutoFillScreen = onNavigateToSetupAutoFillScreen,
|
||||||
onNavigateToImportLogins = onNavigateToImportLogins,
|
onNavigateToImportLogins = onNavigateToImportLogins,
|
||||||
onNavigateToAddFolderScreen = onNavigateToAddFolderScreen,
|
onNavigateToAddFolderScreen = onNavigateToAddFolderScreen,
|
||||||
|
onNavigateToFlightRecorder = onNavigateToFlightRecorder,
|
||||||
|
onNavigateToRecordedLogs = onNavigateToRecordedLogs,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,6 +64,8 @@ fun VaultUnlockedNavBarScreen(
|
|||||||
onNavigateToPasswordHistory: () -> Unit,
|
onNavigateToPasswordHistory: () -> Unit,
|
||||||
onNavigateToSetupUnlockScreen: () -> Unit,
|
onNavigateToSetupUnlockScreen: () -> Unit,
|
||||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||||
|
onNavigateToFlightRecorder: () -> Unit,
|
||||||
|
onNavigateToRecordedLogs: () -> Unit,
|
||||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -133,6 +135,8 @@ fun VaultUnlockedNavBarScreen(
|
|||||||
onNavigateToSetupAutoFillScreen = onNavigateToSetupAutoFillScreen,
|
onNavigateToSetupAutoFillScreen = onNavigateToSetupAutoFillScreen,
|
||||||
onNavigateToImportLogins = onNavigateToImportLogins,
|
onNavigateToImportLogins = onNavigateToImportLogins,
|
||||||
onNavigateToAddFolderScreen = onNavigateToAddFolderScreen,
|
onNavigateToAddFolderScreen = onNavigateToAddFolderScreen,
|
||||||
|
onNavigateToFlightRecorder = onNavigateToFlightRecorder,
|
||||||
|
onNavigateToRecordedLogs = onNavigateToRecordedLogs,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +166,8 @@ private fun VaultUnlockedNavBarScaffold(
|
|||||||
navigateToPasswordHistory: () -> Unit,
|
navigateToPasswordHistory: () -> Unit,
|
||||||
onNavigateToSetupUnlockScreen: () -> Unit,
|
onNavigateToSetupUnlockScreen: () -> Unit,
|
||||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||||
|
onNavigateToFlightRecorder: () -> Unit,
|
||||||
|
onNavigateToRecordedLogs: () -> Unit,
|
||||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||||
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -237,6 +243,8 @@ private fun VaultUnlockedNavBarScaffold(
|
|||||||
onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen,
|
onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen,
|
||||||
onNavigateToSetupAutoFillScreen = onNavigateToSetupAutoFillScreen,
|
onNavigateToSetupAutoFillScreen = onNavigateToSetupAutoFillScreen,
|
||||||
onNavigateToImportLogins = onNavigateToImportLogins,
|
onNavigateToImportLogins = onNavigateToImportLogins,
|
||||||
|
onNavigateToFlightRecorder = onNavigateToFlightRecorder,
|
||||||
|
onNavigateToRecordedLogs = onNavigateToRecordedLogs,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1228,4 +1228,9 @@ Do you want to switch to this account?</string>
|
|||||||
<string name="add_field">Add field</string>
|
<string name="add_field">Add field</string>
|
||||||
<string name="x_ellipses">%s...</string>
|
<string name="x_ellipses">%s...</string>
|
||||||
<string name="share_error_details">Share error details</string>
|
<string name="share_error_details">Share error details</string>
|
||||||
|
<string name="flight_recorder">Flight recorder</string>
|
||||||
|
<string name="flight_recorder_help">Flight recorder help</string>
|
||||||
|
<string name="view_recorded_logs">View recorded logs</string>
|
||||||
|
<string name="enable_flight_recorder_title">Enable flight recorder</string>
|
||||||
|
<string name="recorded_logs_title">Recorded logs</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -35,17 +35,10 @@ import java.time.ZoneOffset
|
|||||||
|
|
||||||
class AboutScreenTest : BaseComposeTest() {
|
class AboutScreenTest : BaseComposeTest() {
|
||||||
private var haveCalledNavigateBack = false
|
private var haveCalledNavigateBack = false
|
||||||
|
private var haveCalledNavigateToFlightRecorder = false
|
||||||
|
private var haveCalledNavigateToRecordedLogs = false
|
||||||
|
|
||||||
private val mutableStateFlow = MutableStateFlow(
|
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||||
AboutState(
|
|
||||||
version = "Version: 1.0.0 (1)".asText(),
|
|
||||||
deviceData = "device_data".asText(),
|
|
||||||
ciData = "ci_data".asText(),
|
|
||||||
isSubmitCrashLogsEnabled = false,
|
|
||||||
copyrightInfo = "".asText(),
|
|
||||||
shouldShowCrashLogsButton = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
private val mutableEventFlow = bufferedMutableSharedFlow<AboutEvent>()
|
private val mutableEventFlow = bufferedMutableSharedFlow<AboutEvent>()
|
||||||
val viewModel: AboutViewModel = mockk {
|
val viewModel: AboutViewModel = mockk {
|
||||||
every { stateFlow } returns mutableStateFlow
|
every { stateFlow } returns mutableStateFlow
|
||||||
@ -65,6 +58,8 @@ class AboutScreenTest : BaseComposeTest() {
|
|||||||
AboutScreen(
|
AboutScreen(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onNavigateBack = { haveCalledNavigateBack = true },
|
onNavigateBack = { haveCalledNavigateBack = true },
|
||||||
|
onNavigateToFlightRecorder = { haveCalledNavigateToFlightRecorder = true },
|
||||||
|
onNavigateToRecordedLogs = { haveCalledNavigateToRecordedLogs = true },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,6 +70,39 @@ class AboutScreenTest : BaseComposeTest() {
|
|||||||
verify { viewModel.trySendAction(AboutAction.BackClick) }
|
verify { viewModel.trySendAction(AboutAction.BackClick) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on flight recorder tooltip click should emit FlightRecorderTooltipClick`() {
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithContentDescription("Flight recorder help")
|
||||||
|
.performScrollTo()
|
||||||
|
.performClick()
|
||||||
|
verify {
|
||||||
|
viewModel.trySendAction(AboutAction.FlightRecorderTooltipClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on view recorded logs click should emit ViewRecordedLogsClick`() {
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText("View recorded logs")
|
||||||
|
.performScrollTo()
|
||||||
|
.performClick()
|
||||||
|
verify {
|
||||||
|
viewModel.trySendAction(AboutAction.ViewRecordedLogsClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on view recorded logs click should emit FlightRecorderCheckedChange`() {
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText("Flight recorder")
|
||||||
|
.performScrollTo()
|
||||||
|
.performClick()
|
||||||
|
verify {
|
||||||
|
viewModel.trySendAction(AboutAction.FlightRecorderCheckedChange(isEnabled = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `on bitwarden help center click should display confirmation dialog and confirm click should emit HelpCenterClick`() {
|
fun `on bitwarden help center click should display confirmation dialog and confirm click should emit HelpCenterClick`() {
|
||||||
@ -127,7 +155,10 @@ class AboutScreenTest : BaseComposeTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `on learn about organizations click should display confirmation dialog and confirm click should emit LearnAboutOrganizationsClick`() {
|
fun `on learn about organizations click should display confirmation dialog and confirm click should emit LearnAboutOrganizationsClick`() {
|
||||||
composeTestRule.onNode(isDialog()).assertDoesNotExist()
|
composeTestRule.onNode(isDialog()).assertDoesNotExist()
|
||||||
composeTestRule.onNodeWithText("Learn about organizations").performClick()
|
composeTestRule
|
||||||
|
.onNodeWithText("Learn about organizations")
|
||||||
|
.performScrollTo()
|
||||||
|
.performClick()
|
||||||
composeTestRule.onNode(isDialog()).assertExists()
|
composeTestRule.onNode(isDialog()).assertExists()
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onAllNodesWithText("Continue")
|
.onAllNodesWithText("Continue")
|
||||||
@ -145,6 +176,26 @@ class AboutScreenTest : BaseComposeTest() {
|
|||||||
assertTrue(haveCalledNavigateBack)
|
assertTrue(haveCalledNavigateBack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on NavigateToFlightRecorder should call onNavigateToFlightRecorder`() {
|
||||||
|
mutableEventFlow.tryEmit(AboutEvent.NavigateToFlightRecorder)
|
||||||
|
assertTrue(haveCalledNavigateToFlightRecorder)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on NavigateToRecordedLogs should call onNavigateToRecordedLogs`() {
|
||||||
|
mutableEventFlow.tryEmit(AboutEvent.NavigateToRecordedLogs)
|
||||||
|
assertTrue(haveCalledNavigateToRecordedLogs)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on NavigateToFlightRecorderHelp should call launchUri on IntentManager`() {
|
||||||
|
mutableEventFlow.tryEmit(AboutEvent.NavigateToFlightRecorderHelp)
|
||||||
|
verify(exactly = 1) {
|
||||||
|
intentManager.launchUri("https://bitwarden.com/help".toUri())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on NavigateToHelpCenter should call launchUri on IntentManager`() {
|
fun `on NavigateToHelpCenter should call launchUri on IntentManager`() {
|
||||||
mutableEventFlow.tryEmit(AboutEvent.NavigateToHelpCenter)
|
mutableEventFlow.tryEmit(AboutEvent.NavigateToHelpCenter)
|
||||||
@ -243,3 +294,14 @@ class AboutScreenTest : BaseComposeTest() {
|
|||||||
.assertIsDisplayed()
|
.assertIsDisplayed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val DEFAULT_STATE = AboutState(
|
||||||
|
version = "Version: 1.0.0 (1)".asText(),
|
||||||
|
deviceData = "device_data".asText(),
|
||||||
|
ciData = "ci_data".asText(),
|
||||||
|
isSubmitCrashLogsEnabled = false,
|
||||||
|
shouldShowCrashLogsButton = true,
|
||||||
|
isFlightRecorderEnabled = false,
|
||||||
|
shouldShowFlightRecorder = true,
|
||||||
|
copyrightInfo = "".asText(),
|
||||||
|
)
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.about
|
|||||||
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.LogsManager
|
import com.x8bit.bitwarden.data.platform.manager.LogsManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault
|
import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||||
@ -15,6 +17,7 @@ import io.mockk.just
|
|||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.runs
|
import io.mockk.runs
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
@ -33,6 +36,10 @@ class AboutViewModelTest : BaseViewModelTest() {
|
|||||||
every { isEnabled } returns false
|
every { isEnabled } returns false
|
||||||
every { isEnabled = any() } just runs
|
every { isEnabled = any() } just runs
|
||||||
}
|
}
|
||||||
|
private val featureFlagManager = mockk<FeatureFlagManager> {
|
||||||
|
every { getFeatureFlag(FlagKey.FlightRecorder) } returns true
|
||||||
|
every { getFeatureFlagFlow(FlagKey.FlightRecorder) } returns flowOf(true)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on BackClick should emit NavigateBack`() = runTest {
|
fun `on BackClick should emit NavigateBack`() = runTest {
|
||||||
@ -43,6 +50,44 @@ class AboutViewModelTest : BaseViewModelTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on FlightRecorderTooltipClick should emit NavigateToFlightRecorderHelp`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(AboutAction.FlightRecorderTooltipClick)
|
||||||
|
assertEquals(AboutEvent.NavigateToFlightRecorderHelp, awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `on FlightRecorderCheckedChange with isEnabled true should emit NavigateToFlightRecorder`() =
|
||||||
|
runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(AboutAction.FlightRecorderCheckedChange(isEnabled = true))
|
||||||
|
assertEquals(AboutEvent.NavigateToFlightRecorder, awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on FlightRecorderCheckedChange with isEnabled false should do nothing`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(AboutAction.FlightRecorderCheckedChange(isEnabled = false))
|
||||||
|
expectNoEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on ViewRecordedLogsClick should emit NavigateToRecordedLogs`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(AboutAction.ViewRecordedLogsClick)
|
||||||
|
assertEquals(AboutEvent.NavigateToRecordedLogs, awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on HelpCenterClick should emit NavigateToHelpCenter`() = runTest {
|
fun `on HelpCenterClick should emit NavigateToHelpCenter`() = runTest {
|
||||||
val viewModel = createViewModel(DEFAULT_ABOUT_STATE)
|
val viewModel = createViewModel(DEFAULT_ABOUT_STATE)
|
||||||
@ -133,6 +178,7 @@ class AboutViewModelTest : BaseViewModelTest() {
|
|||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
environmentRepository = environmentRepository,
|
environmentRepository = environmentRepository,
|
||||||
logsManager = logsManager,
|
logsManager = logsManager,
|
||||||
|
featureFlagManager = featureFlagManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +190,9 @@ private val DEFAULT_ABOUT_STATE: AboutState = AboutState(
|
|||||||
version = "Version: <version_name> (<version_code>)".asText(),
|
version = "Version: <version_name> (<version_code>)".asText(),
|
||||||
deviceData = "<device_data>".asText(),
|
deviceData = "<device_data>".asText(),
|
||||||
ciData = "\n<ci_info>".asText(),
|
ciData = "\n<ci_info>".asText(),
|
||||||
isSubmitCrashLogsEnabled = false,
|
|
||||||
copyrightInfo = "© Bitwarden Inc. 2015-${Year.now(fixedClock).value}".asText(),
|
copyrightInfo = "© Bitwarden Inc. 2015-${Year.now(fixedClock).value}".asText(),
|
||||||
|
isSubmitCrashLogsEnabled = false,
|
||||||
shouldShowCrashLogsButton = true,
|
shouldShowCrashLogsButton = true,
|
||||||
|
isFlightRecorderEnabled = false,
|
||||||
|
shouldShowFlightRecorder = true,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class FlightRecorderScreenTest : BaseComposeTest() {
|
||||||
|
private var onNavigateBackCalled = false
|
||||||
|
private val mutableEventFlow = bufferedMutableSharedFlow<FlightRecorderEvent>()
|
||||||
|
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||||
|
|
||||||
|
private val viewModel = mockk<FlightRecorderViewModel>(relaxed = true) {
|
||||||
|
every { eventFlow } returns mutableEventFlow
|
||||||
|
every { stateFlow } returns mutableStateFlow
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
setContent {
|
||||||
|
FlightRecorderScreen(
|
||||||
|
onNavigateBack = { onNavigateBackCalled = true },
|
||||||
|
viewModel = viewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on navigation icon click should emit BackClick action`() {
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithContentDescription(label = "Close")
|
||||||
|
.performClick()
|
||||||
|
verify(exactly = 1) {
|
||||||
|
viewModel.trySendAction(FlightRecorderAction.BackClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on NavigateBack event should invoke onNavigateBack`() {
|
||||||
|
mutableEventFlow.tryEmit(FlightRecorderEvent.NavigateBack)
|
||||||
|
assertTrue(onNavigateBackCalled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val DEFAULT_STATE: FlightRecorderState = FlightRecorderState
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class FlightRecorderViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `initial state should be correct`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on BackClick action should send the NavigateBack event`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(FlightRecorderAction.BackClick)
|
||||||
|
assertEquals(FlightRecorderEvent.NavigateBack, awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createViewModel(
|
||||||
|
state: FlightRecorderState? = null,
|
||||||
|
): FlightRecorderViewModel =
|
||||||
|
FlightRecorderViewModel(
|
||||||
|
savedStateHandle = SavedStateHandle().apply {
|
||||||
|
set("state", state)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val DEFAULT_STATE: FlightRecorderState = FlightRecorderState
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedlogs
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.RecordedLogsAction
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.RecordedLogsEvent
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.RecordedLogsScreen
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.RecordedLogsState
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.RecordedLogsViewModel
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class RecordedLogsScreenTest : BaseComposeTest() {
|
||||||
|
private var onNavigateBackCalled = false
|
||||||
|
private val mutableEventFlow = bufferedMutableSharedFlow<RecordedLogsEvent>()
|
||||||
|
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||||
|
|
||||||
|
private val viewModel = mockk<RecordedLogsViewModel>(relaxed = true) {
|
||||||
|
every { eventFlow } returns mutableEventFlow
|
||||||
|
every { stateFlow } returns mutableStateFlow
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
setContent {
|
||||||
|
RecordedLogsScreen(
|
||||||
|
onNavigateBack = { onNavigateBackCalled = true },
|
||||||
|
viewModel = viewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on navigation icon click should emit BackClick action`() {
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithContentDescription(label = "Close")
|
||||||
|
.performClick()
|
||||||
|
verify(exactly = 1) {
|
||||||
|
viewModel.trySendAction(RecordedLogsAction.BackClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on NavigateBack event should invoke onNavigateBack`() {
|
||||||
|
mutableEventFlow.tryEmit(RecordedLogsEvent.NavigateBack)
|
||||||
|
assertTrue(onNavigateBackCalled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val DEFAULT_STATE: RecordedLogsState = RecordedLogsState
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedlogs
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.RecordedLogsAction
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.RecordedLogsEvent
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.RecordedLogsState
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs.RecordedLogsViewModel
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class RecordedLogsViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `initial state should be correct`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on BackClick action should send the NavigateBack event`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(RecordedLogsAction.BackClick)
|
||||||
|
assertEquals(RecordedLogsEvent.NavigateBack, awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createViewModel(
|
||||||
|
state: RecordedLogsState? = null,
|
||||||
|
): RecordedLogsViewModel =
|
||||||
|
RecordedLogsViewModel(
|
||||||
|
savedStateHandle = SavedStateHandle().apply {
|
||||||
|
set("state", state)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val DEFAULT_STATE: RecordedLogsState = RecordedLogsState
|
||||||
@ -57,6 +57,8 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
|||||||
onNavigateToSetupUnlockScreen = {},
|
onNavigateToSetupUnlockScreen = {},
|
||||||
onNavigateToImportLogins = {},
|
onNavigateToImportLogins = {},
|
||||||
onNavigateToAddFolderScreen = {},
|
onNavigateToAddFolderScreen = {},
|
||||||
|
onNavigateToFlightRecorder = {},
|
||||||
|
onNavigateToRecordedLogs = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user