summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-11-03 17:17:02 +0100
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-11-03 17:17:02 +0100
commitd0bf6fb86e8cdf561f3443cfdb8f53e5928749f9 (patch)
tree488ac2ee820453fb751a40eca668aeaa4e9f5037
parent03261020fb1ee4599427fc2543100da0bf1293c3 (diff)
parent27491ea74e763b908546313d01164169d9f06d53 (diff)
downloadmullvadvpn-d0bf6fb86e8cdf561f3443cfdb8f53e5928749f9.tar.xz
mullvadvpn-d0bf6fb86e8cdf561f3443cfdb8f53e5928749f9.zip
Merge branch 'improve-include-account-id-droid-2274'
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CheckboxCell.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/ReportProblemUiStatePreviewParameterProvider.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ReportProblemScreen.kt270
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/ClickableString.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModel.kt21
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModelTest.kt1
-rw-r--r--android/lib/resource/src/main/res/values/strings.xml6
-rw-r--r--android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt1
-rw-r--r--desktop/packages/mullvad-vpn/locales/messages.pot14
-rw-r--r--mullvad-problem-report/src/lib.rs2
11 files changed, 246 insertions, 95 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CheckboxCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CheckboxCell.kt
index 633b32e420..75b6182039 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CheckboxCell.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CheckboxCell.kt
@@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import net.mullvad.mullvadvpn.lib.theme.AppTheme
@@ -31,6 +32,7 @@ internal fun CheckboxCell(
checked: Boolean,
enabled: Boolean = true,
onCheckedChange: (Boolean) -> Unit,
+ textStyle: TextStyle = MaterialTheme.typography.bodyLarge,
background: Color = MaterialTheme.colorScheme.surfaceContainerHighest,
startPadding: Dp = Dimens.smallPadding,
endPadding: Dp = Dimens.cellEndPadding,
@@ -50,7 +52,7 @@ internal fun CheckboxCell(
Text(
text = title,
- style = MaterialTheme.typography.bodyLarge,
+ style = textStyle,
color = MaterialTheme.colorScheme.onSurface,
modifier =
Modifier.weight(1f)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/ReportProblemUiStatePreviewParameterProvider.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/ReportProblemUiStatePreviewParameterProvider.kt
index f878ae9d37..b5a32dcb91 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/ReportProblemUiStatePreviewParameterProvider.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/ReportProblemUiStatePreviewParameterProvider.kt
@@ -11,6 +11,12 @@ class ReportProblemUiStatePreviewParameterProvider :
get() =
sequenceOf(
ReportProblemUiState(showIncludeAccountId = true),
+ ReportProblemUiState(showIncludeAccountId = true, includeAccountId = true),
+ ReportProblemUiState(
+ showIncludeAccountId = true,
+ includeAccountId = true,
+ showIncludeAccountWarningMessage = true,
+ ),
ReportProblemUiState(sendingState = SendingReportUiState.Sending),
ReportProblemUiState(sendingState = SendingReportUiState.Success("email@mail.com")),
ReportProblemUiState(
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 ca06b04e74..28ed5e18c6 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
@@ -1,5 +1,8 @@
package net.mullvad.mullvadvpn.compose.screen
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -10,26 +13,22 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material3.Icon
-import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
@@ -38,6 +37,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -55,24 +55,29 @@ import com.ramcosta.composedestinations.result.ResultRecipient
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.button.PrimaryButton
import net.mullvad.mullvadvpn.compose.button.VariantButton
+import net.mullvad.mullvadvpn.compose.cell.CheckboxCell
import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorLarge
import net.mullvad.mullvadvpn.compose.component.NavigateBackIconButton
import net.mullvad.mullvadvpn.compose.component.ScaffoldWithMediumTopBar
+import net.mullvad.mullvadvpn.compose.extensions.createUriHook
import net.mullvad.mullvadvpn.compose.preview.ReportProblemUiStatePreviewParameterProvider
import net.mullvad.mullvadvpn.compose.textfield.mullvadWhiteTextFieldColors
import net.mullvad.mullvadvpn.compose.transitions.SlideInFromRightTransition
import net.mullvad.mullvadvpn.compose.util.CollectSideEffectWithLifecycle
import net.mullvad.mullvadvpn.compose.util.SecureScreenWhileInView
+import net.mullvad.mullvadvpn.compose.util.clickableAnnotatedString
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
-import net.mullvad.mullvadvpn.lib.ui.designsystem.Checkbox
+import net.mullvad.mullvadvpn.lib.theme.color.warning
+import net.mullvad.mullvadvpn.lib.ui.component.ExpandChevron
+import net.mullvad.mullvadvpn.util.appendHideNavOnPlayBuild
import net.mullvad.mullvadvpn.viewmodel.ReportProblemSideEffect
import net.mullvad.mullvadvpn.viewmodel.ReportProblemUiState
import net.mullvad.mullvadvpn.viewmodel.ReportProblemViewModel
import net.mullvad.mullvadvpn.viewmodel.SendingReportUiState
import org.koin.androidx.compose.koinViewModel
-@Preview("Default|Sending|Success|Error")
+@Preview("Default|IncludeAccountNumber|ShowWarning|Sending|Success|Error")
@Composable
private fun PreviewReportProblemScreen(
@PreviewParameter(ReportProblemUiStatePreviewParameterProvider::class)
@@ -87,6 +92,7 @@ private fun PreviewReportProblemScreen(
onEmailChanged = {},
onDescriptionChanged = {},
onIncludeAccountIdCheckChange = {},
+ toggleShowIncludeAccountInformationWarningMessage = {},
onBackClick = {},
)
}
@@ -126,6 +132,8 @@ fun ReportProblem(
onEmailChanged = vm::updateEmail,
onDescriptionChanged = vm::updateDescription,
onIncludeAccountIdCheckChange = vm::onIncludeAccountIdCheckChange,
+ toggleShowIncludeAccountInformationWarningMessage =
+ vm::showIncludeAccountInformationWarningMessage,
onBackClick = dropUnlessResumed { navigator.navigateUp() },
)
}
@@ -139,6 +147,7 @@ private fun ReportProblemScreen(
onEmailChanged: (String) -> Unit,
onDescriptionChanged: (String) -> Unit,
onIncludeAccountIdCheckChange: (Boolean) -> Unit,
+ toggleShowIncludeAccountInformationWarningMessage: (Boolean) -> Unit,
onBackClick: () -> Unit,
) {
@@ -172,99 +181,207 @@ private fun ReportProblemScreen(
end = Dimens.sideMargin,
bottom = Dimens.screenBottomMargin,
)
- .height(IntrinsicSize.Max),
+ .height(IntrinsicSize.Max)
+ .animateContentSize(),
verticalArrangement = Arrangement.spacedBy(Dimens.mediumPadding),
) {
- Description(
+ InputContent(
state = state,
- onIncludeAccountIdCheckChange = onIncludeAccountIdCheckChange,
- )
-
- TextField(
- modifier = Modifier.fillMaxWidth(),
- value = state.email,
- onValueChange = onEmailChanged,
- maxLines = 1,
- singleLine = true,
- placeholder = { Text(text = stringResource(id = R.string.user_email_hint)) },
- colors = mullvadWhiteTextFieldColors(),
- keyboardOptions =
- KeyboardOptions(
- autoCorrectEnabled = false,
- keyboardType = KeyboardType.Email,
- imeAction = ImeAction.Next,
- ),
- )
-
- ProblemMessageTextField(
- modifier = Modifier.weight(1f),
- value = state.description,
+ onEmailChanged = onEmailChanged,
onDescriptionChanged = onDescriptionChanged,
+ onIncludeAccountIdCheckChange = onIncludeAccountIdCheckChange,
+ toggleShowIncludeAccountInformationWarningMessage =
+ toggleShowIncludeAccountInformationWarningMessage,
+ onNavigateToViewLogs = onNavigateToViewLogs,
+ onSendReport = onSendReport,
)
-
- Column {
- PrimaryButton(
- onClick = onNavigateToViewLogs,
- text = stringResource(id = R.string.view_logs),
- )
- Spacer(modifier = Modifier.height(Dimens.buttonSpacing))
- VariantButton(
- onClick = onSendReport,
- isEnabled = state.description.isNotEmpty(),
- text = stringResource(id = R.string.send),
- )
- }
}
}
}
}
@Composable
-private fun Description(
+private fun InputContent(
state: ReportProblemUiState,
+ onEmailChanged: (String) -> Unit,
+ onDescriptionChanged: (String) -> Unit,
onIncludeAccountIdCheckChange: (Boolean) -> Unit,
+ toggleShowIncludeAccountInformationWarningMessage: (Boolean) -> Unit,
+ onNavigateToViewLogs: () -> Unit,
+ onSendReport: () -> Unit,
) {
+ Description()
+
+ TextField(
+ modifier = Modifier.fillMaxWidth(),
+ value = state.email,
+ onValueChange = onEmailChanged,
+ maxLines = 1,
+ singleLine = true,
+ placeholder = { Text(text = stringResource(id = R.string.user_email_hint)) },
+ colors = mullvadWhiteTextFieldColors(),
+ keyboardOptions =
+ KeyboardOptions(
+ autoCorrectEnabled = false,
+ keyboardType = KeyboardType.Email,
+ imeAction = ImeAction.Next,
+ ),
+ )
+
+ ProblemMessageTextField(value = state.description, onDescriptionChanged = onDescriptionChanged)
+
+ if (state.showIncludeAccountId) {
+ IncludeAccountInformationCheckBox(
+ includeAccountInformation = state.includeAccountId,
+ onIncludeAccountInformationCheckChange = onIncludeAccountIdCheckChange,
+ toggleShowIncludeAccountInformationWarningMessage =
+ toggleShowIncludeAccountInformationWarningMessage,
+ showIncludeAccountInformationWarningMessage = state.showIncludeAccountWarningMessage,
+ isPlayBuild = state.isPlayBuild,
+ )
+ }
+
+ Column {
+ PrimaryButton(
+ onClick = onNavigateToViewLogs,
+ text = stringResource(id = R.string.view_logs),
+ )
+ Spacer(modifier = Modifier.height(Dimens.buttonSpacing))
+ VariantButton(
+ onClick = onSendReport,
+ isEnabled = state.description.isNotEmpty(),
+ text = stringResource(id = R.string.send),
+ )
+ }
+}
+
+@Composable
+private fun Description() {
Column {
Text(
text = stringResource(id = R.string.problem_report_description),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.labelLarge,
)
-
- if (state.showIncludeAccountId) {
- IncludeAccountInformationCheckBox(
- includeAccountInformation = state.includeAccountId,
- onIncludeAccountInformationCheckChange = onIncludeAccountIdCheckChange,
- )
- }
}
}
@Composable
private fun IncludeAccountInformationCheckBox(
includeAccountInformation: Boolean,
+ showIncludeAccountInformationWarningMessage: Boolean,
onIncludeAccountInformationCheckChange: (Boolean) -> Unit,
+ toggleShowIncludeAccountInformationWarningMessage: (Boolean) -> Unit,
+ isPlayBuild: Boolean,
) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
+ val openPrivacyPolicy =
+ LocalUriHandler.current.createUriHook(
+ stringResource(R.string.privacy_policy_url).appendHideNavOnPlayBuild(isPlayBuild)
+ )
+ Column(
modifier =
- Modifier.clickable {
- onIncludeAccountInformationCheckChange(!includeAccountInformation)
- }
- .padding(vertical = Dimens.smallPadding)
- .fillMaxWidth(),
+ Modifier.animateContentSize()
+ .border(width = Dp.Hairline, color = MaterialTheme.colorScheme.primary)
+ .padding(bottom = if (includeAccountInformation) Dimens.smallPadding else 0.dp)
) {
- // To align the checkbox with the text
- CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
- Checkbox(
- modifier = Modifier.padding(end = Dimens.smallPadding),
- checked = includeAccountInformation,
- onCheckedChange = onIncludeAccountInformationCheckChange,
+ CheckboxCell(
+ title = stringResource(R.string.include_account_token_checkbox_text),
+ checked = includeAccountInformation,
+ background = MaterialTheme.colorScheme.surface,
+ startPadding = 0.dp,
+ textStyle = MaterialTheme.typography.bodyMedium,
+ onCheckedChange = onIncludeAccountInformationCheckChange,
+ )
+ if (includeAccountInformation) {
+ AccountInformationWarning(
+ showIncludeAccountInformationWarningMessage =
+ showIncludeAccountInformationWarningMessage,
+ toggleShowIncludeAccountInformationWarningMessage =
+ toggleShowIncludeAccountInformationWarningMessage,
+ openPrivacyPolicy = openPrivacyPolicy,
+ )
+ }
+ }
+}
+
+@Composable
+private fun AccountInformationWarning(
+ showIncludeAccountInformationWarningMessage: Boolean,
+ toggleShowIncludeAccountInformationWarningMessage: (Boolean) -> Unit,
+ openPrivacyPolicy: () -> Unit,
+) {
+ Column(
+ modifier =
+ Modifier.padding(horizontal = Dimens.tinyPadding)
+ .background(MaterialTheme.colorScheme.surfaceDim)
+ .animateContentSize()
+ ) {
+ Row(
+ modifier =
+ Modifier.fillMaxWidth()
+ .clickable(
+ onClick = {
+ toggleShowIncludeAccountInformationWarningMessage(
+ !showIncludeAccountInformationWarningMessage
+ )
+ }
+ )
+ .padding(
+ top = Dimens.smallPadding,
+ start = Dimens.smallPadding,
+ end = Dimens.smallPadding,
+ bottom =
+ if (showIncludeAccountInformationWarningMessage) {
+ Dimens.tinyPadding
+ } else {
+ Dimens.smallPadding
+ },
+ )
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.ErrorOutline,
+ contentDescription = stringResource(R.string.include_account_token_warning_title),
+ tint = MaterialTheme.colorScheme.warning,
)
Text(
- text = stringResource(R.string.include_account_token_checkbox_text),
- color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier =
+ Modifier.padding(horizontal = Dimens.smallPadding)
+ .weight(1f)
+ .align(Alignment.CenterVertically),
style = MaterialTheme.typography.labelLarge,
+ text = stringResource(R.string.include_account_token_warning_title),
+ )
+ ExpandChevron(isExpanded = showIncludeAccountInformationWarningMessage)
+ }
+ if (showIncludeAccountInformationWarningMessage) {
+ Text(
+ modifier =
+ Modifier.padding(horizontal = Dimens.smallPadding)
+ .padding(bottom = Dimens.smallPadding),
+ style = MaterialTheme.typography.bodySmall,
+ text =
+ clickableAnnotatedString(
+ text =
+ buildString {
+ appendLine(
+ stringResource(
+ R.string.include_account_token_warning_message_first
+ )
+ )
+ append(
+ stringResource(
+ R.string.include_account_token_warning_message_second
+ )
+ )
+ },
+ argument = stringResource(R.string.privacy_policy_lower_case),
+ linkStyle =
+ SpanStyle(
+ color = MaterialTheme.colorScheme.onSurface,
+ textDecoration = TextDecoration.Underline,
+ ),
+ onClick = { openPrivacyPolicy() },
+ ),
)
}
}
@@ -276,23 +393,12 @@ private fun ProblemMessageTextField(
value: String,
onDescriptionChanged: (String) -> Unit,
) {
- // Stores the height of the text field after the initial onSizeChanged callback is called.
- // This size will be calculated as a weight set from the parent composable.
- var textFieldHeight by remember { mutableStateOf(0.dp) }
-
- val localDensity = LocalDensity.current
TextField(
modifier =
modifier
.fillMaxWidth()
- // Prevents the text field from shrinking when the IME is shown.
- .defaultMinSize(minHeight = if (textFieldHeight > 0.dp) textFieldHeight else 180.dp)
- // Prevents the text field from growing to large when the message is long.
- .heightIn(max = if (textFieldHeight > 0.dp) textFieldHeight else Dp.Unspecified)
- .onSizeChanged { size ->
- textFieldHeight = with(localDensity) { size.height.toDp() }
- },
+ .defaultMinSize(minHeight = Dimens.problemReportTextFieldMinHeight),
value = value,
onValueChange = onDescriptionChanged,
placeholder = { Text(stringResource(R.string.user_message_hint)) },
@@ -356,7 +462,7 @@ private fun ColumnScope.SentContent(sendingState: SendingReportUiState.Success)
val emailStart = emailTemplate.indexOf('%')
buildAnnotatedString {
- append(emailTemplate.substring(0, emailStart))
+ append(emailTemplate.take(emailStart))
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(sendingState.email)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/ClickableString.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/ClickableString.kt
index bcf4e1b76c..97ff867010 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/ClickableString.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/ClickableString.kt
@@ -24,12 +24,7 @@ fun clickableAnnotatedString(
link =
LinkAnnotation.Clickable(
tag = argument,
- linkInteractionListener =
- object : LinkInteractionListener {
- override fun onClick(link: LinkAnnotation) {
- onClick(argument)
- }
- },
+ linkInteractionListener = LinkInteractionListener { onClick(argument) },
),
block = { withStyle(style = linkStyle) { append(argument) } },
)
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 9c02c9011f..622d4f7814 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
@@ -270,7 +270,14 @@ val uiModule = module {
viewModel { VoucherDialogViewModel(get()) }
viewModel { VpnSettingsViewModel(get(), get(), get(), get(), get(), get()) }
viewModel { WelcomeViewModel(get(), get(), get(), get(), isPlayBuild = IS_PLAY_BUILD) }
- viewModel { ReportProblemViewModel(get(), get(), get()) }
+ viewModel {
+ ReportProblemViewModel(
+ mullvadProblemReporter = get(),
+ problemReportRepository = get(),
+ accountRepository = get(),
+ isPlayBuild = IS_PLAY_BUILD,
+ )
+ }
viewModel { ViewLogsViewModel(get()) }
viewModel { OutOfTimeViewModel(get(), get(), get(), get(), get(), isPlayBuild = IS_PLAY_BUILD) }
viewModel { FilterViewModel(get(), get()) }
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 f846303c09..4b2726c945 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
@@ -26,6 +26,8 @@ data class ReportProblemUiState(
val description: String = "",
val showIncludeAccountId: Boolean = false,
val includeAccountId: Boolean = false,
+ val showIncludeAccountWarningMessage: Boolean = false,
+ val isPlayBuild: Boolean = false,
)
sealed interface SendingReportUiState {
@@ -44,30 +46,41 @@ class ReportProblemViewModel(
private val mullvadProblemReporter: MullvadProblemReport,
private val problemReportRepository: ProblemReportRepository,
accountRepository: AccountRepository,
+ private val isPlayBuild: Boolean,
) : ViewModel() {
private val sendingState: MutableStateFlow<SendingReportUiState?> = MutableStateFlow(null)
private val includeAccountIdState: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ private val showIncludeAccountWarningMessage: MutableStateFlow<Boolean> =
+ MutableStateFlow(false)
val uiState =
combine(
sendingState,
includeAccountIdState,
+ showIncludeAccountWarningMessage,
problemReportRepository.problemReport,
accountRepository.accountData,
- ) { sendingState, includeAccountToken, userReport, accountData ->
+ ) {
+ sendingState,
+ includeAccountToken,
+ showIncludeAccountWarningMessage,
+ userReport,
+ accountData ->
ReportProblemUiState(
sendingState = sendingState,
email = userReport.email ?: "",
description = userReport.description,
showIncludeAccountId = accountData != null,
includeAccountId = includeAccountToken,
+ showIncludeAccountWarningMessage = showIncludeAccountWarningMessage,
+ isPlayBuild = isPlayBuild,
)
}
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(VIEW_MODEL_STOP_TIMEOUT),
- ReportProblemUiState(),
+ ReportProblemUiState(isPlayBuild = isPlayBuild),
)
private val _uiSideEffect = Channel<ReportProblemSideEffect>()
@@ -118,6 +131,10 @@ class ReportProblemViewModel(
includeAccountIdState.tryEmit(checked)
}
+ fun showIncludeAccountInformationWarningMessage(show: Boolean) {
+ showIncludeAccountWarningMessage.tryEmit(show)
+ }
+
private fun shouldShowConfirmNoEmail(userEmail: String?): Boolean =
userEmail.isNullOrEmpty() && uiState.value.sendingState !is SendingReportUiState
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModelTest.kt
index ead918124e..1fc1a49422 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModelTest.kt
@@ -45,6 +45,7 @@ class ReportProblemViewModelTest {
mockMullvadProblemReport,
mockProblemReportRepository,
mockAccountRepository,
+ isPlayBuild = false,
)
}
diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml
index 147559f0c2..68d04343d8 100644
--- a/android/lib/resource/src/main/res/values/strings.xml
+++ b/android/lib/resource/src/main/res/values/strings.xml
@@ -462,7 +462,11 @@
<string name="enable_all_methods">Enable all &amp; retry</string>
<string name="send_email">Send email</string>
<string name="no_email_app_available">No email app available on the device</string>
- <string name="include_account_token_checkbox_text">This is a question about account or payments (include account information)</string>
+ <string name="include_account_token_checkbox_text">Include my account token for faster help with payment or account related issues</string>
<string name="no_matching_relay_entry">No entry server match your settings, try changing server or other settings.</string>
<string name="no_matching_relay_exit">No exit server match your settings, try changing server or other settings.</string>
+ <string name="include_account_token_warning_title">This impacts your anonymity</string>
+ <string name="include_account_token_warning_message_first">By attaching your account token it links this report to your account, which helps us resolve your issue quicker. All reports are automatically deleted after a period of time.</string>
+ <string name="include_account_token_warning_message_second">For details, please see our %s.</string>
+ <string name="privacy_policy_lower_case">privacy policy</string>
</resources>
diff --git a/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt b/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt
index 8f4426a907..6a07682d83 100644
--- a/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt
+++ b/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt
@@ -50,6 +50,7 @@ data class Dimensions(
val obfuscationNavigationBoxWidth: Dp = 56.dp,
val outLineButtonBorderWidth: Dp = 1.dp,
val privacyPolicyIconSize: Dp = 16.dp,
+ val problemReportTextFieldMinHeight: Dp = 220.dp,
val reconnectButtonMinInteractiveComponentSize: Dp = 40.dp,
val relayCircleSize: Dp = 16.dp,
val relayCirclePadding: Dp = 8.dp,
diff --git a/desktop/packages/mullvad-vpn/locales/messages.pot b/desktop/packages/mullvad-vpn/locales/messages.pot
index 580b9ee459..c4269f6233 100644
--- a/desktop/packages/mullvad-vpn/locales/messages.pot
+++ b/desktop/packages/mullvad-vpn/locales/messages.pot
@@ -3022,6 +3022,9 @@ msgstr ""
msgid "Blocking..."
msgstr ""
+msgid "By attaching your account token it links this report to your account, which helps us resolve your issue quicker. All reports are automatically deleted after a period of time."
+msgstr ""
+
msgid "Can't connect?"
msgstr ""
@@ -3172,6 +3175,9 @@ msgstr ""
msgid "File"
msgstr ""
+msgid "For details, please see our %s."
+msgstr ""
+
msgid "For the VPN to connect automatically on device startup, the app must have VPN permissions. You can grant these by clicking “Connect” in the map view and selecting “OK” if prompted. Additionally, ensure the app is installed on the internal storage by checking the storage information in the app details in system settings."
msgstr ""
@@ -3217,6 +3223,9 @@ msgstr ""
msgid "In-tunnel IPv6"
msgstr ""
+msgid "Include my account token for faster help with payment or account related issues"
+msgstr ""
+
msgid "Invalid or missing value \"%1$s\""
msgstr ""
@@ -3439,7 +3448,7 @@ msgstr ""
msgid "This field is required"
msgstr ""
-msgid "This is a question about account or payments (include account information)"
+msgid "This impacts your anonymity"
msgstr ""
msgid "This is already set as current"
@@ -3577,6 +3586,9 @@ msgstr ""
msgid "login"
msgstr ""
+msgid "privacy policy"
+msgstr ""
+
msgid "read more here"
msgstr ""
diff --git a/mullvad-problem-report/src/lib.rs b/mullvad-problem-report/src/lib.rs
index 730b06ba0a..61c83e7193 100644
--- a/mullvad-problem-report/src/lib.rs
+++ b/mullvad-problem-report/src/lib.rs
@@ -321,7 +321,7 @@ async fn send_problem_report_inner(
let message: String = match account_token {
Some(account_token) => {
- format!("{user_message}\nAccountToken: {account_token}")
+ format!("{user_message}\naccount-token: {account_token}")
}
None => user_message.to_string(),
};