diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2023-11-17 13:13:03 +0100 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2023-11-17 16:19:07 +0100 |
| commit | f5716edc8eb60adb42a9635f4205511a146f8eb3 (patch) | |
| tree | e671d5cc254d8d733ce3af7e8e73ecf668be0ba5 /android/app/src | |
| parent | 91b71ee128888a5319a8b8e0509e5ddddc4c5a69 (diff) | |
| download | mullvadvpn-f5716edc8eb60adb42a9635f4205511a146f8eb3.tar.xz mullvadvpn-f5716edc8eb60adb42a9635f4205511a146f8eb3.zip | |
Persist problem reports in memory until successfuly submitted
Diffstat (limited to 'android/app/src')
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 = |
