mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 19:08:23 +00:00
Merge #290a348d: feat(permissions): add option to pre-approve permissio…
feat(permissions): add option to pre-approve permissions under manual approval nostr:nevent1qqszjz353hwg2g3pcmf8h6qmc49af4ce0mvj057r9zxk6pffjh30s8gpz3mhxue69uhhyetvv9ujumn8d96zuer9wcxmzkuh PR-Author: greenart7c3 nostr:npub1w4uswmv6lu9yel005l3qgheysmr7tk9uvwluddznju3nuxalevvs2d0jr5 PR description: ## What When connecting a new app while the `Manually approve each permission` policy is selected, a new `Add permission` button lets the user pick extra permissions to be approved automatically 342200224 everything else keeps asking. ## Changes - New `AddPermissionsSheet` bottom sheet: searchable, deduped list of the full supported permission catalog with checkbox multi-select - Custom `sign_event` kind input (numeric-only) for kinds not in the catalog, with validation against existing permissions and in-session duplicates - Wired into all three surfaces: - `LoginWithPubKey` (nostrsigner:// intent connect flow) - `BunkerConnectRequestScreen` (NIP-46 connect flow) - `EditPermission` screen 342200224 persisted as Allow / Always rows, still tunable afterwards - 6 new strings translated in all 14 locales ## Validation - ktlintCheck 342234205 - lintFreeDebug 342234205 - testFreeDebugUnitTest 342234205
This commit is contained in:
@@ -51,6 +51,7 @@ import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.models.Permission
|
||||
import com.greenart7c3.nostrsigner.models.SignerType
|
||||
import com.greenart7c3.nostrsigner.ui.actions.RemoveAllPermissionsDialog
|
||||
import com.greenart7c3.nostrsigner.ui.components.AddPermissionsSheet
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberToggles
|
||||
import com.greenart7c3.nostrsigner.ui.components.LabeledBorderBox
|
||||
@@ -77,6 +78,7 @@ fun EditPermission(
|
||||
}
|
||||
|
||||
var wantsToRemovePermissions by remember { mutableStateOf(false) }
|
||||
var showAddPermissions by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var checked by remember {
|
||||
@@ -194,6 +196,45 @@ fun EditPermission(
|
||||
text = stringResource(R.string.edit_permissions_description),
|
||||
)
|
||||
|
||||
if (applicationData.key.isNotEmpty()) {
|
||||
if (showAddPermissions) {
|
||||
AddPermissionsSheet(
|
||||
existingPermissions = permissions.map { Permission(it.type, it.kind) },
|
||||
onAdd = { addedPermissions ->
|
||||
val newPermissions = addedPermissions.map {
|
||||
ApplicationPermissionsEntity(
|
||||
null,
|
||||
applicationData.key,
|
||||
it.type.uppercase(),
|
||||
it.kind,
|
||||
true,
|
||||
RememberType.ALWAYS.screenCode,
|
||||
Long.MAX_VALUE / 1000,
|
||||
0,
|
||||
)
|
||||
}
|
||||
permissions.addAll(newPermissions)
|
||||
scope.launch(Dispatchers.IO) {
|
||||
Amber.instance
|
||||
.dao(account.npub)
|
||||
.insertPermissions(newPermissions)
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
showAddPermissions = false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
AmberButton(
|
||||
modifier = Modifier.padding(bottom = 20.dp),
|
||||
onClick = {
|
||||
showAddPermissions = true
|
||||
},
|
||||
text = stringResource(R.string.add_permission),
|
||||
)
|
||||
}
|
||||
|
||||
permissions.forEach { permission ->
|
||||
PermissionRow(
|
||||
permission = permission,
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
package com.greenart7c3.nostrsigner.ui.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.BottomAppBar
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.Permission
|
||||
import com.greenart7c3.nostrsigner.models.supportedKindNumbers
|
||||
import com.greenart7c3.nostrsigner.ui.theme.AmberPreview
|
||||
import com.greenart7c3.nostrsigner.ui.theme.ThemePreviews
|
||||
|
||||
private fun permissionKey(permission: Permission) = "${permission.type}-${permission.kind}"
|
||||
|
||||
/**
|
||||
* Bottom sheet that lets the user pick permissions from the full supported list to
|
||||
* be approved automatically, excluding the ones the application already has.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AddPermissionsSheet(
|
||||
existingPermissions: List<Permission>,
|
||||
onAdd: (List<Permission>) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true,
|
||||
)
|
||||
|
||||
val candidates = remember(existingPermissions) {
|
||||
supportedKindNumbers
|
||||
.distinctBy { permissionKey(it) }
|
||||
.filterNot { candidate ->
|
||||
existingPermissions.any {
|
||||
it.type.equals(candidate.type, ignoreCase = true) && it.kind == candidate.kind
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val selectedPermissions = remember { mutableStateMapOf<String, Boolean>() }
|
||||
var search by remember { mutableStateOf("") }
|
||||
|
||||
// Custom sign_event kinds typed by the user, added on top of the catalog list.
|
||||
val customPermissions = remember { mutableStateListOf<Permission>() }
|
||||
var customKind by remember { mutableStateOf("") }
|
||||
val parsedCustomKind = customKind.toIntOrNull()
|
||||
val customKindExists = parsedCustomKind != null &&
|
||||
existingPermissions.any {
|
||||
it.type.equals("sign_event", ignoreCase = true) && it.kind == parsedCustomKind
|
||||
}
|
||||
val canAddCustomKind = parsedCustomKind != null &&
|
||||
!customKindExists &&
|
||||
customPermissions.none { it.kind == parsedCustomKind }
|
||||
|
||||
val customCandidates = customPermissions.toList()
|
||||
val allCandidates = customCandidates + candidates
|
||||
val filteredPermissions = remember(allCandidates, search) {
|
||||
if (search.isBlank()) {
|
||||
allCandidates
|
||||
} else {
|
||||
allCandidates.filter {
|
||||
it.toLocalizedString(context, true).contains(search, ignoreCase = true) ||
|
||||
it.kind.toString().contains(search) ||
|
||||
it.type.contains(search, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val hasSelection = selectedPermissions.values.any { it }
|
||||
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
BottomAppBar {
|
||||
Column {
|
||||
AmberButton(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
enabled = hasSelection,
|
||||
onClick = {
|
||||
onAdd(
|
||||
allCandidates.filter { selectedPermissions[permissionKey(it)] == true },
|
||||
)
|
||||
onDismiss()
|
||||
},
|
||||
text = stringResource(R.string.add),
|
||||
)
|
||||
IconRow(
|
||||
center = true,
|
||||
title = stringResource(R.string.go_back),
|
||||
icon = ImageVector.vectorResource(R.drawable.back),
|
||||
onClick = onDismiss,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
) { padding ->
|
||||
Column(
|
||||
Modifier
|
||||
.padding(padding)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
text = stringResource(R.string.add_permission_description),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = search,
|
||||
onValueChange = { search = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
placeholder = { Text(stringResource(R.string.search_permissions)) },
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Search, contentDescription = null)
|
||||
},
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = customKind,
|
||||
onValueChange = { customKind = it.filter { char -> char.isDigit() } },
|
||||
modifier = Modifier.weight(1f),
|
||||
label = { Text(stringResource(R.string.custom_event_kind)) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true,
|
||||
isError = customKindExists,
|
||||
supportingText = if (customKindExists) {
|
||||
{ Text(stringResource(R.string.custom_event_kind_already_added)) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
TextButton(
|
||||
enabled = canAddCustomKind,
|
||||
onClick = {
|
||||
val kind = parsedCustomKind ?: return@TextButton
|
||||
val permission = Permission("sign_event", kind)
|
||||
customPermissions.add(permission)
|
||||
selectedPermissions[permissionKey(permission)] = true
|
||||
customKind = ""
|
||||
},
|
||||
) {
|
||||
Text(stringResource(R.string.add))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(8.dp))
|
||||
|
||||
if (filteredPermissions.isEmpty()) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
text = stringResource(R.string.no_permissions_found),
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
} else {
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp),
|
||||
contentPadding = PaddingValues(horizontal = 8.dp),
|
||||
) {
|
||||
items(
|
||||
filteredPermissions,
|
||||
key = { permissionKey(it) },
|
||||
) { item ->
|
||||
val itemKey = permissionKey(item)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
selectedPermissions[itemKey] = !(selectedPermissions[itemKey] ?: false)
|
||||
}
|
||||
.padding(vertical = 2.dp),
|
||||
) {
|
||||
Checkbox(
|
||||
checked = selectedPermissions[itemKey] ?: false,
|
||||
onCheckedChange = {
|
||||
selectedPermissions[itemKey] = it
|
||||
},
|
||||
)
|
||||
Text(
|
||||
text = item.toLocalizedString(context, true),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
fun AddPermissionsSheetPreview() {
|
||||
AmberPreview {
|
||||
AddPermissionsSheet(
|
||||
existingPermissions = listOf(
|
||||
Permission("get_public_key", null),
|
||||
Permission("sign_event", 1),
|
||||
),
|
||||
onAdd = {},
|
||||
onDismiss = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
+64
-30
@@ -155,6 +155,7 @@ fun BunkerConnectRequestScreen(
|
||||
var deleteAfterIndex by remember { mutableIntStateOf(DeleteAfterType.NEVER.screenCode) }
|
||||
var closeApp by remember { mutableStateOf(shouldCloseApp) }
|
||||
var showModal by remember { mutableStateOf(false) }
|
||||
var showAddPermissions by remember { mutableStateOf(false) }
|
||||
var advancedOpen by remember { mutableStateOf(false) }
|
||||
val sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true,
|
||||
@@ -281,7 +282,58 @@ fun BunkerConnectRequestScreen(
|
||||
},
|
||||
)
|
||||
|
||||
if (selectedOption == 1 && localPermissions.isNotEmpty()) {
|
||||
if (selectedOption == 1) {
|
||||
if (localPermissions.isNotEmpty()) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 4.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
ElevatedButton(
|
||||
colors = ButtonDefaults.buttonColors().copy(
|
||||
contentColor = Color.Black,
|
||||
),
|
||||
shape = RoundedCornerShape(20),
|
||||
content = {
|
||||
Text(stringResource(R.string.permissions))
|
||||
},
|
||||
onClick = {
|
||||
showModal = true
|
||||
},
|
||||
)
|
||||
}
|
||||
if (showModal) {
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = {
|
||||
showModal = false
|
||||
},
|
||||
) {
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
BottomAppBar {
|
||||
IconRow(
|
||||
center = true,
|
||||
title = stringResource(R.string.go_back),
|
||||
icon = ImageVector.vectorResource(R.drawable.back),
|
||||
onClick = {
|
||||
showModal = false
|
||||
},
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
},
|
||||
) {
|
||||
EnabledPermissions(
|
||||
Modifier.padding(it),
|
||||
localPermissions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -294,41 +346,23 @@ fun BunkerConnectRequestScreen(
|
||||
),
|
||||
shape = RoundedCornerShape(20),
|
||||
content = {
|
||||
Text(stringResource(R.string.permissions))
|
||||
Text(stringResource(R.string.add_permission))
|
||||
},
|
||||
onClick = {
|
||||
showModal = true
|
||||
showAddPermissions = true
|
||||
},
|
||||
)
|
||||
}
|
||||
if (showModal) {
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = {
|
||||
showModal = false
|
||||
if (showAddPermissions) {
|
||||
AddPermissionsSheet(
|
||||
existingPermissions = localPermissions,
|
||||
onAdd = { addedPermissions ->
|
||||
localPermissions.addAll(addedPermissions)
|
||||
},
|
||||
) {
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
BottomAppBar {
|
||||
IconRow(
|
||||
center = true,
|
||||
title = stringResource(R.string.go_back),
|
||||
icon = ImageVector.vectorResource(R.drawable.back),
|
||||
onClick = {
|
||||
showModal = false
|
||||
},
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
},
|
||||
) {
|
||||
EnabledPermissions(
|
||||
Modifier.padding(it),
|
||||
localPermissions,
|
||||
)
|
||||
}
|
||||
}
|
||||
onDismiss = {
|
||||
showAddPermissions = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -306,6 +306,7 @@ fun LoginWithPubKey(
|
||||
)
|
||||
}
|
||||
var showModal by remember { mutableStateOf(false) }
|
||||
var showAddPermissions by remember { mutableStateOf(false) }
|
||||
val sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true,
|
||||
)
|
||||
@@ -365,7 +366,58 @@ fun LoginWithPubKey(
|
||||
},
|
||||
)
|
||||
|
||||
if (selectedOption == 1 && localPermissions.isNotEmpty()) {
|
||||
if (selectedOption == 1) {
|
||||
if (localPermissions.isNotEmpty()) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 4.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
ElevatedButton(
|
||||
colors = ButtonDefaults.buttonColors().copy(
|
||||
contentColor = Color.Black,
|
||||
),
|
||||
shape = RoundedCornerShape(20),
|
||||
content = {
|
||||
Text(stringResource(R.string.permissions))
|
||||
},
|
||||
onClick = {
|
||||
showModal = true
|
||||
},
|
||||
)
|
||||
}
|
||||
if (showModal) {
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = {
|
||||
showModal = false
|
||||
},
|
||||
) {
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
BottomAppBar {
|
||||
IconRow(
|
||||
center = true,
|
||||
title = stringResource(R.string.go_back),
|
||||
icon = ImageVector.vectorResource(R.drawable.back),
|
||||
onClick = {
|
||||
showModal = false
|
||||
},
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
},
|
||||
) {
|
||||
EnabledPermissions(
|
||||
Modifier.padding(it),
|
||||
localPermissions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -378,41 +430,23 @@ fun LoginWithPubKey(
|
||||
),
|
||||
shape = RoundedCornerShape(20),
|
||||
content = {
|
||||
Text(stringResource(R.string.permissions))
|
||||
Text(stringResource(R.string.add_permission))
|
||||
},
|
||||
onClick = {
|
||||
showModal = true
|
||||
showAddPermissions = true
|
||||
},
|
||||
)
|
||||
}
|
||||
if (showModal) {
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = {
|
||||
showModal = false
|
||||
if (showAddPermissions) {
|
||||
AddPermissionsSheet(
|
||||
existingPermissions = localPermissions,
|
||||
onAdd = { addedPermissions ->
|
||||
localPermissions.addAll(addedPermissions)
|
||||
},
|
||||
) {
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
BottomAppBar {
|
||||
IconRow(
|
||||
center = true,
|
||||
title = stringResource(R.string.go_back),
|
||||
icon = ImageVector.vectorResource(R.drawable.back),
|
||||
onClick = {
|
||||
showModal = false
|
||||
},
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
},
|
||||
) {
|
||||
EnabledPermissions(
|
||||
Modifier.padding(it),
|
||||
localPermissions,
|
||||
)
|
||||
}
|
||||
}
|
||||
onDismiss = {
|
||||
showAddPermissions = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">Biometrien konnten den Besitzer dieses Telefons nicht authentifizieren. Fehler: %1$s</string>
|
||||
<string name="delete">Löschen</string>
|
||||
<string name="permissions">Berechtigungen</string>
|
||||
<string name="add_permission">Berechtigung hinzufügen</string>
|
||||
<string name="add_permission_description">Wähle die Berechtigungen aus, die für diese Anwendung automatisch genehmigt werden sollen.</string>
|
||||
<string name="search_permissions">Berechtigungen suchen</string>
|
||||
<string name="no_permissions_found">Keine Berechtigungen gefunden</string>
|
||||
<string name="custom_event_kind">Benutzerdefinierte Ereignis-Art</string>
|
||||
<string name="custom_event_kind_already_added">Diese Berechtigung ist bereits in der Liste</string>
|
||||
<string name="delete_application">Anwendung löschen</string>
|
||||
<string name="reset_bunker">Bunker zurücksetzen</string>
|
||||
<string name="copy_to_clipboard">In Zwischenablage kopieren</string>
|
||||
|
||||
@@ -52,6 +52,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">La biometría no ha podido autenticar al propietario de este teléfono. Error: %1$s</string>
|
||||
<string name="delete">Borrar</string>
|
||||
<string name="permissions">Permisos</string>
|
||||
<string name="add_permission">Añadir permiso</string>
|
||||
<string name="add_permission_description">Selecciona los permisos que quieres que se aprueben automáticamente para esta aplicación.</string>
|
||||
<string name="search_permissions">Buscar permisos</string>
|
||||
<string name="no_permissions_found">No se encontraron permisos</string>
|
||||
<string name="custom_event_kind">Tipo de evento personalizado</string>
|
||||
<string name="custom_event_kind_already_added">Este permiso ya está en la lista</string>
|
||||
<string name="delete_application">Eliminar aplicación</string>
|
||||
<string name="reset_bunker">Restablecer Bunker</string>
|
||||
<string name="copy_to_clipboard">Copiar al portapapeles</string>
|
||||
|
||||
@@ -50,6 +50,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">La biométrie n\'a pas réussi à authentifier le propriétaire de ce téléphone. Erreur : %1$s</string>
|
||||
<string name="delete">Supprimer</string>
|
||||
<string name="permissions">Autorisations</string>
|
||||
<string name="add_permission">Ajouter une autorisation</string>
|
||||
<string name="add_permission_description">Sélectionnez les autorisations que vous souhaitez voir approuvées automatiquement pour cette application.</string>
|
||||
<string name="search_permissions">Rechercher des autorisations</string>
|
||||
<string name="no_permissions_found">Aucune autorisation trouvée</string>
|
||||
<string name="custom_event_kind">Type d\'événement personnalisé</string>
|
||||
<string name="custom_event_kind_already_added">Cette autorisation est déjà dans la liste</string>
|
||||
<string name="delete_application">Supprimer l\'application</string>
|
||||
<string name="reset_bunker">Réinitialiser le Bunker</string>
|
||||
<string name="copy_to_clipboard">Copier dans le presse-papiers</string>
|
||||
|
||||
@@ -52,6 +52,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">Biometrik gagal mengautentikasi pemilik ponsel ini. Error: %1$s</string>
|
||||
<string name="delete">Hapus</string>
|
||||
<string name="permissions">Izin</string>
|
||||
<string name="add_permission">Tambahkan izin</string>
|
||||
<string name="add_permission_description">Pilih izin yang ingin disetujui otomatis untuk aplikasi ini.</string>
|
||||
<string name="search_permissions">Cari izin</string>
|
||||
<string name="no_permissions_found">Tidak ada izin ditemukan</string>
|
||||
<string name="custom_event_kind">Jenis event kustom</string>
|
||||
<string name="custom_event_kind_already_added">Izin ini sudah ada di daftar</string>
|
||||
<string name="delete_application">Hapus Aplikasi</string>
|
||||
<string name="reset_bunker">Atur Ulang Bunker</string>
|
||||
<string name="copy_to_clipboard">Salin ke clipboard</string>
|
||||
|
||||
@@ -52,6 +52,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">La biometria non è riuscita ad autenticare il proprietario del telefono. Errore: %1$s</string>
|
||||
<string name="delete">Elimina</string>
|
||||
<string name="permissions">Permessi</string>
|
||||
<string name="add_permission">Aggiungi permesso</string>
|
||||
<string name="add_permission_description">Seleziona i permessi che vuoi approvare automaticamente per questa applicazione.</string>
|
||||
<string name="search_permissions">Cerca permessi</string>
|
||||
<string name="no_permissions_found">Nessun permesso trovato</string>
|
||||
<string name="custom_event_kind">Tipo di evento personalizzato</string>
|
||||
<string name="custom_event_kind_already_added">Questo permesso è già nell\'elenco</string>
|
||||
<string name="delete_application">Elimina applicazione</string>
|
||||
<string name="reset_bunker">Reimposta Bunker</string>
|
||||
<string name="copy_to_clipboard">Copia negli appunti</string>
|
||||
|
||||
@@ -43,6 +43,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">生体認証でこの端末の所有者を確認できませんでした。エラー: %1$s</string>
|
||||
<string name="delete">削除</string>
|
||||
<string name="permissions">権限</string>
|
||||
<string name="add_permission">権限を追加</string>
|
||||
<string name="add_permission_description">このアプリケーションで自動的に承認したい権限を選択してください。</string>
|
||||
<string name="search_permissions">権限を検索</string>
|
||||
<string name="no_permissions_found">権限が見つかりません</string>
|
||||
<string name="custom_event_kind">カスタムイベント種別</string>
|
||||
<string name="custom_event_kind_already_added">この権限はすでにリストにあります</string>
|
||||
<string name="delete_application">アプリケーションを削除</string>
|
||||
<string name="reset_bunker">Bunker をリセット</string>
|
||||
<string name="copy_to_clipboard">クリップボードにコピー</string>
|
||||
|
||||
@@ -52,6 +52,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">생체 인식으로 이 휴대폰 소유자 인증에 실패했습니다. 오류: %1$s</string>
|
||||
<string name="delete">삭제</string>
|
||||
<string name="permissions">권한</string>
|
||||
<string name="add_permission">권한 추가</string>
|
||||
<string name="add_permission_description">이 애플리케이션에 대해 자동으로 승인할 권한을 선택하세요.</string>
|
||||
<string name="search_permissions">권한 검색</string>
|
||||
<string name="no_permissions_found">권한을 찾을 수 없습니다</string>
|
||||
<string name="custom_event_kind">사용자 지정 이벤트 종류</string>
|
||||
<string name="custom_event_kind_already_added">이 권한은 이미 목록에 있습니다</string>
|
||||
<string name="delete_application">애플리케이션 삭제</string>
|
||||
<string name="reset_bunker">Bunker 초기화</string>
|
||||
<string name="copy_to_clipboard">클립보드에 복사</string>
|
||||
|
||||
@@ -48,6 +48,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">A biometria não conseguiu autenticar o proprietário deste telefone. Erro: %1$s</string>
|
||||
<string name="delete">Excluir</string>
|
||||
<string name="permissions">Permissões</string>
|
||||
<string name="add_permission">Adicionar permissão</string>
|
||||
<string name="add_permission_description">Selecione as permissões que você deseja que sejam aprovadas automaticamente para este aplicativo.</string>
|
||||
<string name="search_permissions">Pesquisar permissões</string>
|
||||
<string name="no_permissions_found">Nenhuma permissão encontrada</string>
|
||||
<string name="custom_event_kind">Tipo de evento personalizado</string>
|
||||
<string name="custom_event_kind_already_added">Esta permissão já está na lista</string>
|
||||
<string name="delete_application">Excluir aplicativo</string>
|
||||
<string name="reset_bunker">Redefinir Bunker</string>
|
||||
<string name="copy_to_clipboard">Copiar para área de transferência</string>
|
||||
|
||||
@@ -52,6 +52,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">Биометрия не смогла подтвердить владельца этого телефона. Ошибка: %1$s</string>
|
||||
<string name="delete">Удалить</string>
|
||||
<string name="permissions">Разрешения</string>
|
||||
<string name="add_permission">Добавить разрешение</string>
|
||||
<string name="add_permission_description">Выберите разрешения, которые должны автоматически одобряться для этого приложения.</string>
|
||||
<string name="search_permissions">Поиск разрешений</string>
|
||||
<string name="no_permissions_found">Разрешения не найдены</string>
|
||||
<string name="custom_event_kind">Пользовательский тип события</string>
|
||||
<string name="custom_event_kind_already_added">Это разрешение уже есть в списке</string>
|
||||
<string name="delete_application">Удалить приложение</string>
|
||||
<string name="reset_bunker">Сбросить Bunker</string>
|
||||
<string name="copy_to_clipboard">Копировать в буфер обмена</string>
|
||||
|
||||
@@ -43,6 +43,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">ไบโอเมตริกไม่สามารถยืนยันตัวตนเจ้าของโทรศัพท์เครื่องนี้ได้ ข้อผิดพลาด: %1$s</string>
|
||||
<string name="delete">ลบ</string>
|
||||
<string name="permissions">สิทธิ์</string>
|
||||
<string name="add_permission">เพิ่มสิทธิ์</string>
|
||||
<string name="add_permission_description">เลือกสิทธิ์ที่คุณต้องการให้อนุมัติโดยอัตโนมัติสำหรับแอปพลิเคชันนี้</string>
|
||||
<string name="search_permissions">ค้นหาสิทธิ์</string>
|
||||
<string name="no_permissions_found">ไม่พบสิทธิ์</string>
|
||||
<string name="custom_event_kind">ชนิดเหตุการณ์แบบกำหนดเอง</string>
|
||||
<string name="custom_event_kind_already_added">สิทธิ์นี้อยู่ในรายการแล้ว</string>
|
||||
<string name="delete_application">ลบแอปพลิเคชัน</string>
|
||||
<string name="reset_bunker">รีเซ็ต Bunker</string>
|
||||
<string name="copy_to_clipboard">คัดลอกไปยังคลิปบอร์ด</string>
|
||||
|
||||
@@ -50,6 +50,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">Biyometrik veriler bu telefonun sahibini doğrulayamadı. Hata: %1$s</string>
|
||||
<string name="delete">Sil</string>
|
||||
<string name="permissions">İzinler</string>
|
||||
<string name="add_permission">İzin ekle</string>
|
||||
<string name="add_permission_description">Bu uygulama için otomatik olarak onaylanmasını istediğiniz izinleri seçin.</string>
|
||||
<string name="search_permissions">İzinleri ara</string>
|
||||
<string name="no_permissions_found">İzin bulunamadı</string>
|
||||
<string name="custom_event_kind">Özel etkinlik türü</string>
|
||||
<string name="custom_event_kind_already_added">Bu izin listede zaten var</string>
|
||||
<string name="delete_application">Uygulamayı Sil</string>
|
||||
<string name="reset_bunker">Bunker\'ı Sıfırla</string>
|
||||
<string name="copy_to_clipboard">Panoya kopyala</string>
|
||||
|
||||
@@ -43,6 +43,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">Sinh trắc học không thể xác thực chủ sở hữu điện thoại này. Lỗi: %1$s</string>
|
||||
<string name="delete">Xóa</string>
|
||||
<string name="permissions">Quyền</string>
|
||||
<string name="add_permission">Thêm quyền</string>
|
||||
<string name="add_permission_description">Chọn các quyền bạn muốn được tự động phê duyệt cho ứng dụng này.</string>
|
||||
<string name="search_permissions">Tìm kiếm quyền</string>
|
||||
<string name="no_permissions_found">Không tìm thấy quyền nào</string>
|
||||
<string name="custom_event_kind">Loại sự kiện tùy chỉnh</string>
|
||||
<string name="custom_event_kind_already_added">Quyền này đã có trong danh sách</string>
|
||||
<string name="delete_application">Xóa Ứng Dụng</string>
|
||||
<string name="reset_bunker">Đặt lại Bunker</string>
|
||||
<string name="copy_to_clipboard">Sao chép vào khay nhớ tạm</string>
|
||||
|
||||
@@ -48,6 +48,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">生物识别验证设备所有者身份失败:%1$s</string>
|
||||
<string name="delete">删除</string>
|
||||
<string name="permissions">权限</string>
|
||||
<string name="add_permission">添加权限</string>
|
||||
<string name="add_permission_description">选择您希望为此应用程序自动批准的权限。</string>
|
||||
<string name="search_permissions">搜索权限</string>
|
||||
<string name="no_permissions_found">未找到权限</string>
|
||||
<string name="custom_event_kind">自定义事件类型</string>
|
||||
<string name="custom_event_kind_already_added">此权限已在列表中</string>
|
||||
<string name="delete_application">删除应用</string>
|
||||
<string name="reset_bunker">重置 Bunker</string>
|
||||
<string name="copy_to_clipboard">复制到剪贴板</string>
|
||||
|
||||
@@ -76,6 +76,12 @@
|
||||
<string name="biometric_authentication_failed_explainer_with_error">Biometrics failed to authenticate the owner of this phone. Error: %1$s</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="permissions">Permissions</string>
|
||||
<string name="add_permission">Add permission</string>
|
||||
<string name="add_permission_description">Select the permissions you want to be approved automatically for this application.</string>
|
||||
<string name="search_permissions">Search permissions</string>
|
||||
<string name="no_permissions_found">No permissions found</string>
|
||||
<string name="custom_event_kind">Custom event kind</string>
|
||||
<string name="custom_event_kind_already_added">This permission is already in the list</string>
|
||||
<string name="delete_application">Delete Application</string>
|
||||
<string name="reset_bunker">Reset Bunker</string>
|
||||
<string name="copy_to_clipboard">Copy to clipboard</string>
|
||||
|
||||
Reference in New Issue
Block a user