diff options
| author | David Göransson <david.goransson@mullvad.net> | 2024-10-16 16:11:12 +0200 |
|---|---|---|
| committer | David Göransson <david.goransson@mullvad.net> | 2024-10-18 09:39:03 +0200 |
| commit | f5157257a7ea6db7df17b6126ae7464f48765915 (patch) | |
| tree | 34f541ed8b624cd7aa02ca179bc6176c27a88569 /android | |
| parent | 0ec8a73b3eee45ffee2096fa582c94d2b055734a (diff) | |
| download | mullvadvpn-f5157257a7ea6db7df17b6126ae7464f48765915.tar.xz mullvadvpn-f5157257a7ea6db7df17b6126ae7464f48765915.zip | |
Improve Unsupported version link
Diffstat (limited to 'android')
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> |
