summaryrefslogtreecommitdiffhomepage
path: root/android/app/src
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2023-11-17 13:13:03 +0100
committerAlbin <albin@mullvad.net>2023-11-17 16:19:07 +0100
commitf5716edc8eb60adb42a9635f4205511a146f8eb3 (patch)
treee671d5cc254d8d733ce3af7e8e73ecf668be0ba5 /android/app/src
parent91b71ee128888a5319a8b8e0509e5ddddc4c5a69 (diff)
downloadmullvadvpn-f5716edc8eb60adb42a9635f4205511a146f8eb3.tar.xz
mullvadvpn-f5716edc8eb60adb42a9635f4205511a146f8eb3.zip
Persist problem reports in memory until successfuly submitted
Diffstat (limited to 'android/app/src')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ReportProblemScreen.kt26
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/ProblemReportRepository.kt19
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ProblemReportFragment.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModel.kt62
6 files changed, 88 insertions, 32 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ReportProblemScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ReportProblemScreen.kt
index fbfb632e23..219bc6b8d3 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ReportProblemScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ReportProblemScreen.kt
@@ -15,9 +15,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -94,16 +92,15 @@ fun ReportProblemScreen(
onDismissNoEmailDialog: () -> Unit = {},
onClearSendResult: () -> Unit = {},
onNavigateToViewLogs: () -> Unit = {},
+ updateEmail: (String) -> Unit = {},
+ updateDescription: (String) -> Unit = {},
onBackClick: () -> Unit = {}
) {
- var email by rememberSaveable { mutableStateOf("") }
- var description by rememberSaveable { mutableStateOf("") }
-
// Dialog to show confirm if no email was added
if (uiState.showConfirmNoEmail) {
ReportProblemNoEmailDialog(
onDismiss = onDismissNoEmailDialog,
- onConfirm = { onSendReport(email, description) }
+ onConfirm = { onSendReport(uiState.email, uiState.description) }
)
}
@@ -124,7 +121,10 @@ fun ReportProblemScreen(
when (uiState.sendingState) {
SendingReportUiState.Sending -> SendingContent()
is SendingReportUiState.Error ->
- ErrorContent({ onSendReport(email, description) }, onClearSendResult)
+ ErrorContent(
+ { onSendReport(uiState.email, uiState.description) },
+ onClearSendResult
+ )
is SendingReportUiState.Success -> SentContent(uiState.sendingState)
}
return@ScaffoldWithMediumTopBar
@@ -146,8 +146,8 @@ fun ReportProblemScreen(
TextField(
modifier = Modifier.fillMaxWidth(),
- value = email,
- onValueChange = { email = it },
+ value = uiState.email,
+ onValueChange = updateEmail,
maxLines = 1,
singleLine = true,
placeholder = { Text(text = stringResource(id = R.string.user_email_hint)) },
@@ -156,8 +156,8 @@ fun ReportProblemScreen(
TextField(
modifier = Modifier.fillMaxWidth().weight(1f),
- value = description,
- onValueChange = { description = it },
+ value = uiState.description,
+ onValueChange = updateDescription,
placeholder = { Text(stringResource(R.string.user_message_hint)) },
colors = mullvadWhiteTextFieldColors()
)
@@ -169,8 +169,8 @@ fun ReportProblemScreen(
)
Spacer(modifier = Modifier.height(Dimens.buttonSpacing))
VariantButton(
- onClick = { onSendReport(email, description) },
- isEnabled = description.isNotEmpty(),
+ onClick = { onSendReport(uiState.email, uiState.description) },
+ isEnabled = uiState.description.isNotEmpty(),
text = stringResource(id = R.string.send)
)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt
index 67eaeca48d..02c2d81a3d 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt
@@ -19,7 +19,7 @@ sealed interface SendProblemReportResult {
}
}
-data class UserReport(val email: String?, val message: String)
+data class UserReport(val email: String?, val description: String)
class MullvadProblemReport(context: Context, val dispatcher: CoroutineDispatcher = Dispatchers.IO) {
@@ -49,7 +49,7 @@ class MullvadProblemReport(context: Context, val dispatcher: CoroutineDispatcher
withContext(dispatcher) {
sendProblemReport(
userReport.email ?: "",
- userReport.message,
+ userReport.description,
logsPath.absolutePath,
cacheDirectory.absolutePath
)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
index 5be527ac0c..56df6699de 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
@@ -18,6 +18,7 @@ import net.mullvad.mullvadvpn.repository.ChangelogRepository
import net.mullvad.mullvadvpn.repository.DeviceRepository
import net.mullvad.mullvadvpn.repository.InAppNotificationController
import net.mullvad.mullvadvpn.repository.PrivacyDisclaimerRepository
+import net.mullvad.mullvadvpn.repository.ProblemReportRepository
import net.mullvad.mullvadvpn.repository.SettingsRepository
import net.mullvad.mullvadvpn.ui.serviceconnection.RelayListListener
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
@@ -116,6 +117,8 @@ val uiModule = module {
}
}
+ single { ProblemReportRepository() }
+
// View models
viewModel { AccountViewModel(get(), get(), get(), get()) }
viewModel {
@@ -131,7 +134,7 @@ val uiModule = module {
viewModel { VoucherDialogViewModel(get(), get()) }
viewModel { VpnSettingsViewModel(get(), get(), get(), get(), get()) }
viewModel { WelcomeViewModel(get(), get(), get(), get()) }
- viewModel { ReportProblemViewModel(get()) }
+ viewModel { ReportProblemViewModel(get(), get()) }
viewModel { ViewLogsViewModel(get()) }
viewModel { OutOfTimeViewModel(get(), get(), get(), get()) }
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/ProblemReportRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/ProblemReportRepository.kt
new file mode 100644
index 0000000000..3086ee9b80
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/ProblemReportRepository.kt
@@ -0,0 +1,19 @@
+package net.mullvad.mullvadvpn.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import net.mullvad.mullvadvpn.dataproxy.UserReport
+
+class ProblemReportRepository {
+ private val _problemReport = MutableStateFlow(UserReport("", ""))
+ val problemReport: StateFlow<UserReport> = _problemReport.asStateFlow()
+
+ fun setEmail(email: String) {
+ _problemReport.value = _problemReport.value.copy(email = email)
+ }
+
+ fun setDescription(description: String) {
+ _problemReport.value = _problemReport.value.copy(description = description)
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ProblemReportFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ProblemReportFragment.kt
index 397039719c..fd489e563f 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ProblemReportFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ProblemReportFragment.kt
@@ -30,7 +30,9 @@ class ProblemReportFragment : BaseFragment() {
onSendReport = { email, description -> vm.sendReport(email, description) },
onDismissNoEmailDialog = vm::dismissConfirmNoEmail,
onClearSendResult = vm::clearSendResult,
- onNavigateToViewLogs = { showLogs() }
+ onNavigateToViewLogs = { showLogs() },
+ updateEmail = vm::updateEmail,
+ updateDescription = vm::updateDescription
) {
activity?.onBackPressed()
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModel.kt
index a7daf9e8d9..82e66b0c4b 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModel.kt
@@ -5,17 +5,21 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.constant.MINIMUM_LOADING_TIME_MILLIS
import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport
import net.mullvad.mullvadvpn.dataproxy.SendProblemReportResult
import net.mullvad.mullvadvpn.dataproxy.UserReport
+import net.mullvad.mullvadvpn.repository.ProblemReportRepository
data class ReportProblemUiState(
val showConfirmNoEmail: Boolean = false,
- val sendingState: SendingReportUiState? = null
+ val sendingState: SendingReportUiState? = null,
+ val email: String = "",
+ val description: String = "",
)
sealed interface SendingReportUiState {
@@ -26,11 +30,28 @@ sealed interface SendingReportUiState {
data class Error(val error: SendProblemReportResult.Error) : SendingReportUiState
}
-class ReportProblemViewModel(private val mullvadProblemReporter: MullvadProblemReport) :
- ViewModel() {
+class ReportProblemViewModel(
+ private val mullvadProblemReporter: MullvadProblemReport,
+ private val problemReportRepository: ProblemReportRepository
+) : ViewModel() {
- private val _uiState = MutableStateFlow(ReportProblemUiState())
- val uiState = _uiState.asStateFlow()
+ private val showConfirmNoEmail = MutableStateFlow(false)
+ private val sendingState: MutableStateFlow<SendingReportUiState?> = MutableStateFlow(null)
+
+ val uiState =
+ combine(
+ showConfirmNoEmail,
+ sendingState,
+ problemReportRepository.problemReport,
+ ) { showConfirmNoEmail, pendingState, userReport ->
+ ReportProblemUiState(
+ showConfirmNoEmail = showConfirmNoEmail,
+ sendingState = pendingState,
+ email = userReport.email ?: "",
+ description = userReport.description,
+ )
+ }
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ReportProblemUiState())
fun sendReport(
email: String,
@@ -40,11 +61,10 @@ class ReportProblemViewModel(private val mullvadProblemReporter: MullvadProblemR
val userEmail = email.trim()
val nullableEmail = if (email.isEmpty()) null else userEmail
if (shouldShowConfirmNoEmail(nullableEmail)) {
- _uiState.update { it.copy(showConfirmNoEmail = true) }
+ showConfirmNoEmail.tryEmit(true)
} else {
- _uiState.update {
- it.copy(sendingState = SendingReportUiState.Sending, showConfirmNoEmail = false)
- }
+ sendingState.tryEmit(SendingReportUiState.Sending)
+ showConfirmNoEmail.tryEmit(false)
// Ensure we show loading for at least MINIMUM_LOADING_TIME_MILLIS
val deferredResult = async {
@@ -52,19 +72,31 @@ class ReportProblemViewModel(private val mullvadProblemReporter: MullvadProblemR
}
delay(MINIMUM_LOADING_TIME_MILLIS)
- _uiState.update {
- it.copy(sendingState = deferredResult.await().toUiResult(nullableEmail))
+ val result = deferredResult.await()
+ // Clear saved problem report if report was sent successfully
+ if (result is SendProblemReportResult.Success) {
+ problemReportRepository.setEmail("")
+ problemReportRepository.setDescription("")
}
+ sendingState.tryEmit(deferredResult.await().toUiResult(nullableEmail))
}
}
}
fun clearSendResult() {
- _uiState.update { it.copy(sendingState = null) }
+ sendingState.tryEmit(null)
}
fun dismissConfirmNoEmail() {
- _uiState.update { it.copy(showConfirmNoEmail = false) }
+ showConfirmNoEmail.tryEmit(false)
+ }
+
+ fun updateEmail(email: String) {
+ problemReportRepository.setEmail(email)
+ }
+
+ fun updateDescription(description: String) {
+ problemReportRepository.setDescription(description)
}
private fun shouldShowConfirmNoEmail(userEmail: String?): Boolean =