diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherScreen.kt
index 06f4ba2c61..151514db0d 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherScreen.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherScreen.kt
@@ -29,6 +29,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.unit.dp
+import androidx.core.net.toUri
import androidx.hilt.navigation.compose.hiltViewModel
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.ClearClipboardFrequency
@@ -41,9 +42,12 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenSelectionDialo
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.dialog.LoadingDialogState
import com.x8bit.bitwarden.ui.platform.components.dialog.row.BitwardenSelectionRow
+import com.x8bit.bitwarden.ui.platform.components.row.BitwardenExternalLinkRow
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.toggle.BitwardenWideSwitch
+import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
+import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager
/**
* Displays the other screen.
@@ -54,11 +58,15 @@ import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenWideSwitch
fun OtherScreen(
onNavigateBack: () -> Unit,
viewModel: OtherViewModel = hiltViewModel(),
+ intentManager: IntentManager = LocalIntentManager.current,
) {
val state by viewModel.stateFlow.collectAsState()
EventsEffect(viewModel = viewModel) { event ->
when (event) {
OtherEvent.NavigateBack -> onNavigateBack.invoke()
+ OtherEvent.NavigateToFeedbackForm -> {
+ intentManager.launchUri("https://livefrontinc.typeform.com/to/irgrRu4a".toUri())
+ }
}
}
@@ -158,6 +166,16 @@ fun OtherScreen(
.semantics { testTag = "AllowScreenCaptureSwitch" }
.padding(horizontal = 16.dp),
)
+
+ BitwardenExternalLinkRow(
+ text = stringResource(R.string.give_feedback),
+ onConfirmClick = remember(viewModel) {
+ { viewModel.trySendAction(OtherAction.GiveFeedbackClick) }
+ },
+ dialogTitle = stringResource(R.string.continue_to_give_feedback),
+ dialogMessage = stringResource(R.string.continue_to_provide_feedback),
+ withDivider = false,
+ )
}
}
}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherViewModel.kt
index a19f32a010..057c4b79c4 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherViewModel.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherViewModel.kt
@@ -60,6 +60,7 @@ class OtherViewModel @Inject constructor(
is OtherAction.AllowSyncToggle -> handleAllowSyncToggled(action)
OtherAction.BackClick -> handleBackClicked()
is OtherAction.ClearClipboardFrequencyChange -> handleClearClipboardFrequencyChanged(action)
+ OtherAction.GiveFeedbackClick -> handleGiveFeedbackClicked()
OtherAction.SyncNowButtonClick -> handleSyncNowButtonClicked()
is OtherAction.Internal -> handleInternalAction(action)
}
@@ -87,6 +88,10 @@ class OtherViewModel @Inject constructor(
settingsRepo.clearClipboardFrequency = action.clearClipboardFrequency
}
+ private fun handleGiveFeedbackClicked() {
+ sendEvent(OtherEvent.NavigateToFeedbackForm)
+ }
+
private fun handleSyncNowButtonClicked() {
mutableStateFlow.update {
it.copy(dialogState = OtherState.DialogState.Loading(R.string.syncing.asText()))
@@ -146,6 +151,11 @@ sealed class OtherEvent {
* Navigate back.
*/
data object NavigateBack : OtherEvent()
+
+ /**
+ * Navigate to the feedback form.
+ */
+ data object NavigateToFeedbackForm : OtherEvent()
}
/**
@@ -178,6 +188,11 @@ sealed class OtherAction {
val clearClipboardFrequency: ClearClipboardFrequency,
) : OtherAction()
+ /**
+ * Indicates that the user clicked the Give feedback button.
+ */
+ data object GiveFeedbackClick : OtherAction()
+
/**
* Indicates that the user clicked the Sync Now button.
*/
diff --git a/app/src/main/res/values/strings_non_localized.xml b/app/src/main/res/values/strings_non_localized.xml
index a8403490fa..bf2781c7c6 100644
--- a/app/src/main/res/values/strings_non_localized.xml
+++ b/app/src/main/res/values/strings_non_localized.xml
@@ -3,4 +3,8 @@
Bitwarden Dev
Duo
Duo (%1$s)
+
+ Give feedback
+ Continue to Give feedback?
+ Select continue to provide feedback on your experience in a web form.
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherScreenTest.kt
index c708281ebd..09fe4d4deb 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherScreenTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherScreenTest.kt
@@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.ClearClipboardFrequenc
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
+import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
import io.mockk.every
import io.mockk.mockk
@@ -32,6 +33,9 @@ class OtherScreenTest : BaseComposeTest() {
every { eventFlow } returns mutableEventFlow
every { stateFlow } returns mutableStateFlow
}
+ private val intentManager: IntentManager = mockk {
+ every { getShareDataFromIntent(any()) } returns null
+ }
@Before
fun setup() {
@@ -39,6 +43,7 @@ class OtherScreenTest : BaseComposeTest() {
OtherScreen(
viewModel = viewModel,
onNavigateBack = { haveCalledNavigateBack = true },
+ intentManager = intentManager,
)
}
}
@@ -144,6 +149,22 @@ class OtherScreenTest : BaseComposeTest() {
.assertIsDisplayed()
.assert(hasAnyAncestor(isDialog()))
}
+
+ @Suppress("MaxLineLength")
+ @Test
+ fun `on give feedback click should display confirmation dialog and confirm click should emit GiveFeedbackClick`() {
+ composeTestRule.onNode(isDialog()).assertDoesNotExist()
+ composeTestRule.onNodeWithText("Give feedback").performClick()
+ composeTestRule.onNode(isDialog()).assertExists()
+ composeTestRule
+ .onAllNodesWithText("Continue")
+ .filterToOne(hasAnyAncestor(isDialog()))
+ .performClick()
+ composeTestRule.onNode(isDialog()).assertDoesNotExist()
+ verify {
+ viewModel.trySendAction(OtherAction.GiveFeedbackClick)
+ }
+ }
}
private val DEFAULT_STATE = OtherState(
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherViewModelTest.kt
index d85f2d9740..1555694994 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherViewModelTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/other/OtherViewModelTest.kt
@@ -108,6 +108,15 @@ class OtherViewModelTest : BaseViewModelTest() {
}
}
+ @Test
+ fun `on GiveFeedbackClick should emit NavigateToFeedbackForm`() = runTest {
+ val viewModel = createViewModel()
+ viewModel.eventFlow.test {
+ viewModel.trySendAction(OtherAction.GiveFeedbackClick)
+ assertEquals(OtherEvent.NavigateToFeedbackForm, awaitItem())
+ }
+ }
+
@Test
fun `on ClearClipboardFrequencyChange should update state`() = runTest {
val viewModel = createViewModel()