feat: encrypted backup

Fixes bug where sometimes restore of backup can fail
Adds support for encrypted backups with better error messages
This commit is contained in:
zaneschepke
2026-06-16 23:09:54 -04:00
parent 6e3c1324b2
commit 379ffdcbbf
37 changed files with 306 additions and 90 deletions
@@ -129,6 +129,7 @@ import com.zaneschepke.wireguardautotunnel.ui.theme.OffWhite
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
import com.zaneschepke.wireguardautotunnel.util.FileUtils
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
import com.zaneschepke.wireguardautotunnel.util.StringValue
import com.zaneschepke.wireguardautotunnel.util.extensions.installApk
@@ -139,6 +140,10 @@ import com.zaneschepke.wireguardautotunnel.viewmodel.ConfigEditViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.SharedAppViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.SplitTunnelViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelViewModel
import de.raphaelebner.roomdatabasebackup.core.OnCompleteListener.Companion.EXIT_CODE_ERROR
import de.raphaelebner.roomdatabasebackup.core.OnCompleteListener.Companion.EXIT_CODE_ERROR_DECRYPTION_ERROR
import de.raphaelebner.roomdatabasebackup.core.OnCompleteListener.Companion.EXIT_CODE_ERROR_RESTORE_BACKUP_IS_ENCRYPTED
import de.raphaelebner.roomdatabasebackup.core.OnCompleteListener.Companion.EXIT_CODE_ERROR_WRONG_DECRYPTION_PASSWORD
import de.raphaelebner.roomdatabasebackup.core.RoomBackup
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.awaitCancellation
@@ -150,6 +155,7 @@ import org.koin.androidx.compose.koinViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import org.orbitmvi.orbit.compose.collectAsState
import timber.log.Timber
import xyz.teamgravity.pin_lock_compose.PinManager
class MainActivity : AppCompatActivity() {
@@ -173,9 +179,9 @@ class MainActivity : AppCompatActivity() {
}
super.onCreate(savedInstanceState)
handleIncomingIntent(intent)
roomBackup = RoomBackup(this).database(appDatabase).enableLogDebug(true).maxFileCount(5)
roomBackup = RoomBackup(this)
handleIncomingIntent(intent)
installSplashScreen().apply {
setKeepOnScreenCondition { !viewModel.container.stateFlow.value.isAppLoaded }
@@ -591,62 +597,76 @@ class MainActivity : AppCompatActivity() {
}
}
fun performBackup() = lifecycleScope.launch {
fun performBackup(encrypt: Boolean = false, password: String? = null) {
roomBackup
.database(appDatabase)
.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_CUSTOM_DIALOG)
.enableLogDebug(true)
.maxFileCount(5)
.apply {
onCompleteListener { success, _, _ ->
lifecycleScope.launch {
val sideEffect =
if (success) {
GlobalSideEffect.Snackbar(
StringValue.StringResource(
R.string.backup_success,
getString(R.string.restarting_app),
),
ToastType.Success,
)
} else {
GlobalSideEffect.Snackbar(
StringValue.StringResource(R.string.backup_failed),
ToastType.Error,
)
}
viewModel.postSideEffect(sideEffect)
}
if (encrypt && !password.isNullOrBlank()) {
backupIsEncrypted(true)
customEncryptPassword(password)
}
}
.onCompleteListener { success, _, _ ->
lifecycleScope.launch {
val sideEffect =
if (success) {
GlobalSideEffect.Snackbar(
StringValue.StringResource(R.string.backup_success),
ToastType.Success,
)
} else {
GlobalSideEffect.Snackbar(
StringValue.StringResource(R.string.backup_failed),
ToastType.Error,
)
}
viewModel.postSideEffect(sideEffect)
}
}
.backup()
}
fun performRestore() = lifecycleScope.launch {
fun performRestore(encrypt: Boolean = false, password: String? = null) {
roomBackup
.database(appDatabase)
.enableLogDebug(true)
.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_CUSTOM_DIALOG)
.apply {
onCompleteListener { success, _, _ ->
lifecycleScope.launch {
val sideEffect =
if (success) {
GlobalSideEffect.Snackbar(
StringValue.StringResource(
R.string.restore_success,
getString(R.string.restarting_app),
),
ToastType.Success,
)
} else {
GlobalSideEffect.Snackbar(
StringValue.StringResource(R.string.restore_failed),
ToastType.Error,
)
if (encrypt && !password.isNullOrBlank()) {
backupIsEncrypted(true)
customEncryptPassword(password)
}
}
.onCompleteListener { success, message, exitCode ->
lifecycleScope.launch {
if (success) {
viewModel.postSideEffect(
GlobalSideEffect.Snackbar(
StringValue.StringResource(R.string.restore_success),
ToastType.Success,
)
)
roomBackup.restartApp(Intent(this@MainActivity, MainActivity::class.java))
} else {
Timber.w("Restore failed, exitCode=$exitCode, message=$message")
val errorMessage =
when (exitCode) {
EXIT_CODE_ERROR_WRONG_DECRYPTION_PASSWORD ->
getString(R.string.restore_failed_wrong_password)
EXIT_CODE_ERROR,
EXIT_CODE_ERROR_DECRYPTION_ERROR,
EXIT_CODE_ERROR_RESTORE_BACKUP_IS_ENCRYPTED ->
getString(R.string.restore_failed_invalid_file)
else -> getString(R.string.restore_failed)
}
viewModel.postSideEffect(sideEffect)
if (success) restartApp()
viewModel.postSideEffect(
GlobalSideEffect.Snackbar(
StringValue.DynamicString(errorMessage),
ToastType.Error,
)
)
}
}
}
@@ -666,13 +686,20 @@ class MainActivity : AppCompatActivity() {
private fun handleIncomingIntent(intent: Intent?) {
intent ?: return
when (intent.action) {
Intent.ACTION_VIEW,
Intent.ACTION_EDIT,
Intent.ACTION_SEND -> {
val uri: Uri? = intent.data
uri?.let { viewModel.importFromUri(it) }
val uri: Uri? = intent.data ?: return
val name = uri?.lastPathSegment?.lowercase() ?: return
if (
!name.endsWith(FileUtils.CONF_FILE_EXTENSION) &&
!name.endsWith(FileUtils.ZIP_FILE_EXTENSION)
) {
Timber.d("Ignoring non-config URI in handleIncomingIntent: $uri")
return
}
viewModel.importFromUri(uri)
}
}
}
@@ -52,6 +52,7 @@ import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.BackupBottomSheet
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.BackupEncryptionDialog
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.proxy.compoents.AppModeBottomSheet
import com.zaneschepke.wireguardautotunnel.util.StringValue
import com.zaneschepke.wireguardautotunnel.util.extensions.asString
@@ -88,6 +89,9 @@ fun SettingsScreen(
}
var showBackupSheet by rememberSaveable { mutableStateOf(false) }
var showEncryptionDialog by rememberSaveable { mutableStateOf(false) }
var isRestoreAction by remember { mutableStateOf(false) }
var showAppModeSheet by rememberSaveable { mutableStateOf(false) }
val appMode = uiState.settings.tunnelMode
@@ -110,13 +114,42 @@ fun SettingsScreen(
action()
}
if (showBackupSheet)
if (showBackupSheet) {
BackupBottomSheet(
{ performBackupRestore { (context as? MainActivity)?.performBackup() } },
{ performBackupRestore { (context as? MainActivity)?.performRestore() } },
) {
showBackupSheet = false
}
onBackup = {
showBackupSheet = false
isRestoreAction = false
showEncryptionDialog = true
},
onRestore = {
showBackupSheet = false
isRestoreAction = true
showEncryptionDialog = true
},
onDismiss = { showBackupSheet = false },
)
}
if (showEncryptionDialog) {
BackupEncryptionDialog(
isRestore = isRestoreAction,
onConfirm = { encrypt, password ->
showEncryptionDialog = false
if (isRestoreAction) {
performBackupRestore {
(context as? MainActivity)?.performRestore(encrypt, password)
}
} else {
performBackupRestore {
(context as? MainActivity)?.performBackup(encrypt, password)
}
}
},
onDismiss = { showEncryptionDialog = false },
)
}
if (showAppModeSheet)
AppModeBottomSheet(sharedViewModel::setAppMode, uiState.settings.tunnelMode) {
showAppModeSheet = false
@@ -0,0 +1,146 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Visibility
import androidx.compose.material.icons.outlined.VisibilityOff
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.common.button.ThemedSwitch
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.CustomTextField
@Composable
fun BackupEncryptionDialog(
isRestore: Boolean,
onConfirm: (encrypt: Boolean, password: String?) -> Unit,
onDismiss: () -> Unit,
) {
var encrypt by remember { mutableStateOf(false) }
var password by remember { mutableStateOf("") }
var confirmPassword by remember { mutableStateOf("") }
var showPasswordError by remember { mutableStateOf(false) }
var passwordVisible by remember { mutableStateOf(false) }
var confirmPasswordVisible by remember { mutableStateOf(false) }
InfoDialog(
title =
if (isRestore) {
stringResource(R.string.restore)
} else {
stringResource(R.string.backup)
},
confirmText =
if (isRestore) {
stringResource(R.string.restore)
} else {
stringResource(R.string.backup)
},
onAttest = {
if (!isRestore && encrypt && password != confirmPassword) {
showPasswordError = true
return@InfoDialog
}
if (encrypt && password.isBlank()) {
return@InfoDialog
}
val finalPassword = if (encrypt) password else null
onConfirm(encrypt, finalPassword)
},
onDismiss = onDismiss,
body = {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxWidth(),
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(stringResource(R.string.encrypted))
ThemedSwitch(checked = encrypt, onClick = { encrypt = it })
}
if (encrypt) {
CustomTextField(
value = password,
onValueChange = {
password = it
showPasswordError = false
},
containerColor = MaterialTheme.colorScheme.surface,
label = { Text(stringResource(R.string.password)) },
visualTransformation =
if (passwordVisible) VisualTransformation.None
else PasswordVisualTransformation(),
trailing = {
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(
imageVector =
if (passwordVisible) Icons.Outlined.VisibilityOff
else Icons.Outlined.Visibility,
contentDescription =
if (passwordVisible) stringResource(R.string.hide_password)
else stringResource(R.string.show_password),
)
}
},
modifier = Modifier.fillMaxWidth(),
)
if (!isRestore) {
CustomTextField(
value = confirmPassword,
onValueChange = {
confirmPassword = it
showPasswordError = false
},
label = { Text(stringResource(R.string.confirm_password)) },
visualTransformation =
if (confirmPasswordVisible) VisualTransformation.None
else PasswordVisualTransformation(),
trailing = {
IconButton(
onClick = { confirmPasswordVisible = !confirmPasswordVisible }
) {
Icon(
imageVector =
if (confirmPasswordVisible) Icons.Outlined.VisibilityOff
else Icons.Outlined.Visibility,
contentDescription =
if (confirmPasswordVisible)
stringResource(R.string.hide_password)
else stringResource(R.string.show_password),
)
}
},
containerColor = MaterialTheme.colorScheme.surface,
modifier = Modifier.fillMaxWidth(),
isError = showPasswordError,
)
}
if (showPasswordError) {
Text(
text = stringResource(R.string.passwords_do_not_match),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
)
}
}
}
},
)
}
+1 -1
View File
@@ -58,7 +58,7 @@
<string name="wifi_settings">Wi-Fi settings</string>
<string name="tunnel_on_wifi">Tunnel on Wi-Fi</string>
<string name="add_peer">Add peer</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="persistent_keepalive">Persistent keepalive</string>
<string name="info">Info</string>
<string name="exclude">Exclude</string>
+1 -1
View File
@@ -245,7 +245,7 @@
<string name="root_required_template">%1$s (vyžaduje root)</string>
<string name="recommended_template">%1$s (doporučeno)</string>
<string name="hint_template">(%1$s)</string>
<string name="backup_success">Úspěšně zazálohováno. %1$s</string>
<string name="backup_success">Úspěšně zazálohováno</string>
<string name="config_error_template">Špatná konfigurace. %1$s v umístění: %2$s.</string>
<string name="donation_dev_message">Jako jediný vývojář neúnavně pracuji na tom, aby se WG Tunnel stal nejlepším bezplatným a open-source WireGuard klientem pro Android, ale to je možné pouze s vaší podporou.</string>
<string name="google_donation_message">Bohužel, kvůli pravidlům společnosti Google nejsou odkazy na darování povoleny ve verzi této aplikace z Obchodu Play. Projděte si prosím webové stránky projektu, abyste zjistili, kde můžete přispět.</string>
+1 -1
View File
@@ -60,7 +60,7 @@
<string name="wifi_settings">Wi-Fi settings</string>
<string name="tunnel_on_wifi">Tunnel on Wi-Fi</string>
<string name="add_peer">Add peer</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="persistent_keepalive">Persistent keepalive</string>
<string name="info">Info</string>
<string name="exclude">Exclude</string>
+1 -1
View File
@@ -202,7 +202,7 @@
<string name="auto_tunnel_running">Auto-Tunnel läuft</string>
<string name="auto_tunnel_not_running">Auto-Tunnel läuft nicht</string>
<string name="tunnel_monitoring">Tunnelüberwachung</string>
<string name="backup_success">Backuperfolg. %1$s</string>
<string name="backup_success">Backuperfolg</string>
<string name="restore_success">Wiederherstellerfolg. %1$s</string>
<string name="restarting_app">Starte App neu, um Änderungen anzuwenden …</string>
<string name="restore_failed">Wiederherstellung aus Backup fehlgeschlagen.</string>
+1 -1
View File
@@ -202,7 +202,7 @@
<string name="auto_tunnel_running">El túnel automático está activo</string>
<string name="auto_tunnel_not_running">El túnel automático no está activo</string>
<string name="tunnel_monitoring">Monitoreo del túnel</string>
<string name="backup_success">Copia de seguridad realizada con éxito. %1$s</string>
<string name="backup_success">Copia de seguridad realizada con éxito</string>
<string name="restore_success">Restauración completada. %1$s</string>
<string name="restarting_app">Reiniciando la app para aplicar los cambios…</string>
<string name="restore_failed">No se pudo restaurar la copia de seguridad.</string>
+1 -1
View File
@@ -194,7 +194,7 @@
<string name="recommended_template">%1$s (soovitatav)</string>
<string name="hint_template">(%1$s)</string>
<string name="tunnel_monitoring">Tunnelo monitooring</string>
<string name="backup_success">Varundus õnnestus. %1$s</string>
<string name="backup_success">Varundus õnnestus</string>
<string name="restore_success">Taastamine õnnestus. %1$s</string>
<string name="restarting_app">Muudatuste jõustamiseks taaskäivitan rakenduse…</string>
<string name="restore_failed">Varukoopiast taastamine ei õnnestunud.</string>
+1 -1
View File
@@ -70,7 +70,7 @@
<string name="wifi_settings">Wi-Fi settings</string>
<string name="tunnel_on_wifi">Tunnel on Wi-Fi</string>
<string name="add_peer">Add peer</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="persistent_keepalive">Persistent keepalive</string>
<string name="info">Info</string>
<string name="exclude">Exclude</string>
+1 -1
View File
@@ -136,7 +136,7 @@
<string name="show_qr">Show QR</string>
<string name="wifi_settings">Wi-Fi settings</string>
<string name="add_peer">Add peer</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="persistent_keepalive">Persistent keepalive</string>
<string name="info">Info</string>
<string name="backup_failed">Failed to create backup.</string>
+1 -1
View File
@@ -171,7 +171,7 @@
<string name="mimic_quic">Imiter QUIC</string>
<string name="show_qr">Afficher le QR</string>
<string name="wifi_settings">Paramètres Wi-Fi</string>
<string name="backup_success">Sauvegarde réussie. %1$s</string>
<string name="backup_success">Sauvegarde réussie</string>
<string name="info">Info</string>
<string name="backup_failed">Échec de la création de la sauvegarde.</string>
<string name="location_permissions">Permissions de localisation</string>
+1 -1
View File
@@ -58,7 +58,7 @@
<string name="wifi_settings">Wi-Fi settings</string>
<string name="tunnel_on_wifi">Tunnel on Wi-Fi</string>
<string name="add_peer">Add peer</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="persistent_keepalive">Persistent keepalive</string>
<string name="info">Info</string>
<string name="exclude">Exclude</string>
+1 -1
View File
@@ -60,7 +60,7 @@
<string name="wifi_settings">Wi-Fi beállítások</string>
<string name="tunnel_on_wifi">Alagút Wi-Fi-n</string>
<string name="add_peer">Peer hozzáadása</string>
<string name="backup_success">Sikeres mentés. %1$s</string>
<string name="backup_success">Sikeres mentés</string>
<string name="persistent_keepalive">Kapcsolatmegőrzés</string>
<string name="info">Infó</string>
<string name="exclude">Kizárás</string>
+1 -1
View File
@@ -116,7 +116,7 @@
<string name="auto_tunnel_channel_description">Saluran untuk notifikasi status terowongan otomatis</string>
<string name="show_qr">Tampilkan QR</string>
<string name="wifi_settings">Pengaturan Wi-Fi</string>
<string name="backup_success">Pencadangan berhasil. %1$s</string>
<string name="backup_success">Pencadangan berhasil</string>
<string name="info">Info</string>
<string name="exclude">Kecualikan</string>
<string name="backup_failed">Gagal membuat cadangan.</string>
+1 -1
View File
@@ -206,7 +206,7 @@
<string name="restore_failed">Impossibile ripristinare dal backup.</string>
<string name="restarting_app">Riavviando l\'app per applicare le modifiche…</string>
<string name="restore_success">Ripristino riuscito. %1$s</string>
<string name="backup_success">Bakcup riuscito. %1$s</string>
<string name="backup_success">Bakcup riuscito</string>
<string name="current_template">Corrente: %1$s</string>
<string name="root_required_template">%1$s (root richiesto)</string>
<string name="recommended_template">%1$s (raccomandato)</string>
+1 -1
View File
@@ -107,7 +107,7 @@
<string name="auto_tunnel_channel_description">A channel for auto-tunnel state notifications</string>
<string name="show_qr">Show QR</string>
<string name="wifi_settings">Wi-Fi settings</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="info">Info</string>
<string name="backup_failed">Failed to create backup.</string>
<string name="junk_packet_minimum_size">Junk packet minimum size</string>
+1 -1
View File
@@ -166,7 +166,7 @@
<string name="auto_tunnel_channel_description">A channel for auto-tunnel state notifications</string>
<string name="show_qr">Show QR</string>
<string name="wifi_settings">Wi-Fi settings</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="info">Info</string>
<string name="backup_failed">Failed to create backup.</string>
<string name="junk_packet_minimum_size">Junk packet minimum size</string>
+1 -1
View File
@@ -58,7 +58,7 @@
<string name="wifi_settings">Wi-Fi 설정</string>
<string name="tunnel_on_wifi">Wi-Fi에서 터널 사용</string>
<string name="add_peer">피어 추가</string>
<string name="backup_success">백업 성공. %1$s</string>
<string name="backup_success">백업 성공</string>
<string name="persistent_keepalive">지속적 연결 유지</string>
<string name="info">정보</string>
<string name="exclude">제외</string>
+1 -1
View File
@@ -60,7 +60,7 @@
<string name="wifi_settings">Wi-Fi settings</string>
<string name="tunnel_on_wifi">Tunnel on Wi-Fi</string>
<string name="add_peer">Add peer</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="persistent_keepalive">Persistent keepalive</string>
<string name="info">Info</string>
<string name="exclude">Exclude</string>
+1 -1
View File
@@ -222,7 +222,7 @@
<string name="website">App website</string>
<string name="mimic_quic">Mimic QUIC</string>
<string name="wifi_settings">Wi-Fi instellingen</string>
<string name="backup_success">Backup succesvol. %1$s</string>
<string name="backup_success">Backup succesvol</string>
<string name="info">Info</string>
<string name="backup_failed">Backup maken mislukt.</string>
<string name="unknown">Onbekend</string>
+1 -1
View File
@@ -58,7 +58,7 @@
<string name="wifi_settings">Wi-Fi settings</string>
<string name="tunnel_on_wifi">Tunnel on Wi-Fi</string>
<string name="add_peer">Add peer</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="persistent_keepalive">Persistent keepalive</string>
<string name="info">Info</string>
<string name="exclude">Exclude</string>
+1 -1
View File
@@ -203,7 +203,7 @@
<string name="auto_tunnel_running">Autotunel jest uruchomiony</string>
<string name="auto_tunnel_not_running">Autotunel nie jest uruchomiony</string>
<string name="tunnel_monitoring">Monitorowanie tunelu</string>
<string name="backup_success">Powodzenie tworzenia kopii zapasowej. %1$s</string>
<string name="backup_success">Powodzenie tworzenia kopii zapasowej</string>
<string name="restore_success">Powodzenie przywracania. %1$s</string>
<string name="restarting_app">Ponowne uruchomienie aplikacji w celu zastosowania zmian…</string>
<string name="restore_failed">Nie udało się przywrócić danych z kopii zapasowej.</string>
+1 -1
View File
@@ -157,7 +157,7 @@
<string name="auto_tunnel_channel_description">A channel for auto-tunnel state notifications</string>
<string name="show_qr">Show QR</string>
<string name="wifi_settings">Wi-Fi settings</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="info">Info</string>
<string name="backup_failed">Failed to create backup.</string>
<string name="location_permissions">Location Permissions</string>
+1 -1
View File
@@ -158,7 +158,7 @@
<string name="auto_tunnel_channel_description">A channel for auto-tunnel state notifications</string>
<string name="show_qr">Show QR</string>
<string name="wifi_settings">Wi-Fi settings</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="info">Info</string>
<string name="backup_failed">Failed to create backup.</string>
<string name="location_permissions">Location Permissions</string>
+1 -1
View File
@@ -207,7 +207,7 @@
<string name="backup_application">Резервирование данных</string>
<string name="restore_application">Восстановление данных</string>
<string name="tunnel_monitoring">Отслеживание туннеля</string>
<string name="backup_success">Резервное копирование выполнено. %1$s</string>
<string name="backup_success">Резервное копирование выполнено</string>
<string name="restore_success">Восстановление выполнено. %1$s</string>
<string name="root_required_template">%1$s (требуется root)</string>
<string name="recommended_template">%1$s (рекомендуется)</string>
+1 -1
View File
@@ -108,7 +108,7 @@
<string name="auto_tunnel_channel_description">A channel for auto-tunnel state notifications</string>
<string name="show_qr">Show QR</string>
<string name="wifi_settings">Wi-Fi settings</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="info">Info</string>
<string name="backup_failed">Failed to create backup.</string>
<string name="junk_packet_minimum_size">Junk packet minimálna veľkosť</string>
+1 -1
View File
@@ -58,7 +58,7 @@
<string name="wifi_settings">Wi-Fi settings</string>
<string name="tunnel_on_wifi">Tunnel on Wi-Fi</string>
<string name="add_peer">Add peer</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="persistent_keepalive">Persistent keepalive</string>
<string name="info">Info</string>
<string name="exclude">Exclude</string>
+1 -1
View File
@@ -198,7 +198,7 @@
<string name="mimic_quic">Mimic QUIC</string>
<string name="show_qr">Show QR</string>
<string name="wifi_settings">Wi-Fi settings</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="info">Info</string>
<string name="backup_failed">Failed to create backup.</string>
<string name="location_permissions">Location Permissions</string>
+1 -1
View File
@@ -58,7 +58,7 @@
<string name="wifi_settings">Wi-Fi settings</string>
<string name="tunnel_on_wifi">Tunnel on Wi-Fi</string>
<string name="add_peer">Add peer</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="persistent_keepalive">Persistent keepalive</string>
<string name="info">Info</string>
<string name="exclude">Exclude</string>
+1 -1
View File
@@ -176,7 +176,7 @@
<string name="mimic_quic">Mimic QUIC</string>
<string name="show_qr">Show QR</string>
<string name="wifi_settings">Wi-Fi settings</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="info">Info</string>
<string name="backup_failed">Failed to create backup.</string>
<string name="location_permissions">Location Permissions</string>
+1 -1
View File
@@ -165,7 +165,7 @@
<string name="mimic_quic">Імітація QUIC</string>
<string name="show_qr">Показати QR-код</string>
<string name="wifi_settings">Параметри Wi-Fi</string>
<string name="backup_success">Рез. копіювання успішне. %1$s</string>
<string name="backup_success">Рез. копіювання успішне</string>
<string name="info">Інформація</string>
<string name="backup_failed">Не вдалося створити резервну копію.</string>
<string name="location_permissions">Дозволи на доступ до геолокації</string>
+1 -1
View File
@@ -202,7 +202,7 @@
<string name="auto_tunnel_running">آٹو ٹنل چل رہا ہے</string>
<string name="auto_tunnel_not_running">آٹو ٹنل نہیں چل رہا ہے</string>
<string name="tunnel_monitoring">ٹنل کی نگرانی</string>
<string name="backup_success">بیک اپ کامیاب۔ %1$s</string>
<string name="backup_success">بیک اپ کامیاب۔</string>
<string name="restore_success">بحالی کامیاب۔ %1$s</string>
<string name="restarting_app">تبدیلیاں لاگو کرنے کے لیے ایپ کو دوبارہ شروع کیا جا رہا ہے…</string>
<string name="restore_failed">بیک اپ سے بحالی ناکام۔</string>
+1 -1
View File
@@ -60,7 +60,7 @@
<string name="wifi_settings">Wi-Fi settings</string>
<string name="tunnel_on_wifi">Tunnel on Wi-Fi</string>
<string name="add_peer">Add peer</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="persistent_keepalive">Persistent keepalive</string>
<string name="info">Info</string>
<string name="exclude">Loại trừ</string>
+1 -1
View File
@@ -202,7 +202,7 @@
<string name="auto_tunnel_running">自动隧道运行中</string>
<string name="auto_tunnel_not_running">自动隧道未运行</string>
<string name="tunnel_monitoring">隧道监控</string>
<string name="backup_success">成功备份%1$s</string>
<string name="backup_success">成功备份</string>
<string name="restore_success">成功恢复:%1$s</string>
<string name="restarting_app">正重启应用来应用更改…</string>
<string name="restore_failed">未能从备份恢复。</string>
+1 -1
View File
@@ -179,7 +179,7 @@
<string name="restarting_app">正在重啟應用程式以應用變更…</string>
<string name="restore_application">從備份還原</string>
<string name="restore_success">還原成功。 %1$s</string>
<string name="backup_success">備份成功。%1$s</string>
<string name="backup_success">備份成功</string>
<string name="backup_application">備份應用程式資料</string>
<string name="restore_failed">還原備份失敗。</string>
<string name="backup_failed">建立備份失敗。</string>
+12 -2
View File
@@ -231,8 +231,8 @@
<string name="auto_tunnel_running">Auto-tunnel is running</string>
<string name="auto_tunnel_not_running">Auto-tunnel is not running</string>
<string name="tunnel_monitoring">Tunnel monitoring</string>
<string name="backup_success">Backup success. %1$s</string>
<string name="restore_success">Restore success. %1$s</string>
<string name="backup_success">Backup success</string>
<string name="restore_success">Restore success</string>
<string name="restarting_app">Restarting app to apply changes…</string>
<string name="restore_failed">Failed to restore from backup.</string>
<string name="backup_failed">Failed to create backup.</string>
@@ -525,4 +525,14 @@
<string name="remote_control">Remote control</string>
<string name="remote_control_desc">Allow other apps (like Tasker) to control tunnels</string>
<string name="endpoint_template">endpoint: %1$s</string>
<string name="encrypted">Encrypted</string>
<string name="confirm_password">Confirm password</string>
<string name="passwords_do_not_match">Passwords do not match</string>
<string name="backup">Backup</string>
<string name="restore">Restore</string>
<string name="hide_password">Hide password</string>
<string name="restore_failed_wrong_password">Restore failed. Wrong password</string>
<string name="restore_failed_invalid_file">Restore failed. Select a valid backup file (.sqlite3 or .sqlite3.aes)</string>
</resources>