diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt index da61c223..9dcb4849 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -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, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AddPermissionsSheet.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AddPermissionsSheet.kt new file mode 100644 index 00000000..45be640b --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AddPermissionsSheet.kt @@ -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, + onAdd: (List) -> 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() } + var search by remember { mutableStateOf("") } + + // Custom sign_event kinds typed by the user, added on top of the catalog list. + val customPermissions = remember { mutableStateListOf() } + 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 = {}, + ) + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerConnectRequestScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerConnectRequestScreen.kt index 9a9a0213..a0d5d94c 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerConnectRequestScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerConnectRequestScreen.kt @@ -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 + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LoginWithPubKey.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LoginWithPubKey.kt index 3651e0f5..521a48f0 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LoginWithPubKey.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LoginWithPubKey.kt @@ -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 + }, + ) } } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ceafc14a..75b31de6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -50,6 +50,12 @@ Biometrien konnten den Besitzer dieses Telefons nicht authentifizieren. Fehler: %1$s Löschen Berechtigungen + Berechtigung hinzufügen + Wähle die Berechtigungen aus, die für diese Anwendung automatisch genehmigt werden sollen. + Berechtigungen suchen + Keine Berechtigungen gefunden + Benutzerdefinierte Ereignis-Art + Diese Berechtigung ist bereits in der Liste Anwendung löschen Bunker zurücksetzen In Zwischenablage kopieren diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 940a490a..f3004ada 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -52,6 +52,12 @@ La biometría no ha podido autenticar al propietario de este teléfono. Error: %1$s Borrar Permisos + Añadir permiso + Selecciona los permisos que quieres que se aprueben automáticamente para esta aplicación. + Buscar permisos + No se encontraron permisos + Tipo de evento personalizado + Este permiso ya está en la lista Eliminar aplicación Restablecer Bunker Copiar al portapapeles diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index df05225f..a83665c1 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -50,6 +50,12 @@ La biométrie n\'a pas réussi à authentifier le propriétaire de ce téléphone. Erreur : %1$s Supprimer Autorisations + Ajouter une autorisation + Sélectionnez les autorisations que vous souhaitez voir approuvées automatiquement pour cette application. + Rechercher des autorisations + Aucune autorisation trouvée + Type d\'événement personnalisé + Cette autorisation est déjà dans la liste Supprimer l\'application Réinitialiser le Bunker Copier dans le presse-papiers diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index f60cf284..bfda6135 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -52,6 +52,12 @@ Biometrik gagal mengautentikasi pemilik ponsel ini. Error: %1$s Hapus Izin + Tambahkan izin + Pilih izin yang ingin disetujui otomatis untuk aplikasi ini. + Cari izin + Tidak ada izin ditemukan + Jenis event kustom + Izin ini sudah ada di daftar Hapus Aplikasi Atur Ulang Bunker Salin ke clipboard diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index dd2e1768..568405dd 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -52,6 +52,12 @@ La biometria non è riuscita ad autenticare il proprietario del telefono. Errore: %1$s Elimina Permessi + Aggiungi permesso + Seleziona i permessi che vuoi approvare automaticamente per questa applicazione. + Cerca permessi + Nessun permesso trovato + Tipo di evento personalizzato + Questo permesso è già nell\'elenco Elimina applicazione Reimposta Bunker Copia negli appunti diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 2e0bbfda..7424e89d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -43,6 +43,12 @@ 生体認証でこの端末の所有者を確認できませんでした。エラー: %1$s 削除 権限 + 権限を追加 + このアプリケーションで自動的に承認したい権限を選択してください。 + 権限を検索 + 権限が見つかりません + カスタムイベント種別 + この権限はすでにリストにあります アプリケーションを削除 Bunker をリセット クリップボードにコピー diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 1904265d..ec70cb1a 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -52,6 +52,12 @@ 생체 인식으로 이 휴대폰 소유자 인증에 실패했습니다. 오류: %1$s 삭제 권한 + 권한 추가 + 이 애플리케이션에 대해 자동으로 승인할 권한을 선택하세요. + 권한 검색 + 권한을 찾을 수 없습니다 + 사용자 지정 이벤트 종류 + 이 권한은 이미 목록에 있습니다 애플리케이션 삭제 Bunker 초기화 클립보드에 복사 diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index bb63c493..1ae947ab 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -48,6 +48,12 @@ A biometria não conseguiu autenticar o proprietário deste telefone. Erro: %1$s Excluir Permissões + Adicionar permissão + Selecione as permissões que você deseja que sejam aprovadas automaticamente para este aplicativo. + Pesquisar permissões + Nenhuma permissão encontrada + Tipo de evento personalizado + Esta permissão já está na lista Excluir aplicativo Redefinir Bunker Copiar para área de transferência diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fb895178..99e03232 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -52,6 +52,12 @@ Биометрия не смогла подтвердить владельца этого телефона. Ошибка: %1$s Удалить Разрешения + Добавить разрешение + Выберите разрешения, которые должны автоматически одобряться для этого приложения. + Поиск разрешений + Разрешения не найдены + Пользовательский тип события + Это разрешение уже есть в списке Удалить приложение Сбросить Bunker Копировать в буфер обмена diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 04f9fa6a..2a693ff0 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -43,6 +43,12 @@ ไบโอเมตริกไม่สามารถยืนยันตัวตนเจ้าของโทรศัพท์เครื่องนี้ได้ ข้อผิดพลาด: %1$s ลบ สิทธิ์ + เพิ่มสิทธิ์ + เลือกสิทธิ์ที่คุณต้องการให้อนุมัติโดยอัตโนมัติสำหรับแอปพลิเคชันนี้ + ค้นหาสิทธิ์ + ไม่พบสิทธิ์ + ชนิดเหตุการณ์แบบกำหนดเอง + สิทธิ์นี้อยู่ในรายการแล้ว ลบแอปพลิเคชัน รีเซ็ต Bunker คัดลอกไปยังคลิปบอร์ด diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 3eea9b6d..e8e1f5dc 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -50,6 +50,12 @@ Biyometrik veriler bu telefonun sahibini doğrulayamadı. Hata: %1$s Sil İzinler + İzin ekle + Bu uygulama için otomatik olarak onaylanmasını istediğiniz izinleri seçin. + İzinleri ara + İzin bulunamadı + Özel etkinlik türü + Bu izin listede zaten var Uygulamayı Sil Bunker\'ı Sıfırla Panoya kopyala diff --git a/app/src/main/res/values-vi-rVN/strings.xml b/app/src/main/res/values-vi-rVN/strings.xml index 0537ee76..1810a44e 100644 --- a/app/src/main/res/values-vi-rVN/strings.xml +++ b/app/src/main/res/values-vi-rVN/strings.xml @@ -43,6 +43,12 @@ 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 Xóa Quyền + Thêm quyền + Chọn các quyền bạn muốn được tự động phê duyệt cho ứng dụng này. + Tìm kiếm quyền + Không tìm thấy quyền nào + Loại sự kiện tùy chỉnh + Quyền này đã có trong danh sách Xóa Ứng Dụng Đặt lại Bunker Sao chép vào khay nhớ tạm diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 4f63417b..1fe0a2fe 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -48,6 +48,12 @@ 生物识别验证设备所有者身份失败:%1$s 删除 权限 + 添加权限 + 选择您希望为此应用程序自动批准的权限。 + 搜索权限 + 未找到权限 + 自定义事件类型 + 此权限已在列表中 删除应用 重置 Bunker 复制到剪贴板 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9b305009..de30106d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,6 +76,12 @@ Biometrics failed to authenticate the owner of this phone. Error: %1$s Delete Permissions + Add permission + Select the permissions you want to be approved automatically for this application. + Search permissions + No permissions found + Custom event kind + This permission is already in the list Delete Application Reset Bunker Copy to clipboard