diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceScreen.kt
index c4f3f3a479..88396527a3 100644
--- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceScreen.kt
+++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceScreen.kt
@@ -21,12 +21,14 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import androidx.core.net.toUri
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bitwarden.ui.platform.base.util.EventsEffect
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.bitwarden.ui.platform.components.model.CardStyle
+import com.bitwarden.ui.platform.components.model.TooltipData
import com.bitwarden.ui.platform.components.toggle.BitwardenSwitch
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
@@ -35,7 +37,9 @@ import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
+import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
+import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.util.displayLabel
import kotlinx.collections.immutable.toImmutableList
@@ -48,11 +52,15 @@ import kotlinx.collections.immutable.toImmutableList
fun AppearanceScreen(
onNavigateBack: () -> Unit,
viewModel: AppearanceViewModel = hiltViewModel(),
+ intentManager: IntentManager = LocalIntentManager.current,
) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
EventsEffect(viewModel = viewModel) { event ->
when (event) {
AppearanceEvent.NavigateBack -> onNavigateBack.invoke()
+ AppearanceEvent.NavigateToWebsiteIconsHelp -> {
+ intentManager.launchUri("https://bitwarden.com/help/website-icons/".toUri())
+ }
}
}
@@ -134,6 +142,12 @@ fun AppearanceScreen(
onCheckedChange = remember(viewModel) {
{ viewModel.trySendAction(AppearanceAction.ShowWebsiteIconsToggle(it)) }
},
+ tooltip = TooltipData(
+ onClick = remember(viewModel) {
+ { viewModel.trySendAction(AppearanceAction.ShowWebsiteIconsTooltipClick) }
+ },
+ contentDescription = stringResource(id = R.string.show_website_icons_help),
+ ),
cardStyle = CardStyle.Full,
modifier = Modifier
.testTag("ShowWebsiteIconsSwitch")
diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModel.kt
index 2d8e96445c..950a7d10cb 100644
--- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModel.kt
+++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModel.kt
@@ -22,6 +22,7 @@ private const val KEY_STATE = "state"
/**
* View model for the appearance screen.
*/
+@Suppress("TooManyFunctions")
@HiltViewModel
class AppearanceViewModel @Inject constructor(
private val settingsRepository: SettingsRepository,
@@ -58,6 +59,7 @@ class AppearanceViewModel @Inject constructor(
AppearanceAction.BackClick -> handleBackClicked()
is AppearanceAction.LanguageChange -> handleLanguageChanged(action)
is AppearanceAction.ShowWebsiteIconsToggle -> handleShowWebsiteIconsToggled(action)
+ AppearanceAction.ShowWebsiteIconsTooltipClick -> handleShowWebsiteIconsTooltipClick()
is AppearanceAction.ThemeChange -> handleThemeChanged(action)
is AppearanceAction.DynamicColorsToggle -> handleDynamicColorsToggled(action)
AppearanceAction.DismissDialog -> handleDismissDialog()
@@ -68,6 +70,7 @@ class AppearanceViewModel @Inject constructor(
is AppearanceAction.Internal.AppLanguageStateUpdateReceive -> {
handleLanguageStateChange(action)
}
+
is AppearanceAction.Internal.DynamicColorsStateUpdateReceive -> {
handleDynamicColorsStateChange(action)
}
@@ -106,6 +109,10 @@ class AppearanceViewModel @Inject constructor(
settingsRepository.isIconLoadingDisabled = !action.showWebsiteIcons
}
+ private fun handleShowWebsiteIconsTooltipClick() {
+ sendEvent(AppearanceEvent.NavigateToWebsiteIconsHelp)
+ }
+
private fun handleThemeChanged(action: AppearanceAction.ThemeChange) {
mutableStateFlow.update { it.copy(theme = action.theme) }
settingsRepository.appTheme = action.theme
@@ -170,6 +177,11 @@ sealed class AppearanceEvent {
* Navigate back.
*/
data object NavigateBack : AppearanceEvent()
+
+ /**
+ * Navigate to the website icons help URL.
+ */
+ data object NavigateToWebsiteIconsHelp : AppearanceEvent()
}
/**
@@ -195,6 +207,11 @@ sealed class AppearanceAction {
val showWebsiteIcons: Boolean,
) : AppearanceAction()
+ /**
+ * User clicked the website icons tooltip.
+ */
+ data object ShowWebsiteIconsTooltipClick : AppearanceAction()
+
/**
* Indicates that the user selected a new theme.
*/
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6fa03a6ed0..fb6f63ec15 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -265,6 +265,7 @@ Scanning will happen automatically.
Address
Expiration
Show website icons
+ Show website icons help
Show a recognizable image next to each login
Icons server URL
Vault is locked
diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceScreenTest.kt
index 00b656f7f3..e6b9a32c94 100644
--- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceScreenTest.kt
+++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceScreenTest.kt
@@ -10,15 +10,19 @@ import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
+import androidx.core.net.toUri
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.core.util.isBuildVersionAtLeast
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import com.bitwarden.ui.util.assertNoDialogExists
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
+import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import io.mockk.every
+import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
+import io.mockk.runs
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
@@ -37,12 +41,17 @@ class AppearanceScreenTest : BitwardenComposeTest() {
every { eventFlow } returns mutableEventFlow
every { stateFlow } returns mutableStateFlow
}
+ private val intentManager: IntentManager = mockk {
+ every { launchUri(uri = any()) } just runs
+ }
@Before
fun setup() {
mockkStatic(::isBuildVersionAtLeast)
every { isBuildVersionAtLeast(any()) } returns true
- setContent {
+ setContent(
+ intentManager = intentManager,
+ ) {
AppearanceScreen(
onNavigateBack = { haveCalledNavigateBack = true },
viewModel = viewModel,
@@ -172,12 +181,31 @@ class AppearanceScreenTest : BitwardenComposeTest() {
verify { viewModel.trySendAction(AppearanceAction.ShowWebsiteIconsToggle(true)) }
}
+ @Test
+ fun `on show website icons tooltip click should send ShowWebsiteIconsToggled`() {
+ composeTestRule
+ .onNodeWithContentDescription(label = "Show website icons help")
+ .performScrollTo()
+ .performClick()
+ verify {
+ viewModel.trySendAction(AppearanceAction.ShowWebsiteIconsTooltipClick)
+ }
+ }
+
@Test
fun `on NavigateBack should call onNavigateBack`() {
mutableEventFlow.tryEmit(AppearanceEvent.NavigateBack)
assertTrue(haveCalledNavigateBack)
}
+ @Test
+ fun `on NavigateToWebsiteIconsHelp should call launchUri`() {
+ mutableEventFlow.tryEmit(AppearanceEvent.NavigateToWebsiteIconsHelp)
+ verify {
+ intentManager.launchUri("https://bitwarden.com/help/website-icons/".toUri())
+ }
+ }
+
@Test
fun `dynamic colors should be displayed based on state`() {
composeTestRule.onNodeWithText("Dynamic colors")
diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModelTest.kt
index f9d940a17d..4fdfae618c 100644
--- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModelTest.kt
+++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModelTest.kt
@@ -136,6 +136,15 @@ class AppearanceViewModelTest : BaseViewModelTest() {
}
}
+ @Test
+ fun `on ShowWebsiteIconsTooltipClick should emit NavigateToWebsiteIconsHelp`() = runTest {
+ val viewModel = createViewModel()
+ viewModel.eventFlow.test {
+ viewModel.trySendAction(AppearanceAction.ShowWebsiteIconsTooltipClick)
+ assertEquals(AppearanceEvent.NavigateToWebsiteIconsHelp, awaitItem())
+ }
+ }
+
@Test
fun `on ThemeChange should update state and set theme in SettingsRepository`() = runTest {
val viewModel = createViewModel()