summaryrefslogtreecommitdiffhomepage
path: root/android/app
diff options
context:
space:
mode:
authorKalle Lindström <karl.lindstrom@mullvad.net>2025-03-11 11:00:15 +0100
committerKalle Lindström <karl.lindstrom@mullvad.net>2025-03-19 09:33:07 +0100
commite2d5d4f1f444f1dfbee5077889a62731aec080c6 (patch)
tree94e8bfca4a946f931ae3ce6b22a8a50eec795b48 /android/app
parent793c39338a2fcd2bf188952061edaeaa925d614a (diff)
downloadmullvadvpn-e2d5d4f1f444f1dfbee5077889a62731aec080c6.tar.xz
mullvadvpn-e2d5d4f1f444f1dfbee5077889a62731aec080c6.zip
Improve TV connect screen UI
- Implements the navigation rail design for Android TV - Implements the TV notification banner design - Adds two new Gradle modules: * tv: contains the Android TV specific Compose components (e.g. the NavigationDrawerTV component) * ui/compose: contains Compose-specific code that is needed by both the app module and the tv module.
Diffstat (limited to 'android/app')
-rw-r--r--android/app/build.gradle.kts3
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt244
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationData.kt252
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RemoveDeviceConfirmationDialog.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/InfoDialog.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt26
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/SpannedExtensions.kt43
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AppInfoUiStatePreviewParameterProvider.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/ConnectUiStatePreviewParameterProvider.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AutoConnectAndLockdownModeScreen.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt183
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Device.kt13
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/RememberPrevious.kt43
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/InAppNotificationController.kt44
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/VersionInfo.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AppVersionInfoRepository.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCase.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/NewChangelogNotificationUseCase.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCase.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCase.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCase.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AppInfoViewModel.kt2
-rw-r--r--android/app/src/main/res/drawable/daita_illustration_1.xml342
-rw-r--r--android/app/src/main/res/drawable/daita_illustration_2.xml402
-rw-r--r--android/app/src/main/res/drawable/logo_text.xml26
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCaseTest.kt2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt4
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt2
35 files changed, 215 insertions, 1470 deletions
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 9145e8411b..261cc9a48e 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -372,6 +372,8 @@ dependencies {
implementation(projects.lib.resource)
implementation(projects.lib.shared)
implementation(projects.lib.talpid)
+ implementation(projects.lib.tv)
+ implementation(projects.lib.ui.component)
implementation(projects.tile)
implementation(projects.lib.theme)
implementation(projects.service)
@@ -388,6 +390,7 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.lifecycle.runtime.compose)
+ implementation(libs.androidx.tv)
implementation(libs.arrow)
implementation(libs.arrow.optics)
implementation(libs.arrow.resilience)
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
index f1a81d4d91..43bc448805 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
@@ -19,8 +19,6 @@ import net.mullvad.mullvadvpn.compose.state.ConnectUiState
import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR
import net.mullvad.mullvadvpn.compose.test.CONNECT_BUTTON_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.CONNECT_CARD_HEADER_TEST_TAG
-import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER_ACTION
-import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER_TEXT_ACTION
import net.mullvad.mullvadvpn.compose.test.RECONNECT_BUTTON_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.SELECT_LOCATION_BUTTON_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.TOP_BAR_ACCOUNT_BUTTON
@@ -28,11 +26,13 @@ import net.mullvad.mullvadvpn.lib.model.ActionAfterDisconnect
import net.mullvad.mullvadvpn.lib.model.ErrorState
import net.mullvad.mullvadvpn.lib.model.ErrorStateCause
import net.mullvad.mullvadvpn.lib.model.GeoIpLocation
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
import net.mullvad.mullvadvpn.lib.model.TransportProtocol
import net.mullvad.mullvadvpn.lib.model.TunnelEndpoint
import net.mullvad.mullvadvpn.lib.model.TunnelState
-import net.mullvad.mullvadvpn.repository.InAppNotification
-import net.mullvad.mullvadvpn.ui.VersionInfo
+import net.mullvad.mullvadvpn.lib.model.VersionInfo
+import net.mullvad.mullvadvpn.lib.ui.component.test.NOTIFICATION_BANNER_ACTION
+import net.mullvad.mullvadvpn.lib.ui.component.test.NOTIFICATION_BANNER_TEXT_ACTION
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt
index 4f527a94c5..9ff9ec5a00 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt
@@ -1,49 +1,25 @@
package net.mullvad.mullvadvpn.compose.component.notificationbanner
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.animateContentSize
-import androidx.compose.animation.slideInVertically
-import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.wrapContentWidth
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
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.graphics.vector.ImageVector
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.text.toUpperCase
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.constraintlayout.compose.ConstraintLayout
-import androidx.constraintlayout.compose.Dimension
import java.time.Duration
import net.mullvad.mullvadvpn.compose.component.MullvadTopBar
-import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER
-import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER_ACTION
-import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER_TEXT_ACTION
-import net.mullvad.mullvadvpn.compose.util.rememberPrevious
+import net.mullvad.mullvadvpn.compose.util.isTv
import net.mullvad.mullvadvpn.lib.model.ErrorState
import net.mullvad.mullvadvpn.lib.model.ErrorStateCause
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
+import net.mullvad.mullvadvpn.lib.model.VersionInfo
import net.mullvad.mullvadvpn.lib.theme.AppTheme
-import net.mullvad.mullvadvpn.lib.theme.Dimens
-import net.mullvad.mullvadvpn.lib.theme.color.warning
-import net.mullvad.mullvadvpn.repository.InAppNotification
-import net.mullvad.mullvadvpn.ui.VersionInfo
-import net.mullvad.mullvadvpn.ui.notification.StatusLevel
+import net.mullvad.mullvadvpn.lib.tv.NotificationBannerTv
+import net.mullvad.mullvadvpn.lib.ui.component.AnimatedNotificationBanner
@Preview
@Composable
@@ -52,18 +28,17 @@ private fun PreviewNotificationBanner() {
Column(Modifier.background(color = MaterialTheme.colorScheme.surface)) {
val bannerDataList =
listOf(
- InAppNotification.UnsupportedVersion(
- versionInfo = VersionInfo(currentVersion = "1.0", isSupported = false)
- ),
- InAppNotification.AccountExpiry(expiry = Duration.ZERO),
- InAppNotification.TunnelStateBlocked,
- InAppNotification.NewDevice("Courageous Turtle"),
- InAppNotification.TunnelStateError(
- error = ErrorState(ErrorStateCause.FirewallPolicyError.Generic, true)
- ),
- InAppNotification.NewVersionChangelog,
- )
- .map { it.toNotificationData(false, {}, {}, {}, {}, {}) }
+ InAppNotification.UnsupportedVersion(
+ versionInfo = VersionInfo(currentVersion = "1.0", isSupported = false)
+ ),
+ InAppNotification.AccountExpiry(expiry = Duration.ZERO),
+ InAppNotification.TunnelStateBlocked,
+ InAppNotification.NewDevice("Courageous Turtle"),
+ InAppNotification.TunnelStateError(
+ error = ErrorState(ErrorStateCause.FirewallPolicyError.Generic, true)
+ ),
+ InAppNotification.NewVersionChangelog,
+ )
bannerDataList.forEach {
MullvadTopBar(
@@ -72,7 +47,15 @@ private fun PreviewNotificationBanner() {
onAccountClicked = {},
iconTintColor = MaterialTheme.colorScheme.primary,
)
- Notification(it)
+ NotificationBanner(
+ notification = it,
+ isPlayBuild = false,
+ openAppListing = {},
+ onClickShowAccount = {},
+ onClickShowChangelog = {},
+ onClickDismissChangelog = {},
+ onClickDismissNewDevice = {},
+ )
Spacer(modifier = Modifier.size(16.dp))
}
}
@@ -90,163 +73,28 @@ fun NotificationBanner(
onClickDismissChangelog: () -> Unit,
onClickDismissNewDevice: () -> Unit,
) {
- // Fix for animating to invisible state
- val previous = rememberPrevious(current = notification, shouldUpdate = { _, _ -> true })
- AnimatedVisibility(
- visible = notification != null,
- enter = slideInVertically(initialOffsetY = { -it }),
- exit = slideOutVertically(targetOffsetY = { -it }),
- modifier = modifier,
- ) {
- val visibleNotification = notification ?: previous
- if (visibleNotification != null)
- Notification(
- visibleNotification.toNotificationData(
- isPlayBuild = isPlayBuild,
- openAppListing,
- onClickShowAccount,
- onClickShowChangelog,
- onClickDismissChangelog,
- onClickDismissNewDevice,
- )
- )
- }
-}
-
-@Composable
-@Suppress("LongMethod")
-private fun Notification(notificationBannerData: NotificationData) {
- val (title, message, statusLevel, action) = notificationBannerData
- ConstraintLayout(
- modifier =
- Modifier.fillMaxWidth()
- .background(color = MaterialTheme.colorScheme.surfaceContainer)
- .padding(
- start = Dimens.notificationBannerStartPadding,
- end = Dimens.notificationBannerEndPadding,
- top = Dimens.smallPadding,
- bottom = Dimens.smallPadding,
- )
- .animateContentSize()
- .testTag(NOTIFICATION_BANNER)
- ) {
- val (status, textTitle, textMessage, actionIcon) = createRefs()
- NotificationDot(
- statusLevel,
- Modifier.constrainAs(status) {
- top.linkTo(textTitle.top)
- start.linkTo(parent.start)
- bottom.linkTo(textTitle.bottom)
- },
- )
- Text(
- text = title.toUpperCase(),
- modifier =
- Modifier.constrainAs(textTitle) {
- top.linkTo(parent.top)
- start.linkTo(status.end)
- if (message != null) {
- bottom.linkTo(textMessage.top)
- } else {
- bottom.linkTo(parent.bottom)
- }
- if (action != null) {
- end.linkTo(actionIcon.start)
- } else {
- end.linkTo(parent.end)
- }
- width = Dimension.fillToConstraints
- }
- .padding(start = Dimens.smallPadding),
- style = MaterialTheme.typography.bodySmall,
- color = MaterialTheme.colorScheme.onSurface,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
+ if (isTv()) {
+ NotificationBannerTv(
+ modifier = modifier,
+ notification = notification,
+ isPlayBuild = isPlayBuild,
+ openAppListing = openAppListing,
+ onClickShowAccount = onClickShowAccount,
+ onClickShowChangelog = onClickShowChangelog,
+ onClickDismissChangelog = onClickDismissChangelog,
+ onClickDismissNewDevice = onClickDismissNewDevice,
)
- message?.let { message ->
- Text(
- text = message.text,
- modifier =
- Modifier.constrainAs(textMessage) {
- top.linkTo(textTitle.bottom)
- start.linkTo(textTitle.start)
- if (action != null) {
- end.linkTo(actionIcon.start)
- bottom.linkTo(parent.bottom)
- } else {
- end.linkTo(parent.end)
- bottom.linkTo(parent.bottom)
- }
- width = Dimension.fillToConstraints
- height = Dimension.wrapContent
- }
- .padding(start = Dimens.smallPadding, top = Dimens.tinyPadding)
- .wrapContentWidth(Alignment.Start)
- .let {
- if (message is NotificationMessage.ClickableText) {
- it.clickable(
- onClickLabel = message.contentDescription,
- role = Role.Button,
- ) {
- message.onClick()
- }
- .testTag(NOTIFICATION_BANNER_TEXT_ACTION)
- } else {
- it
- }
- },
- color = MaterialTheme.colorScheme.onSurfaceVariant,
- style = MaterialTheme.typography.labelMedium,
- )
- }
- action?.let {
- NotificationAction(
- it.icon,
- onClick = it.onClick,
- contentDescription = it.contentDescription,
- modifier =
- Modifier.constrainAs(actionIcon) {
- top.linkTo(parent.top)
- end.linkTo(parent.end)
- bottom.linkTo(parent.bottom)
- },
- )
- }
- }
-}
-
-@Composable
-private fun NotificationDot(statusLevel: StatusLevel, modifier: Modifier) {
- Box(
- modifier =
- modifier
- .background(
- color =
- when (statusLevel) {
- StatusLevel.Error -> MaterialTheme.colorScheme.error
- StatusLevel.Warning -> MaterialTheme.colorScheme.warning
- StatusLevel.Info -> MaterialTheme.colorScheme.tertiary
- },
- shape = CircleShape,
- )
- .size(Dimens.notificationStatusIconSize)
- )
-}
-
-@Composable
-private fun NotificationAction(
- imageVector: ImageVector,
- contentDescription: String?,
- onClick: () -> Unit,
- modifier: Modifier = Modifier,
-) {
-
- IconButton(modifier = modifier.testTag(NOTIFICATION_BANNER_ACTION), onClick = onClick) {
- Icon(
- modifier = Modifier.padding(Dimens.notificationIconPadding),
- imageVector = imageVector,
- contentDescription = contentDescription,
- tint = MaterialTheme.colorScheme.onSurface,
+ } else {
+ AnimatedNotificationBanner(
+ modifier = modifier,
+ notificationModifier = Modifier.fillMaxWidth(),
+ notification = notification,
+ isPlayBuild = isPlayBuild,
+ openAppListing = openAppListing,
+ onClickShowAccount = onClickShowAccount,
+ onClickShowChangelog = onClickShowChangelog,
+ onClickDismissChangelog = onClickDismissChangelog,
+ onClickDismissNewDevice = onClickDismissNewDevice,
)
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationData.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationData.kt
deleted file mode 100644
index 58798978bc..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationData.kt
+++ /dev/null
@@ -1,252 +0,0 @@
-package net.mullvad.mullvadvpn.compose.component.notificationbanner
-
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.OpenInNew
-import androidx.compose.material.icons.filled.Clear
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.SpanStyle
-import androidx.compose.ui.text.buildAnnotatedString
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.text.withStyle
-import androidx.core.text.HtmlCompat
-import java.net.InetAddress
-import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.compose.extensions.getExpiryQuantityString
-import net.mullvad.mullvadvpn.compose.extensions.toAnnotatedString
-import net.mullvad.mullvadvpn.lib.model.AuthFailedError
-import net.mullvad.mullvadvpn.lib.model.ErrorState
-import net.mullvad.mullvadvpn.lib.model.ErrorStateCause
-import net.mullvad.mullvadvpn.lib.model.ParameterGenerationError
-import net.mullvad.mullvadvpn.repository.InAppNotification
-import net.mullvad.mullvadvpn.ui.notification.StatusLevel
-
-data class NotificationData(
- val title: AnnotatedString,
- val message: NotificationMessage? = null,
- val statusLevel: StatusLevel,
- val action: NotificationAction? = null,
-) {
- constructor(
- title: String,
- message: String? = null,
- statusLevel: StatusLevel,
- action: NotificationAction? = null,
- ) : this(
- AnnotatedString(title),
- message?.let { NotificationMessage.Text(AnnotatedString(it)) },
- statusLevel,
- action,
- )
-
- constructor(
- title: String,
- message: NotificationMessage,
- statusLevel: StatusLevel,
- action: NotificationAction? = null,
- ) : this(AnnotatedString(title), message, statusLevel, action)
-}
-
-sealed interface NotificationMessage {
- val text: AnnotatedString
-
- data class Text(override val text: AnnotatedString) : NotificationMessage
-
- data class ClickableText(
- override val text: AnnotatedString,
- val onClick: () -> Unit,
- val contentDescription: String,
- ) : NotificationMessage
-}
-
-data class NotificationAction(
- val icon: ImageVector,
- val onClick: (() -> Unit),
- val contentDescription: String,
-)
-
-@Composable
-fun InAppNotification.toNotificationData(
- isPlayBuild: Boolean,
- openAppListing: () -> Unit,
- onClickShowAccount: () -> Unit,
- onClickShowChangelog: () -> Unit,
- onClickDismissChangelog: () -> Unit,
- onClickDismissNewDevice: () -> Unit,
-) =
- when (this) {
- is InAppNotification.NewDevice ->
- NotificationData(
- title =
- AnnotatedString(stringResource(id = R.string.new_device_notification_title)),
- message =
- NotificationMessage.Text(
- stringResource(id = R.string.new_device_notification_message, deviceName)
- .formatWithHtml()
- ),
- statusLevel = StatusLevel.Info,
- action =
- NotificationAction(
- Icons.Default.Clear,
- onClickDismissNewDevice,
- stringResource(id = R.string.dismiss),
- ),
- )
- is InAppNotification.AccountExpiry ->
- NotificationData(
- title = stringResource(id = R.string.account_credit_expires_soon),
- message = LocalContext.current.resources.getExpiryQuantityString(expiry),
- statusLevel = StatusLevel.Error,
- action =
- if (isPlayBuild) null
- else
- NotificationAction(
- Icons.AutoMirrored.Default.OpenInNew,
- onClickShowAccount,
- stringResource(id = R.string.open_url),
- ),
- )
- InAppNotification.TunnelStateBlocked ->
- NotificationData(
- title = stringResource(id = R.string.blocking_internet),
- statusLevel = StatusLevel.Error,
- )
- is InAppNotification.TunnelStateError -> errorMessageBannerData(error)
- is InAppNotification.UnsupportedVersion ->
- NotificationData(
- title = stringResource(id = R.string.unsupported_version),
- message = stringResource(id = R.string.unsupported_version_description),
- statusLevel = StatusLevel.Error,
- action =
- NotificationAction(
- Icons.AutoMirrored.Default.OpenInNew,
- openAppListing,
- stringResource(id = R.string.open_url),
- ),
- )
- is InAppNotification.NewVersionChangelog ->
- NotificationData(
- title = stringResource(id = R.string.new_changelog_notification_title),
- message =
- NotificationMessage.ClickableText(
- text =
- buildAnnotatedString {
- withStyle(SpanStyle(textDecoration = TextDecoration.Underline)) {
- append(
- stringResource(
- id = R.string.new_changelog_notification_message
- )
- )
- }
- },
- onClick = onClickShowChangelog,
- contentDescription =
- stringResource(id = R.string.new_changelog_notification_message),
- ),
- statusLevel = StatusLevel.Info,
- action =
- NotificationAction(
- Icons.Default.Clear,
- onClickDismissChangelog,
- stringResource(id = R.string.dismiss),
- ),
- )
- }
-
-@Composable
-private fun errorMessageBannerData(error: ErrorState) =
- NotificationData(
- title = error.title().formatWithHtml(),
- message = NotificationMessage.Text(error.message().formatWithHtml()),
- statusLevel = StatusLevel.Error,
- )
-
-@Composable
-private fun String.formatWithHtml(): AnnotatedString =
- HtmlCompat.fromHtml(this, HtmlCompat.FROM_HTML_MODE_COMPACT)
- .toAnnotatedString(
- boldSpanStyle =
- SpanStyle(
- color = MaterialTheme.colorScheme.onSurface,
- fontWeight = FontWeight.ExtraBold,
- )
- )
-
-@Composable
-private fun ErrorState.title(): String {
- val cause = this.cause
- return when {
- cause is ErrorStateCause.InvalidDnsServers -> stringResource(R.string.blocking_internet)
- cause is ErrorStateCause.NotPrepared ->
- stringResource(R.string.vpn_permission_error_notification_title)
- cause is ErrorStateCause.OtherAlwaysOnApp ->
- stringResource(R.string.always_on_vpn_error_notification_title, cause.appName)
- cause is ErrorStateCause.OtherLegacyAlwaysOnApp ->
- stringResource(R.string.legacy_always_on_vpn_error_notification_title)
- isBlocking -> stringResource(R.string.blocking_internet)
- else -> stringResource(R.string.critical_error)
- }
-}
-
-@Composable
-private fun ErrorState.message(): String {
- val cause = this.cause
- return when {
- isBlocking -> cause.errorMessageId()
- else -> stringResource(R.string.failed_to_block_internet)
- }
-}
-
-@Composable
-private fun ErrorStateCause.errorMessageId(): String =
- when (this) {
- is ErrorStateCause.AuthFailed -> stringResource(error.errorMessageId())
- is ErrorStateCause.Ipv6Unavailable -> stringResource(R.string.ipv6_unavailable)
- is ErrorStateCause.FirewallPolicyError -> stringResource(R.string.set_firewall_policy_error)
- is ErrorStateCause.DnsError -> stringResource(R.string.set_dns_error)
- is ErrorStateCause.StartTunnelError -> stringResource(R.string.start_tunnel_error)
- is ErrorStateCause.IsOffline -> stringResource(R.string.is_offline)
- is ErrorStateCause.TunnelParameterError -> stringResource(error.errorMessageId())
- is ErrorStateCause.NotPrepared ->
- stringResource(R.string.vpn_permission_error_notification_message)
- is ErrorStateCause.OtherAlwaysOnApp ->
- stringResource(R.string.always_on_vpn_error_notification_content, appName)
- is ErrorStateCause.OtherLegacyAlwaysOnApp ->
- stringResource(R.string.legacy_always_on_vpn_error_notification_content)
- is ErrorStateCause.InvalidDnsServers ->
- stringResource(
- R.string.invalid_dns_servers,
- addresses.joinToString { address -> address.addressString() },
- )
- }
-
-private fun AuthFailedError.errorMessageId(): Int =
- when (this) {
- AuthFailedError.ExpiredAccount -> R.string.account_credit_has_expired
- AuthFailedError.InvalidAccount,
- AuthFailedError.TooManyConnections,
- AuthFailedError.Unknown -> R.string.auth_failed
- }
-
-private fun ParameterGenerationError.errorMessageId(): Int =
- when (this) {
- ParameterGenerationError.NoMatchingRelay,
- ParameterGenerationError.NoMatchingBridgeRelay -> {
- R.string.no_matching_relay
- }
- ParameterGenerationError.NoWireguardKey -> R.string.no_wireguard_key
- ParameterGenerationError.CustomTunnelHostResultionError ->
- R.string.custom_tunnel_host_resolution_error
- }
-
-private fun InetAddress.addressString(): String {
- val hostNameAndAddress = this.toString().split('/', limit = 2)
- val address = hostNameAndAddress[1]
-
- return address
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RemoveDeviceConfirmationDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RemoveDeviceConfirmationDialog.kt
index d31325f140..867f614699 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RemoveDeviceConfirmationDialog.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RemoveDeviceConfirmationDialog.kt
@@ -15,11 +15,11 @@ import com.ramcosta.composedestinations.result.ResultBackNavigator
import com.ramcosta.composedestinations.spec.DestinationStyle
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.component.textResource
-import net.mullvad.mullvadvpn.compose.extensions.toAnnotatedString
import net.mullvad.mullvadvpn.compose.preview.DevicePreviewParameterProvider
import net.mullvad.mullvadvpn.lib.model.Device
import net.mullvad.mullvadvpn.lib.model.DeviceId
import net.mullvad.mullvadvpn.lib.theme.AppTheme
+import net.mullvad.mullvadvpn.lib.ui.component.toAnnotatedString
@Preview
@Composable
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/InfoDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/InfoDialog.kt
index b77decc9f0..30e4ebcc87 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/InfoDialog.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/InfoDialog.kt
@@ -24,10 +24,10 @@ import androidx.core.text.HtmlCompat
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.button.PrimaryButton
import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
-import net.mullvad.mullvadvpn.compose.extensions.toAnnotatedString
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
import net.mullvad.mullvadvpn.lib.theme.color.AlphaScrollbar
+import net.mullvad.mullvadvpn.lib.ui.component.toAnnotatedString
@Preview
@Composable
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt
deleted file mode 100644
index 28459c9a5d..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package net.mullvad.mullvadvpn.compose.extensions
-
-import android.content.res.Resources
-import java.time.Duration
-import net.mullvad.mullvadvpn.R
-
-private const val DAYS_IN_STANDARD_YEAR = 365
-
-fun Resources.getExpiryQuantityString(accountExpiry: Duration): String {
- val days = accountExpiry.toDays().toInt()
- val years = (accountExpiry.toDays() / DAYS_IN_STANDARD_YEAR).toInt()
-
- return if (accountExpiry.toMillis() <= 0) {
- getString(R.string.out_of_time)
- } else if (years > 1) {
- getRemainingText(this, R.plurals.years_left, years)
- } else if (days >= 1) {
- getRemainingText(this, R.plurals.days_left, days)
- } else {
- getString(R.string.less_than_a_day_left)
- }
-}
-
-private fun getRemainingText(resources: Resources, pluralId: Int, quantity: Int): String {
- return resources.getQuantityString(pluralId, quantity, quantity)
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/SpannedExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/SpannedExtensions.kt
deleted file mode 100644
index 6c294e6207..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/SpannedExtensions.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package net.mullvad.mullvadvpn.compose.extensions
-
-import android.graphics.Typeface
-import android.text.Spanned
-import android.text.style.StyleSpan
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.SpanStyle
-import androidx.compose.ui.text.buildAnnotatedString
-import androidx.compose.ui.text.font.FontWeight
-
-fun Spanned.toAnnotatedString(boldFontWeight: FontWeight = FontWeight.Bold): AnnotatedString =
- buildAnnotatedString {
- val spanned = this@toAnnotatedString
- append(spanned.toString())
- getSpans(0, spanned.length, Any::class.java).forEach { span ->
- val start = getSpanStart(span)
- val end = getSpanEnd(span)
- when (span) {
- is StyleSpan ->
- when (span.style) {
- Typeface.BOLD ->
- addStyle(SpanStyle(fontWeight = boldFontWeight), start, end)
- }
- }
- }
- }
-
-fun Spanned.toAnnotatedString(
- boldSpanStyle: SpanStyle = SpanStyle(fontWeight = FontWeight.ExtraBold)
-): AnnotatedString = buildAnnotatedString {
- val spanned = this@toAnnotatedString
- append(spanned.toString())
- getSpans(0, spanned.length, Any::class.java).forEach { span ->
- val start = getSpanStart(span)
- val end = getSpanEnd(span)
- when (span) {
- is StyleSpan ->
- when (span.style) {
- Typeface.BOLD -> addStyle(boldSpanStyle, start, end)
- }
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AppInfoUiStatePreviewParameterProvider.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AppInfoUiStatePreviewParameterProvider.kt
index 42d23a1d03..bd11a5f654 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AppInfoUiStatePreviewParameterProvider.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AppInfoUiStatePreviewParameterProvider.kt
@@ -1,7 +1,7 @@
package net.mullvad.mullvadvpn.compose.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
-import net.mullvad.mullvadvpn.ui.VersionInfo
+import net.mullvad.mullvadvpn.lib.model.VersionInfo
import net.mullvad.mullvadvpn.viewmodel.AppInfoUiState
class AppInfoUiStatePreviewParameterProvider : PreviewParameterProvider<AppInfoUiState> {
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/ConnectUiStatePreviewParameterProvider.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/ConnectUiStatePreviewParameterProvider.kt
index b2150d0037..d3c704d0a2 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/ConnectUiStatePreviewParameterProvider.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/ConnectUiStatePreviewParameterProvider.kt
@@ -5,6 +5,7 @@ import java.net.InetAddress
import net.mullvad.mullvadvpn.compose.state.ConnectUiState
import net.mullvad.mullvadvpn.lib.model.ActionAfterDisconnect
import net.mullvad.mullvadvpn.lib.model.GeoIpLocation
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
class ConnectUiStatePreviewParameterProvider : PreviewParameterProvider<ConnectUiState> {
override val values = sequenceOf(ConnectUiState.INITIAL) + generateOtherStates()
@@ -29,7 +30,7 @@ private fun generateOtherStates(): Sequence<ConnectUiState> =
),
TunnelStatePreviewData.generateErrorState(isBlocking = true),
)
- .map { state ->
+ .mapIndexed { index, state ->
ConnectUiState(
location =
GeoIpLocation(
@@ -45,7 +46,8 @@ private fun generateOtherStates(): Sequence<ConnectUiState> =
selectedRelayItemTitle = "Relay Title",
tunnelState = state,
showLocation = true,
- inAppNotification = null,
+ inAppNotification =
+ if (index == 0) InAppNotification.NewDevice("Test Device") else null,
deviceName = "Cool Beans",
daysLeftUntilExpiry = 42,
isPlayBuild = true,
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AutoConnectAndLockdownModeScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AutoConnectAndLockdownModeScreen.kt
index 39b9e174f2..ceddf9dc99 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AutoConnectAndLockdownModeScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AutoConnectAndLockdownModeScreen.kt
@@ -54,13 +54,13 @@ import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.component.NavigateBackIconButton
import net.mullvad.mullvadvpn.compose.component.ScaffoldWithLargeTopBarAndButton
-import net.mullvad.mullvadvpn.compose.extensions.toAnnotatedString
import net.mullvad.mullvadvpn.compose.transitions.SlideInFromRightTransition
import net.mullvad.mullvadvpn.lib.common.util.openVpnSettings
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
import net.mullvad.mullvadvpn.lib.theme.color.AlphaInvisible
import net.mullvad.mullvadvpn.lib.theme.color.AlphaVisible
+import net.mullvad.mullvadvpn.lib.ui.component.toAnnotatedString
import net.mullvad.mullvadvpn.service.constant.IS_PLAY_BUILD
import net.mullvad.mullvadvpn.util.appendHideNavOnPlayBuild
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
index 7c4bdbd3b3..eb6df6c820 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
@@ -10,6 +10,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.calculateEndPadding
@@ -25,6 +26,8 @@ import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -71,6 +74,7 @@ import net.mullvad.mullvadvpn.compose.button.SwitchLocationButton
import net.mullvad.mullvadvpn.compose.component.ConnectionStatusText
import net.mullvad.mullvadvpn.compose.component.ExpandChevron
import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorLarge
+import net.mullvad.mullvadvpn.compose.component.MullvadSnackbar
import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBarAndDeviceName
import net.mullvad.mullvadvpn.compose.component.connectioninfo.ConnectionDetailPanel
import net.mullvad.mullvadvpn.compose.component.connectioninfo.FeatureIndicatorsPanel
@@ -89,6 +93,7 @@ import net.mullvad.mullvadvpn.compose.transitions.HomeTransition
import net.mullvad.mullvadvpn.compose.util.CollectSideEffectWithLifecycle
import net.mullvad.mullvadvpn.compose.util.CreateVpnProfile
import net.mullvad.mullvadvpn.compose.util.OnNavResultValue
+import net.mullvad.mullvadvpn.compose.util.isTv
import net.mullvad.mullvadvpn.compose.util.showSnackbarImmediately
import net.mullvad.mullvadvpn.constant.SECURE_ZOOM
import net.mullvad.mullvadvpn.constant.SECURE_ZOOM_ANIMATION_MILLIS
@@ -115,6 +120,7 @@ import net.mullvad.mullvadvpn.lib.theme.color.AlphaScrollbar
import net.mullvad.mullvadvpn.lib.theme.color.AlphaVisible
import net.mullvad.mullvadvpn.lib.theme.typeface.connectionStatus
import net.mullvad.mullvadvpn.lib.theme.typeface.hostname
+import net.mullvad.mullvadvpn.lib.tv.NavigationDrawerTv
import net.mullvad.mullvadvpn.util.removeHtmlTags
import net.mullvad.mullvadvpn.viewmodel.ConnectViewModel
import org.koin.androidx.compose.koinViewModel
@@ -267,71 +273,130 @@ fun ConnectScreen(
onAccountClick: () -> Unit,
onDismissNewDeviceClick: () -> Unit,
) {
+ val content =
+ @Composable { padding: PaddingValues ->
+ Content(
+ padding,
+ state,
+ onDisconnectClick,
+ onReconnectClick,
+ onConnectClick,
+ onCancelClick,
+ onSwitchLocationClick,
+ onOpenAppListing,
+ onManageAccountClick,
+ onChangelogClick,
+ onDismissChangelogClick,
+ onDismissNewDeviceClick,
+ )
+ }
- ScaffoldWithTopBarAndDeviceName(
- topBarColor = state.tunnelState.topBarColor(),
- iconTintColor = state.tunnelState.iconTintColor(),
- onSettingsClicked = onSettingsClick,
- onAccountClicked = onAccountClick,
- deviceName = state.deviceName,
- timeLeft = state.daysLeftUntilExpiry,
- snackbarHostState = snackbarHostState,
- ) {
- val configuration = LocalConfiguration.current
- val screenHeight = configuration.screenHeightDp.dp
- val indicatorPercentOffset =
- if (screenHeight < SCREEN_HEIGHT_THRESHOLD) SHORT_SCREEN_INDICATOR_BIAS
- else TALL_SCREEN_INDICATOR_BIAS
-
- Box(
- Modifier.padding(
- top = it.calculateTopPadding(),
- start = it.calculateStartPadding(LocalLayoutDirection.current),
- end = it.calculateEndPadding(LocalLayoutDirection.current),
+ if (isTv()) {
+ Scaffold(
+ snackbarHost = {
+ SnackbarHost(
+ snackbarHostState,
+ snackbar = { snackbarData -> MullvadSnackbar(snackbarData = snackbarData) },
)
- .fillMaxSize()
+ }
) {
- MullvadMap(state, indicatorPercentOffset)
+ NavigationDrawerTv(
+ daysLeftUntilExpiry = state.daysLeftUntilExpiry,
+ deviceName = state.deviceName,
+ onSettingsClick = onSettingsClick,
+ onAccountClick = onAccountClick,
+ ) {
+ content(it)
+ }
+ }
+ } else {
+ ScaffoldWithTopBarAndDeviceName(
+ topBarColor = state.tunnelState.topBarColor(),
+ iconTintColor = state.tunnelState.iconTintColor(),
+ onSettingsClicked = onSettingsClick,
+ onAccountClicked = onAccountClick,
+ deviceName = state.deviceName,
+ timeLeft = state.daysLeftUntilExpiry,
+ snackbarHostState = snackbarHostState,
+ ) {
+ content(it)
+ }
+ }
+}
- MullvadCircularProgressIndicatorLarge(
- color = MaterialTheme.colorScheme.onSurface,
- modifier =
- Modifier.layout { measurable, constraints ->
- val placeable = measurable.measure(constraints)
- layout(placeable.width, placeable.height) {
- placeable.placeRelative(
- x = (constraints.maxWidth * 0.5f - placeable.width / 2).toInt(),
- y =
- (constraints.maxHeight * indicatorPercentOffset -
- placeable.height / 2)
- .toInt(),
- )
- }
- }
- .alpha(if (state.showLoading) AlphaVisible else AlphaInvisible)
- .testTag(CIRCULAR_PROGRESS_INDICATOR),
+@Composable
+private fun Content(
+ paddingValues: PaddingValues,
+ state: ConnectUiState,
+ onDisconnectClick: () -> Unit,
+ onReconnectClick: () -> Unit,
+ onConnectClick: () -> Unit,
+ onCancelClick: () -> Unit,
+ onSwitchLocationClick: () -> Unit,
+ onOpenAppListing: () -> Unit,
+ onManageAccountClick: () -> Unit,
+ onChangelogClick: () -> Unit,
+ onDismissChangelogClick: () -> Unit,
+ onDismissNewDeviceClick: () -> Unit,
+) {
+ val configuration = LocalConfiguration.current
+ val screenHeight = configuration.screenHeightDp.dp
+ val indicatorPercentOffset =
+ if (screenHeight < SCREEN_HEIGHT_THRESHOLD) SHORT_SCREEN_INDICATOR_BIAS
+ else TALL_SCREEN_INDICATOR_BIAS
+
+ Box(
+ Modifier.padding(
+ top = paddingValues.calculateTopPadding(),
+ start = paddingValues.calculateStartPadding(LocalLayoutDirection.current),
+ end = paddingValues.calculateEndPadding(LocalLayoutDirection.current),
)
+ .fillMaxSize()
+ ) {
+ MullvadMap(state, indicatorPercentOffset)
- Box(modifier = Modifier.fillMaxSize().padding(bottom = it.calculateBottomPadding())) {
- NotificationBanner(
- notification = state.inAppNotification,
- isPlayBuild = state.isPlayBuild,
- openAppListing = onOpenAppListing,
- onClickShowAccount = onManageAccountClick,
- onClickShowChangelog = onChangelogClick,
- onClickDismissChangelog = onDismissChangelogClick,
- onClickDismissNewDevice = onDismissNewDeviceClick,
- )
- ConnectionCard(
- state = state,
- modifier = Modifier.align(Alignment.BottomCenter),
- onSwitchLocationClick = onSwitchLocationClick,
- onDisconnectClick = onDisconnectClick,
- onReconnectClick = onReconnectClick,
- onCancelClick = onCancelClick,
- onConnectClick = onConnectClick,
- )
- }
+ MullvadCircularProgressIndicatorLarge(
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier =
+ Modifier.layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) {
+ placeable.placeRelative(
+ x = (constraints.maxWidth * 0.5f - placeable.width / 2).toInt(),
+ y =
+ (constraints.maxHeight * indicatorPercentOffset -
+ placeable.height / 2)
+ .toInt(),
+ )
+ }
+ }
+ .alpha(if (state.showLoading) AlphaVisible else AlphaInvisible)
+ .testTag(CIRCULAR_PROGRESS_INDICATOR),
+ )
+
+ Box(
+ modifier =
+ Modifier.fillMaxSize().padding(bottom = paddingValues.calculateBottomPadding())
+ ) {
+ NotificationBanner(
+ modifier = Modifier.align(Alignment.TopCenter),
+ notification = state.inAppNotification,
+ isPlayBuild = state.isPlayBuild,
+ openAppListing = onOpenAppListing,
+ onClickShowAccount = onManageAccountClick,
+ onClickShowChangelog = onChangelogClick,
+ onClickDismissChangelog = onDismissChangelogClick,
+ onClickDismissNewDevice = onDismissNewDeviceClick,
+ )
+ ConnectionCard(
+ state = state,
+ modifier = Modifier.align(Alignment.BottomCenter),
+ onSwitchLocationClick = onSwitchLocationClick,
+ onDisconnectClick = onDisconnectClick,
+ onReconnectClick = onReconnectClick,
+ onCancelClick = onCancelClick,
+ onConnectClick = onConnectClick,
+ )
}
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt
index 4e17b6918b..63b596d513 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt
@@ -1,8 +1,8 @@
package net.mullvad.mullvadvpn.compose.state
import net.mullvad.mullvadvpn.lib.model.GeoIpLocation
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
import net.mullvad.mullvadvpn.lib.model.TunnelState
-import net.mullvad.mullvadvpn.repository.InAppNotification
data class ConnectUiState(
val location: GeoIpLocation?,
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
index f38e349a7f..4b6a339f61 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
@@ -49,11 +49,6 @@ const val LOCATION_INFO_TEST_TAG = "location_info_test_tag"
const val LOCATION_INFO_CONNECTION_IN_TEST_TAG = "location_info_connection_in_test_tag"
const val LOCATION_INFO_CONNECTION_OUT_TEST_TAG = "location_info_connection_out_test_tag"
-// ConnectScreen - Notification banner
-const val NOTIFICATION_BANNER = "notification_banner"
-const val NOTIFICATION_BANNER_ACTION = "notification_banner_action"
-const val NOTIFICATION_BANNER_TEXT_ACTION = "notification_banner_text_action"
-
// PlayPayment
const val PLAY_PAYMENT_INFO_ICON_TEST_TAG = "play_payment_info_icon_test_tag"
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Device.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Device.kt
new file mode 100644
index 0000000000..a1fed50acb
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Device.kt
@@ -0,0 +1,13 @@
+package net.mullvad.mullvadvpn.compose.util
+
+import android.content.pm.PackageManager
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.booleanResource
+import net.mullvad.mullvadvpn.R
+
+@Composable
+fun isTv(): Boolean {
+ return booleanResource(R.bool.isTv) ||
+ LocalContext.current.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/RememberPrevious.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/RememberPrevious.kt
deleted file mode 100644
index 6782e0ab55..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/RememberPrevious.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package net.mullvad.mullvadvpn.compose.util
-
-/*
- * Code snippet taken from:
- * https://stackoverflow.com/questions/67801939/get-previous-value-of-state-in-composable-jetpack-compose
- */
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.remember
-
-@Composable
-fun <T> rememberPrevious(
- current: T,
- shouldUpdate: (prev: T?, curr: T) -> Boolean = { a: T?, b: T -> a != b },
-): T? {
- val ref = rememberRef<T>()
-
- // launched after render, so the current render will have the old value anyway
- SideEffect {
- if (shouldUpdate(ref.value, current)) {
- ref.value = current
- }
- }
-
- return ref.value
-}
-
-@Composable
-private fun <T> rememberRef(): MutableState<T?> {
- // for some reason it always recreated the value with vararg keys,
- // leaving out the keys as a parameter for remember for now
- return remember {
- object : MutableState<T?> {
- override var value: T? = null
-
- override fun component1(): T? = value
-
- override fun component2(): (T?) -> Unit = { value = it }
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/InAppNotificationController.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/InAppNotificationController.kt
index 0e3e004f0b..752e185d14 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/InAppNotificationController.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/InAppNotificationController.kt
@@ -1,60 +1,16 @@
package net.mullvad.mullvadvpn.repository
-import java.time.Duration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import net.mullvad.mullvadvpn.lib.model.ErrorState
-import net.mullvad.mullvadvpn.ui.VersionInfo
import net.mullvad.mullvadvpn.usecase.AccountExpiryInAppNotificationUseCase
import net.mullvad.mullvadvpn.usecase.NewChangelogNotificationUseCase
import net.mullvad.mullvadvpn.usecase.NewDeviceNotificationUseCase
import net.mullvad.mullvadvpn.usecase.TunnelStateNotificationUseCase
import net.mullvad.mullvadvpn.usecase.VersionNotificationUseCase
-enum class StatusLevel {
- Error,
- Warning,
- Info,
-}
-
-sealed class InAppNotification {
- abstract val statusLevel: StatusLevel
- abstract val priority: Long
-
- data class TunnelStateError(val error: ErrorState) : InAppNotification() {
- override val statusLevel = StatusLevel.Error
- override val priority: Long = 1001
- }
-
- data object TunnelStateBlocked : InAppNotification() {
- override val statusLevel = StatusLevel.Error
- override val priority: Long = 1000
- }
-
- data class UnsupportedVersion(val versionInfo: VersionInfo) : InAppNotification() {
- override val statusLevel = StatusLevel.Error
- override val priority: Long = 999
- }
-
- data class AccountExpiry(val expiry: Duration) : InAppNotification() {
- override val statusLevel = StatusLevel.Warning
- override val priority: Long = 1001
- }
-
- data class NewDevice(val deviceName: String) : InAppNotification() {
- override val statusLevel = StatusLevel.Info
- override val priority: Long = 1001
- }
-
- data object NewVersionChangelog : InAppNotification() {
- override val statusLevel = StatusLevel.Info
- override val priority: Long = 1001
- }
-}
-
class InAppNotificationController(
accountExpiryInAppNotificationUseCase: AccountExpiryInAppNotificationUseCase,
newDeviceNotificationUseCase: NewDeviceNotificationUseCase,
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/VersionInfo.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/VersionInfo.kt
deleted file mode 100644
index 7e2550974d..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/VersionInfo.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package net.mullvad.mullvadvpn.ui
-
-data class VersionInfo(val currentVersion: String, val isSupported: Boolean)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AppVersionInfoRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AppVersionInfoRepository.kt
index 7a74c0f0d2..d78521b9be 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AppVersionInfoRepository.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AppVersionInfoRepository.kt
@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
import net.mullvad.mullvadvpn.lib.model.BuildVersion
-import net.mullvad.mullvadvpn.ui.VersionInfo
+import net.mullvad.mullvadvpn.lib.model.VersionInfo
class AppVersionInfoRepository(
private val buildVersion: BuildVersion,
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCase.kt
index 057494f762..a39afe9c39 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCase.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCase.kt
@@ -5,8 +5,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
import net.mullvad.mullvadvpn.lib.shared.AccountRepository
-import net.mullvad.mullvadvpn.repository.InAppNotification
import net.mullvad.mullvadvpn.service.notifications.accountexpiry.ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD
import net.mullvad.mullvadvpn.service.notifications.accountexpiry.ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL
import net.mullvad.mullvadvpn.service.notifications.accountexpiry.AccountExpiryTicker
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/NewChangelogNotificationUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/NewChangelogNotificationUseCase.kt
index 157de67013..5936d7b3a6 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/NewChangelogNotificationUseCase.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/NewChangelogNotificationUseCase.kt
@@ -2,8 +2,8 @@ package net.mullvad.mullvadvpn.usecase
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
import net.mullvad.mullvadvpn.repository.ChangelogRepository
-import net.mullvad.mullvadvpn.repository.InAppNotification
class NewChangelogNotificationUseCase(private val changelogRepository: ChangelogRepository) {
operator fun invoke() =
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCase.kt
index 2faca012b7..4374ca6037 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCase.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCase.kt
@@ -3,8 +3,8 @@ package net.mullvad.mullvadvpn.usecase
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
-import net.mullvad.mullvadvpn.repository.InAppNotification
import net.mullvad.mullvadvpn.repository.NewDeviceRepository
class NewDeviceNotificationUseCase(
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCase.kt
index 888f9f67bf..85ea7cf11a 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCase.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCase.kt
@@ -4,9 +4,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import net.mullvad.mullvadvpn.lib.model.ActionAfterDisconnect
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
import net.mullvad.mullvadvpn.lib.model.TunnelState
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
-import net.mullvad.mullvadvpn.repository.InAppNotification
class TunnelStateNotificationUseCase(private val connectionProxy: ConnectionProxy) {
operator fun invoke(): Flow<List<InAppNotification>> =
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCase.kt
index d46089a9d3..6575871f21 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCase.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCase.kt
@@ -2,8 +2,8 @@ package net.mullvad.mullvadvpn.usecase
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import net.mullvad.mullvadvpn.repository.InAppNotification
-import net.mullvad.mullvadvpn.ui.VersionInfo
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
+import net.mullvad.mullvadvpn.lib.model.VersionInfo
import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoRepository
class VersionNotificationUseCase(
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AppInfoViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AppInfoViewModel.kt
index 16ec17be5d..ae197fa7ef 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AppInfoViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AppInfoViewModel.kt
@@ -14,8 +14,8 @@ import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.lib.model.VersionInfo
import net.mullvad.mullvadvpn.repository.ChangelogRepository
-import net.mullvad.mullvadvpn.ui.VersionInfo
import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoRepository
class AppInfoViewModel(
diff --git a/android/app/src/main/res/drawable/daita_illustration_1.xml b/android/app/src/main/res/drawable/daita_illustration_1.xml
deleted file mode 100644
index 918f0c9e6e..0000000000
--- a/android/app/src/main/res/drawable/daita_illustration_1.xml
+++ /dev/null
@@ -1,342 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:width="328dp"
- android:height="103dp"
- android:viewportWidth="328"
- android:viewportHeight="103"
- tools:ignore="VectorRaster">
- <path
- android:pathData="M0,12C0,5.37 5.37,0 12,0H316C322.63,0 328,5.37 328,12V90.5C328,97.13 322.63,102.5 316,102.5H12C5.37,102.5 0,97.13 0,90.5V12Z"
- android:fillColor="#152637"/>
- <path
- android:pathData="M1,12C1,5.92 5.92,1 12,1H316C322.08,1 327,5.92 327,12V90.5C327,96.58 322.08,101.5 316,101.5H12C5.92,101.5 1,96.58 1,90.5V12Z"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#304358"/>
- <path
- android:pathData="M75.17,52.39C74.54,52.39 74.03,51.88 74.03,51.25L74.03,50.11C74.03,49.48 74.54,48.97 75.17,48.97C75.8,48.97 76.31,49.48 76.31,50.11L76.31,51.25C76.31,51.88 75.8,52.39 75.17,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M102.5,52.39C101.87,52.39 101.36,51.88 101.36,51.25L101.36,50.11C101.36,49.48 101.87,48.97 102.5,48.97C103.13,48.97 103.64,49.48 103.64,50.11L103.64,51.25C103.64,51.88 103.13,52.39 102.5,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M107.06,52.39C106.43,52.39 105.92,51.88 105.92,51.25L105.92,50.11C105.92,49.48 106.43,48.97 107.06,48.97C107.68,48.97 108.19,49.48 108.19,50.11L108.19,51.25C108.19,51.88 107.68,52.39 107.06,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M111.61,52.39C110.98,52.39 110.47,51.88 110.47,51.25L110.47,50.11C110.47,49.48 110.98,48.97 111.61,48.97C112.24,48.97 112.75,49.48 112.75,50.11L112.75,51.25C112.75,51.88 112.24,52.39 111.61,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M116.17,52.39C115.54,52.39 115.03,51.88 115.03,51.25L115.03,50.11C115.03,49.48 115.54,48.97 116.17,48.97C116.8,48.97 117.31,49.48 117.31,50.11L117.31,51.25C117.31,51.88 116.8,52.39 116.17,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M120.72,52.39C120.09,52.39 119.58,51.88 119.58,51.25L119.58,50.11C119.58,49.48 120.09,48.97 120.72,48.97C121.35,48.97 121.86,49.48 121.86,50.11L121.86,51.25C121.86,51.88 121.35,52.39 120.72,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M125.28,52.39C124.65,52.39 124.14,51.88 124.14,51.25L124.14,50.11C124.14,49.48 124.65,48.97 125.28,48.97C125.91,48.97 126.42,49.48 126.42,50.11L126.42,51.25C126.42,51.88 125.91,52.39 125.28,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M129.83,52.39C129.2,52.39 128.69,51.88 128.69,51.25L128.69,50.11C128.69,49.48 129.2,48.97 129.83,48.97C130.46,48.97 130.97,49.48 130.97,50.11L130.97,51.25C130.97,51.88 130.46,52.39 129.83,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M134.39,52.39C133.76,52.39 133.25,51.88 133.25,51.25L133.25,50.11C133.25,49.48 133.76,48.97 134.39,48.97C135.02,48.97 135.53,49.48 135.53,50.11L135.53,51.25C135.53,51.88 135.02,52.39 134.39,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M138.94,52.39C138.32,52.39 137.81,51.88 137.81,51.25L137.81,50.11C137.81,49.48 138.32,48.97 138.94,48.97C139.57,48.97 140.08,49.48 140.08,50.11L140.08,51.25C140.08,51.88 139.57,52.39 138.94,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M143.5,52.39C142.87,52.39 142.36,51.88 142.36,51.25L142.36,50.11C142.36,49.48 142.87,48.97 143.5,48.97C144.13,48.97 144.64,49.48 144.64,50.11L144.64,51.25C144.64,51.88 144.13,52.39 143.5,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M184.5,52.39C183.87,52.39 183.36,51.88 183.36,51.25L183.36,50.11C183.36,49.48 183.87,48.97 184.5,48.97C185.13,48.97 185.64,49.48 185.64,50.11L185.64,51.25C185.64,51.88 185.13,52.39 184.5,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M189.06,52.39C188.43,52.39 187.92,51.88 187.92,51.25L187.92,50.11C187.92,49.48 188.43,48.97 189.06,48.97C189.68,48.97 190.19,49.48 190.19,50.11L190.19,51.25C190.19,51.88 189.68,52.39 189.06,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M193.61,52.39C192.98,52.39 192.47,51.88 192.47,51.25L192.47,50.11C192.47,49.48 192.98,48.97 193.61,48.97C194.24,48.97 194.75,49.48 194.75,50.11L194.75,51.25C194.75,51.88 194.24,52.39 193.61,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M198.17,52.39C197.54,52.39 197.03,51.88 197.03,51.25L197.03,50.11C197.03,49.48 197.54,48.97 198.17,48.97C198.8,48.97 199.31,49.48 199.31,50.11L199.31,51.25C199.31,51.88 198.8,52.39 198.17,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M202.72,52.39C202.09,52.39 201.58,51.88 201.58,51.25L201.58,50.11C201.58,49.48 202.09,48.97 202.72,48.97C203.35,48.97 203.86,49.48 203.86,50.11L203.86,51.25C203.86,51.88 203.35,52.39 202.72,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M207.28,52.39C206.65,52.39 206.14,51.88 206.14,51.25L206.14,50.11C206.14,49.48 206.65,48.97 207.28,48.97C207.91,48.97 208.42,49.48 208.42,50.11L208.42,51.25C208.42,51.88 207.91,52.39 207.28,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M211.83,52.39C211.2,52.39 210.69,51.88 210.69,51.25L210.69,50.11C210.69,49.48 211.2,48.97 211.83,48.97C212.46,48.97 212.97,49.48 212.97,50.11L212.97,51.25C212.97,51.88 212.46,52.39 211.83,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M216.39,52.39C215.76,52.39 215.25,51.88 215.25,51.25L215.25,50.11C215.25,49.48 215.76,48.97 216.39,48.97C217.02,48.97 217.53,49.48 217.53,50.11L217.53,51.25C217.53,51.88 217.02,52.39 216.39,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M220.94,52.39C220.32,52.39 219.81,51.88 219.81,51.25L219.81,50.11C219.81,49.48 220.32,48.97 220.94,48.97C221.57,48.97 222.08,49.48 222.08,50.11L222.08,51.25C222.08,51.88 221.57,52.39 220.94,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M225.5,52.39C224.87,52.39 224.36,51.88 224.36,51.25L224.36,50.11C224.36,49.48 224.87,48.97 225.5,48.97C226.13,48.97 226.64,49.48 226.64,50.11L226.64,51.25C226.64,51.88 226.13,52.39 225.5,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M230.06,52.39C229.43,52.39 228.92,51.88 228.92,51.25L228.92,50.11C228.92,49.48 229.43,48.97 230.06,48.97C230.68,48.97 231.19,49.48 231.19,50.11L231.19,51.25C231.19,51.88 230.68,52.39 230.06,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M234.61,52.39C233.98,52.39 233.47,51.88 233.47,51.25L233.47,50.11C233.47,49.48 233.98,48.97 234.61,48.97C235.24,48.97 235.75,49.48 235.75,50.11L235.75,51.25C235.75,51.88 235.24,52.39 234.61,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M239.17,52.39C238.54,52.39 238.03,51.88 238.03,51.25L238.03,50.11C238.03,49.48 238.54,48.97 239.17,48.97C239.8,48.97 240.31,49.48 240.31,50.11L240.31,51.25C240.31,51.88 239.8,52.39 239.17,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M243.72,52.39C243.09,52.39 242.58,51.88 242.58,51.25L242.58,50.11C242.58,49.48 243.09,48.97 243.72,48.97C244.35,48.97 244.86,49.48 244.86,50.11L244.86,51.25C244.86,51.88 244.35,52.39 243.72,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M248.28,52.39C247.65,52.39 247.14,51.88 247.14,51.25L247.14,50.11C247.14,49.48 247.65,48.97 248.28,48.97C248.91,48.97 249.42,49.48 249.42,50.11L249.42,51.25C249.42,51.88 248.91,52.39 248.28,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M252.83,52.39C252.2,52.39 251.69,51.88 251.69,51.25L251.69,50.11C251.69,49.48 252.2,48.97 252.83,48.97C253.46,48.97 253.97,49.48 253.97,50.11L253.97,51.25C253.97,51.88 253.46,52.39 252.83,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M257.39,52.39C256.76,52.39 256.25,51.88 256.25,51.25L256.25,50.11C256.25,49.48 256.76,48.97 257.39,48.97C258.02,48.97 258.53,49.48 258.53,50.11L258.53,51.25C258.53,51.88 258.02,52.39 257.39,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M261.94,52.39C261.32,52.39 260.81,51.88 260.81,51.25L260.81,50.11C260.81,49.48 261.32,48.97 261.94,48.97C262.57,48.97 263.08,49.48 263.08,50.11L263.08,51.25C263.08,51.88 262.57,52.39 261.94,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M266.5,52.39C265.87,52.39 265.36,51.88 265.36,51.25L265.36,50.11C265.36,49.48 265.87,48.97 266.5,48.97C267.13,48.97 267.64,49.48 267.64,50.11L267.64,51.25C267.64,51.88 267.13,52.39 266.5,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M271.06,52.39C270.43,52.39 269.92,51.88 269.92,51.25L269.92,50.11C269.92,49.48 270.43,48.97 271.06,48.97C271.68,48.97 272.19,49.48 272.19,50.11L272.19,51.25C272.19,51.88 271.68,52.39 271.06,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M275.61,52.39C274.98,52.39 274.47,51.88 274.47,51.25L274.47,50.11C274.47,49.48 274.98,48.97 275.61,48.97C276.24,48.97 276.75,49.48 276.75,50.11L276.75,51.25C276.75,51.88 276.24,52.39 275.61,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M70.61,52.39C69.98,52.39 69.47,51.88 69.47,51.25L69.47,50.11C69.47,49.48 69.98,48.97 70.61,48.97C71.24,48.97 71.75,49.48 71.75,50.11L71.75,51.25C71.75,51.88 71.24,52.39 70.61,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M97.94,52.39C97.32,52.39 96.81,51.88 96.81,51.25L96.81,50.11C96.81,49.48 97.32,48.97 97.94,48.97C98.57,48.97 99.08,49.48 99.08,50.11L99.08,51.25C99.08,51.88 98.57,52.39 97.94,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M93.39,52.39C92.76,52.39 92.25,51.88 92.25,51.25L92.25,50.11C92.25,49.48 92.76,48.97 93.39,48.97C94.02,48.97 94.53,49.48 94.53,50.11L94.53,51.25C94.53,51.88 94.02,52.39 93.39,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M52.39,52.39C51.76,52.39 51.25,51.88 51.25,51.25L51.25,50.11C51.25,49.48 51.76,48.97 52.39,48.97C53.02,48.97 53.53,49.48 53.53,50.11L53.53,51.25C53.53,51.88 53.02,52.39 52.39,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M56.94,52.39C56.32,52.39 55.81,51.88 55.81,51.25L55.81,50.11C55.81,49.48 56.32,48.97 56.94,48.97C57.57,48.97 58.08,49.48 58.08,50.11L58.08,51.25C58.08,51.88 57.57,52.39 56.94,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M61.5,52.39C60.87,52.39 60.36,51.88 60.36,51.25L60.36,50.11C60.36,49.48 60.87,48.97 61.5,48.97C62.13,48.97 62.64,49.48 62.64,50.11L62.64,51.25C62.64,51.88 62.13,52.39 61.5,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M66.06,52.39C65.43,52.39 64.92,51.88 64.92,51.25L64.92,50.11C64.92,49.48 65.43,48.97 66.06,48.97C66.68,48.97 67.19,49.48 67.19,50.11L67.19,51.25C67.19,51.88 66.68,52.39 66.06,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M79.72,52.39C79.09,52.39 78.58,51.88 78.58,51.25L78.58,50.11C78.58,49.48 79.09,48.97 79.72,48.97C80.35,48.97 80.86,49.48 80.86,50.11L80.86,51.25C80.86,51.88 80.35,52.39 79.72,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M84.28,52.39C83.65,52.39 83.14,51.88 83.14,51.25L83.14,50.11C83.14,49.48 83.65,48.97 84.28,48.97C84.91,48.97 85.42,49.48 85.42,50.11L85.42,51.25C85.42,51.88 84.91,52.39 84.28,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M88.83,52.39C88.2,52.39 87.69,51.88 87.69,51.25L87.69,50.11C87.69,49.48 88.2,48.97 88.83,48.97C89.46,48.97 89.97,49.48 89.97,50.11L89.97,51.25C89.97,51.88 89.46,52.39 88.83,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M52.39,60.36L52.39,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M61.5,55.81L61.5,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M56.94,74.03L56.94,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M79.72,60.36L79.72,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M88.83,55.81L88.83,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M84.28,74.03L84.28,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M107.06,60.36L107.06,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M116.17,55.81L116.17,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M111.61,74.03L111.61,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M134.39,60.36L134.39,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M143.5,55.81L143.5,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M138.94,74.03L138.94,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M184.5,60.36L184.5,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M193.61,55.81L193.61,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M189.06,74.03L189.06,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M211.83,60.36L211.83,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M220.94,55.81L220.94,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M216.39,74.03L216.39,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M239.17,60.36L239.17,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M248.28,55.81L248.28,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M243.72,74.03L243.72,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M266.5,60.36L266.5,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M275.61,55.81L275.61,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M271.06,74.03L271.06,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M301.08,52.39C301.17,51.64 301.24,50.89 301.24,50.11C301.24,49.34 301.17,48.58 301.08,47.83H304.93C305.11,48.56 305.22,49.33 305.22,50.11C305.22,50.9 305.11,51.66 304.93,52.39M299.06,58.72C299.74,57.46 300.27,56.09 300.63,54.67H303.99C302.89,56.57 301.14,58.01 299.06,58.72ZM298.78,52.39H293.45C293.33,51.64 293.26,50.89 293.26,50.11C293.26,49.34 293.33,48.57 293.45,47.83H298.78C298.88,48.57 298.96,49.34 298.96,50.11C298.96,50.89 298.88,51.64 298.78,52.39ZM296.11,59.18C295.17,57.81 294.4,56.3 293.94,54.67H298.29C297.82,56.3 297.06,57.81 296.11,59.18ZM291.56,45.56H288.23C289.32,43.65 291.07,42.21 293.15,41.5C292.47,42.77 291.95,44.13 291.56,45.56ZM288.23,54.67H291.56C291.95,56.09 292.47,57.46 293.15,58.72C291.08,58.01 289.33,56.57 288.23,54.67ZM287.3,52.39C287.11,51.66 287,50.9 287,50.11C287,49.33 287.11,48.56 287.3,47.83H291.15C291.05,48.58 290.99,49.34 290.99,50.11C290.99,50.89 291.05,51.64 291.15,52.39M296.11,41.03C297.06,42.4 297.82,43.93 298.29,45.56H293.94C294.4,43.93 295.17,42.4 296.11,41.03ZM303.99,45.56H300.63C300.28,44.15 299.75,42.78 299.06,41.5C301.16,42.22 302.9,43.67 303.99,45.56ZM296.11,38.72C289.81,38.72 284.72,43.85 284.72,50.11C284.72,53.13 285.92,56.03 288.06,58.16C289.12,59.22 290.37,60.06 291.75,60.63C293.14,61.21 294.62,61.5 296.11,61.5C299.13,61.5 302.03,60.3 304.16,58.16C306.3,56.03 307.5,53.13 307.5,50.11C307.5,48.62 307.2,47.13 306.63,45.75C306.06,44.37 305.22,43.12 304.16,42.06C303.11,41 301.85,40.16 300.47,39.59C299.09,39.02 297.61,38.72 296.11,38.72Z"
- android:fillColor="#ffffff"
- tools:ignore="VectorPath" />
- <group>
- <clip-path
- android:pathData="M18.22,37.58h27.33v27.33h-27.33z"/>
- <path
- android:pathData="M26.19,63.78C25.57,63.78 25.03,63.55 24.59,63.11C24.14,62.66 23.92,62.13 23.92,61.5V41C23.92,40.37 24.14,39.84 24.59,39.39C25.03,38.95 25.57,38.72 26.19,38.72H37.58C38.21,38.72 38.75,38.95 39.19,39.39C39.64,39.84 39.86,40.37 39.86,41V61.5C39.86,62.13 39.64,62.66 39.19,63.11C38.75,63.55 38.21,63.78 37.58,63.78H26.19ZM26.19,60.36V61.5H37.58V60.36H26.19ZM26.19,58.08H37.58V44.42H26.19V58.08ZM26.19,42.14H37.58V41H26.19V42.14Z"
- android:fillColor="#ffffff"/>
- </group>
- <path
- android:pathData="M152.61,42.82V48.29C152.61,49.31 153.18,50.11 153.98,50.11H174.14C174.82,50.11 175.5,49.31 175.5,48.29V42.82C175.39,41.8 174.82,41 174.02,41H153.98C153.18,41 152.61,41.8 152.61,42.82ZM161.72,46.69V44.42H160.58V46.69H161.72ZM156.03,46.69H158.31V44.42H156.03V46.69ZM173.11,47.83H154.89V43.28H173.11V47.83ZM152.61,54.21V59.68C152.61,60.7 153.18,61.5 153.98,61.5H174.14C174.82,61.5 175.5,60.7 175.5,59.68V54.21C175.5,53.19 174.93,52.39 174.14,52.39H153.98C153.18,52.39 152.61,53.19 152.61,54.21ZM161.72,58.08V55.81H160.58V58.08H161.72ZM156.03,58.08H158.31V55.81H156.03V58.08ZM173.11,59.22H154.89V54.67H173.11V59.22Z"
- android:fillColor="#ffffff"/>
-</vector>
diff --git a/android/app/src/main/res/drawable/daita_illustration_2.xml b/android/app/src/main/res/drawable/daita_illustration_2.xml
deleted file mode 100644
index b8de37fadf..0000000000
--- a/android/app/src/main/res/drawable/daita_illustration_2.xml
+++ /dev/null
@@ -1,402 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:width="328dp"
- android:height="103dp"
- android:viewportWidth="328"
- android:viewportHeight="103"
- tools:ignore="VectorRaster">
- <path
- android:pathData="M0,12C0,5.37 5.37,0 12,0H316C322.63,0 328,5.37 328,12V90.5C328,97.13 322.63,102.5 316,102.5H12C5.37,102.5 0,97.13 0,90.5V12Z"
- android:fillColor="#152637"/>
- <path
- android:pathData="M1,12C1,5.92 5.92,1 12,1H316C322.08,1 327,5.92 327,12V90.5C327,96.58 322.08,101.5 316,101.5H12C5.92,101.5 1,96.58 1,90.5V12Z"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#304358"/>
- <path
- android:pathData="M75.17,52.39C74.54,52.39 74.03,51.88 74.03,51.25L74.03,50.11C74.03,49.48 74.54,48.97 75.17,48.97C75.8,48.97 76.31,49.48 76.31,50.11L76.31,51.25C76.31,51.88 75.8,52.39 75.17,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M102.5,52.39C101.87,52.39 101.36,51.88 101.36,51.25L101.36,50.11C101.36,49.48 101.87,48.97 102.5,48.97C103.13,48.97 103.64,49.48 103.64,50.11L103.64,51.25C103.64,51.88 103.13,52.39 102.5,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M107.06,52.39C106.43,52.39 105.92,51.88 105.92,51.25L105.92,50.11C105.92,49.48 106.43,48.97 107.06,48.97C107.68,48.97 108.19,49.48 108.19,50.11L108.19,51.25C108.19,51.88 107.68,52.39 107.06,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M111.61,52.39C110.98,52.39 110.47,51.88 110.47,51.25L110.47,50.11C110.47,49.48 110.98,48.97 111.61,48.97C112.24,48.97 112.75,49.48 112.75,50.11L112.75,51.25C112.75,51.88 112.24,52.39 111.61,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M116.17,52.39C115.54,52.39 115.03,51.88 115.03,51.25L115.03,50.11C115.03,49.48 115.54,48.97 116.17,48.97C116.8,48.97 117.31,49.48 117.31,50.11L117.31,51.25C117.31,51.88 116.8,52.39 116.17,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M120.72,52.39C120.09,52.39 119.58,51.88 119.58,51.25L119.58,50.11C119.58,49.48 120.09,48.97 120.72,48.97C121.35,48.97 121.86,49.48 121.86,50.11L121.86,51.25C121.86,51.88 121.35,52.39 120.72,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M125.28,52.39C124.65,52.39 124.14,51.88 124.14,51.25L124.14,50.11C124.14,49.48 124.65,48.97 125.28,48.97C125.91,48.97 126.42,49.48 126.42,50.11L126.42,51.25C126.42,51.88 125.91,52.39 125.28,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M129.83,52.39C129.2,52.39 128.69,51.88 128.69,51.25L128.69,50.11C128.69,49.48 129.2,48.97 129.83,48.97C130.46,48.97 130.97,49.48 130.97,50.11L130.97,51.25C130.97,51.88 130.46,52.39 129.83,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M134.39,52.39C133.76,52.39 133.25,51.88 133.25,51.25L133.25,50.11C133.25,49.48 133.76,48.97 134.39,48.97C135.02,48.97 135.53,49.48 135.53,50.11L135.53,51.25C135.53,51.88 135.02,52.39 134.39,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M138.94,52.39C138.32,52.39 137.81,51.88 137.81,51.25L137.81,50.11C137.81,49.48 138.32,48.97 138.94,48.97C139.57,48.97 140.08,49.48 140.08,50.11L140.08,51.25C140.08,51.88 139.57,52.39 138.94,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M143.5,52.39C142.87,52.39 142.36,51.88 142.36,51.25L142.36,50.11C142.36,49.48 142.87,48.97 143.5,48.97C144.13,48.97 144.64,49.48 144.64,50.11L144.64,51.25C144.64,51.88 144.13,52.39 143.5,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M184.5,52.39C183.87,52.39 183.36,51.88 183.36,51.25L183.36,50.11C183.36,49.48 183.87,48.97 184.5,48.97C185.13,48.97 185.64,49.48 185.64,50.11L185.64,51.25C185.64,51.88 185.13,52.39 184.5,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M189.06,52.39C188.43,52.39 187.92,51.88 187.92,51.25L187.92,50.11C187.92,49.48 188.43,48.97 189.06,48.97C189.68,48.97 190.19,49.48 190.19,50.11L190.19,51.25C190.19,51.88 189.68,52.39 189.06,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M193.61,52.39C192.98,52.39 192.47,51.88 192.47,51.25L192.47,50.11C192.47,49.48 192.98,48.97 193.61,48.97C194.24,48.97 194.75,49.48 194.75,50.11L194.75,51.25C194.75,51.88 194.24,52.39 193.61,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M198.17,52.39C197.54,52.39 197.03,51.88 197.03,51.25L197.03,50.11C197.03,49.48 197.54,48.97 198.17,48.97C198.8,48.97 199.31,49.48 199.31,50.11L199.31,51.25C199.31,51.88 198.8,52.39 198.17,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M202.72,52.39C202.09,52.39 201.58,51.88 201.58,51.25L201.58,50.11C201.58,49.48 202.09,48.97 202.72,48.97C203.35,48.97 203.86,49.48 203.86,50.11L203.86,51.25C203.86,51.88 203.35,52.39 202.72,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M207.28,52.39C206.65,52.39 206.14,51.88 206.14,51.25L206.14,50.11C206.14,49.48 206.65,48.97 207.28,48.97C207.91,48.97 208.42,49.48 208.42,50.11L208.42,51.25C208.42,51.88 207.91,52.39 207.28,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M211.83,52.39C211.2,52.39 210.69,51.88 210.69,51.25L210.69,50.11C210.69,49.48 211.2,48.97 211.83,48.97C212.46,48.97 212.97,49.48 212.97,50.11L212.97,51.25C212.97,51.88 212.46,52.39 211.83,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M216.39,52.39C215.76,52.39 215.25,51.88 215.25,51.25L215.25,50.11C215.25,49.48 215.76,48.97 216.39,48.97C217.02,48.97 217.53,49.48 217.53,50.11L217.53,51.25C217.53,51.88 217.02,52.39 216.39,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M220.94,52.39C220.32,52.39 219.81,51.88 219.81,51.25L219.81,50.11C219.81,49.48 220.32,48.97 220.94,48.97C221.57,48.97 222.08,49.48 222.08,50.11L222.08,51.25C222.08,51.88 221.57,52.39 220.94,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M225.5,52.39C224.87,52.39 224.36,51.88 224.36,51.25L224.36,50.11C224.36,49.48 224.87,48.97 225.5,48.97C226.13,48.97 226.64,49.48 226.64,50.11L226.64,51.25C226.64,51.88 226.13,52.39 225.5,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M230.06,52.39C229.43,52.39 228.92,51.88 228.92,51.25L228.92,50.11C228.92,49.48 229.43,48.97 230.06,48.97C230.68,48.97 231.19,49.48 231.19,50.11L231.19,51.25C231.19,51.88 230.68,52.39 230.06,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M234.61,52.39C233.98,52.39 233.47,51.88 233.47,51.25L233.47,50.11C233.47,49.48 233.98,48.97 234.61,48.97C235.24,48.97 235.75,49.48 235.75,50.11L235.75,51.25C235.75,51.88 235.24,52.39 234.61,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M239.17,52.39C238.54,52.39 238.03,51.88 238.03,51.25L238.03,50.11C238.03,49.48 238.54,48.97 239.17,48.97C239.8,48.97 240.31,49.48 240.31,50.11L240.31,51.25C240.31,51.88 239.8,52.39 239.17,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M243.72,52.39C243.09,52.39 242.58,51.88 242.58,51.25L242.58,50.11C242.58,49.48 243.09,48.97 243.72,48.97C244.35,48.97 244.86,49.48 244.86,50.11L244.86,51.25C244.86,51.88 244.35,52.39 243.72,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M248.28,52.39C247.65,52.39 247.14,51.88 247.14,51.25L247.14,50.11C247.14,49.48 247.65,48.97 248.28,48.97C248.91,48.97 249.42,49.48 249.42,50.11L249.42,51.25C249.42,51.88 248.91,52.39 248.28,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M252.83,52.39C252.2,52.39 251.69,51.88 251.69,51.25L251.69,50.11C251.69,49.48 252.2,48.97 252.83,48.97C253.46,48.97 253.97,49.48 253.97,50.11L253.97,51.25C253.97,51.88 253.46,52.39 252.83,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M257.39,52.39C256.76,52.39 256.25,51.88 256.25,51.25L256.25,50.11C256.25,49.48 256.76,48.97 257.39,48.97C258.02,48.97 258.53,49.48 258.53,50.11L258.53,51.25C258.53,51.88 258.02,52.39 257.39,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M261.94,52.39C261.32,52.39 260.81,51.88 260.81,51.25L260.81,50.11C260.81,49.48 261.32,48.97 261.94,48.97C262.57,48.97 263.08,49.48 263.08,50.11L263.08,51.25C263.08,51.88 262.57,52.39 261.94,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M266.5,52.39C265.87,52.39 265.36,51.88 265.36,51.25L265.36,50.11C265.36,49.48 265.87,48.97 266.5,48.97C267.13,48.97 267.64,49.48 267.64,50.11L267.64,51.25C267.64,51.88 267.13,52.39 266.5,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M271.06,52.39C270.43,52.39 269.92,51.88 269.92,51.25L269.92,50.11C269.92,49.48 270.43,48.97 271.06,48.97C271.68,48.97 272.19,49.48 272.19,50.11L272.19,51.25C272.19,51.88 271.68,52.39 271.06,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M275.61,52.39C274.98,52.39 274.47,51.88 274.47,51.25L274.47,50.11C274.47,49.48 274.98,48.97 275.61,48.97C276.24,48.97 276.75,49.48 276.75,50.11L276.75,51.25C276.75,51.88 276.24,52.39 275.61,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M70.61,52.39C69.98,52.39 69.47,51.88 69.47,51.25L69.47,50.11C69.47,49.48 69.98,48.97 70.61,48.97C71.24,48.97 71.75,49.48 71.75,50.11L71.75,51.25C71.75,51.88 71.24,52.39 70.61,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M97.94,52.39C97.32,52.39 96.81,51.88 96.81,51.25L96.81,50.11C96.81,49.48 97.32,48.97 97.94,48.97C98.57,48.97 99.08,49.48 99.08,50.11L99.08,51.25C99.08,51.88 98.57,52.39 97.94,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M93.39,52.39C92.76,52.39 92.25,51.88 92.25,51.25L92.25,50.11C92.25,49.48 92.76,48.97 93.39,48.97C94.02,48.97 94.53,49.48 94.53,50.11L94.53,51.25C94.53,51.88 94.02,52.39 93.39,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M52.39,52.39C51.76,52.39 51.25,51.88 51.25,51.25L51.25,50.11C51.25,49.48 51.76,48.97 52.39,48.97C53.02,48.97 53.53,49.48 53.53,50.11L53.53,51.25C53.53,51.88 53.02,52.39 52.39,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M56.94,52.39C56.32,52.39 55.81,51.88 55.81,51.25L55.81,50.11C55.81,49.48 56.32,48.97 56.94,48.97C57.57,48.97 58.08,49.48 58.08,50.11L58.08,51.25C58.08,51.88 57.57,52.39 56.94,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M61.5,52.39C60.87,52.39 60.36,51.88 60.36,51.25L60.36,50.11C60.36,49.48 60.87,48.97 61.5,48.97C62.13,48.97 62.64,49.48 62.64,50.11L62.64,51.25C62.64,51.88 62.13,52.39 61.5,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M66.06,52.39C65.43,52.39 64.92,51.88 64.92,51.25L64.92,50.11C64.92,49.48 65.43,48.97 66.06,48.97C66.68,48.97 67.19,49.48 67.19,50.11L67.19,51.25C67.19,51.88 66.68,52.39 66.06,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M79.72,52.39C79.09,52.39 78.58,51.88 78.58,51.25L78.58,50.11C78.58,49.48 79.09,48.97 79.72,48.97C80.35,48.97 80.86,49.48 80.86,50.11L80.86,51.25C80.86,51.88 80.35,52.39 79.72,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M84.28,52.39C83.65,52.39 83.14,51.88 83.14,51.25L83.14,50.11C83.14,49.48 83.65,48.97 84.28,48.97C84.91,48.97 85.42,49.48 85.42,50.11L85.42,51.25C85.42,51.88 84.91,52.39 84.28,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M88.83,52.39C88.2,52.39 87.69,51.88 87.69,51.25L87.69,50.11C87.69,49.48 88.2,48.97 88.83,48.97C89.46,48.97 89.97,49.48 89.97,50.11L89.97,51.25C89.97,51.88 89.46,52.39 88.83,52.39Z"
- android:fillColor="#3E5F81"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M52.39,75.17C51.76,75.17 51.25,74.66 51.25,74.03L51.25,28.47C51.25,27.84 51.76,27.33 52.39,27.33C53.02,27.33 53.53,27.84 53.53,28.47L53.53,74.03C53.53,74.66 53.02,75.17 52.39,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M61.5,75.17C60.87,75.17 60.36,74.66 60.36,74.03L60.36,28.47C60.36,27.84 60.87,27.33 61.5,27.33C62.13,27.33 62.64,27.84 62.64,28.47L62.64,74.03C62.64,74.66 62.13,75.17 61.5,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M79.72,75.17C79.09,75.17 78.58,74.66 78.58,74.03L78.58,28.47C78.58,27.84 79.09,27.33 79.72,27.33C80.35,27.33 80.86,27.84 80.86,28.47L80.86,74.03C80.86,74.66 80.35,75.17 79.72,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M88.83,75.17C88.2,75.17 87.69,74.66 87.69,74.03L87.69,28.47C87.69,27.84 88.2,27.33 88.83,27.33C89.46,27.33 89.97,27.84 89.97,28.47L89.97,74.03C89.97,74.66 89.46,75.17 88.83,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M107.06,75.17C106.43,75.17 105.92,74.66 105.92,74.03L105.92,28.47C105.92,27.84 106.43,27.33 107.06,27.33C107.68,27.33 108.19,27.84 108.19,28.47L108.19,74.03C108.19,74.66 107.68,75.17 107.06,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M116.17,75.17C115.54,75.17 115.03,74.66 115.03,74.03L115.03,28.47C115.03,27.84 115.54,27.33 116.17,27.33C116.8,27.33 117.31,27.84 117.31,28.47L117.31,74.03C117.31,74.66 116.8,75.17 116.17,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M134.39,75.17C133.76,75.17 133.25,74.66 133.25,74.03L133.25,28.47C133.25,27.84 133.76,27.33 134.39,27.33C135.02,27.33 135.53,27.84 135.53,28.47L135.53,74.03C135.53,74.66 135.02,75.17 134.39,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M143.5,75.17C142.87,75.17 142.36,74.66 142.36,74.03L142.36,28.47C142.36,27.84 142.87,27.33 143.5,27.33C144.13,27.33 144.64,27.84 144.64,28.47L144.64,74.03C144.64,74.66 144.13,75.17 143.5,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M97.94,75.17C97.32,75.17 96.81,74.66 96.81,74.03L96.81,28.47C96.81,27.84 97.32,27.33 97.94,27.33C98.57,27.33 99.08,27.84 99.08,28.47L99.08,74.03C99.08,74.66 98.57,75.17 97.94,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M66.06,75.17C65.43,75.17 64.92,74.66 64.92,74.03L64.92,28.47C64.92,27.84 65.43,27.33 66.06,27.33C66.68,27.33 67.19,27.84 67.19,28.47L67.19,74.03C67.19,74.66 66.68,75.17 66.06,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M129.83,75.17C129.2,75.17 128.69,74.66 128.69,74.03L128.69,28.47C128.69,27.84 129.2,27.33 129.83,27.33C130.46,27.33 130.97,27.84 130.97,28.47L130.97,74.03C130.97,74.66 130.46,75.17 129.83,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M56.94,75.17C56.32,75.17 55.81,74.66 55.81,74.03L55.81,28.47C55.81,27.84 56.32,27.33 56.94,27.33C57.57,27.33 58.08,27.84 58.08,28.47L58.08,74.03C58.08,74.66 57.57,75.17 56.94,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M84.28,75.17C83.65,75.17 83.14,74.66 83.14,74.03L83.14,28.47C83.14,27.84 83.65,27.33 84.28,27.33C84.91,27.33 85.42,27.84 85.42,28.47L85.42,74.03C85.42,74.66 84.91,75.17 84.28,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M111.61,75.17C110.98,75.17 110.47,74.66 110.47,74.03L110.47,28.47C110.47,27.84 110.98,27.33 111.61,27.33C112.24,27.33 112.75,27.84 112.75,28.47L112.75,74.03C112.75,74.66 112.24,75.17 111.61,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M138.94,75.17C138.32,75.17 137.81,74.66 137.81,74.03L137.81,28.47C137.81,27.84 138.32,27.33 138.94,27.33C139.57,27.33 140.08,27.84 140.08,28.47L140.08,74.03C140.08,74.66 139.57,75.17 138.94,75.17Z"
- android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M52.39,60.36L52.39,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M61.5,55.81L61.5,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M56.94,74.03L56.94,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M79.72,60.36L79.72,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M88.83,55.81L88.83,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M84.28,74.03L84.28,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M107.06,60.36L107.06,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M116.17,55.81L116.17,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M111.61,74.03L111.61,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M134.39,60.36L134.39,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M143.5,55.81L143.5,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M138.94,74.03L138.94,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M184.5,60.36L184.5,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M193.61,55.81L193.61,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M189.06,74.03L189.06,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M211.83,60.36L211.83,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M220.94,55.81L220.94,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M216.39,74.03L216.39,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M239.17,60.36L239.17,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M248.28,55.81L248.28,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M243.72,74.03L243.72,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M266.5,60.36L266.5,42.14"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M275.61,55.81L275.61,46.69"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M271.06,74.03L271.06,28.47"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeLineCap="round"/>
- <path
- android:pathData="M301.08,52.39C301.17,51.64 301.24,50.89 301.24,50.11C301.24,49.34 301.17,48.58 301.08,47.83H304.93C305.11,48.56 305.22,49.33 305.22,50.11C305.22,50.9 305.11,51.66 304.93,52.39M299.06,58.72C299.74,57.46 300.27,56.09 300.63,54.67H303.99C302.89,56.57 301.14,58.01 299.06,58.72ZM298.78,52.39H293.45C293.33,51.64 293.26,50.89 293.26,50.11C293.26,49.34 293.33,48.57 293.45,47.83H298.78C298.88,48.57 298.96,49.34 298.96,50.11C298.96,50.89 298.88,51.64 298.78,52.39ZM296.11,59.18C295.17,57.81 294.4,56.3 293.94,54.67H298.29C297.82,56.3 297.06,57.81 296.11,59.18ZM291.56,45.56H288.23C289.32,43.65 291.07,42.21 293.15,41.5C292.47,42.77 291.95,44.13 291.56,45.56ZM288.23,54.67H291.56C291.95,56.09 292.47,57.46 293.15,58.72C291.08,58.01 289.33,56.57 288.23,54.67ZM287.3,52.39C287.11,51.66 287,50.9 287,50.11C287,49.33 287.11,48.56 287.3,47.83H291.15C291.05,48.58 290.99,49.34 290.99,50.11C290.99,50.89 291.05,51.64 291.15,52.39M296.11,41.03C297.06,42.4 297.82,43.93 298.29,45.56H293.94C294.4,43.93 295.17,42.4 296.11,41.03ZM303.99,45.56H300.63C300.28,44.15 299.75,42.78 299.06,41.5C301.16,42.22 302.9,43.67 303.99,45.56ZM296.11,38.72C289.81,38.72 284.72,43.85 284.72,50.11C284.72,53.13 285.92,56.03 288.06,58.16C289.12,59.22 290.37,60.06 291.75,60.63C293.14,61.21 294.62,61.5 296.11,61.5C299.13,61.5 302.03,60.3 304.16,58.16C306.3,56.03 307.5,53.13 307.5,50.11C307.5,48.62 307.2,47.13 306.63,45.75C306.06,44.37 305.22,43.12 304.16,42.06C303.11,41 301.85,40.16 300.47,39.59C299.09,39.02 297.61,38.72 296.11,38.72Z"
- android:fillColor="#ffffff"
- tools:ignore="VectorPath" />
- <group>
- <clip-path
- android:pathData="M18.22,37.58h27.33v27.33h-27.33z"/>
- <path
- android:pathData="M26.19,63.78C25.57,63.78 25.03,63.55 24.59,63.11C24.14,62.66 23.92,62.13 23.92,61.5V41C23.92,40.37 24.14,39.84 24.59,39.39C25.03,38.95 25.57,38.72 26.19,38.72H37.58C38.21,38.72 38.75,38.95 39.19,39.39C39.64,39.84 39.86,40.37 39.86,41V61.5C39.86,62.13 39.64,62.66 39.19,63.11C38.75,63.55 38.21,63.78 37.58,63.78H26.19ZM26.19,60.36V61.5H37.58V60.36H26.19ZM26.19,58.08H37.58V44.42H26.19V58.08ZM26.19,42.14H37.58V41H26.19V42.14Z"
- android:fillColor="#ffffff"/>
- </group>
- <path
- android:pathData="M152.61,42.82V48.29C152.61,49.31 153.18,50.11 153.98,50.11H174.14C174.82,50.11 175.5,49.31 175.5,48.29V42.82C175.39,41.8 174.82,41 174.02,41H153.98C153.18,41 152.61,41.8 152.61,42.82ZM161.72,46.69V44.42H160.58V46.69H161.72ZM156.03,46.69H158.31V44.42H156.03V46.69ZM173.11,47.83H154.89V43.28H173.11V47.83ZM152.61,54.21V59.68C152.61,60.7 153.18,61.5 153.98,61.5H174.14C174.82,61.5 175.5,60.7 175.5,59.68V54.21C175.5,53.19 174.93,52.39 174.14,52.39H153.98C153.18,52.39 152.61,53.19 152.61,54.21ZM161.72,58.08V55.81H160.58V58.08H161.72ZM156.03,58.08H158.31V55.81H156.03V58.08ZM173.11,59.22H154.89V54.67H173.11V59.22Z"
- android:fillColor="#ffffff"/>
-</vector>
diff --git a/android/app/src/main/res/drawable/logo_text.xml b/android/app/src/main/res/drawable/logo_text.xml
deleted file mode 100644
index aacefa3579..0000000000
--- a/android/app/src/main/res/drawable/logo_text.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="959.5dp"
- android:height="103.7dp"
- android:viewportWidth="959.5"
- android:viewportHeight="103.7">
- <path android:pathData="m0,1.6c0,-0.9 0.6,-1.5 1.5,-1.5h17.8c1.2,0 2,0.4 2.4,1.5l25.8,58.2h0.6l25.3,-58.2c0.4,-1 1.2,-1.5 2.4,-1.5h17.5c0.9,0 1.5,0.6 1.5,1.5v99c0,0.9 -0.6,1.5 -1.5,1.5h-17c-0.9,0 -1.5,-0.6 -1.5,-1.5v-57.4h-0.6l-18.9,43c-0.6,1.4 -1.5,2 -2.8,2h-10.3c-1.3,0 -2.2,-0.6 -2.8,-2l-18.9,-43h-0.6v57.4c0,0.9 -0.6,1.5 -1.5,1.5h-16.9c-0.9,0 -1.5,-0.6 -1.5,-1.5z"
- android:fillColor="#FFFFFF" />
- <path android:pathData="m117,64.6v-63c0,-0.9 0.6,-1.5 1.5,-1.5h19c0.9,0 1.5,0.6 1.5,1.5v63.6c0,12.1 6.8,19 17.1,19 10.2,0 16.9,-6.9 16.9,-19v-63.6c0,-0.9 0.6,-1.5 1.5,-1.5h19c0.9,0 1.5,0.6 1.5,1.5v63c0,25.3 -16.2,39.1 -39,39.1s-39,-13.8 -39,-39.1z"
- android:fillColor="#FFFFFF" />
- <path android:pathData="m217.2,1.6c0,-0.9 0.6,-1.5 1.5,-1.5h19c0.9,0 1.5,0.6 1.5,1.5v79.9c0,0.6 0.3,0.9 0.9,0.9h45c0.9,0 1.5,0.6 1.5,1.5v16.6c0,0.9 -0.6,1.5 -1.5,1.5h-66.4c-0.9,0 -1.5,-0.6 -1.5,-1.5z"
- android:fillColor="#FFFFFF" />
- <path android:pathData="m302.7,1.6c0,-0.9 0.6,-1.5 1.5,-1.5h19c0.9,0 1.5,0.6 1.5,1.5v79.9c0,0.6 0.3,0.9 0.9,0.9h45c0.9,0 1.5,0.6 1.5,1.5v16.6c0,0.9 -0.6,1.5 -1.5,1.5h-66.4c-0.9,0 -1.5,-0.6 -1.5,-1.5z"
- android:fillColor="#FFFFFF" />
- <path android:pathData="m403.7,102c-1,0 -1.6,-0.6 -1.9,-1.5l-32.4,-98.8c-0.3,-1.1 0.3,-1.6 1.3,-1.6h19.5c1,0 1.7,0.4 2,1.5l20.4,66.9h0.4l19.8,-66.9c0.3,-1 0.9,-1.5 1.9,-1.5h19.3c0.9,0 1.5,0.6 1.2,1.6l-32.4,98.8c-0.3,0.9 -0.9,1.5 -1.8,1.5z"
- android:fillColor="#FFFFFF" />
- <path android:pathData="m491.1,1.6c0.3,-0.9 0.9,-1.5 2,-1.5h18.9c1,0 1.6,0.6 1.9,1.5l34.5,99c0.3,0.9 0,1.5 -1,1.5h-19.5c-1,0 -1.7,-0.5 -2,-1.5l-5.8,-17.8h-35.7l-5.7,17.8c-0.3,1 -0.9,1.5 -2,1.5h-19.6c-1,0 -1.3,-0.6 -1,-1.5zM514.1,64 L502.6,28.3h-0.4l-11.5,35.7z"
- android:fillColor="#FFFFFF" />
- <path android:pathData="m563.3,1.6c0,-0.9 0.6,-1.5 1.5,-1.5h37.9c17.8,0 30.3,7.6 35.2,22.9 1.8,5.7 2.7,11.4 2.7,28s-0.9,22.3 -2.7,28c-4.9,15.3 -17.4,22.9 -35.2,22.9h-37.9c-0.9,0 -1.5,-0.6 -1.5,-1.5zM586.2,82.4h11.5c10.2,0 16.3,-3 18.9,-11.2 1,-3 1.7,-6.9 1.7,-20.1s-0.6,-17.1 -1.7,-20.1c-2.5,-8.2 -8.7,-11.2 -18.9,-11.2h-11.5c-0.6,0 -0.9,0.3 -0.9,0.9v60.9c0,0.5 0.3,0.8 0.9,0.8z"
- android:fillColor="#FFFFFF" />
- <path android:pathData="m721.1,102c-1.1,0 -1.7,-0.6 -2,-1.5l-32.4,-98.8c-0.3,-1.1 0.3,-1.6 1.3,-1.6h19.5c1,0 1.7,0.4 2,1.5l20.4,66.9h0.4l19.8,-66.9c0.3,-1 0.9,-1.5 1.9,-1.5h19.3c0.9,0 1.5,0.6 1.2,1.6l-32.4,98.8c-0.3,0.9 -0.9,1.5 -1.8,1.5z"
- android:fillColor="#FFFFFF" />
- <path android:pathData="m788.6,102c-0.9,0 -1.5,-0.6 -1.5,-1.5v-99c0,-0.9 0.6,-1.5 1.5,-1.5h39.6c22.2,0 35.5,13.3 35.5,32.8 0,19.2 -13.5,32.7 -35.5,32.7h-18.1c-0.6,0 -0.9,0.3 -0.9,0.9v34c0,0.9 -0.6,1.5 -1.5,1.5h-19.1zM841.7,32.9c0,-8.2 -5.5,-13.8 -14.8,-13.8h-16.8c-0.6,0 -0.9,0.3 -0.9,0.9v25.6c0,0.6 0.3,0.9 0.9,0.9h16.8c9.2,0.1 14.8,-5.3 14.8,-13.6z"
- android:fillColor="#FFFFFF" />
- <path android:pathData="m880.8,1.6c0,-0.9 0.6,-1.5 1.5,-1.5h18c1,0 2,0.4 2.5,1.5l36,64.2h0.8v-64.2c0,-0.9 0.6,-1.5 1.5,-1.5h16.9c0.9,0 1.5,0.6 1.5,1.5v99c0,0.9 -0.6,1.5 -1.5,1.5h-17.8c-1.2,0 -2,-0.5 -2.5,-1.5l-36.1,-64h-0.8v64c0,0.9 -0.6,1.5 -1.5,1.5h-16.9c-0.9,0 -1.5,-0.6 -1.5,-1.5v-99z"
- android:fillColor="#FFFFFF" />
-</vector>
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt
index 9db14ad914..b7be4e574d 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt
@@ -15,7 +15,7 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.lib.model.ErrorState
-import net.mullvad.mullvadvpn.repository.InAppNotification
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
import net.mullvad.mullvadvpn.repository.InAppNotificationController
import net.mullvad.mullvadvpn.usecase.AccountExpiryInAppNotificationUseCase
import net.mullvad.mullvadvpn.usecase.NewChangelogNotificationUseCase
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt
index 68b29790ac..df7d561f84 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt
@@ -17,8 +17,8 @@ import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.lib.model.AccountData
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
import net.mullvad.mullvadvpn.lib.shared.AccountRepository
-import net.mullvad.mullvadvpn.repository.InAppNotification
import net.mullvad.mullvadvpn.service.notifications.accountexpiry.ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD
import net.mullvad.mullvadvpn.service.notifications.accountexpiry.ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL
import org.junit.jupiter.api.AfterEach
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCaseTest.kt
index 2c97ea36a1..414c7c1e08 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCaseTest.kt
@@ -16,8 +16,8 @@ import net.mullvad.mullvadvpn.lib.model.AccountNumber
import net.mullvad.mullvadvpn.lib.model.Device
import net.mullvad.mullvadvpn.lib.model.DeviceId
import net.mullvad.mullvadvpn.lib.model.DeviceState
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
-import net.mullvad.mullvadvpn.repository.InAppNotification
import net.mullvad.mullvadvpn.repository.NewDeviceRepository
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt
index 20a6a1bef0..8d2ece124b 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt
@@ -12,9 +12,9 @@ import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.lib.model.ActionAfterDisconnect
import net.mullvad.mullvadvpn.lib.model.ErrorState
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
import net.mullvad.mullvadvpn.lib.model.TunnelState
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
-import net.mullvad.mullvadvpn.repository.InAppNotification
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt
index e9452884cf..78f2fb72df 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt
@@ -10,8 +10,8 @@ import kotlin.test.assertTrue
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.repository.InAppNotification
-import net.mullvad.mullvadvpn.ui.VersionInfo
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
+import net.mullvad.mullvadvpn.lib.model.VersionInfo
import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoRepository
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
index 5950f6475f..ec4e9c0bbb 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
@@ -24,6 +24,7 @@ import net.mullvad.mullvadvpn.lib.model.AccountData
import net.mullvad.mullvadvpn.lib.model.DeviceState
import net.mullvad.mullvadvpn.lib.model.ErrorState
import net.mullvad.mullvadvpn.lib.model.GeoIpLocation
+import net.mullvad.mullvadvpn.lib.model.InAppNotification
import net.mullvad.mullvadvpn.lib.model.TunnelEndpoint
import net.mullvad.mullvadvpn.lib.model.TunnelState
import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
@@ -31,7 +32,6 @@ import net.mullvad.mullvadvpn.lib.shared.AccountRepository
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
import net.mullvad.mullvadvpn.repository.ChangelogRepository
-import net.mullvad.mullvadvpn.repository.InAppNotification
import net.mullvad.mullvadvpn.repository.InAppNotificationController
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt
index b71d217408..f0a60c50c2 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt
@@ -13,11 +13,11 @@ import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.lib.model.Constraint
import net.mullvad.mullvadvpn.lib.model.DeviceState
import net.mullvad.mullvadvpn.lib.model.Settings
+import net.mullvad.mullvadvpn.lib.model.VersionInfo
import net.mullvad.mullvadvpn.lib.model.WireguardConstraints
import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
import net.mullvad.mullvadvpn.repository.SettingsRepository
import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository
-import net.mullvad.mullvadvpn.ui.VersionInfo
import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoRepository
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach