diff --git a/.github/workflows/check-date.yml b/.github/workflows/check-date.yml deleted file mode 100644 index 1471201..0000000 --- a/.github/workflows/check-date.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: check-date - -on: - workflow_dispatch: - workflow_call: - -jobs: - check_date: - runs-on: ubuntu-latest - name: Check latest commit - outputs: - should_run: ${{ steps.should_run.outputs.should_run }} - steps: - - uses: actions/checkout@v2 - - name: print latest_commit - run: echo ${{ github.sha }} - - id: should_run - continue-on-error: true - name: check latest commit is less than a day - if: ${{ github.event_name == 'schedule' }} - run: test -z $(git rev-list --after="23 hours" ${{ github.sha }}) && echo "::set-output name=should_run::false" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml deleted file mode 100644 index fd15728..0000000 --- a/.github/workflows/nightly.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: nightly - -on: - schedule: - - cron: "4 3 * * *" - workflow_dispatch: -jobs: - check-date: - uses: ./.github/workflows/check-date.yml - nightly-publish-check: - name: Nightly publish check - runs-on: ubuntu-latest - needs: check-date - if: ${{ needs.check-date.outputs.should_run != 'false' }} - publish-nightly: - name: Publish nightly - runs-on: ubuntu-latest - needs: nightly-publish-check - uses: ./.github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0cd7545..b72001f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,8 @@ name: release-android on: + schedule: + - cron: "4 3 * * *" workflow_dispatch: inputs: track: @@ -31,10 +33,25 @@ on: workflow_call: jobs: + check_date: + runs-on: ubuntu-latest + name: Check latest commit + outputs: + should_run: ${{ steps.should_run.outputs.should_run }} + steps: + - uses: actions/checkout@v4 + - name: print latest_commit + run: echo ${{ github.sha }} + - id: should_run + continue-on-error: true + name: check latest commit is less than a day + if: ${{ github.event_name == 'schedule' }} + run: test -z $(git rev-list --after="23 hours" ${{ github.sha }}) && echo "::set-output name=should_run::false" build: + needs: check_date + if: ${{ (needs.check_date.outputs.should_run != 'false' && github.event_name == 'schedule') || github.event_name != 'schedule'}} name: Build Signed APK runs-on: ubuntu-latest - env: SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} @@ -47,7 +64,7 @@ jobs: GH_REPO: ${{ github.repository }} steps: - - uses: actions/checkout@4 + - uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 with: diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b509f5d..e09e282 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -164,8 +164,6 @@ dependencies { implementation(libs.androidx.navigation.compose) implementation(libs.androidx.hilt.navigation.compose) - implementation(libs.zaneschepke.multifab) - // hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/TunnelConfig.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/TunnelConfig.kt index 4a38a76..245a940 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/TunnelConfig.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/TunnelConfig.kt @@ -58,6 +58,11 @@ data class TunnelConfig( ) var pingIp: String? = null, ) { + + fun toAmConfig(): org.amnezia.awg.config.Config { + return configFromAmQuick(if (amQuick != "") amQuick else wgQuick) + } + companion object { fun configFromWgQuick(wgQuick: String): Config { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt index 6a2487a..6c6a16e 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt @@ -41,6 +41,7 @@ import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar import com.zaneschepke.wireguardautotunnel.ui.common.prompt.CustomSnackBar import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarControllerProvider import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigScreen +import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigViewModel import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen import com.zaneschepke.wireguardautotunnel.ui.screens.options.OptionsScreen @@ -179,16 +180,14 @@ class MainActivity : AppCompatActivity() { ), ) { val id = it.arguments?.getString("id") - val configType = - ConfigType.valueOf( - it.arguments?.getString("configType") ?: ConfigType.WIREGUARD.name, - ) if (!id.isNullOrBlank()) { + val viewModel = hiltViewModel { factory -> + factory.create(id.toInt()) + } ConfigScreen( - navController = navController, - tunnelId = id, + viewModel = viewModel, focusRequester = focusRequester, - configType = configType, + tunnelId = id.toInt(), ) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationToggle.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationToggle.kt index bae787d..1621061 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationToggle.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationToggle.kt @@ -18,7 +18,7 @@ fun ConfigurationToggle( enabled: Boolean = true, checked: Boolean, padding: Dp, - onCheckChanged: () -> Unit, + onCheckChanged: (checked: Boolean) -> Unit, modifier: Modifier = Modifier, ) { Row( @@ -44,7 +44,7 @@ fun ConfigurationToggle( modifier = modifier, enabled = enabled, checked = checked, - onCheckedChange = { onCheckChanged() }, + onCheckedChange = { onCheckChanged(it) }, ) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/SubmitConfigurationTextBox.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/SubmitConfigurationTextBox.kt index 303ff72..ea94ce8 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/SubmitConfigurationTextBox.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/SubmitConfigurationTextBox.kt @@ -10,6 +10,8 @@ import androidx.compose.material.icons.outlined.Save import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -23,8 +25,6 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization -import androidx.compose.ui.text.input.KeyboardType -import androidx.core.text.isDigitsOnly import com.zaneschepke.wireguardautotunnel.R @Composable @@ -45,36 +45,31 @@ fun SubmitConfigurationTextBox( val isFocused by interactionSource.collectIsFocusedAsState() val keyboardController = LocalSoftwareKeyboardController.current - var stateValue by remember { mutableStateOf(value) } + var stateValue by remember { mutableStateOf(value ?: "") } - ConfigurationTextBox( + OutlinedTextField( isError = isErrorValue(stateValue), - interactionSource = interactionSource, - value = stateValue ?: "", - onValueChange = { - when (keyboardOptions.keyboardType) { - KeyboardType.Number -> { - if (it.isDigitsOnly()) stateValue = it - } - else -> stateValue = it - } - }, - keyboardOptions = keyboardOptions, - label = label, - keyboardActions = KeyboardActions( - onDone = { - onSubmit(stateValue!!) - keyboardController?.hide() - }, - ), - hint = hint, modifier = Modifier .fillMaxWidth() .focusRequester(focusRequester), - trailing = { - if (!stateValue.isNullOrBlank() && !isErrorValue(stateValue) && isFocused) { + value = stateValue, + singleLine = true, + interactionSource = interactionSource, + onValueChange = { stateValue = it }, + label = { Text(label) }, + maxLines = 1, + placeholder = { Text(hint) }, + keyboardOptions = keyboardOptions, + keyboardActions = KeyboardActions( + onDone = { + onSubmit(stateValue) + keyboardController?.hide() + }, + ), + trailingIcon = { + if (!isErrorValue(stateValue) && isFocused) { IconButton(onClick = { - onSubmit(stateValue!!) + onSubmit(stateValue) keyboardController?.hide() focusManager.clearFocus() }) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt index 326cc02..f981f6b 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt @@ -42,7 +42,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -53,7 +52,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.platform.ClipboardManager import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext @@ -65,21 +63,17 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavController import com.google.accompanist.drawablepainter.DrawablePainter import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.Screen import com.zaneschepke.wireguardautotunnel.ui.common.SearchBar import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationTextBox +import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt -import com.zaneschepke.wireguardautotunnel.ui.common.screen.LoadingScreen import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType import com.zaneschepke.wireguardautotunnel.util.Constants -import com.zaneschepke.wireguardautotunnel.util.extensions.getMessage import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv import kotlinx.coroutines.delay @@ -88,13 +82,7 @@ import kotlinx.coroutines.delay ExperimentalMaterial3Api::class, ) @Composable -fun ConfigScreen( - viewModel: ConfigViewModel = hiltViewModel(), - focusRequester: FocusRequester, - navController: NavController, - tunnelId: String, - configType: ConfigType, -) { +fun ConfigScreen(tunnelId: Int, viewModel: ConfigViewModel, focusRequester: FocusRequester) { val context = LocalContext.current val snackbar = SnackbarController.current val clipboardManager: ClipboardManager = LocalClipboardManager.current @@ -102,12 +90,11 @@ fun ConfigScreen( var showApplicationsDialog by remember { mutableStateOf(false) } var showAuthPrompt by remember { mutableStateOf(false) } var isAuthenticated by remember { mutableStateOf(false) } + var configType by remember { mutableStateOf(ConfigType.WIREGUARD) } val uiState by viewModel.uiState.collectAsStateWithLifecycle() - LaunchedEffect(Unit) { viewModel.init(tunnelId) } - - LaunchedEffect(uiState.loading) { + LaunchedEffect(Unit) { if (!uiState.loading && context.isRunningOnTv()) { delay(Constants.FOCUS_REQUEST_DELAY) kotlin.runCatching { @@ -119,13 +106,7 @@ fun ConfigScreen( } } - if (uiState.loading) { - LoadingScreen() - return - } - val keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }) - val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done) val fillMaxHeight = .85f @@ -329,27 +310,11 @@ fun ConfigScreen( Scaffold( floatingActionButtonPosition = FabPosition.End, floatingActionButton = { - val secondaryColor = MaterialTheme.colorScheme.secondary - val hoverColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp) - var fobColor by remember { mutableStateOf(secondaryColor) } FloatingActionButton( - modifier = - Modifier.onFocusChanged { - if (context.isRunningOnTv()) { - fobColor = if (it.isFocused) hoverColor else secondaryColor - } - }, onClick = { - viewModel.onSaveAllChanges(configType).onSuccess { - snackbar.showMessage( - context.getString(R.string.config_changes_saved), - ) - navController.navigate(Screen.Main.route) - }.onFailure { - snackbar.showMessage(it.getMessage(context)) - } + viewModel.onSaveAllChanges() }, - containerColor = fobColor, + containerColor = MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(16.dp), ) { Icon( @@ -399,9 +364,16 @@ fun ConfigScreen( stringResource(R.string.interface_), padding = screenPadding, ) + ConfigurationToggle( + stringResource(id = R.string.show_amnezia_properties), + checked = configType == ConfigType.AMNEZIA, + padding = screenPadding, + onCheckChanged = { configType = if (it) ConfigType.AMNEZIA else ConfigType.WIREGUARD }, + modifier = Modifier.focusRequester(focusRequester), + ) ConfigurationTextBox( value = uiState.tunnelName, - onValueChange = { value -> viewModel.onTunnelNameChange(value) }, + onValueChange = viewModel::onTunnelNameChange, keyboardActions = keyboardActions, label = stringResource(R.string.name), hint = stringResource(R.string.tunnel_name).lowercase(), @@ -417,12 +389,12 @@ fun ConfigScreen( .clickable { showAuthPrompt = true }, value = uiState.interfaceProxy.privateKey, visualTransformation = - if ((tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated) { + if ((tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID.toInt()) || isAuthenticated) { VisualTransformation.None } else { PasswordVisualTransformation() }, - enabled = (tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated, + enabled = (tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID.toInt()) || isAuthenticated, onValueChange = { value -> viewModel.onPrivateKeyChange(value) }, trailingIcon = { IconButton( @@ -472,31 +444,29 @@ fun ConfigScreen( keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, ) - Row(modifier = Modifier.fillMaxWidth()) { - ConfigurationTextBox( - value = uiState.interfaceProxy.addresses, - onValueChange = { value -> viewModel.onAddressesChanged(value) }, - keyboardActions = keyboardActions, - label = stringResource(R.string.addresses), - hint = stringResource(R.string.comma_separated_list), - modifier = - Modifier - .fillMaxWidth(3 / 5f) - .padding(end = 5.dp), - ) - ConfigurationTextBox( - value = uiState.interfaceProxy.listenPort, - onValueChange = { value -> viewModel.onListenPortChanged(value) }, - keyboardActions = keyboardActions, - label = stringResource(R.string.listen_port), - hint = stringResource(R.string.random), - modifier = Modifier.width(IntrinsicSize.Min), - ) - } + ConfigurationTextBox( + value = uiState.interfaceProxy.addresses, + onValueChange = viewModel::onAddressesChanged, + keyboardActions = keyboardActions, + label = stringResource(R.string.addresses), + hint = stringResource(R.string.comma_separated_list), + modifier = + Modifier + .fillMaxWidth() + .padding(end = 5.dp), + ) + ConfigurationTextBox( + value = uiState.interfaceProxy.listenPort, + onValueChange = viewModel::onListenPortChanged, + keyboardActions = keyboardActions, + label = stringResource(R.string.listen_port), + hint = stringResource(R.string.random), + modifier = Modifier.fillMaxWidth(), + ) Row(modifier = Modifier.fillMaxWidth()) { ConfigurationTextBox( value = uiState.interfaceProxy.dnsServers, - onValueChange = { value -> viewModel.onDnsServersChanged(value) }, + onValueChange = viewModel::onDnsServersChanged, keyboardActions = keyboardActions, label = stringResource(R.string.dns_servers), hint = stringResource(R.string.comma_separated_list), @@ -507,7 +477,7 @@ fun ConfigScreen( ) ConfigurationTextBox( value = uiState.interfaceProxy.mtu, - onValueChange = { value -> viewModel.onMtuChanged(value) }, + onValueChange = viewModel::onMtuChanged, keyboardActions = keyboardActions, label = stringResource(R.string.mtu), hint = stringResource(R.string.auto), @@ -517,10 +487,7 @@ fun ConfigScreen( if (configType == ConfigType.AMNEZIA) { ConfigurationTextBox( value = uiState.interfaceProxy.junkPacketCount, - onValueChange = { - value -> - viewModel.onJunkPacketCountChanged(value) - }, + onValueChange = viewModel::onJunkPacketCountChanged, keyboardActions = keyboardActions, label = stringResource(R.string.junk_packet_count), hint = stringResource(R.string.junk_packet_count).lowercase(), @@ -531,11 +498,7 @@ fun ConfigScreen( ) ConfigurationTextBox( value = uiState.interfaceProxy.junkPacketMinSize, - onValueChange = { value -> - viewModel.onJunkPacketMinSizeChanged( - value, - ) - }, + onValueChange = viewModel::onJunkPacketMinSizeChanged, keyboardActions = keyboardActions, label = stringResource(R.string.junk_packet_minimum_size), hint = @@ -549,11 +512,7 @@ fun ConfigScreen( ) ConfigurationTextBox( value = uiState.interfaceProxy.junkPacketMaxSize, - onValueChange = { value -> - viewModel.onJunkPacketMaxSizeChanged( - value, - ) - }, + onValueChange = viewModel::onJunkPacketMaxSizeChanged, keyboardActions = keyboardActions, label = stringResource(R.string.junk_packet_maximum_size), hint = @@ -567,11 +526,7 @@ fun ConfigScreen( ) ConfigurationTextBox( value = uiState.interfaceProxy.initPacketJunkSize, - onValueChange = { value -> - viewModel.onInitPacketJunkSizeChanged( - value, - ) - }, + onValueChange = viewModel::onInitPacketJunkSizeChanged, keyboardActions = keyboardActions, label = stringResource(R.string.init_packet_junk_size), hint = stringResource(R.string.init_packet_junk_size).lowercase(), @@ -582,10 +537,7 @@ fun ConfigScreen( ) ConfigurationTextBox( value = uiState.interfaceProxy.responsePacketJunkSize, - onValueChange = { - value -> - viewModel.onResponsePacketJunkSize(value) - }, + onValueChange = viewModel::onResponsePacketJunkSize, keyboardActions = keyboardActions, label = stringResource(R.string.response_packet_junk_size), hint = @@ -599,10 +551,7 @@ fun ConfigScreen( ) ConfigurationTextBox( value = uiState.interfaceProxy.initPacketMagicHeader, - onValueChange = { - value -> - viewModel.onInitPacketMagicHeader(value) - }, + onValueChange = viewModel::onInitPacketMagicHeader, keyboardActions = keyboardActions, label = stringResource(R.string.init_packet_magic_header), hint = @@ -616,11 +565,7 @@ fun ConfigScreen( ) ConfigurationTextBox( value = uiState.interfaceProxy.responsePacketMagicHeader, - onValueChange = { value -> - viewModel.onResponsePacketMagicHeader( - value, - ) - }, + onValueChange = viewModel::onResponsePacketMagicHeader, keyboardActions = keyboardActions, label = stringResource(R.string.response_packet_magic_header), hint = @@ -634,11 +579,7 @@ fun ConfigScreen( ) ConfigurationTextBox( value = uiState.interfaceProxy.underloadPacketMagicHeader, - onValueChange = { value -> - viewModel.onUnderloadPacketMagicHeader( - value, - ) - }, + onValueChange = viewModel::onUnderloadPacketMagicHeader, keyboardActions = keyboardActions, label = stringResource(R.string.underload_packet_magic_header), hint = @@ -652,11 +593,7 @@ fun ConfigScreen( ) ConfigurationTextBox( value = uiState.interfaceProxy.transportPacketMagicHeader, - onValueChange = { value -> - viewModel.onTransportPacketMagicHeader( - value, - ) - }, + onValueChange = viewModel::onTransportPacketMagicHeader, keyboardActions = keyboardActions, label = stringResource(R.string.transport_packet_magic_header), hint = @@ -814,9 +751,6 @@ fun ConfigScreen( } } } - if (context.isRunningOnTv()) { - Spacer(modifier = Modifier.weight(.17f)) - } } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigUiState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigUiState.kt index 05d9b9e..a5c3caf 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigUiState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigUiState.kt @@ -15,7 +15,7 @@ data class ConfigUiState( val isAllApplicationsEnabled: Boolean = false, val loading: Boolean = true, val tunnel: TunnelConfig? = null, - val tunnelName: String = "", + var tunnelName: String = "", val isAmneziaEnabled: Boolean = false, ) { companion object { @@ -45,7 +45,6 @@ data class ConfigUiState( } fun from(config: org.amnezia.awg.config.Config): ConfigUiState { - // TODO update with new values val proxyPeers = config.peers.map { PeerProxy.from(it) } val proxyInterface = InterfaceProxy.from(config.`interface`) var include = true @@ -69,5 +68,13 @@ data class ConfigUiState( isAllApplicationsEnabled, ) } + + fun from(tunnel: TunnelConfig): ConfigUiState { + val config = tunnel.toAmConfig() + return from(config).copy( + tunnelName = tunnel.name, + tunnel = tunnel, + ) + } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt index 4df067b..995d0ae 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt @@ -6,6 +6,7 @@ import android.content.pm.PackageManager import android.os.Build import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.navigation.NavHostController import com.wireguard.config.Config import com.wireguard.config.Interface import com.wireguard.config.Peer @@ -15,102 +16,82 @@ import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository -import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository import com.zaneschepke.wireguardautotunnel.module.IoDispatcher +import com.zaneschepke.wireguardautotunnel.ui.Screen +import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy -import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.NumberUtils import com.zaneschepke.wireguardautotunnel.util.StringValue -import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions import com.zaneschepke.wireguardautotunnel.util.extensions.removeAt import com.zaneschepke.wireguardautotunnel.util.extensions.update +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.plus import timber.log.Timber -import javax.inject.Inject -@HiltViewModel +@HiltViewModel(assistedFactory = ConfigViewModel.ConfigViewModelFactory::class) class ConfigViewModel -@Inject +@AssistedInject constructor( - private val settingsRepository: SettingsRepository, private val appDataRepository: AppDataRepository, + private val navController: NavHostController, + @Assisted val id: Int, @IoDispatcher private val ioDispatcher: CoroutineDispatcher, ) : ViewModel() { private val packageManager = WireGuardAutoTunnel.instance.packageManager private val _uiState = MutableStateFlow(ConfigUiState()) - val uiState = _uiState.asStateFlow() - - fun init(tunnelId: String) = viewModelScope.launch(ioDispatcher) { - val packages = getQueriedPackages("") - val state = - if (tunnelId != Constants.MANUAL_TUNNEL_CONFIG_ID) { - val tunnelConfig = - appDataRepository.tunnels.getAll() - .firstOrNull { it.id.toString() == tunnelId } - val isAmneziaEnabled = settingsRepository.getSettings().isAmneziaEnabled - if (tunnelConfig != null) { - ( - if (isAmneziaEnabled) { - val amConfig = - if (tunnelConfig.amQuick == "") tunnelConfig.wgQuick else tunnelConfig.amQuick - ConfigUiState.from(TunnelConfig.configFromAmQuick(amConfig)) - } else { - ConfigUiState.from( - TunnelConfig.configFromWgQuick(tunnelConfig.wgQuick), - ) - } - ).copy( - packages = packages, - loading = false, - tunnel = tunnelConfig, - tunnelName = tunnelConfig.name, - isAmneziaEnabled = isAmneziaEnabled, - ) - } else { - ConfigUiState(loading = false, packages = packages) - } - } else { - ConfigUiState(loading = false, packages = packages) - } - _uiState.value = state - } + val uiState = _uiState.onStart { + appDataRepository.tunnels.getById(id)?.let { + _uiState.value = ConfigUiState.from(it) + } + }.stateIn( + viewModelScope + ioDispatcher, + SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT), + ConfigUiState(), + ) fun onTunnelNameChange(name: String) { - _uiState.value = _uiState.value.copy(tunnelName = name) + _uiState.update { + it.copy(tunnelName = name) + } } fun onIncludeChange(include: Boolean) { - _uiState.value = _uiState.value.copy(include = include) + _uiState.update { + it.copy(include = include) + } } fun onAddCheckedPackage(packageName: String) { - _uiState.value = - _uiState.value.copy( - checkedPackageNames = _uiState.value.checkedPackageNames + packageName, + _uiState.update { + it.copy( + checkedPackageNames = it.checkedPackageNames + packageName, ) + } } fun onAllApplicationsChange(isAllApplicationsEnabled: Boolean) { - _uiState.value = _uiState.value.copy(isAllApplicationsEnabled = isAllApplicationsEnabled) + _uiState.update { + it.copy(isAllApplicationsEnabled = isAllApplicationsEnabled) + } } fun onRemoveCheckedPackage(packageName: String) { - _uiState.value = - _uiState.value.copy( - checkedPackageNames = _uiState.value.checkedPackageNames - packageName, + _uiState.update { + it.copy( + checkedPackageNames = it.checkedPackageNames - packageName, ) - } - - private fun getQueriedPackages(query: String): List { - return getAllInternetCapablePackages().filter { - getPackageLabel(it).lowercase().contains(query.lowercase()) } } @@ -137,7 +118,9 @@ constructor( return _uiState.value.isAllApplicationsEnabled } - private fun saveConfig(tunnelConfig: TunnelConfig) = viewModelScope.launch { appDataRepository.tunnels.save(tunnelConfig) } + private fun saveConfig(tunnelConfig: TunnelConfig) = viewModelScope.launch { + appDataRepository.tunnels.save(tunnelConfig) + } private fun updateTunnelConfig(tunnelConfig: TunnelConfig?) = viewModelScope.launch { if (tunnelConfig != null) { @@ -174,105 +157,113 @@ constructor( } private fun emptyCheckedPackagesList() { - _uiState.value = _uiState.value.copy(checkedPackageNames = emptyList()) + _uiState.update { + it.copy(checkedPackageNames = emptyList()) + } } private fun buildInterfaceListFromProxyInterface(): Interface { val builder = Interface.Builder() - builder.parsePrivateKey(_uiState.value.interfaceProxy.privateKey.trim()) - builder.parseAddresses(_uiState.value.interfaceProxy.addresses.trim()) - if (_uiState.value.interfaceProxy.dnsServers.isNotEmpty()) { - builder.parseDnsServers(_uiState.value.interfaceProxy.dnsServers.trim()) - } - if (_uiState.value.interfaceProxy.mtu.isNotEmpty()) { - builder.parseMtu(_uiState.value.interfaceProxy.mtu.trim()) - } - if (_uiState.value.interfaceProxy.listenPort.isNotEmpty()) { - builder.parseListenPort(_uiState.value.interfaceProxy.listenPort.trim()) - } - if (isAllApplicationsEnabled()) emptyCheckedPackagesList() - if (_uiState.value.include) { - builder.includeApplications( - _uiState.value.checkedPackageNames, - ) - } - if (!_uiState.value.include) { - builder.excludeApplications( - _uiState.value.checkedPackageNames, - ) + with(_uiState.value.interfaceProxy) { + builder.parsePrivateKey(this.privateKey.trim()) + builder.parseAddresses(this.addresses.trim()) + if (this.dnsServers.isNotEmpty()) { + builder.parseDnsServers(this.dnsServers.trim()) + } + if (this.mtu.isNotEmpty()) { + builder.parseMtu(this.mtu.trim()) + } + if (this.listenPort.isNotEmpty()) { + builder.parseListenPort(this.listenPort.trim()) + } + if (isAllApplicationsEnabled()) emptyCheckedPackagesList() + if (_uiState.value.include) { + builder.includeApplications( + _uiState.value.checkedPackageNames, + ) + } + if (!_uiState.value.include) { + builder.excludeApplications( + _uiState.value.checkedPackageNames, + ) + } } + return builder.build() } private fun buildAmInterfaceListFromProxyInterface(): org.amnezia.awg.config.Interface { val builder = org.amnezia.awg.config.Interface.Builder() - builder.parsePrivateKey(_uiState.value.interfaceProxy.privateKey.trim()) - builder.parseAddresses(_uiState.value.interfaceProxy.addresses.trim()) - if (_uiState.value.interfaceProxy.dnsServers.isNotEmpty()) { - builder.parseDnsServers(_uiState.value.interfaceProxy.dnsServers.trim()) - } - if (_uiState.value.interfaceProxy.mtu.isNotEmpty()) { - builder.parseMtu(_uiState.value.interfaceProxy.mtu.trim()) - } - if (_uiState.value.interfaceProxy.listenPort.isNotEmpty()) { - builder.parseListenPort(_uiState.value.interfaceProxy.listenPort.trim()) - } - if (isAllApplicationsEnabled()) emptyCheckedPackagesList() - if (_uiState.value.include) { - builder.includeApplications( - _uiState.value.checkedPackageNames, - ) - } - if (!_uiState.value.include) { - builder.excludeApplications( - _uiState.value.checkedPackageNames, - ) - } - if (_uiState.value.interfaceProxy.junkPacketCount.isNotEmpty()) { - builder.setJunkPacketCount( - _uiState.value.interfaceProxy.junkPacketCount.trim().toInt(), - ) - } - if (_uiState.value.interfaceProxy.junkPacketMinSize.isNotEmpty()) { - builder.setJunkPacketMinSize( - _uiState.value.interfaceProxy.junkPacketMinSize.trim().toInt(), - ) - } - if (_uiState.value.interfaceProxy.junkPacketMaxSize.isNotEmpty()) { - builder.setJunkPacketMaxSize( - _uiState.value.interfaceProxy.junkPacketMaxSize.trim().toInt(), - ) - } - if (_uiState.value.interfaceProxy.initPacketJunkSize.isNotEmpty()) { - builder.setInitPacketJunkSize( - _uiState.value.interfaceProxy.initPacketJunkSize.trim().toInt(), - ) - } - if (_uiState.value.interfaceProxy.responsePacketJunkSize.isNotEmpty()) { - builder.setResponsePacketJunkSize( - _uiState.value.interfaceProxy.responsePacketJunkSize.trim().toInt(), - ) - } - if (_uiState.value.interfaceProxy.initPacketMagicHeader.isNotEmpty()) { - builder.setInitPacketMagicHeader( - _uiState.value.interfaceProxy.initPacketMagicHeader.trim().toLong(), - ) - } - if (_uiState.value.interfaceProxy.responsePacketMagicHeader.isNotEmpty()) { - builder.setResponsePacketMagicHeader( - _uiState.value.interfaceProxy.responsePacketMagicHeader.trim().toLong(), - ) - } - if (_uiState.value.interfaceProxy.transportPacketMagicHeader.isNotEmpty()) { - builder.setTransportPacketMagicHeader( - _uiState.value.interfaceProxy.transportPacketMagicHeader.trim().toLong(), - ) - } - if (_uiState.value.interfaceProxy.underloadPacketMagicHeader.isNotEmpty()) { - builder.setUnderloadPacketMagicHeader( - _uiState.value.interfaceProxy.underloadPacketMagicHeader.trim().toLong(), - ) + with(_uiState.value.interfaceProxy) { + builder.parsePrivateKey(this.privateKey.trim()) + builder.parseAddresses(this.addresses.trim()) + if (this.dnsServers.isNotEmpty()) { + builder.parseDnsServers(this.dnsServers.trim()) + } + if (this.mtu.isNotEmpty()) { + builder.parseMtu(this.mtu.trim()) + } + if (this.listenPort.isNotEmpty()) { + builder.parseListenPort(this.listenPort.trim()) + } + if (isAllApplicationsEnabled()) emptyCheckedPackagesList() + if (_uiState.value.include) { + builder.includeApplications( + _uiState.value.checkedPackageNames, + ) + } + if (!_uiState.value.include) { + builder.excludeApplications( + _uiState.value.checkedPackageNames, + ) + } + if (this.junkPacketCount.isNotEmpty()) { + builder.setJunkPacketCount( + this.junkPacketCount.trim().toInt(), + ) + } + if (this.junkPacketMinSize.isNotEmpty()) { + builder.setJunkPacketMinSize( + this.junkPacketMinSize.trim().toInt(), + ) + } + if (this.junkPacketMaxSize.isNotEmpty()) { + builder.setJunkPacketMaxSize( + this.junkPacketMaxSize.trim().toInt(), + ) + } + if (this.initPacketJunkSize.isNotEmpty()) { + builder.setInitPacketJunkSize( + this.initPacketJunkSize.trim().toInt(), + ) + } + if (this.responsePacketJunkSize.isNotEmpty()) { + builder.setResponsePacketJunkSize( + this.responsePacketJunkSize.trim().toInt(), + ) + } + if (this.initPacketMagicHeader.isNotEmpty()) { + builder.setInitPacketMagicHeader( + this.initPacketMagicHeader.trim().toLong(), + ) + } + if (this.responsePacketMagicHeader.isNotEmpty()) { + builder.setResponsePacketMagicHeader( + this.responsePacketMagicHeader.trim().toLong(), + ) + } + if (this.transportPacketMagicHeader.isNotEmpty()) { + builder.setTransportPacketMagicHeader( + this.transportPacketMagicHeader.trim().toLong(), + ) + } + if (this.underloadPacketMagicHeader.isNotEmpty()) { + builder.setUnderloadPacketMagicHeader( + this.underloadPacketMagicHeader.trim().toLong(), + ) + } } + return builder.build() } @@ -291,41 +282,32 @@ constructor( .build() } - fun onSaveAllChanges(configType: ConfigType): Result { - return try { + fun onSaveAllChanges() = viewModelScope.launch { + kotlin.runCatching { val wgQuick = buildConfig().toWgQuickString(true) - val amQuick = - if (configType == ConfigType.AMNEZIA) { - buildAmConfig().toAwgQuickString(true) - } else { - TunnelConfig.AM_QUICK_DEFAULT - } - val tunnelConfig = - when (uiState.value.tunnel) { - null -> - TunnelConfig( - name = _uiState.value.tunnelName, - wgQuick = wgQuick, - amQuick = amQuick, - ) - - else -> - uiState.value.tunnel!!.copy( - name = _uiState.value.tunnelName, - wgQuick = wgQuick, - amQuick = amQuick, - ) - } + val amQuick = buildAmConfig().toAwgQuickString(true) + val tunnelConfig = uiState.value.tunnel?.copy( + name = _uiState.value.tunnelName, + amQuick = amQuick, + wgQuick = wgQuick, + ) ?: TunnelConfig( + name = _uiState.value.tunnelName, + wgQuick = wgQuick, + amQuick = amQuick, + ) updateTunnelConfig(tunnelConfig) - Result.success(Unit) - } catch (e: Exception) { - Timber.e(e) - val message = e.message?.substringAfter(":", missingDelimiterValue = "") + SnackbarController.showMessage( + StringValue.StringResource(R.string.config_changes_saved), + ) + navController.navigate(Screen.Main.route) + }.onFailure { + Timber.e(it) + val message = it.message?.substringAfter(":", missingDelimiterValue = "") val stringValue = message?.let { StringValue.DynamicString(message) } ?: StringValue.StringResource(R.string.unknown_error) - Result.failure(WgTunnelExceptions.ConfigParseError(stringValue)) + SnackbarController.showMessage(stringValue) } } @@ -408,7 +390,7 @@ constructor( _uiState.update { it.copy( interfaceProxy = - _uiState.value.interfaceProxy.copy( + it.interfaceProxy.copy( privateKey = keyPair.privateKey.toBase64(), publicKey = keyPair.publicKey.toBase64(), ), @@ -419,7 +401,7 @@ constructor( fun onAddressesChanged(value: String) { _uiState.update { it.copy( - interfaceProxy = _uiState.value.interfaceProxy.copy(addresses = value), + interfaceProxy = it.interfaceProxy.copy(addresses = value), ) } } @@ -427,7 +409,7 @@ constructor( fun onListenPortChanged(value: String) { _uiState.update { it.copy( - interfaceProxy = _uiState.value.interfaceProxy.copy(listenPort = value), + interfaceProxy = it.interfaceProxy.copy(listenPort = value), ) } } @@ -435,21 +417,21 @@ constructor( fun onDnsServersChanged(value: String) { _uiState.update { it.copy( - interfaceProxy = _uiState.value.interfaceProxy.copy(dnsServers = value), + interfaceProxy = it.interfaceProxy.copy(dnsServers = value), ) } } fun onMtuChanged(value: String) { _uiState.update { - it.copy(interfaceProxy = _uiState.value.interfaceProxy.copy(mtu = value)) + it.copy(interfaceProxy = it.interfaceProxy.copy(mtu = value)) } } private fun onInterfacePublicKeyChange(value: String) { _uiState.update { it.copy( - interfaceProxy = _uiState.value.interfaceProxy.copy(publicKey = value), + interfaceProxy = it.interfaceProxy.copy(publicKey = value), ) } } @@ -457,7 +439,7 @@ constructor( fun onPrivateKeyChange(value: String) { _uiState.update { it.copy( - interfaceProxy = _uiState.value.interfaceProxy.copy(privateKey = value), + interfaceProxy = it.interfaceProxy.copy(privateKey = value), ) } if (NumberUtils.isValidKey(value)) { @@ -479,7 +461,7 @@ constructor( fun onJunkPacketCountChanged(value: String) { _uiState.update { it.copy( - interfaceProxy = _uiState.value.interfaceProxy.copy(junkPacketCount = value), + interfaceProxy = it.interfaceProxy.copy(junkPacketCount = value), ) } } @@ -487,7 +469,7 @@ constructor( fun onJunkPacketMinSizeChanged(value: String) { _uiState.update { it.copy( - interfaceProxy = _uiState.value.interfaceProxy.copy(junkPacketMinSize = value), + interfaceProxy = it.interfaceProxy.copy(junkPacketMinSize = value), ) } } @@ -495,7 +477,7 @@ constructor( fun onJunkPacketMaxSizeChanged(value: String) { _uiState.update { it.copy( - interfaceProxy = _uiState.value.interfaceProxy.copy(junkPacketMaxSize = value), + interfaceProxy = it.interfaceProxy.copy(junkPacketMaxSize = value), ) } } @@ -503,7 +485,7 @@ constructor( fun onInitPacketJunkSizeChanged(value: String) { _uiState.update { it.copy( - interfaceProxy = _uiState.value.interfaceProxy.copy(initPacketJunkSize = value), + interfaceProxy = it.interfaceProxy.copy(initPacketJunkSize = value), ) } } @@ -512,7 +494,7 @@ constructor( _uiState.update { it.copy( interfaceProxy = - _uiState.value.interfaceProxy.copy( + it.interfaceProxy.copy( responsePacketJunkSize = value, ), ) @@ -523,7 +505,7 @@ constructor( _uiState.update { it.copy( interfaceProxy = - _uiState.value.interfaceProxy.copy( + it.interfaceProxy.copy( initPacketMagicHeader = value, ), ) @@ -534,7 +516,7 @@ constructor( _uiState.update { it.copy( interfaceProxy = - _uiState.value.interfaceProxy.copy( + it.interfaceProxy.copy( responsePacketMagicHeader = value, ), ) @@ -545,7 +527,7 @@ constructor( _uiState.update { it.copy( interfaceProxy = - _uiState.value.interfaceProxy.copy( + it.interfaceProxy.copy( transportPacketMagicHeader = value, ), ) @@ -556,10 +538,15 @@ constructor( _uiState.update { it.copy( interfaceProxy = - _uiState.value.interfaceProxy.copy( + it.interfaceProxy.copy( underloadPacketMagicHeader = value, ), ) } } + + @AssistedFactory + interface ConfigViewModelFactory { + fun create(id: Int): ConfigViewModel + } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt index 5a5cec2..047bf5d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.overscroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.rounded.Bolt import androidx.compose.material.icons.rounded.Circle import androidx.compose.material.icons.rounded.CopyAll @@ -29,6 +30,7 @@ import androidx.compose.material.icons.rounded.Star import androidx.compose.material3.FabPosition import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.Text @@ -73,20 +75,18 @@ import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberFileImportLauncherForResult import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.GettingStartedLabel -import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissMultiFab +import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissFab import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelImportSheet import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.VpnDeniedDialog import com.zaneschepke.wireguardautotunnel.ui.theme.corn import com.zaneschepke.wireguardautotunnel.ui.theme.mint import com.zaneschepke.wireguardautotunnel.util.Constants -import com.zaneschepke.wireguardautotunnel.util.extensions.getMessage import com.zaneschepke.wireguardautotunnel.util.extensions.handshakeStatus import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv import com.zaneschepke.wireguardautotunnel.util.extensions.mapPeerStats import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import timber.log.Timber @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @@ -99,7 +99,6 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, val scope = rememberCoroutineScope() var showBottomSheet by remember { mutableStateOf(false) } - var configType by remember { mutableStateOf(ConfigType.WIREGUARD) } var showVpnPermissionDialog by remember { mutableStateOf(false) } val isVisible = rememberSaveable { mutableStateOf(true) } var showDeleteTunnelAlertDialog by remember { mutableStateOf(false) } @@ -152,11 +151,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, context.getString(R.string.error_no_file_explorer), ) }, onData = { data -> - scope.launch { - viewModel.onTunnelFileSelected(data, configType, context).onFailure { - snackbar.showMessage(it.getMessage(context)) - } - } + viewModel.onTunnelFileSelected(data, context) }) val scanLauncher = @@ -164,11 +159,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, contract = ScanContract(), onResult = { if (it.contents != null) { - scope.launch { - viewModel.onTunnelQrResult(it.contents, configType).onFailure { error -> - snackbar.showMessage(error.getMessage(context)) - } - } + viewModel.onTunnelQrResult(it.contents) } }, ) @@ -227,9 +218,15 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, }, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { - ScrollDismissMultiFab(R.drawable.add, focusRequester, isVisible = isVisible.value, onFabItemClicked = { + ScrollDismissFab(icon = { + val icon = Icons.Filled.Add + Icon( + imageVector = icon, + contentDescription = icon.name, + tint = MaterialTheme.colorScheme.onPrimary, + ) + }, focusRequester, isVisible = isVisible.value, onClick = { showBottomSheet = true - configType = ConfigType.valueOf(it.value) }) }, ) { @@ -240,7 +237,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, onQrClick = { launchQrScanner() }, onManualImportClick = { navController.navigate( - "${Screen.Config.route}/${Constants.MANUAL_TUNNEL_CONFIG_ID}?configType=$configType", + "${Screen.Config.route}/${Constants.MANUAL_TUNNEL_CONFIG_ID}", ) }, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt index e6bd8fb..fcab195 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt @@ -6,16 +6,19 @@ import android.net.Uri import android.provider.OpenableColumns import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.wireguard.config.Config +import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.data.domain.Settings import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService +import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.util.Constants +import com.zaneschepke.wireguardautotunnel.util.FileReadException +import com.zaneschepke.wireguardautotunnel.util.InvalidFileExtensionException import com.zaneschepke.wireguardautotunnel.util.NumberUtils -import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions +import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher @@ -70,32 +73,17 @@ constructor( tunnelService.stopTunnel(tunnel) } - private fun validateConfigString(config: String, configType: ConfigType) { - when (configType) { - ConfigType.AMNEZIA -> TunnelConfig.configFromAmQuick(config) - ConfigType.WIREGUARD -> TunnelConfig.configFromWgQuick(config) - } - } - - private fun generateQrCodeDefaultName(config: String, configType: ConfigType): String { + private fun generateQrCodeDefaultName(config: String): String { return try { - when (configType) { - ConfigType.AMNEZIA -> { - TunnelConfig.configFromAmQuick(config).peers[0].endpoint.get().host - } - - ConfigType.WIREGUARD -> { - TunnelConfig.configFromWgQuick(config).peers[0].endpoint.get().host - } - } + TunnelConfig.configFromAmQuick(config).peers[0].endpoint.get().host } catch (e: Exception) { Timber.e(e) NumberUtils.generateRandomTunnelName() } } - private fun generateQrCodeTunnelName(config: String, configType: ConfigType): String { - var defaultName = generateQrCodeDefaultName(config, configType) + private fun generateQrCodeTunnelName(config: String): String { + var defaultName = generateQrCodeDefaultName(config) val lines = config.lines().toMutableList() val linesIterator = lines.iterator() while (linesIterator.hasNext()) { @@ -108,37 +96,18 @@ constructor( return defaultName } - suspend fun onTunnelQrResult(result: String, configType: ConfigType): Result { - return withContext(ioDispatcher) { - try { - validateConfigString(result, configType) - val tunnelName = - makeTunnelNameUnique(generateQrCodeTunnelName(result, configType)) - val tunnelConfig = - when (configType) { - ConfigType.AMNEZIA -> { - TunnelConfig( - name = tunnelName, - amQuick = result, - wgQuick = - TunnelConfig.configFromAmQuick( - result, - ).toWgQuickString(), - ) - } + fun onTunnelQrResult(result: String) = viewModelScope.launch(ioDispatcher) { + kotlin.runCatching { + val amConfig = TunnelConfig.configFromAmQuick(result) + val amQuick = amConfig.toAwgQuickString(true) + val wgQuick = amConfig.toWgQuickString() - ConfigType.WIREGUARD -> - TunnelConfig( - name = tunnelName, - wgQuick = result, - ) - } - addTunnel(tunnelConfig) - Result.success(Unit) - } catch (e: Exception) { - Timber.e(e) - Result.failure(WgTunnelExceptions.InvalidQrCode()) - } + val tunnelName = makeTunnelNameUnique(generateQrCodeTunnelName(result)) + val tunnelConfig = TunnelConfig(name = tunnelName, wgQuick = wgQuick, amQuick = amQuick) + saveTunnel(tunnelConfig) + }.onFailure { + Timber.e(it) + SnackbarController.showMessage(StringValue.StringResource(R.string.error_invalid_code)) } } @@ -155,130 +124,70 @@ constructor( } } - private fun saveTunnelConfigFromStream(stream: InputStream, fileName: String, type: ConfigType) { - var amQuick: String? = null - val wgQuick = - stream.use { - when (type) { - ConfigType.AMNEZIA -> { - val config = org.amnezia.awg.config.Config.parse(it) - amQuick = config.toAwgQuickString(true) - config.toWgQuickString() - } - - ConfigType.WIREGUARD -> { - Config.parse(it).toWgQuickString(true) - } - } - } - viewModelScope.launch { - val tunnelName = makeTunnelNameUnique(getNameFromFileName(fileName)) - addTunnel( - TunnelConfig( - name = tunnelName, - wgQuick = wgQuick, - amQuick = amQuick ?: TunnelConfig.AM_QUICK_DEFAULT, - ), - ) - } + private suspend fun saveTunnelConfigFromStream(stream: InputStream, fileName: String) { + val amConfig = stream.use { org.amnezia.awg.config.Config.parse(it) } + val tunnelName = makeTunnelNameUnique(getNameFromFileName(fileName)) + saveTunnel( + TunnelConfig( + name = tunnelName, + wgQuick = amConfig.toWgQuickString(), + amQuick = amConfig.toAwgQuickString(true), + ), + ) } private fun getInputStreamFromUri(uri: Uri, context: Context): InputStream? { return context.applicationContext.contentResolver.openInputStream(uri) } - suspend fun onTunnelFileSelected(uri: Uri, configType: ConfigType, context: Context): Result { - return withContext(ioDispatcher) { - try { - if (isValidUriContentScheme(uri)) { - val fileName = getFileName(context, uri) - return@withContext when (getFileExtensionFromFileName(fileName)) { - Constants.CONF_FILE_EXTENSION -> - saveTunnelFromConfUri(fileName, uri, configType, context) - - Constants.ZIP_FILE_EXTENSION -> - saveTunnelsFromZipUri( - uri, - configType, - context, - ) - - else -> Result.failure(WgTunnelExceptions.InvalidFileExtension()) - } - } else { - Result.failure(WgTunnelExceptions.InvalidFileExtension()) - } - } catch (e: Exception) { - Timber.e(e) - Result.failure(WgTunnelExceptions.FileReadFailed()) + fun onTunnelFileSelected(uri: Uri, context: Context) = viewModelScope.launch(ioDispatcher) { + kotlin.runCatching { + if (!isValidUriContentScheme(uri)) throw InvalidFileExtensionException + val fileName = getFileName(context, uri) + when (getFileExtensionFromFileName(fileName)) { + Constants.CONF_FILE_EXTENSION -> + saveTunnelFromConfUri(fileName, uri, context) + Constants.ZIP_FILE_EXTENSION -> + saveTunnelsFromZipUri( + uri, + context, + ) + else -> throw InvalidFileExtensionException } - } - } - - private suspend fun saveTunnelsFromZipUri(uri: Uri, configType: ConfigType, context: Context): Result { - return withContext(ioDispatcher) { - ZipInputStream(getInputStreamFromUri(uri, context)).use { zip -> - generateSequence { zip.nextEntry } - .filterNot { - it.isDirectory || - getFileExtensionFromFileName(it.name) != Constants.CONF_FILE_EXTENSION - } - .forEach { - val name = getNameFromFileName(it.name) - withContext(viewModelScope.coroutineContext) { - try { - var amQuick: String? = null - val wgQuick = - when (configType) { - ConfigType.AMNEZIA -> { - val config = - org.amnezia.awg.config.Config.parse( - zip, - ) - amQuick = config.toAwgQuickString(true) - config.toWgQuickString() - } - - ConfigType.WIREGUARD -> { - Config.parse(zip).toWgQuickString(true) - } - } - addTunnel( - TunnelConfig( - name = makeTunnelNameUnique(name), - wgQuick = wgQuick, - amQuick = amQuick ?: TunnelConfig.AM_QUICK_DEFAULT, - ), - ) - Result.success(Unit) - } catch (e: Exception) { - Result.failure(WgTunnelExceptions.FileReadFailed()) - } - } - } - Result.success(Unit) - } - } - } - - private suspend fun saveTunnelFromConfUri(name: String, uri: Uri, configType: ConfigType, context: Context): Result { - return withContext(ioDispatcher) { - val stream = getInputStreamFromUri(uri, context) - return@withContext if (stream != null) { - try { - saveTunnelConfigFromStream(stream, name, configType) - } catch (e: Exception) { - return@withContext Result.failure(WgTunnelExceptions.ConfigParseError()) - } - Result.success(Unit) + }.onFailure { + Timber.e(it) + if (it is InvalidFileExtensionException) { + SnackbarController.showMessage(StringValue.StringResource(R.string.error_file_extension)) } else { - Result.failure(WgTunnelExceptions.FileReadFailed()) + SnackbarController.showMessage(StringValue.StringResource(R.string.error_file_format)) } } } - private fun addTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch { - saveTunnel(tunnelConfig) + private suspend fun saveTunnelsFromZipUri(uri: Uri, context: Context) { + ZipInputStream(getInputStreamFromUri(uri, context)).use { zip -> + generateSequence { zip.nextEntry } + .filterNot { + it.isDirectory || + getFileExtensionFromFileName(it.name) != Constants.CONF_FILE_EXTENSION + } + .forEach { entry -> + val name = getNameFromFileName(entry.name) + val amConf = org.amnezia.awg.config.Config.parse(zip.bufferedReader()) + saveTunnel( + TunnelConfig( + name = makeTunnelNameUnique(name), + wgQuick = amConf.toWgQuickString(), + amQuick = amConf.toAwgQuickString(true), + ), + ) + } + } + } + + private suspend fun saveTunnelFromConfUri(name: String, uri: Uri, context: Context) { + val stream = getInputStreamFromUri(uri, context) ?: throw FileReadException + saveTunnelConfigFromStream(stream, name) } fun pauseAutoTunneling() = viewModelScope.launch { @@ -300,32 +209,23 @@ constructor( } private fun getFileNameByCursor(context: Context, uri: Uri): String? { - context.contentResolver.query(uri, null, null, null, null)?.use { - return getDisplayNameByCursor(it) + return context.contentResolver.query(uri, null, null, null, null)?.use { + getDisplayNameByCursor(it) } - return null } private fun getDisplayNameColumnIndex(cursor: Cursor): Int? { val columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) - return if (columnIndex != -1) { - return columnIndex - } else { - null - } + if (columnIndex == -1) return null + return columnIndex } private fun getDisplayNameByCursor(cursor: Cursor): String? { - return if (cursor.moveToFirst()) { - val index = getDisplayNameColumnIndex(cursor) - if (index != null) { - cursor.getString(index) - } else { - null - } - } else { - null - } + val move = cursor.moveToFirst() + if (!move) return null + val index = getDisplayNameColumnIndex(cursor) + if (index == null) return index + return cursor.getString(index) } private fun isValidUriContentScheme(uri: Uri): Boolean { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/components/ScrollDismissMultiFab.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/components/ScrollDismissMultiFab.kt index 6a1cbb0..fb4706a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/components/ScrollDismissMultiFab.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/components/ScrollDismissMultiFab.kt @@ -1,41 +1,20 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.main.components -import androidx.annotation.DrawableRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.focusGroup -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.iamageo.multifablibrary.FabIcon -import com.iamageo.multifablibrary.FabOption -import com.iamageo.multifablibrary.MultiFabItem -import com.iamageo.multifablibrary.MultiFloatingActionButton -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType @Composable -fun ScrollDismissMultiFab( - @DrawableRes res: Int, - focusRequester: FocusRequester, - isVisible: Boolean, - onFabItemClicked: (fabItem: MultiFabItem) -> Unit, -) { - // Nested scroll for control FAB - - val context = LocalContext.current - +fun ScrollDismissFab(icon: @Composable () -> Unit, focusRequester: FocusRequester, isVisible: Boolean, onClick: () -> Unit) { AnimatedVisibility( visible = isVisible, enter = slideInVertically(initialOffsetY = { it * 2 }), @@ -45,64 +24,14 @@ fun ScrollDismissMultiFab( .focusRequester(focusRequester) .focusGroup(), ) { - val fobColor = MaterialTheme.colorScheme.secondary - val fobIconColor = MaterialTheme.colorScheme.background - MultiFloatingActionButton( - fabIcon = - FabIcon( - iconRes = res, - iconResAfterRotate = R.drawable.close, - iconRotate = 180f, - ), - fabOption = - FabOption( - iconTint = fobIconColor, - backgroundTint = fobColor, - ), - itemsMultiFab = - listOf( - MultiFabItem( - label = { - Text( - stringResource(id = R.string.amnezia), - color = MaterialTheme.colorScheme.onBackground, - textAlign = TextAlign.Center, - modifier = Modifier.padding(end = 10.dp), - ) - }, - modifier = - Modifier - .size(40.dp), - icon = res, - value = ConfigType.AMNEZIA.name, - miniFabOption = - FabOption( - backgroundTint = fobColor, - fobIconColor, - ), - ), - MultiFabItem( - label = { - Text( - stringResource(id = R.string.wireguard), - color = MaterialTheme.colorScheme.onBackground, - textAlign = TextAlign.Center, - modifier = Modifier.padding(end = 10.dp), - ) - }, - icon = res, - value = ConfigType.WIREGUARD.name, - miniFabOption = - FabOption( - backgroundTint = fobColor, - fobIconColor, - ), - ), - ), - onFabItemClicked = { - onFabItemClicked(it) + FloatingActionButton( + onClick = { + onClick() }, shape = RoundedCornerShape(16.dp), - ) + containerColor = MaterialTheme.colorScheme.primary, + ) { + icon() + } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt index f83e1b6..b9b8d80 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.outlined.Add import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -54,8 +55,7 @@ import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationTextBox import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle -import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType -import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissMultiFab +import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissFab import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Address @@ -103,10 +103,16 @@ fun OptionsScreen( Scaffold( floatingActionButton = { - ScrollDismissMultiFab(R.drawable.edit, focusRequester, isVisible = true, onFabItemClicked = { - val configType = ConfigType.valueOf(it.value) + ScrollDismissFab(icon = { + val icon = Icons.Filled.Edit + Icon( + imageVector = icon, + contentDescription = icon.name, + tint = MaterialTheme.colorScheme.onPrimary, + ) + }, focusRequester, isVisible = true, onClick = { navController.navigate( - "${Screen.Config.route}/${config.id}?configType=${configType.name}", + "${Screen.Config.route}/${config.id}", ) }) }, @@ -279,10 +285,10 @@ fun OptionsScreen( stringResource(R.string.set_custom_ping_ip), stringResource(R.string.default_ping_ip), focusRequester, - isErrorValue = { !(it?.isValidIpv4orIpv6Address() ?: true) }, + isErrorValue = { !it.isNullOrBlank() && !it.isValidIpv4orIpv6Address() }, onSubmit = { optionsViewModel.saveTunnelChanges( - config.copy(pingIp = it), + config.copy(pingIp = it.ifBlank { null }), ) }, ) @@ -301,7 +307,7 @@ fun OptionsScreen( isErrorValue = ::isSecondsError, onSubmit = { optionsViewModel.saveTunnelChanges( - config.copy(pingInterval = it.toLong() * 1000), + config.copy(pingInterval = if (it.isBlank()) null else it.toLong() * 1000), ) }, ) @@ -316,7 +322,7 @@ fun OptionsScreen( isErrorValue = ::isSecondsError, onSubmit = { optionsViewModel.saveTunnelChanges( - config.copy(pingCooldown = it.toLong() * 1000), + config.copy(pingCooldown = if (it.isBlank()) null else it.toLong() * 1000), ) }, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt index af73f52..688f3e4 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt @@ -16,7 +16,6 @@ object Constants { const val ZIP_FILE_MIME_TYPE = "application/zip" const val GOOGLE_TV_EXPLORER_STUB = "com.google.android.tv.frameworkpackagestubs" const val ANDROID_TV_EXPLORER_STUB = "com.android.tv.frameworkpackagestubs" - const val ALWAYS_ON_VPN_ACTION = "android.net.VpnService" const val VPN_SETTINGS_PACKAGE = "android.net.vpn.SETTINGS" const val EMAIL_MIME_TYPE = "plain/text" const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1024 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Exceptions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Exceptions.kt new file mode 100644 index 0000000..a083f05 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Exceptions.kt @@ -0,0 +1,13 @@ +package com.zaneschepke.wireguardautotunnel.util + +object InvalidFileExtensionException : Exception() { + private fun readResolve(): Any = InvalidFileExtensionException +} + +object FileReadException : Exception() { + private fun readResolve(): Any = FileReadException +} + +object ConfigExportException : Exception() { + private fun readResolve(): Any = ConfigExportException +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt index 23fdc38..5a35a05 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt @@ -118,7 +118,7 @@ class FileUtils( } } catch (e: Exception) { Timber.e(e) - Result.failure(WgTunnelExceptions.ConfigExportFailed()) + Result.failure(ConfigExportException) } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/WgTunnelExceptions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/WgTunnelExceptions.kt deleted file mode 100644 index dd3dee8..0000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/WgTunnelExceptions.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.util - -import android.content.Context -import com.zaneschepke.wireguardautotunnel.R - -sealed class WgTunnelExceptions : Exception() { - abstract fun getMessage(context: Context): String - - data class ConfigExportFailed( - private val userMessage: StringValue = - StringValue.StringResource( - R.string.export_configs_failed, - ), - ) : WgTunnelExceptions() { - override fun getMessage(context: Context): String { - return userMessage.asString(context) - } - } - - data class ConfigParseError(private val appendMessage: StringValue = StringValue.Empty) : - WgTunnelExceptions() { - override fun getMessage(context: Context): String { - return StringValue.StringResource(R.string.config_parse_error).asString(context) + ( - if (appendMessage != StringValue.Empty) ": ${appendMessage.asString(context)}" else "" - ) - } - } - - data class InvalidQrCode( - private val userMessage: StringValue = - StringValue.StringResource( - R.string.error_invalid_code, - ), - ) : - WgTunnelExceptions() { - override fun getMessage(context: Context): String { - return userMessage.asString(context) - } - } - - data class InvalidFileExtension( - private val userMessage: StringValue = - StringValue.StringResource( - R.string.error_file_extension, - ), - ) : WgTunnelExceptions() { - override fun getMessage(context: Context): String { - return userMessage.asString(context) - } - } - - data class FileReadFailed( - private val userMessage: StringValue = - StringValue.StringResource( - R.string.error_file_format, - ), - ) : - WgTunnelExceptions() { - override fun getMessage(context: Context): String { - return userMessage.asString(context) - } - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/Extensions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/Extensions.kt index bde4156..0c4db8a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/Extensions.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/Extensions.kt @@ -1,11 +1,7 @@ package com.zaneschepke.wireguardautotunnel.util.extensions -import android.content.Context import android.content.pm.PackageInfo -import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig -import com.zaneschepke.wireguardautotunnel.util.StringValue -import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions import java.math.BigDecimal import java.text.DecimalFormat @@ -21,10 +17,3 @@ fun List.removeAt(index: Int): List = toMutableList().apply { this.rem typealias TunnelConfigs = List typealias Packages = List - -fun Throwable.getMessage(context: Context): String { - return when (this) { - is WgTunnelExceptions -> this.getMessage(context) - else -> this.message ?: StringValue.StringResource(R.string.unknown_error).asString(context) - } -} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5cd3497..3122c5e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -194,4 +194,5 @@ Ping restart cooldown (sec) Learn about supported wildcards. details + Show Amnezia properties diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index be5bd24..66f5548 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,6 @@ junit = "4.13.2" kotlinx-serialization-json = "1.7.2" lifecycle-runtime-compose = "2.8.6" material3 = "1.3.0" -multifabVersion = "1.1.1" navigationCompose = "2.8.1" pinLockCompose = "1.0.3" roomVersion = "2.6.1" @@ -90,7 +89,6 @@ pin-lock-compose = { module = "com.zaneschepke:pin_lock_compose", version.ref = timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } tunnel = { module = "com.zaneschepke:wireguard-android", version.ref = "tunnel" } -zaneschepke-multifab = { module = "com.zaneschepke:multifab", version.ref = "multifabVersion" } zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxingAndroidEmbedded" } material = { group = "com.google.android.material", name = "material", version.ref = "material" }