Skip to content

Commit

Permalink
android: integrate state machine (#1393)
Browse files Browse the repository at this point in the history
* android: initial state machine integration

* add scripts

* remove sudo command

* fix go mod version

* add vpnservice lifecycle

* APPLE: update credential view, add new core (#1371)

* add error state changes

* add initial error messaging

* add qa consts

* add qa env

* add qa to env screen

* add qa links

* fmt

* update account link

* remove export

* android: initial state machine integration

* add scripts

* remove sudo command

* fix go mod version

* add vpnservice lifecycle

* APPLE: update credential view, add new core (#1371)

* add error state changes

* add initial error messaging

* add qa consts

* add qa env

* add qa to env screen

* add qa links

* fmt

* update account link

* remove export

* Merge branch 'android/integrate-state-mach' of github.com:nymtech/nym-vpn-client into android/integrate-state-mach

* refactor

* remove unused

* fix clippy

* update uniffi swift

* remove rustflags, add prebuild script

* Update fdroid-prebuild.sh

* fix script

* fix script

* fix script

* add flag

* fix script

* remove prebuild script

* add cargo deps install

* update kotlin bindings

Co-Authored-By: Rokas Ambrazevicius <[email protected]>
  • Loading branch information
zaneschepke and rokas-ambrazevicius authored Oct 26, 2024
1 parent 0148a33 commit 68d70bd
Show file tree
Hide file tree
Showing 21 changed files with 1,229 additions and 896 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import net.nymtech.vpn.backend.Backend
import net.nymtech.vpn.backend.Tunnel
import net.nymtech.vpn.model.BackendMessage
import net.nymtech.vpn.model.Statistics
import nym_vpn_lib.BandwidthStatus
import nym_vpn_lib.VpnException
import nym_vpn_lib.BandwidthEvent
import nym_vpn_lib.BandwidthStatus.NoBandwidth
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Provider
Expand Down Expand Up @@ -128,7 +128,8 @@ class NymTunnelManager @Inject constructor(
private fun onMissingMnemonic() {
val message = context.getString(R.string.missing_mnemonic)
if (NymVpn.isForeground()) {
emitMessage(BackendMessage.Failure(VpnException.InvalidCredential(details = message)))
// TODO add message for mnemonic
// emitMessage(BackendMessage.Failure(VpnException.InvalidCredential(details = message)))
} else {
launchMnemonicNotification(message)
}
Expand Down Expand Up @@ -161,32 +162,22 @@ class NymTunnelManager @Inject constructor(
private fun launchBackendNotification(backendMessage: BackendMessage) {
when (backendMessage) {
is BackendMessage.Failure -> {
val launchNotification = when (backendMessage.exception) {
is VpnException.InvalidCredential -> !NymVpn.isForeground()
else -> true
}
if (launchNotification) {
notificationService.showNotification(
title = context.getString(R.string.connection_failed),
description = backendMessage.exception.toUserMessage(context),
)
}
// TODO if credential error we might need to handle differently if app is in foreground
notificationService.showNotification(
title = context.getString(R.string.connection_failed),
description = backendMessage.reason.toUserMessage(context),
)
}
is BackendMessage.BandwidthAlert -> {
when (val alert = backendMessage.status) {
BandwidthStatus.NoBandwidth -> {
notificationService.showNotification(
title = context.getString(R.string.bandwidth_alert),
description = context.getString(R.string.no_bandwidth),
)
}

is BandwidthStatus.RemainingBandwidth -> {
notificationService.showNotification(
title = context.getString(R.string.bandwidth_alert),
description = context.getString(R.string.low_bandwidth) + " ${alert.bandwidth}",
)
}
BandwidthEvent.NoBandwidth -> notificationService.showNotification(
title = context.getString(R.string.bandwidth_alert),
description = context.getString(R.string.no_bandwidth),
)
is BandwidthEvent.RemainingBandwidth -> notificationService.showNotification(
title = context.getString(R.string.bandwidth_alert),
description = context.getString(R.string.low_bandwidth) + " ${alert.v1}",
)
}
}
BackendMessage.None -> Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,11 @@ import kotlinx.coroutines.delay
import net.nymtech.localizationutil.LocaleStorage
import net.nymtech.localizationutil.LocaleUtil
import net.nymtech.nymvpn.NymVpn
import net.nymtech.nymvpn.R
import net.nymtech.nymvpn.manager.shortcut.ShortcutManager
import net.nymtech.nymvpn.service.notification.NotificationService
import net.nymtech.nymvpn.ui.common.labels.CustomSnackBar
import net.nymtech.nymvpn.ui.common.navigation.LocalNavController
import net.nymtech.nymvpn.ui.common.navigation.NavBar
import net.nymtech.nymvpn.ui.common.snackbar.SnackbarController
import net.nymtech.nymvpn.ui.common.snackbar.SnackbarControllerProvider
import net.nymtech.nymvpn.ui.screens.analytics.AnalyticsScreen
import net.nymtech.nymvpn.ui.screens.hop.GatewayLocation
Expand All @@ -67,13 +65,10 @@ import net.nymtech.nymvpn.ui.screens.settings.support.SupportScreen
import net.nymtech.nymvpn.ui.theme.NymVPNTheme
import net.nymtech.nymvpn.ui.theme.Theme
import net.nymtech.nymvpn.util.Constants
import net.nymtech.nymvpn.util.StringValue
import net.nymtech.nymvpn.util.extensions.goFromRoot
import net.nymtech.nymvpn.util.extensions.isCurrentRoute
import net.nymtech.nymvpn.util.extensions.requestTileServiceStateUpdate
import net.nymtech.nymvpn.util.extensions.resetTile
import net.nymtech.vpn.model.BackendMessage
import nym_vpn_lib.VpnException
import java.util.Locale
import javax.inject.Inject

Expand Down Expand Up @@ -136,16 +131,17 @@ class MainActivity : ComponentActivity() {
LaunchedEffect(appState.backendMessage) {
when (val message = appState.backendMessage) {
is BackendMessage.Failure -> {
when (message.exception) {
is VpnException.InvalidCredential -> {
if (NymVpn.isForeground()) {
SnackbarController.showMessage(StringValue.StringResource(R.string.exception_cred_invalid))
navController.goFromRoot(Route.Credential)
}
}

else -> Unit
}
// TODO invalid credential errors?
// when (message.exception) {
// is VpnException.InvalidCredential -> {
// if (NymVpn.isForeground()) {
// SnackbarController.showMessage(StringValue.StringResource(R.string.exception_cred_invalid))
// navController.goFromRoot(Route.Credential)
// }
// }
//
// else -> Unit
// }
}

else -> Unit
Expand Down Expand Up @@ -231,7 +227,7 @@ class MainActivity : ComponentActivity() {
composable<Route.Feedback> { FeedbackScreen(appViewModel) }
composable<Route.Legal> { LegalScreen(appViewModel) }
composable<Route.Credential> {
CredentialScreen(appViewModel)
CredentialScreen(appState, appViewModel)
}
composable<Route.Account> { AccountScreen(appViewModel, appState) }
composable<Route.Licenses> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package net.nymtech.nymvpn.ui.model

import net.nymtech.nymvpn.util.StringValue
import nym_vpn_lib.VpnException
import nym_vpn_lib.ErrorStateReason

sealed class StateMessage {
data class Status(val message: StringValue) : StateMessage()
data class Error(val exception: VpnException) : StateMessage()
data class Error(val reason: ErrorStateReason) : StateMessage()
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ fun MainScreen(appViewModel: AppViewModel, appUiState: AppUiState, autoStart: Bo

is StateMessage.Error ->
StatusInfoLabel(
message = it.exception.toUserMessage(context),
message = it.reason.toUserMessage(context),
textColor = CustomColors.error,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ constructor(
var stateMessage = connectionState.stateMessage
when (manager.backendMessage) {
is BackendMessage.Failure -> {
stateMessage = StateMessage.Error(manager.backendMessage.exception)
stateMessage = StateMessage.Error(manager.backendMessage.reason)
}
BackendMessage.None -> stateMessage = connectionState.stateMessage
is BackendMessage.BandwidthAlert -> Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,13 @@ fun SettingsScreen(appViewModel: AppViewModel, appUiState: AppUiState, viewModel
},
title = { Text(stringResource(R.string.account), style = MaterialTheme.typography.bodyLarge.copy(MaterialTheme.colorScheme.onSurface)) },
onClick = {
context.openWebUrl(context.getString(R.string.account_url))
val url = when (appUiState.settings.environment) {
Tunnel.Environment.MAINNET -> context.getString(
R.string.account_url,
)
else -> context.getString(R.string.account_url_qa)
}
context.openWebUrl(url)
},
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import net.nymtech.nymvpn.R
import net.nymtech.nymvpn.ui.AppUiState
import net.nymtech.nymvpn.ui.AppViewModel
import net.nymtech.nymvpn.ui.Route
import net.nymtech.nymvpn.ui.common.buttons.MainStyledButton
Expand All @@ -55,10 +56,11 @@ import net.nymtech.nymvpn.util.extensions.navigateAndForget
import net.nymtech.nymvpn.util.extensions.openWebUrl
import net.nymtech.nymvpn.util.extensions.scaledHeight
import net.nymtech.nymvpn.util.extensions.scaledWidth
import net.nymtech.vpn.backend.Tunnel

@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun CredentialScreen(appViewModel: AppViewModel, viewModel: CredentialViewModel = hiltViewModel()) {
fun CredentialScreen(appUiState: AppUiState, appViewModel: AppViewModel, viewModel: CredentialViewModel = hiltViewModel()) {
val snackbar = SnackbarController.current
val imeState = rememberImeState()
val scrollState = rememberScrollState()
Expand Down Expand Up @@ -219,7 +221,15 @@ fun CredentialScreen(appViewModel: AppViewModel, viewModel: CredentialViewModel
val createAccountMessage = buildAnnotatedString {
append(stringResource(id = R.string.new_to_nym))
append(" ")
pushStringAnnotation(tag = "create", annotation = stringResource(id = R.string.create_account_link))
pushStringAnnotation(
tag = "create",
annotation = when (appUiState.settings.environment) {
Tunnel.Environment.MAINNET -> stringResource(
id = R.string.create_account_link,
)
else -> stringResource(R.string.create_account_link_qa)
},
)
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.primary)) {
append(stringResource(id = R.string.create_account))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,29 +51,15 @@ fun EnvironmentScreen(appUiState: AppUiState, appViewModel: AppViewModel, viewMo
.padding(top = 24.dp.scaledHeight())
.padding(horizontal = 24.dp.scaledWidth()),
) {
IconSurfaceButton(
title = Tunnel.Environment.CANARY.name,
onClick = {
if (appUiState.settings.environment == Tunnel.Environment.CANARY) return@IconSurfaceButton
viewModel.onEnvironmentChange(Tunnel.Environment.CANARY)
},
selected = appUiState.settings.environment == Tunnel.Environment.CANARY,
)
IconSurfaceButton(
title = Tunnel.Environment.SANDBOX.name,
onClick = {
if (appUiState.settings.environment == Tunnel.Environment.SANDBOX) return@IconSurfaceButton
viewModel.onEnvironmentChange(Tunnel.Environment.SANDBOX)
},
selected = appUiState.settings.environment == Tunnel.Environment.SANDBOX,
)
IconSurfaceButton(
title = Tunnel.Environment.MAINNET.name,
onClick = {
if (appUiState.settings.environment == Tunnel.Environment.MAINNET) return@IconSurfaceButton
viewModel.onEnvironmentChange(Tunnel.Environment.MAINNET)
},
selected = appUiState.settings.environment == Tunnel.Environment.MAINNET,
)
enumValues<Tunnel.Environment>().forEach {
IconSurfaceButton(
title = it.name,
onClick = {
if (appUiState.settings.environment == it) return@IconSurfaceButton
viewModel.onEnvironmentChange(it)
},
selected = appUiState.settings.environment == it,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import net.nymtech.nymvpn.NymVpn
import net.nymtech.nymvpn.R
import net.nymtech.nymvpn.ui.Route
import nym_vpn_lib.VpnException
import nym_vpn_lib.ErrorStateReason
import kotlin.reflect.KClass

fun Dp.scaledHeight(): Dp {
Expand Down Expand Up @@ -56,20 +55,16 @@ fun NavController.goFromRoot(route: Route) {
}
}

fun VpnException.toUserMessage(context: Context): String {
fun ErrorStateReason.toUserMessage(context: Context): String {
return when (this) {
is VpnException.GatewayException -> context.getString(R.string.gateway_error)
is VpnException.InternalException -> {
// TODO we need improved errors for this scenario from backend
when {
this.details.contains("no exit gateway available for location") -> context.getString(R.string.selected_exit_unavailable)
this.details.contains("no entry gateway available for location") -> context.getString(R.string.selected_entry_unavailable)
else -> context.getString(R.string.internal_error)
}
}
is VpnException.InvalidCredential -> context.getString(R.string.exception_cred_invalid)
is VpnException.InvalidStateException -> context.getString(R.string.state_error)
is VpnException.NetworkConnectionException -> context.getString(R.string.network_error)
is VpnException.OutOfBandwidth -> context.getString(R.string.out_of_bandwidth_error)
ErrorStateReason.FIREWALL -> "A firewall issue occurred"
ErrorStateReason.ROUTING -> "A routing issue occurred"
ErrorStateReason.DNS -> "A dns issue occurred"
ErrorStateReason.TUN_DEVICE -> "A tunnel device issue occurred"
ErrorStateReason.TUNNEL_PROVIDER -> "A tunnel provider issue occurred"
ErrorStateReason.ESTABLISH_MIXNET_CONNECTION -> "Failed to establish mixnet connection"
ErrorStateReason.ESTABLISH_WIREGUARD_CONNECTION -> "Failed to establish wireguard connection"
ErrorStateReason.TUNNEL_DOWN -> "Tunnel down error"
ErrorStateReason.INTERNAL -> "Internal error"
}
}
Loading

0 comments on commit 68d70bd

Please sign in to comment.