diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-03-26 13:47:18 +0100 |
|---|---|---|
| committer | David Göransson <david.goransson@mullvad.net> | 2025-03-27 09:46:00 +0100 |
| commit | cd400acd426cbe927f1d242bc549b82b7afe63b0 (patch) | |
| tree | 3d7dccf8a7aad876687e83a7e2f5ea78365b0af4 | |
| parent | c5120a6fd115fecc146af0c367300f8594d94224 (diff) | |
| download | mullvadvpn-cd400acd426cbe927f1d242bc549b82b7afe63b0.tar.xz mullvadvpn-cd400acd426cbe927f1d242bc549b82b7afe63b0.zip | |
Handle open app page when no app store is installed
7 files changed, 59 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 diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml index c770e5aa1d..4b9c50fc08 100644 --- a/android/lib/resource/src/main/res/values/strings.xml +++ b/android/lib/resource/src/main/res/values/strings.xml @@ -403,4 +403,6 @@ <string name="device_ip_version_title">Device IP version</string> <string name="confirm_ipv6_dns">The IPv6 DNS server will not work unless you enable \"IPv6\" under VPN settings.</string> <string name="enable_ipv6">Enable IPv6</string> + <string name="uri_market_app_not_found">No Android app store installed, could not open link</string> + <string name="uri_browser_app_not_found">No browser app installed, could not open link</string> </resources> diff --git a/desktop/packages/mullvad-vpn/locales/messages.pot b/desktop/packages/mullvad-vpn/locales/messages.pot index 38132c9cab..bbd68b2f08 100644 --- a/desktop/packages/mullvad-vpn/locales/messages.pot +++ b/desktop/packages/mullvad-vpn/locales/messages.pot @@ -2644,6 +2644,12 @@ msgstr "" msgid "New list" msgstr "" +msgid "No Android app store installed, could not open link" +msgstr "" + +msgid "No browser app installed, could not open link" +msgstr "" + msgid "No changelog was added for this version" msgstr "" |
