summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2024-10-16 16:11:12 +0200
committerDavid Göransson <david.goransson@mullvad.net>2024-10-18 09:39:03 +0200
commitf5157257a7ea6db7df17b6126ae7464f48765915 (patch)
tree34f541ed8b624cd7aa02ca179bc6176c27a88569 /android
parent0ec8a73b3eee45ffee2096fa582c94d2b055734a (diff)
downloadmullvadvpn-f5157257a7ea6db7df17b6126ae7464f48765915.tar.xz
mullvadvpn-f5157257a7ea6db7df17b6126ae7464f48765915.zip
Improve Unsupported version link
Diffstat (limited to 'android')
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationData.kt14
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AppInfoScreen.kt66
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt31
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AppInfoViewModel.kt28
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt18
-rw-r--r--android/lib/resource/src/main/res/values/strings_non_translatable.xml1
9 files changed, 106 insertions, 64 deletions
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 f55e07c1cf..05862a5bc4 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
@@ -594,7 +594,7 @@ class ConnectScreenTest {
val versionInfo = VersionInfo(isSupported = false, currentVersion = "")
setContentWithTheme {
ConnectScreen(
- onUpdateVersionClick = mockedClickHandler,
+ onOpenAppListing = mockedClickHandler,
state =
ConnectUiState(
location = null,
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 df7d3dede0..f2b33c251e 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
@@ -77,7 +77,7 @@ fun NotificationBanner(
modifier: Modifier = Modifier,
notification: InAppNotification?,
isPlayBuild: Boolean,
- onClickUpdateVersion: () -> Unit,
+ openAppListing: () -> Unit,
onClickShowAccount: () -> Unit,
onClickDismissNewDevice: () -> Unit,
) {
@@ -94,7 +94,7 @@ fun NotificationBanner(
Notification(
visibleNotification.toNotificationData(
isPlayBuild = isPlayBuild,
- onClickUpdateVersion,
+ openAppListing,
onClickShowAccount,
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
index de36f76ac7..5318933852 100644
--- 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
@@ -44,7 +44,7 @@ data class NotificationAction(
@Composable
fun InAppNotification.toNotificationData(
isPlayBuild: Boolean,
- onClickUpdateVersion: () -> Unit,
+ openAppListing: () -> Unit,
onClickShowAccount: () -> Unit,
onDismissNewDevice: () -> Unit,
) =
@@ -101,13 +101,11 @@ fun InAppNotification.toNotificationData(
message = stringResource(id = R.string.unsupported_version_description),
statusLevel = StatusLevel.Error,
action =
- if (isPlayBuild) null
- else
- NotificationAction(
- Icons.AutoMirrored.Default.OpenInNew,
- onClickUpdateVersion,
- stringResource(id = R.string.open_url),
- ),
+ NotificationAction(
+ Icons.AutoMirrored.Default.OpenInNew,
+ openAppListing,
+ stringResource(id = R.string.open_url),
+ ),
)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AppInfoScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AppInfoScreen.kt
index f871aa6e7b..e65dc2c8d8 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AppInfoScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AppInfoScreen.kt
@@ -1,7 +1,5 @@
package net.mullvad.mullvadvpn.compose.screen
-import android.content.Context
-import android.net.Uri
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
@@ -10,7 +8,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material.icons.filled.Error
import androidx.compose.material.icons.filled.Info
-import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
@@ -21,7 +18,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.compose.dropUnlessResumed
@@ -35,9 +32,9 @@ import net.mullvad.mullvadvpn.compose.cell.TwoRowCell
import net.mullvad.mullvadvpn.compose.component.NavigateBackIconButton
import net.mullvad.mullvadvpn.compose.component.ScaffoldWithMediumTopBar
import net.mullvad.mullvadvpn.compose.transitions.SlideInFromRightTransition
-import net.mullvad.mullvadvpn.lib.common.util.openLink
+import net.mullvad.mullvadvpn.compose.util.CollectSideEffectWithLifecycle
import net.mullvad.mullvadvpn.lib.theme.Dimens
-import net.mullvad.mullvadvpn.util.appendHideNavOnPlayBuild
+import net.mullvad.mullvadvpn.viewmodel.AppInfoSideEffect
import net.mullvad.mullvadvpn.viewmodel.AppInfoUiState
import net.mullvad.mullvadvpn.viewmodel.AppInfoViewModel
import org.koin.androidx.compose.koinViewModel
@@ -49,38 +46,55 @@ fun AppInfo(navigator: DestinationsNavigator) {
val vm = koinViewModel<AppInfoViewModel>()
val state by vm.uiState.collectAsStateWithLifecycle()
+ val uriHandler = LocalUriHandler.current
+
+ CollectSideEffectWithLifecycle(vm.uiSideEffect) {
+ when (it) {
+ is AppInfoSideEffect.OpenUri -> uriHandler.openUri(it.uri.toString())
+ }
+ }
+
AppInfo(
state = state,
onBackClick = dropUnlessResumed { navigator.navigateUp() },
- dropUnlessResumed { navigator.navigate(ChangelogDestination) },
+ navigateToChangelog = dropUnlessResumed { navigator.navigate(ChangelogDestination) },
+ openAppListing = dropUnlessResumed { vm.openAppListing() },
)
}
@ExperimentalMaterial3Api
@Composable
-fun AppInfo(state: AppInfoUiState, onBackClick: () -> Unit, navigateToChangelog: () -> Unit) {
-
+fun AppInfo(
+ state: AppInfoUiState,
+ onBackClick: () -> Unit,
+ navigateToChangelog: () -> Unit,
+ openAppListing: () -> Unit,
+) {
ScaffoldWithMediumTopBar(
appBarTitle = stringResource(id = R.string.app_info),
navigationIcon = { NavigateBackIconButton(onNavigateBack = onBackClick) },
) { modifier ->
Column(horizontalAlignment = Alignment.Start, modifier = modifier.animateContentSize()) {
- AppInfoContent(state, navigateToChangelog)
+ AppInfoContent(state, navigateToChangelog, openAppListing)
}
}
}
@Composable
-fun AppInfoContent(state: AppInfoUiState, navigateToChangelog: () -> Unit) {
+fun AppInfoContent(
+ state: AppInfoUiState,
+ navigateToChangelog: () -> Unit,
+ openAppListing: () -> Unit,
+) {
Column(modifier = Modifier.padding(bottom = Dimens.smallPadding).animateContentSize()) {
- AppVersionRow(LocalContext.current, state)
+ AppVersionRow(state, openAppListing)
ChangelogRow(navigateToChangelog)
}
}
@Composable
-private fun AppVersionRow(context: Context, state: AppInfoUiState) {
+private fun AppVersionRow(state: AppInfoUiState, openAppListing: () -> Unit) {
Column {
TwoRowCell(
titleText = stringResource(id = R.string.version),
@@ -96,27 +110,13 @@ private fun AppVersionRow(context: Context, state: AppInfoUiState) {
}
},
bodyView = {
- if (!state.isPlayBuild) {
- Icon(
- Icons.AutoMirrored.Default.OpenInNew,
- contentDescription = stringResource(R.string.app_info),
- tint = MaterialTheme.colorScheme.onPrimary,
- )
- }
+ Icon(
+ Icons.AutoMirrored.Default.OpenInNew,
+ contentDescription = stringResource(R.string.app_info),
+ tint = MaterialTheme.colorScheme.onPrimary,
+ )
},
- onCellClicked =
- if (state.isPlayBuild) null
- else {
- {
- context.openLink(
- Uri.parse(
- context.resources
- .getString(R.string.download_url)
- .appendHideNavOnPlayBuild(state.isPlayBuild)
- )
- )
- }
- },
+ onCellClicked = openAppListing,
)
if (!state.version.isSupported) {
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 a9d47893b1..9e99ae2daa 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
@@ -1,8 +1,6 @@
package net.mullvad.mullvadvpn.compose.screen
import android.content.Context
-import android.content.Intent
-import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateColorAsState
@@ -51,6 +49,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.compose.dropUnlessResumed
+import co.touchlab.kermit.Logger
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.NavGraphs
@@ -109,7 +108,6 @@ 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.util.appendHideNavOnPlayBuild
import net.mullvad.mullvadvpn.util.removeHtmlTags
import net.mullvad.mullvadvpn.viewmodel.ConnectViewModel
import org.koin.androidx.compose.koinViewModel
@@ -147,6 +145,7 @@ fun Connect(
}
val openAccountPage = LocalUriHandler.current.createOpenAccountPageHook()
+ val uriHandler = LocalUriHandler.current
CollectSideEffectWithLifecycle(
connectViewModel.uiSideEffect,
minActiveState = Lifecycle.State.RESUMED,
@@ -175,6 +174,14 @@ fun Connect(
message = sideEffect.toMessage(context)
)
}
+
+ is ConnectViewModel.UiSideEffect.OpenUri -> {
+ try {
+ uriHandler.openUri(sideEffect.uri.toString())
+ } catch (e: IllegalArgumentException) {
+ Logger.w("Failed to open uri", e)
+ }
+ }
}
}
@@ -192,19 +199,7 @@ fun Connect(
onConnectClick = connectViewModel::onConnectClick,
onCancelClick = connectViewModel::onCancelClick,
onSwitchLocationClick = dropUnlessResumed { navigator.navigate(SelectLocationDestination) },
- onUpdateVersionClick = {
- val intent =
- Intent(
- Intent.ACTION_VIEW,
- Uri.parse(
- context
- .getString(R.string.download_url)
- .appendHideNavOnPlayBuild(state.isPlayBuild)
- ),
- )
- .apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
- context.startActivity(intent)
- },
+ onOpenAppListing = connectViewModel::openAppListing,
onManageAccountClick = connectViewModel::onManageAccountClick,
onSettingsClick = dropUnlessResumed { navigator.navigate(SettingsDestination) },
onAccountClick = dropUnlessResumed { navigator.navigate(AccountDestination) },
@@ -221,7 +216,7 @@ fun ConnectScreen(
onConnectClick: () -> Unit = {},
onCancelClick: () -> Unit = {},
onSwitchLocationClick: () -> Unit = {},
- onUpdateVersionClick: () -> Unit = {},
+ onOpenAppListing: () -> Unit = {},
onManageAccountClick: () -> Unit = {},
onSettingsClick: () -> Unit = {},
onAccountClick: () -> Unit = {},
@@ -268,7 +263,7 @@ fun ConnectScreen(
NotificationBanner(
notification = state.inAppNotification,
isPlayBuild = state.isPlayBuild,
- onClickUpdateVersion = onUpdateVersionClick,
+ openAppListing = onOpenAppListing,
onClickShowAccount = onManageAccountClick,
onClickDismissNewDevice = onDismissNewDeviceClick,
)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
index cc06c417fc..2605075ef8 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
@@ -183,7 +183,9 @@ val uiModule = module {
// View models
viewModel { AccountViewModel(get(), get(), get(), IS_PLAY_BUILD) }
viewModel { ChangelogViewModel(get(), get()) }
- viewModel { AppInfoViewModel(get(), get(), IS_PLAY_BUILD) }
+ viewModel {
+ AppInfoViewModel(get(), get(), get(), IS_PLAY_BUILD, get(named(SELF_PACKAGE_NAME)))
+ }
viewModel {
ConnectViewModel(
get(),
@@ -196,7 +198,9 @@ val uiModule = module {
get(),
get(),
get(),
+ get(),
IS_PLAY_BUILD,
+ get(named(SELF_PACKAGE_NAME)),
)
}
viewModel { DeviceListViewModel(get(), get()) }
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 6e78fb2f10..0177fdb586 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
@@ -1,12 +1,18 @@
package net.mullvad.mullvadvpn.viewmodel
+import android.content.res.Resources
+import android.net.Uri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.repository.ChangelogRepository
import net.mullvad.mullvadvpn.ui.VersionInfo
import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoRepository
@@ -14,9 +20,14 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoRepository
class AppInfoViewModel(
changelogRepository: ChangelogRepository,
appVersionInfoRepository: AppVersionInfoRepository,
- isPlayBuild: Boolean,
+ val resources: Resources,
+ val isPlayBuild: Boolean,
+ val packageName: String,
) : ViewModel() {
+ private val _uiSideEffect = Channel<AppInfoSideEffect>()
+ val uiSideEffect = _uiSideEffect.receiveAsFlow()
+
val uiState: StateFlow<AppInfoUiState> =
combine(
appVersionInfoRepository.versionInfo,
@@ -34,6 +45,17 @@ class AppInfoViewModel(
true,
),
)
+
+ fun openAppListing() =
+ viewModelScope.launch {
+ val uri =
+ if (isPlayBuild) {
+ resources.getString(R.string.market_uri, packageName)
+ } else {
+ resources.getString(R.string.download_url)
+ }
+ _uiSideEffect.send(AppInfoSideEffect.OpenUri(Uri.parse(uri)))
+ }
}
data class AppInfoUiState(
@@ -41,3 +63,7 @@ data class AppInfoUiState(
val changes: List<String>,
val isPlayBuild: Boolean,
)
+
+sealed interface AppInfoSideEffect {
+ data class OpenUri(val uri: Uri) : AppInfoSideEffect
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt
index 33d81f3ba1..d9ca922f1f 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt
@@ -1,5 +1,7 @@
package net.mullvad.mullvadvpn.viewmodel
+import android.content.res.Resources
+import android.net.Uri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.FlowPreview
@@ -14,6 +16,7 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.state.ConnectUiState
import net.mullvad.mullvadvpn.lib.model.ActionAfterDisconnect
import net.mullvad.mullvadvpn.lib.model.ConnectError
@@ -46,7 +49,9 @@ class ConnectViewModel(
private val connectionProxy: ConnectionProxy,
lastKnownLocationUseCase: LastKnownLocationUseCase,
private val vpnPermissionRepository: VpnPermissionRepository,
+ private val resources: Resources,
private val isPlayBuild: Boolean,
+ private val packageName: String,
) : ViewModel() {
private val _uiSideEffect = Channel<UiSideEffect>()
@@ -169,6 +174,17 @@ class ConnectViewModel(
}
}
+ fun openAppListing() =
+ viewModelScope.launch {
+ val uri =
+ if (isPlayBuild) {
+ resources.getString(R.string.market_uri, packageName)
+ } else {
+ resources.getString(R.string.download_url)
+ }
+ _uiSideEffect.send(UiSideEffect.OpenUri(Uri.parse(uri)))
+ }
+
fun dismissNewDeviceNotification() {
newDeviceRepository.clearNewDeviceCreatedNotification()
}
@@ -186,6 +202,8 @@ class ConnectViewModel(
data object OutOfTime : UiSideEffect
+ data class OpenUri(val uri: Uri) : UiSideEffect
+
data object RevokedDevice : UiSideEffect
data object NoVpnPermission : UiSideEffect
diff --git a/android/lib/resource/src/main/res/values/strings_non_translatable.xml b/android/lib/resource/src/main/res/values/strings_non_translatable.xml
index 9cf571171a..0f0a64e796 100644
--- a/android/lib/resource/src/main/res/values/strings_non_translatable.xml
+++ b/android/lib/resource/src/main/res/values/strings_non_translatable.xml
@@ -4,6 +4,7 @@
<string name="voucher_hint" translatable="false">XXXX-XXXX-XXXX-XXXX</string>
<string name="account_url" translatable="false">https://mullvad.net/account</string>
<string name="download_url" translatable="false">https://mullvad.net/download/vpn/android</string>
+ <string name="market_uri" translatable="false">market://details?id=%s</string>
<string name="faqs_and_guides_url" translatable="false">https://mullvad.net/help/tag/mullvad-app/</string>
<string name="privacy_policy_url" translatable="false">https://mullvad.net/help/privacy-policy/</string>
<string name="lockdown_url" translatable="false">https://mullvad.net/l/android-lockdown</string>