summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorsaber safavi <saber.safavi@codic.se>2023-10-09 09:19:17 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2023-10-11 10:37:47 +0200
commitfd3d62d603911c3521791a70f7b36bd20498d295 (patch)
treef50e28d7b4c30624c5daa2b450b74c85c02b2e6e /android
parent03cb890fec0b6d17e05529de832ce075431c2f9f (diff)
downloadmullvadvpn-fd3d62d603911c3521791a70f7b36bd20498d295.tar.xz
mullvadvpn-fd3d62d603911c3521791a70f7b36bd20498d295.zip
Implement voucher dialog in compose
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt286
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogScreen.kt32
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/VoucherVisualTransformation.kt38
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/CommonConstant.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/RedeemVoucherDialogFragment.kt182
-rw-r--r--android/app/src/main/res/layout/redeem_voucher.xml58
-rw-r--r--android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt1
7 files changed, 380 insertions, 220 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt
new file mode 100644
index 0000000000..4d6f3f1261
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt
@@ -0,0 +1,286 @@
+package net.mullvad.mullvadvpn.compose.dialog
+
+import android.content.res.Configuration
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.compositeOver
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.pluralStringResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.tooling.preview.Devices
+import androidx.compose.ui.tooling.preview.Preview
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.button.ActionButton
+import net.mullvad.mullvadvpn.compose.state.VoucherDialogState
+import net.mullvad.mullvadvpn.compose.state.VoucherDialogUiState
+import net.mullvad.mullvadvpn.compose.textfield.GroupedTextField
+import net.mullvad.mullvadvpn.compose.util.vouchersVisualTransformation
+import net.mullvad.mullvadvpn.constant.VOUCHER_LENGTH
+import net.mullvad.mullvadvpn.lib.theme.AlphaDescription
+import net.mullvad.mullvadvpn.lib.theme.AlphaDisabled
+import net.mullvad.mullvadvpn.lib.theme.AlphaInactive
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
+import net.mullvad.mullvadvpn.lib.theme.Dimens
+import org.joda.time.DateTimeConstants
+
+@Preview(device = Devices.TV_720p)
+@Composable
+private fun PreviewRedeemVoucherDialog() {
+ AppTheme {
+ RedeemVoucherDialog(
+ uiState = VoucherDialogUiState.INITIAL,
+ onVoucherInputChange = {},
+ onRedeem = {},
+ onDismiss = {}
+ )
+ }
+}
+
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL_3)
+@Composable
+private fun PreviewRedeemVoucherDialogVerifying() {
+ AppTheme {
+ RedeemVoucherDialog(
+ uiState = VoucherDialogUiState("", VoucherDialogState.Verifying),
+ onVoucherInputChange = {},
+ onRedeem = {},
+ onDismiss = {}
+ )
+ }
+}
+
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL_3)
+@Composable
+private fun PreviewRedeemVoucherDialogError() {
+ AppTheme {
+ RedeemVoucherDialog(
+ uiState = VoucherDialogUiState("", VoucherDialogState.Error("An Error message")),
+ onVoucherInputChange = {},
+ onRedeem = {},
+ onDismiss = {}
+ )
+ }
+}
+
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL_3)
+@Composable
+private fun PreviewRedeemVoucherDialogSuccess() {
+ AppTheme {
+ RedeemVoucherDialog(
+ uiState = VoucherDialogUiState("", VoucherDialogState.Success(3600)),
+ onVoucherInputChange = {},
+ onRedeem = {},
+ onDismiss = {}
+ )
+ }
+}
+
+@Composable
+fun RedeemVoucherDialog(
+ uiState: VoucherDialogUiState,
+ onVoucherInputChange: (String) -> Unit = {},
+ onRedeem: (voucherCode: String) -> Unit,
+ onDismiss: () -> Unit
+) {
+ AlertDialog(
+ title = {
+ if (uiState.voucherViewModelState !is VoucherDialogState.Success)
+ Text(
+ text = stringResource(id = R.string.enter_voucher_code),
+ style = MaterialTheme.typography.titleMedium
+ )
+ },
+ confirmButton = {
+ Column {
+ if (uiState.voucherViewModelState !is VoucherDialogState.Success) {
+ ActionButton(
+ text = stringResource(id = R.string.redeem),
+ onClick = { onRedeem(uiState.voucherInput) },
+ modifier = Modifier.padding(bottom = Dimens.mediumPadding),
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.surface,
+ contentColor = MaterialTheme.colorScheme.onSurface,
+ disabledContentColor =
+ MaterialTheme.colorScheme.onSurface
+ .copy(alpha = AlphaInactive)
+ .compositeOver(MaterialTheme.colorScheme.surface),
+ disabledContainerColor =
+ MaterialTheme.colorScheme.surface
+ .copy(alpha = AlphaDisabled)
+ .compositeOver(MaterialTheme.colorScheme.surface)
+ ),
+ isEnabled = uiState.voucherInput.length == VOUCHER_LENGTH
+ )
+ }
+ ActionButton(
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.primary,
+ contentColor = MaterialTheme.colorScheme.onPrimary,
+ ),
+ text =
+ stringResource(
+ id =
+ if (uiState.voucherViewModelState is VoucherDialogState.Success)
+ R.string.changes_dialog_dismiss_button
+ else R.string.cancel
+ ),
+ onClick = onDismiss
+ )
+ }
+ },
+ text = {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ if (uiState.voucherViewModelState is VoucherDialogState.Success) {
+ val days: Int =
+ (uiState.voucherViewModelState.addedTime /
+ DateTimeConstants.SECONDS_PER_DAY)
+ .toInt()
+ val message =
+ stringResource(
+ R.string.added_to_your_account,
+ when (days) {
+ 0 -> {
+ stringResource(R.string.less_than_one_day)
+ }
+ in 1..59 -> {
+ pluralStringResource(id = R.plurals.days, count = days, days)
+ }
+ else -> {
+ pluralStringResource(
+ id = R.plurals.months,
+ count = days / 30,
+ days / 30
+ )
+ }
+ }
+ )
+ RedeemSuccessBody(message = message)
+ } else {
+
+ EnterVoucherBody(
+ uiState = uiState,
+ onVoucherInputChange = onVoucherInputChange,
+ onRedeem = onRedeem
+ )
+ }
+ }
+ },
+ containerColor = MaterialTheme.colorScheme.background,
+ titleContentColor = MaterialTheme.colorScheme.onBackground,
+ onDismissRequest = onDismiss
+ )
+}
+
+@Composable
+private fun RedeemSuccessBody(message: String) {
+ Image(
+ painter = painterResource(R.drawable.icon_success),
+ contentDescription = null,
+ modifier = Modifier.fillMaxWidth().height(Dimens.buttonHeight)
+ )
+ Text(
+ text = stringResource(id = R.string.voucher_success_title),
+ modifier =
+ Modifier.padding(
+ start = Dimens.smallPadding,
+ top = Dimens.successIconVerticalPadding,
+ )
+ .fillMaxWidth(),
+ color = MaterialTheme.colorScheme.onPrimary,
+ style = MaterialTheme.typography.titleMedium
+ )
+
+ Text(
+ text = message,
+ modifier =
+ Modifier.padding(start = Dimens.smallPadding, top = Dimens.cellTopPadding)
+ .fillMaxWidth(),
+ color = MaterialTheme.colorScheme.onPrimary.copy(alpha = AlphaDescription),
+ style = MaterialTheme.typography.labelMedium
+ )
+}
+
+@Composable
+private fun EnterVoucherBody(
+ uiState: VoucherDialogUiState,
+ onVoucherInputChange: (String) -> Unit = {},
+ onRedeem: (voucherCode: String) -> Unit
+) {
+ val textFieldFocusRequester = FocusRequester()
+ Box(Modifier.wrapContentSize().clickable { textFieldFocusRequester.requestFocus() }) {
+ GroupedTextField(
+ value = uiState.voucherInput,
+ onSubmit = { input ->
+ if (uiState.voucherInput.length == VOUCHER_LENGTH) {
+ onRedeem(input)
+ }
+ },
+ onValueChanged = { input -> onVoucherInputChange(input.uppercase()) },
+ isValidValue = uiState.voucherInput.isNotEmpty(),
+ keyboardType = KeyboardType.Password,
+ placeholderText = stringResource(id = R.string.voucher_hint),
+ placeHolderColor =
+ MaterialTheme.colorScheme.onPrimary
+ .copy(alpha = AlphaDisabled)
+ .compositeOver(MaterialTheme.colorScheme.primary),
+ visualTransformation = vouchersVisualTransformation(),
+ maxCharLength = VOUCHER_LENGTH,
+ onFocusChange = {},
+ isDigitsOnlyAllowed = false,
+ isEnabled = true,
+ modifier = Modifier.focusRequester(textFieldFocusRequester),
+ validateRegex = "^[A-Za-z0-9]*$".toRegex()
+ )
+ }
+ Spacer(modifier = Modifier.height(Dimens.smallPadding))
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.height(Dimens.listIconSize).fillMaxWidth()
+ ) {
+ if (uiState.voucherViewModelState is VoucherDialogState.Verifying) {
+ CircularProgressIndicator(
+ modifier =
+ Modifier.height(Dimens.loadingSpinnerSizeMedium)
+ .width(Dimens.loadingSpinnerSizeMedium),
+ color = MaterialTheme.colorScheme.onSecondary
+ )
+ Text(
+ text = stringResource(id = R.string.verifying_voucher),
+ modifier = Modifier.padding(start = Dimens.smallPadding),
+ color = MaterialTheme.colorScheme.onPrimary,
+ style = MaterialTheme.typography.bodySmall
+ )
+ } else if (uiState.voucherViewModelState is VoucherDialogState.Error) {
+ Text(
+ text = uiState.voucherViewModelState.errorMessage,
+ color = MaterialTheme.colorScheme.error,
+ style = MaterialTheme.typography.bodySmall
+ )
+ }
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogScreen.kt
new file mode 100644
index 0000000000..1344db5b20
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogScreen.kt
@@ -0,0 +1,32 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+import android.content.res.Configuration
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Devices
+import androidx.compose.ui.tooling.preview.Preview
+import net.mullvad.mullvadvpn.compose.dialog.RedeemVoucherDialog
+import net.mullvad.mullvadvpn.compose.state.VoucherDialogUiState
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
+
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL_3)
+@Composable
+private fun PreviewRedeemVoucherDialogScreen() {
+ AppTheme {
+ RedeemVoucherDialogScreen(
+ uiState = VoucherDialogUiState.INITIAL,
+ onVoucherInputChange = {},
+ onRedeem = {},
+ onDismiss = {}
+ )
+ }
+}
+
+@Composable
+internal fun RedeemVoucherDialogScreen(
+ uiState: VoucherDialogUiState,
+ onVoucherInputChange: (String) -> Unit = {},
+ onRedeem: (voucherCode: String) -> Unit,
+ onDismiss: () -> Unit
+) {
+ RedeemVoucherDialog(uiState, onVoucherInputChange, onRedeem, onDismiss)
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/VoucherVisualTransformation.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/VoucherVisualTransformation.kt
new file mode 100644
index 0000000000..c4d5eec0a1
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/VoucherVisualTransformation.kt
@@ -0,0 +1,38 @@
+package net.mullvad.mullvadvpn.compose.util
+
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.input.OffsetMapping
+import androidx.compose.ui.text.input.TransformedText
+import androidx.compose.ui.text.input.VisualTransformation
+import java.lang.Integer.min
+
+const val VOUCHER_SEPARATOR = "-"
+const val VOUCHER_CHUNK_SIZE = 4
+const val MAX_VOUCHER_LENGTH = 16
+
+fun vouchersVisualTransformation() = VisualTransformation { text ->
+ var out = text.chunked(VOUCHER_CHUNK_SIZE).joinToString(VOUCHER_SEPARATOR)
+ if (
+ text.length % VOUCHER_CHUNK_SIZE == 0 &&
+ text.isNotEmpty() &&
+ text.length < MAX_VOUCHER_LENGTH
+ ) {
+ out += VOUCHER_SEPARATOR
+ }
+ TransformedText(
+ AnnotatedString(out),
+ object : OffsetMapping {
+ override fun originalToTransformed(offset: Int): Int {
+ val res = offset + offset / ACCOUNT_TOKEN_CHUNK_SIZE
+ // Limit max input to 19 characters (16 voucher - 3 dividers)
+ return min(
+ res,
+ MAX_VOUCHER_LENGTH + MAX_VOUCHER_LENGTH / ACCOUNT_TOKEN_CHUNK_SIZE - 1
+ )
+ }
+
+ override fun transformedToOriginal(offset: Int): Int =
+ offset - offset / (ACCOUNT_TOKEN_CHUNK_SIZE + 1)
+ }
+ )
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/CommonConstant.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/CommonConstant.kt
new file mode 100644
index 0000000000..a01aa08d8b
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/CommonConstant.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.constant
+
+const val VOUCHER_LENGTH = 16
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/RedeemVoucherDialogFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/RedeemVoucherDialogFragment.kt
index 46472ea6cc..61f24e45fc 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/RedeemVoucherDialogFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/RedeemVoucherDialogFragment.kt
@@ -1,187 +1,45 @@
package net.mullvad.mullvadvpn.ui.fragment
import android.app.Dialog
-import android.content.Context
-import android.graphics.drawable.ColorDrawable
import android.os.Bundle
-import android.text.Editable
-import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams
-import android.widget.EditText
-import android.widget.TextView
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.DialogFragment
import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.lib.common.util.JobTracker
-import net.mullvad.mullvadvpn.model.VoucherSubmissionError
-import net.mullvad.mullvadvpn.model.VoucherSubmissionResult
-import net.mullvad.mullvadvpn.repository.AccountRepository
-import net.mullvad.mullvadvpn.ui.MainActivity
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
-import net.mullvad.mullvadvpn.ui.serviceconnection.VoucherRedeemer
-import net.mullvad.mullvadvpn.ui.widget.Button
-import net.mullvad.mullvadvpn.util.SegmentedInputFormatter
-import org.joda.time.DateTime
-import org.koin.android.ext.android.inject
-
-const val FULL_VOUCHER_CODE_LENGTH = "XXXX-XXXX-XXXX-XXXX".length
+import net.mullvad.mullvadvpn.compose.screen.RedeemVoucherDialogScreen
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
+import net.mullvad.mullvadvpn.viewmodel.VoucherDialogViewModel
+import org.koin.androidx.viewmodel.ext.android.viewModel
class RedeemVoucherDialogFragment : DialogFragment() {
- // Injected dependencies
- private val accountRepository: AccountRepository by inject()
- private val serviceConnectionManager: ServiceConnectionManager by inject()
-
- private val jobTracker = JobTracker()
-
- private lateinit var parentActivity: MainActivity
- private lateinit var errorMessage: TextView
- private lateinit var voucherInput: EditText
-
- private var accountExpiry: DateTime? = null
- private var redeemButton: Button? = null
- private var voucherRedeemer: VoucherRedeemer? = null
-
- private var voucherInputIsValid = false
- set(value) {
- field = value
- updateRedeemButton()
- }
-
- override fun onAttach(context: Context) {
- super.onAttach(context)
-
- parentActivity = context as MainActivity
-
- serviceConnectionManager.serviceNotifier.subscribe(this) { connection ->
- voucherRedeemer = connection?.voucherRedeemer
- }
-
- jobTracker.newUiJob("updateExpiry") {
- accountRepository.accountExpiryState.collect { accountExpiry = it.date() }
- }
-
- updateRedeemButton()
- }
+ private val vm by viewModel<VoucherDialogViewModel>()
+ private lateinit var voucherDialog: Dialog
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- val view = inflater.inflate(R.layout.redeem_voucher, container, false)
-
- voucherInput =
- view.findViewById<EditText>(R.id.voucher_code).apply {
- addTextChangedListener(ValidVoucherCodeChecker())
+ return inflater.inflate(R.layout.fragment_compose, container, false).apply {
+ findViewById<ComposeView>(R.id.compose_view).setContent {
+ AppTheme {
+ RedeemVoucherDialogScreen(
+ uiState = vm.uiState.collectAsState().value,
+ onVoucherInputChange = { vm.onVoucherInputChange(it) },
+ onRedeem = { vm.onRedeem(it) },
+ onDismiss = { onDismiss(voucherDialog) }
+ )
+ }
}
-
- SegmentedInputFormatter(voucherInput, '-').apply {
- allCaps = true
-
- isValidInputCharacter = { character ->
- ('A' <= character && character <= 'Z') || ('0' <= character && character <= '9')
- }
- }
-
- redeemButton =
- view.findViewById<Button>(R.id.redeem).apply {
- isEnabled = false
-
- setOnClickAction("action", jobTracker) { submitVoucher() }
- }
-
- errorMessage = view.findViewById(R.id.error)
-
- view.findViewById<Button>(R.id.cancel).setOnClickAction("action", jobTracker) {
- activity?.onBackPressed()
}
-
- return view
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val dialog = super.onCreateDialog(savedInstanceState)
-
- dialog.window?.setBackgroundDrawable(ColorDrawable(android.R.color.transparent))
-
- return dialog
- }
-
- override fun onStart() {
- super.onStart()
-
- dialog?.window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
- }
-
- override fun onDestroyView() {
- jobTracker.cancelAllJobs()
-
- super.onDestroyView()
- }
-
- override fun onDetach() {
- jobTracker.cancelJob("updateExpiry")
- serviceConnectionManager.serviceNotifier.unsubscribe(this)
-
- super.onDetach()
- }
-
- private fun updateRedeemButton() {
- redeemButton?.isEnabled = voucherInputIsValid && voucherRedeemer != null
- }
-
- private suspend fun submitVoucher() {
- errorMessage.visibility = View.INVISIBLE
-
- val result = voucherRedeemer?.submit(voucherInput.text.toString())
-
- when (result) {
- is VoucherSubmissionResult.Ok -> handleAddedTime(result.submission.timeAdded)
- is VoucherSubmissionResult.Error -> showError(result.error)
- else -> {
- /* NOOP */
- }
- }
- }
-
- private fun handleAddedTime(timeAdded: Long) {
- if (timeAdded > 0) {
- dismiss()
- }
- }
-
- private fun showError(error: VoucherSubmissionError) {
- val message =
- when (error) {
- VoucherSubmissionError.InvalidVoucher -> R.string.invalid_voucher
- VoucherSubmissionError.VoucherAlreadyUsed -> R.string.voucher_already_used
- else -> R.string.error_occurred
- }
-
- errorMessage.apply {
- setText(message)
- visibility = View.VISIBLE
- }
- }
-
- inner class ValidVoucherCodeChecker : TextWatcher {
- private var editRecursionCount = 0
-
- override fun beforeTextChanged(text: CharSequence, start: Int, count: Int, after: Int) {
- editRecursionCount += 1
- }
-
- override fun onTextChanged(text: CharSequence, start: Int, before: Int, count: Int) {}
-
- override fun afterTextChanged(text: Editable) {
- editRecursionCount -= 1
-
- if (editRecursionCount == 0) {
- voucherInputIsValid = text.length == FULL_VOUCHER_CODE_LENGTH
- }
- }
+ voucherDialog = super.onCreateDialog(savedInstanceState)
+ return voucherDialog
}
}
diff --git a/android/app/src/main/res/layout/redeem_voucher.xml b/android/app/src/main/res/layout/redeem_voucher.xml
deleted file mode 100644
index 3f6d91d183..0000000000
--- a/android/app/src/main/res/layout/redeem_voucher.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:mullvad="http://schemas.android.com/apk/res-auto"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:scrollbars="none">
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="30dp"
- android:background="@drawable/dialog_background"
- android:orientation="vertical"
- android:gravity="start">
- <TextView android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="0"
- android:layout_marginBottom="9dp"
- android:textColor="@color/white"
- android:textSize="@dimen/text_medium"
- android:text="@string/enter_voucher_code" />
- <EditText android:id="@+id/voucher_code"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="14dp"
- android:background="@drawable/edit_text_background"
- android:singleLine="true"
- android:imeActionLabel="@string/redeem"
- android:imeOptions="flagNoPersonalizedLearning"
- android:inputType="textCapCharacters"
- android:textCursorDrawable="@drawable/text_input_cursor"
- android:hint="@string/voucher_hint"
- android:maxLength="19"
- android:digits="0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
- android:textAllCaps="true"
- android:textColorHint="@color/blue40"
- android:textColor="@color/blue"
- android:textSize="@dimen/text_small"
- android:textStyle="bold" />
- <TextView android:id="@+id/error"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:textColor="@color/red"
- android:textSize="@dimen/text_small"
- android:textStyle="bold"
- android:visibility="invisible" />
- <net.mullvad.mullvadvpn.ui.widget.Button android:id="@+id/redeem"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginVertical="@dimen/button_separation"
- mullvad:showSpinner="true"
- mullvad:buttonColor="green"
- mullvad:text="@string/redeem" />
- <net.mullvad.mullvadvpn.ui.widget.Button android:id="@+id/cancel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- mullvad:buttonColor="blue"
- mullvad:text="@string/cancel" />
- </LinearLayout>
-</ScrollView>
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 c3fd722a12..bb56f7df48 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
@@ -55,6 +55,7 @@ data class Dimensions(
val sideMargin: Dp = 22.dp,
val smallPadding: Dp = 8.dp,
val spacingAboveButton: Dp = 22.dp,
+ val successIconVerticalPadding: Dp = 26.dp,
val titleIconSize: Dp = 24.dp,
val topBarHeight: Dp = 64.dp,
val verticalSpace: Dp = 20.dp,