summaryrefslogtreecommitdiffhomepage
path: root/android/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'android/app/src')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ReportProblemScreen.kt43
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt15
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModel.kt17
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ViewLogsViewModel.kt3
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemModelTest.kt117
5 files changed, 163 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 007b7bacf2..6f030f37fe 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
@@ -113,18 +113,29 @@ fun ReportProblemScreen(
) {
var email by rememberSaveable { mutableStateOf("") }
var description by rememberSaveable { mutableStateOf("") }
+
+ // Dialog to show sending states
+ if (uiState.sendingState != null) {
+ ShowReportProblemStateDialog(
+ uiState.sendingState,
+ onDismiss = onClearSendResult,
+ onClearForm = {
+ email = ""
+ description = ""
+ },
+ retry = { onSendReport(email, description) }
+ )
+ }
+
+ // Dialog to show confirm if no email was added
+ if (uiState.showConfirmNoEmail) {
+ ReportProblemNoEmailDialog(
+ onDismiss = onDismissNoEmailDialog,
+ onConfirm = { onSendReport(email, description) }
+ )
+ }
+
Surface(color = MaterialTheme.colorScheme.background) {
- if (uiState.sendingState != null) {
- ShowReportProblemStateDialog(
- uiState.sendingState,
- onDismiss = onClearSendResult,
- onClearForm = {
- email = ""
- description = ""
- },
- retry = { onSendReport(email, description) }
- )
- }
Column(
modifier =
Modifier.padding(
@@ -134,6 +145,7 @@ fun ReportProblemScreen(
verticalArrangement = Arrangement.spacedBy(Dimens.mediumPadding)
) {
Text(text = stringResource(id = R.string.problem_report_description))
+
TextField(
modifier = Modifier.fillMaxWidth(),
value = email,
@@ -143,6 +155,7 @@ fun ReportProblemScreen(
placeholder = { Text(text = stringResource(id = R.string.user_email_hint)) },
colors = mullvadWhiteTextFieldColors()
)
+
TextField(
modifier = Modifier.fillMaxWidth().weight(1f),
value = description,
@@ -160,6 +173,7 @@ fun ReportProblemScreen(
onClick = onNavigateToViewLogs,
text = stringResource(id = R.string.view_logs)
)
+
ActionButton(
colors =
ButtonDefaults.buttonColors(
@@ -170,13 +184,6 @@ fun ReportProblemScreen(
isEnabled = description.isNotEmpty(),
text = stringResource(id = R.string.send)
)
-
- if (uiState.showConfirmNoEmail) {
- ReportProblemNoEmailDialog(
- onDismiss = onDismissNoEmailDialog,
- onConfirm = { onSendReport(email, description) }
- )
- }
}
}
}
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 ca9f4c7e23..67eaeca48d 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
@@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn.dataproxy
import android.content.Context
import java.io.File
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -18,9 +19,10 @@ sealed interface SendProblemReportResult {
}
}
-data class UserReport(val email: String, val message: String)
+data class UserReport(val email: String?, val message: String)
+
+class MullvadProblemReport(context: Context, val dispatcher: CoroutineDispatcher = Dispatchers.IO) {
-class MullvadProblemReport(context: Context) {
private val cacheDirectory = File(context.cacheDir.toURI())
private val logDirectory = File(context.filesDir.toURI())
private val logsPath = File(logDirectory, PROBLEM_REPORT_LOGS_FILE)
@@ -30,7 +32,7 @@ class MullvadProblemReport(context: Context) {
}
suspend fun collectLogs(): Boolean =
- withContext(Dispatchers.IO) {
+ withContext(dispatcher) {
// Delete any old report
deleteLogs()
@@ -44,9 +46,9 @@ class MullvadProblemReport(context: Context) {
}
val sentSuccessfully =
- withContext(Dispatchers.IO) {
+ withContext(dispatcher) {
sendProblemReport(
- userReport.email,
+ userReport.email ?: "",
userReport.message,
logsPath.absolutePath,
cacheDirectory.absolutePath
@@ -73,8 +75,7 @@ class MullvadProblemReport(context: Context) {
}
}
- private fun logsExists() =
- logsPath.exists()
+ private fun logsExists() = logsPath.exists()
fun deleteLogs() {
logsPath.delete()
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 75b65eaca6..5b69ec9317 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
@@ -38,7 +38,8 @@ class ReportProblemViewModel(private val mullvadProblemReporter: MullvadProblemR
) {
viewModelScope.launch {
val userEmail = email.trim()
- if (shouldShowConfirmNoEmail(userEmail)) {
+ val nullableEmail = if (email.isEmpty()) null else userEmail
+ if (shouldShowConfirmNoEmail(nullableEmail)) {
_uiState.update { it.copy(showConfirmNoEmail = true) }
} else {
_uiState.update {
@@ -46,10 +47,14 @@ class ReportProblemViewModel(private val mullvadProblemReporter: MullvadProblemR
}
// Ensure we show loading for at least 500 ms
- val deferredResult = async { mullvadProblemReporter.sendReport(UserReport(userEmail, description)) }
+ val deferredResult = async {
+ mullvadProblemReporter.sendReport(UserReport(nullableEmail, description))
+ }
delay(MINIMUM_LOADING_SPINNER_TIME_MILLIS)
- _uiState.update { it.copy(sendingState = deferredResult.await().toUiResult(userEmail)) }
+ _uiState.update {
+ it.copy(sendingState = deferredResult.await().toUiResult(nullableEmail))
+ }
}
}
}
@@ -62,12 +67,12 @@ class ReportProblemViewModel(private val mullvadProblemReporter: MullvadProblemR
_uiState.update { it.copy(showConfirmNoEmail = false) }
}
- private fun shouldShowConfirmNoEmail(userEmail: String): Boolean =
- userEmail.isEmpty() &&
+ private fun shouldShowConfirmNoEmail(userEmail: String?): Boolean =
+ userEmail.isNullOrEmpty() &&
!uiState.value.showConfirmNoEmail &&
uiState.value.sendingState !is SendingReportUiState
- private fun SendProblemReportResult.toUiResult(email: String): SendingReportUiState =
+ private fun SendProblemReportResult.toUiResult(email: String?): SendingReportUiState =
when (this) {
is SendProblemReportResult.Error -> SendingReportUiState.Error(this)
SendProblemReportResult.Success -> SendingReportUiState.Success(email)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ViewLogsViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ViewLogsViewModel.kt
index cff6bf6043..8ceae43289 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ViewLogsViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ViewLogsViewModel.kt
@@ -20,7 +20,8 @@ class ViewLogsViewModel(private val mullvadProblemReporter: MullvadProblemReport
viewModelScope.launch {
// Loading this much text takes a while, so we show a loading indicator until the
// fragment transitions is done. I'd very much prefer to use LazyColumn in the view
- // which would make the loading way faster but then the SelectionContainer is broken.
+ // which would make the loading way faster but then the SelectionContainer is broken and
+ // text would not be copyable.
delay(500)
_uiState.update {
it.copy(
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemModelTest.kt
new file mode 100644
index 0000000000..4f91189d0a
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemModelTest.kt
@@ -0,0 +1,117 @@
+package net.mullvad.mullvadvpn.viewmodel
+
+import androidx.lifecycle.viewModelScope
+import app.cash.turbine.test
+import io.mockk.MockKAnnotations
+import io.mockk.coEvery
+import io.mockk.impl.annotations.MockK
+import kotlin.test.assertEquals
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport
+import net.mullvad.mullvadvpn.dataproxy.SendProblemReportResult
+import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class ReportProblemModelTest {
+ @get:Rule val testCoroutineRule = TestCoroutineRule()
+
+ @MockK private lateinit var mockMullvadProblemReport: MullvadProblemReport
+
+ private lateinit var viewModel: ReportProblemViewModel
+
+ @Before
+ fun setUp() {
+ MockKAnnotations.init(this)
+ coEvery { mockMullvadProblemReport.collectLogs() } returns true
+ viewModel = ReportProblemViewModel(mockMullvadProblemReport)
+ }
+
+ @After
+ fun tearDown() {
+ viewModel.viewModelScope.coroutineContext.cancel()
+ }
+
+ @Test
+ fun sendReportFailedToCollectLogs() = runTest {
+ // Arrange
+ coEvery { mockMullvadProblemReport.sendReport(any()) } returns
+ SendProblemReportResult.Error.CollectLog
+ val email = "my@email.com"
+
+ // Act, Assert
+ viewModel.uiState.test {
+ assertEquals(null, awaitItem().sendingState)
+ viewModel.sendReport(email, "My description")
+ assertEquals(SendingReportUiState.Sending, awaitItem().sendingState)
+ assertEquals(
+ SendingReportUiState.Error(SendProblemReportResult.Error.CollectLog),
+ awaitItem().sendingState
+ )
+ }
+ }
+
+ @Test
+ fun sendReportFailedToSendReport() = runTest {
+ // Arrange
+ coEvery { mockMullvadProblemReport.sendReport(any()) } returns
+ SendProblemReportResult.Error.SendReport
+ val email = "my@email.com"
+
+ // Act, Assert
+ viewModel.uiState.test {
+ assertEquals(null, awaitItem().sendingState)
+ viewModel.sendReport(email, "My description")
+ assertEquals(SendingReportUiState.Sending, awaitItem().sendingState)
+ assertEquals(
+ SendingReportUiState.Error(SendProblemReportResult.Error.SendReport),
+ awaitItem().sendingState
+ )
+ }
+ }
+
+ @Test
+ fun sendReportWithoutEmailSuccessfully() = runTest {
+ // Arrange
+ coEvery { mockMullvadProblemReport.sendReport(any()) } returns
+ SendProblemReportResult.Success
+ val email = ""
+
+ // Act, Assert
+ viewModel.uiState.test {
+ assertEquals(ReportProblemUiState(false, null), awaitItem())
+ viewModel.sendReport(email, "My description")
+ assertEquals(ReportProblemUiState(true, null), awaitItem())
+ viewModel.sendReport(email, "My description")
+ assertEquals(ReportProblemUiState(false, SendingReportUiState.Sending), awaitItem())
+ assertEquals(
+ ReportProblemUiState(false, SendingReportUiState.Success(null)),
+ awaitItem()
+ )
+ }
+ }
+
+ @Test
+ fun sendReportSuccessfully() = runTest {
+ // Arrange
+ coEvery { mockMullvadProblemReport.collectLogs() } returns true
+ coEvery { mockMullvadProblemReport.sendReport(any()) } returns
+ SendProblemReportResult.Success
+ val email = "my@email.com"
+
+ // Act, Assert
+ viewModel.uiState.test {
+ assertEquals(awaitItem(), ReportProblemUiState(false, null))
+ viewModel.sendReport(email, "My description")
+
+ assertEquals(awaitItem(), ReportProblemUiState(false, SendingReportUiState.Sending))
+ assertEquals(
+ awaitItem(),
+ ReportProblemUiState(false, SendingReportUiState.Success(email))
+ )
+ }
+ }
+}