summaryrefslogtreecommitdiffhomepage
path: root/android/app/src
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-03-26 13:47:18 +0100
committerDavid Göransson <david.goransson@mullvad.net>2025-03-27 09:46:00 +0100
commitcd400acd426cbe927f1d242bc549b82b7afe63b0 (patch)
tree3d7dccf8a7aad876687e83a7e2f5ea78365b0af4 /android/app/src
parentc5120a6fd115fecc146af0c367300f8594d94224 (diff)
downloadmullvadvpn-cd400acd426cbe927f1d242bc549b82b7afe63b0.tar.xz
mullvadvpn-cd400acd426cbe927f1d242bc549b82b7afe63b0.zip
Handle open app page when no app store is installed
Diffstat (limited to 'android/app/src')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/UriHandlerExtensions.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AppInfoScreen.kt26
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AppInfoViewModel.kt16
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt16
5 files changed, 51 insertions, 23 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/UriHandlerExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/UriHandlerExtensions.kt
index a642dc72fe..e897748dfb 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/UriHandlerExtensions.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/UriHandlerExtensions.kt
@@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.compose.extensions
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.res.stringResource
+import arrow.core.Either
import co.touchlab.kermit.Logger
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.lib.common.util.createAccountUri
@@ -19,12 +20,11 @@ fun UriHandler.createOpenAccountPageHook(): (WebsiteAuthToken?) -> Unit {
fun UriHandler.createUriHook(uri: String): () -> Unit = { safeOpenUri(uri) }
-private fun UriHandler.safeOpenUri(uri: String) {
+fun UriHandler.safeOpenUri(uri: String): Either<IllegalArgumentException, Unit> =
try {
- openUri(uri)
+ Either.Right(openUri(uri))
} catch (e: IllegalArgumentException) {
// E.g user has no browser or invalid uri
Logger.e("Failed to open uri: $uri", e)
- e.cause?.let { Logger.e("cause:", it) }
+ Either.Left(e)
}
-}
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 3c57587637..c83f2c84fe 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
@@ -11,9 +11,11 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
@@ -31,9 +33,11 @@ import net.mullvad.mullvadvpn.compose.cell.NavigationComposeCell
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.extensions.safeOpenUri
import net.mullvad.mullvadvpn.compose.preview.AppInfoUiStatePreviewParameterProvider
import net.mullvad.mullvadvpn.compose.transitions.SlideInFromRightTransition
import net.mullvad.mullvadvpn.compose.util.CollectSideEffectWithLifecycle
+import net.mullvad.mullvadvpn.compose.util.showSnackbarImmediately
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
import net.mullvad.mullvadvpn.viewmodel.AppInfoSideEffect
@@ -48,7 +52,13 @@ private fun PreviewAppInfoScreen(
@PreviewParameter(AppInfoUiStatePreviewParameterProvider::class) state: AppInfoUiState
) {
AppTheme {
- AppInfo(state = state, onBackClick = {}, navigateToChangelog = {}, openAppListing = {})
+ AppInfo(
+ state = state,
+ snackbarHostState = SnackbarHostState(),
+ onBackClick = {},
+ navigateToChangelog = {},
+ openAppListing = {},
+ )
}
}
@@ -60,15 +70,21 @@ fun AppInfo(navigator: DestinationsNavigator) {
val state by vm.uiState.collectAsStateWithLifecycle()
val uriHandler = LocalUriHandler.current
+ val snackbarHostState = remember { SnackbarHostState() }
- CollectSideEffectWithLifecycle(vm.uiSideEffect) {
- when (it) {
- is AppInfoSideEffect.OpenUri -> uriHandler.openUri(it.uri.toString())
+ CollectSideEffectWithLifecycle(vm.uiSideEffect) { sideEffect ->
+ when (sideEffect) {
+ is AppInfoSideEffect.OpenUri -> {
+ uriHandler.safeOpenUri(sideEffect.uri.toString()).onLeft {
+ snackbarHostState.showSnackbarImmediately(message = sideEffect.errorMessage)
+ }
+ }
}
}
AppInfo(
state = state,
+ snackbarHostState = snackbarHostState,
onBackClick = dropUnlessResumed { navigator.navigateUp() },
navigateToChangelog =
dropUnlessResumed { navigator.navigate(ChangelogDestination(ChangelogNavArgs())) },
@@ -80,6 +96,7 @@ fun AppInfo(navigator: DestinationsNavigator) {
@Composable
fun AppInfo(
state: AppInfoUiState,
+ snackbarHostState: SnackbarHostState,
onBackClick: () -> Unit,
navigateToChangelog: () -> Unit,
openAppListing: () -> Unit,
@@ -87,6 +104,7 @@ fun AppInfo(
ScaffoldWithMediumTopBar(
appBarTitle = stringResource(id = R.string.app_info),
navigationIcon = { NavigateBackIconButton(onNavigateBack = onBackClick) },
+ snackbarHostState = snackbarHostState,
) { modifier ->
Column(horizontalAlignment = Alignment.Start, modifier = modifier.animateContentSize()) {
AppInfoContent(state, navigateToChangelog, openAppListing)
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 db7492607e..a774dfd668 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
@@ -55,7 +55,6 @@ 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
@@ -82,6 +81,7 @@ import net.mullvad.mullvadvpn.compose.component.connectioninfo.toInAddress
import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
import net.mullvad.mullvadvpn.compose.component.notificationbanner.NotificationBanner
import net.mullvad.mullvadvpn.compose.extensions.createOpenAccountPageHook
+import net.mullvad.mullvadvpn.compose.extensions.safeOpenUri
import net.mullvad.mullvadvpn.compose.preview.ConnectUiStatePreviewParameterProvider
import net.mullvad.mullvadvpn.compose.state.ConnectUiState
import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR
@@ -234,10 +234,8 @@ fun Connect(
}
is ConnectViewModel.UiSideEffect.OpenUri ->
- try {
- uriHandler.openUri(sideEffect.uri.toString())
- } catch (e: IllegalArgumentException) {
- Logger.w("Failed to open uri", e)
+ uriHandler.safeOpenUri(sideEffect.uri.toString()).onLeft {
+ snackbarHostState.showSnackbarImmediately(message = sideEffect.errorMessage)
}
}
}
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 ae197fa7ef..c4c3be4b85 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
@@ -46,18 +46,24 @@ class AppInfoViewModel(
fun openAppListing() =
viewModelScope.launch {
- val uri =
+ val sideEffect =
if (isPlayBuild || isFdroidBuild) {
- resources.getString(R.string.market_uri, packageName)
+ AppInfoSideEffect.OpenUri(
+ uri = resources.getString(R.string.market_uri, packageName).toUri(),
+ errorMessage = resources.getString(R.string.uri_market_app_not_found),
+ )
} else {
- resources.getString(R.string.download_url)
+ AppInfoSideEffect.OpenUri(
+ uri = resources.getString(R.string.download_url).toUri(),
+ errorMessage = resources.getString(R.string.uri_browser_app_not_found),
+ )
}
- _uiSideEffect.send(AppInfoSideEffect.OpenUri(uri.toUri()))
+ _uiSideEffect.send(sideEffect)
}
}
data class AppInfoUiState(val version: VersionInfo, val isPlayBuild: Boolean)
sealed interface AppInfoSideEffect {
- data class OpenUri(val uri: Uri) : AppInfoSideEffect
+ data class OpenUri(val uri: Uri, val errorMessage: String) : 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 c8de8a20a8..3a100736b2 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
@@ -185,13 +185,19 @@ class ConnectViewModel(
fun openAppListing() =
viewModelScope.launch {
- val uri =
+ val sideEffect =
if (isPlayBuild || isFdroidBuild) {
- resources.getString(R.string.market_uri, packageName)
+ UiSideEffect.OpenUri(
+ uri = resources.getString(R.string.market_uri, packageName).toUri(),
+ errorMessage = resources.getString(R.string.uri_market_app_not_found),
+ )
} else {
- resources.getString(R.string.download_url)
+ UiSideEffect.OpenUri(
+ uri = resources.getString(R.string.download_url).toUri(),
+ errorMessage = resources.getString(R.string.uri_browser_app_not_found),
+ )
}
- _uiSideEffect.send(UiSideEffect.OpenUri(uri.toUri()))
+ _uiSideEffect.send(sideEffect)
}
fun dismissNewDeviceNotification() {
@@ -214,7 +220,7 @@ class ConnectViewModel(
data object OutOfTime : UiSideEffect
- data class OpenUri(val uri: Uri) : UiSideEffect
+ data class OpenUri(val uri: Uri, val errorMessage: String) : UiSideEffect
data object RevokedDevice : UiSideEffect