Add support for importing multiple QR code images in one go

This is also part of the preparation needed for scanning Google
Authenticator Export QR codes from images.
This commit is contained in:
Alexander Bakker 2022-08-07 20:18:21 +02:00
parent b875baacef
commit e46857a26e
36 changed files with 232 additions and 110 deletions

View file

@ -0,0 +1,27 @@
package com.beemdevelopment.aegis.helpers;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.OpenableColumns;
public class SafHelper {
private SafHelper() {
}
public static String getFileName(Context context, Uri uri) {
if (uri.getScheme() != null && uri.getScheme().equals("content")) {
try (Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
int i = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (i != -1) {
return cursor.getString(i);
}
}
}
}
return uri.getLastPathSegment();
}
}

View file

@ -1,11 +1,7 @@
package com.beemdevelopment.aegis.ui;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
@ -36,7 +32,6 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class ImportEntriesActivity extends AegisActivity {
@ -190,39 +185,11 @@ public class ImportEntriesActivity extends AegisActivity {
List<DatabaseImporterEntryException> errors = result.getErrors();
if (errors.size() > 0) {
showErrorDialog(errors);
String message = getResources().getQuantityString(R.plurals.import_error_dialog, errors.size(), errors.size());
Dialogs.showMultiErrorDialog(this, R.string.import_error_title, message, errors, null);
}
}
private void showErrorDialog(List<DatabaseImporterEntryException> errors) {
Dialogs.showSecureDialog(new AlertDialog.Builder(this)
.setTitle(R.string.import_error_title)
.setMessage(getResources().getQuantityString(R.plurals.import_error_dialog, errors.size(), errors.size()))
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(getString(R.string.details), (dialog, which) -> showDetailedErrorDialog(errors))
.create());
}
private void showDetailedErrorDialog(List<DatabaseImporterEntryException> errors) {
List<String> messages = new ArrayList<>();
for (DatabaseImporterEntryException e : errors) {
messages.add(e.getMessage());
}
String message = TextUtils.join("\n\n", messages);
Dialogs.showSecureDialog(new AlertDialog.Builder(this)
.setTitle(R.string.import_error_title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(android.R.string.copy, (dialog2, which2) -> {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text/plain", message);
clipboard.setPrimaryClip(clip);
Toast.makeText(this, R.string.errors_copied, Toast.LENGTH_SHORT).show();
})
.create());
}
private void showWipeEntriesDialog() {
Dialogs.showCheckboxDialog(this, R.string.dialog_wipe_entries_title,
R.string.dialog_wipe_entries_message,

View file

@ -4,10 +4,15 @@ import android.Manifest;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.StyleSpan;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
@ -38,6 +43,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
@ -275,17 +281,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private void onScanResult(Intent data) {
List<VaultEntry> entries = (ArrayList<VaultEntry>) data.getSerializableExtra("entries");
if (entries.size() == 1) {
startEditEntryActivityForNew(CODE_ADD_ENTRY, entries.get(0));
} else {
for (VaultEntry entry : entries) {
_vaultManager.getVault().addEntry(entry);
if (_loaded) {
_entryListView.addEntry(entry);
}
}
saveAndBackupVault();
if (entries != null) {
importScannedEntries(entries);
}
}
@ -311,25 +308,78 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
private void onScanImageResult(Intent intent) {
startDecodeQrCodeImage(intent.getData());
if (intent.getData() != null) {
startDecodeQrCodeImages(Collections.singletonList(intent.getData()));
return;
}
if (intent.getClipData() != null) {
ClipData data = intent.getClipData();
List<Uri> uris = new ArrayList<>();
for (int i = 0; i < data.getItemCount(); i++) {
ClipData.Item item = data.getItemAt(i);
if (item.getUri() != null) {
uris.add(item.getUri());
}
}
if (uris.size() > 0) {
startDecodeQrCodeImages(uris);
}
}
}
private void startDecodeQrCodeImage(Uri uri) {
QrDecodeTask task = new QrDecodeTask(this, (result) -> {
if (result.getException() != null) {
Dialogs.showErrorDialog(this, R.string.unable_to_read_qrcode, result.getException());
return;
private static CharSequence buildImportError(String fileName, Throwable e) {
SpannableStringBuilder builder = new SpannableStringBuilder(String.format("%s:\n%s", fileName, e));
builder.setSpan(new StyleSpan(Typeface.BOLD), 0, fileName.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return builder;
}
private void startDecodeQrCodeImages(List<Uri> uris) {
QrDecodeTask task = new QrDecodeTask(this, (results) -> {
List<CharSequence> errors = new ArrayList<>();
List<VaultEntry> entries = new ArrayList<>();
for (QrDecodeTask.Result res : results) {
if (res.getException() != null) {
errors.add(buildImportError(res.getFileName(), res.getException()));
continue;
}
try {
GoogleAuthInfo info = GoogleAuthInfo.parseUri(res.getResult().getText());
VaultEntry entry = new VaultEntry(info);
entries.add(entry);
} catch (GoogleAuthInfoException e) {
errors.add(buildImportError(res.getFileName(), e));
}
}
try {
GoogleAuthInfo info = GoogleAuthInfo.parseUri(result.getResult().getText());
VaultEntry entry = new VaultEntry(info);
startEditEntryActivityForNew(CODE_ADD_ENTRY, entry);
} catch (GoogleAuthInfoException e) {
Dialogs.showErrorDialog(this, R.string.unable_to_read_qrcode, e);
final DialogInterface.OnClickListener dialogDismissHandler = (dialog, which) -> importScannedEntries(entries);
if ((errors.size() > 0 && results.size() > 1) || errors.size() > 1) {
Dialogs.showMultiMessageDialog(this, R.string.import_error_title, getString(R.string.unable_to_read_qrcode_files, uris.size() - errors.size(), uris.size()), errors, dialogDismissHandler);
} else if (errors.size() > 0) {
Dialogs.showErrorDialog(this, getString(R.string.unable_to_read_qrcode_file, results.get(0).getFileName()), errors.get(0), dialogDismissHandler);
} else {
importScannedEntries(entries);
}
});
task.execute(getLifecycle(), uri);
task.execute(getLifecycle(), uris);
}
private void importScannedEntries(List<VaultEntry> entries) {
if (entries.size() == 1) {
startEditEntryActivityForNew(CODE_ADD_ENTRY, entries.get(0));
} else if (entries.size() > 1) {
for (VaultEntry entry: entries) {
_vaultManager.getVault().addEntry(entry);
_entryListView.addEntry(entry);
}
if (saveAndBackupVault()) {
Toast.makeText(this, getResources().getQuantityString(R.plurals.added_new_entries, entries.size(), entries.size()), Toast.LENGTH_LONG).show();
}
}
}
private void updateSortCategoryMenu() {
@ -368,9 +418,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private void startScanImageActivity() {
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
galleryIntent.setDataAndType(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
Intent fileIntent = new Intent(Intent.ACTION_GET_CONTENT);
fileIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
fileIntent.setType("image/*");
Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.select_picture));
@ -421,7 +473,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
info = GoogleAuthInfo.parseUri(uri);
} catch (GoogleAuthInfoException e) {
e.printStackTrace();
Dialogs.showErrorDialog(this, R.string.unable_to_read_qrcode, e);
Dialogs.showErrorDialog(this, R.string.unable_to_process_deeplink, e);
}
if (info != null) {
@ -444,7 +496,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
intent.setAction(null);
intent.removeExtra(Intent.EXTRA_STREAM);
startDecodeQrCodeImage(uri);
startDecodeQrCodeImages(Collections.singletonList(uri));
}
}

View file

@ -10,6 +10,7 @@ import android.content.res.ColorStateList;
import android.graphics.Color;
import android.text.Editable;
import android.text.InputType;
import android.text.SpannableStringBuilder;
import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod;
import android.view.LayoutInflater;
@ -24,6 +25,7 @@ import android.widget.ListView;
import android.widget.NumberPicker;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.ComponentActivity;
import androidx.annotation.StringRes;
@ -44,6 +46,7 @@ import com.google.android.material.textfield.TextInputLayout;
import com.nulabinc.zxcvbn.Strength;
import com.nulabinc.zxcvbn.Zxcvbn;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@ -340,6 +343,10 @@ public class Dialogs {
showErrorDialog(context, message, e, null);
}
public static void showErrorDialog(Context context, String message, Exception e) {
showErrorDialog(context, message, e, null);
}
public static void showErrorDialog(Context context, @StringRes int message, CharSequence error) {
showErrorDialog(context, message, error, null);
}
@ -348,7 +355,15 @@ public class Dialogs {
showErrorDialog(context, message, e.toString(), listener);
}
public static void showErrorDialog(Context context, String message, Exception e, DialogInterface.OnClickListener listener) {
showErrorDialog(context, message, e.toString(), listener);
}
public static void showErrorDialog(Context context, @StringRes int message, CharSequence error, DialogInterface.OnClickListener listener) {
showErrorDialog(context, context.getString(message), error, listener);
}
public static void showErrorDialog(Context context, String message, CharSequence error, DialogInterface.OnClickListener listener) {
View view = LayoutInflater.from(context).inflate(R.layout.dialog_error, null);
TextView textDetails = view.findViewById(R.id.error_details);
textDetails.setText(error);
@ -388,6 +403,66 @@ public class Dialogs {
Dialogs.showSecureDialog(dialog);
}
public static void showMultiMessageDialog(
Context context, @StringRes int title, String message, List<CharSequence> messages, DialogInterface.OnClickListener listener) {
Dialogs.showSecureDialog(new AlertDialog.Builder(context)
.setTitle(title)
.setMessage(message)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
if (listener != null) {
listener.onClick(dialog, which);
}
})
.setNeutralButton(context.getString(R.string.details), (dialog, which) -> {
showDetailedMultiMessageDialog(context, title, messages, listener);
})
.create());
}
public static <T extends Throwable> void showMultiErrorDialog(
Context context, @StringRes int title, String message, List<T> errors, DialogInterface.OnClickListener listener) {
List<CharSequence> messages = new ArrayList<>();
for (Throwable e : errors) {
messages.add(e.toString());
}
showMultiMessageDialog(context, title, message, messages, listener);
}
private static void showDetailedMultiMessageDialog(
Context context, @StringRes int title, List<CharSequence> messages, DialogInterface.OnClickListener listener) {
SpannableStringBuilder builder = new SpannableStringBuilder();
for (CharSequence message : messages) {
builder.append(message);
builder.append("\n\n");
}
AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle(title)
.setMessage(builder)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, (dialog1, which) -> {
if (listener != null) {
listener.onClick(dialog1, which);
}
})
.setNeutralButton(android.R.string.copy, null)
.create();
dialog.setOnShowListener(d -> {
Button button = dialog.getButton(AlertDialog.BUTTON_NEUTRAL);
button.setOnClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text/plain", builder.toString());
clipboard.setPrimaryClip(clip);
Toast.makeText(context, R.string.errors_copied, Toast.LENGTH_SHORT).show();
});
});
Dialogs.showSecureDialog(dialog);
}
public static void showTimeSyncWarningDialog(Context context, Dialog.OnClickListener listener) {
Preferences prefs = new Preferences(context);
View view = LayoutInflater.from(context).inflate(R.layout.dialog_time_sync, null);

View file

@ -5,12 +5,14 @@ import android.net.Uri;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.helpers.QrCodeHelper;
import com.google.zxing.Result;
import com.beemdevelopment.aegis.helpers.SafHelper;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class QrDecodeTask extends ProgressDialogTask<Uri, QrDecodeTask.Response> {
public class QrDecodeTask extends ProgressDialogTask<List<Uri>, List<QrDecodeTask.Result>> {
private final Callback _cb;
public QrDecodeTask(Context context, Callback cb) {
@ -19,39 +21,61 @@ public class QrDecodeTask extends ProgressDialogTask<Uri, QrDecodeTask.Response>
}
@Override
protected Response doInBackground(Uri... params) {
protected List<Result> doInBackground(List<Uri>... params) {
List<Result> res = new ArrayList<>();
Context context = getDialog().getContext();
Uri uri = params[0];
try (InputStream inStream = context.getContentResolver().openInputStream(uri)) {
Result result = QrCodeHelper.decodeFromStream(inStream);
return new Response(result, null);
} catch (QrCodeHelper.DecodeError | IOException e) {
e.printStackTrace();
return new Response(null, e);
List<Uri> uris = params[0];
for (Uri uri : uris) {
String fileName = SafHelper.getFileName(context, uri);
if (uris.size() > 1) {
publishProgress(context.getString(R.string.analyzing_qr_multiple, uris.indexOf(uri) + 1, uris.size(), fileName));
}
try (InputStream inStream = context.getContentResolver().openInputStream(uri)) {
com.google.zxing.Result result = QrCodeHelper.decodeFromStream(inStream);
res.add(new Result(uri, fileName, result, null));
} catch (QrCodeHelper.DecodeError | IOException e) {
e.printStackTrace();
res.add(new Result(uri, fileName, null, e));
}
}
return res;
}
@Override
protected void onPostExecute(Response result) {
super.onPostExecute(result);
_cb.onTaskFinished(result);
protected void onPostExecute(List<Result> results) {
super.onPostExecute(results);
_cb.onTaskFinished(results);
}
public interface Callback {
void onTaskFinished(Response result);
void onTaskFinished(List<Result> results);
}
public static class Response {
private final Result _result;
public static class Result {
private final Uri _uri;
private final String _fileName;
private final com.google.zxing.Result _result;
private final Exception _e;
public Response(Result result, Exception e) {
public Result(Uri uri, String fileName, com.google.zxing.Result result, Exception e) {
_uri = uri;
_fileName = fileName;
_result = result;
_e = e;
}
public Result getResult() {
public Uri getUri() {
return _uri;
}
public String getFileName() {
return _fileName;
}
public com.google.zxing.Result getResult() {
return _result;
}

View file

@ -201,7 +201,6 @@
<string name="small_mode_title">صغير</string>
<string name="unknown_issuer">مصدِّر غير معروف</string>
<string name="unknown_account_name">اسم الحساب غير معروف</string>
<string name="unable_to_read_qrcode">تعذّر قراءة ومعالجة كود QR</string>
<string name="unable_to_generate_qrcode">تعذّر توليد كود QR</string>
<string name="select_picture">اختر صورة</string>
<string name="select_icon">اختر أيقونة</string>

View file

@ -226,7 +226,6 @@
<item quantity="one">Aegis не можа да импортира %d токен. Тези токени ще бъдат пропуснати. Натиснете „подробности“, за да видите повече информация за грешките.</item>
<item quantity="other">Aegis не можа да импортира %d токена. Тези токени ще бъдат пропуснати. Натиснете „подробности“, за да видите повече информация за грешките.</item>
</plurals>
<string name="unable_to_read_qrcode">Не може да се прочете и обработи QR код</string>
<string name="unable_to_generate_qrcode">Не може да се генерира QR код</string>
<string name="select_picture">Изберете картина</string>
<string name="select_icon">Изберете икона</string>

View file

@ -231,7 +231,6 @@
<string name="small_mode_title">Malý</string>
<string name="unknown_issuer">Neznámý poskytovatel</string>
<string name="unknown_account_name">Neznámý název účtu</string>
<string name="unable_to_read_qrcode">QR kód nelze přečíst a zpracovat</string>
<string name="unable_to_generate_qrcode">QR kód nelze vygenerovat</string>
<string name="select_picture">Vyberte obrázek</string>
<string name="select_icon">Vybrat ikonu</string>

View file

@ -251,7 +251,6 @@
<item quantity="one">Aegis kunne ikke importere %d token. Disse tokens vil blive sprunget over. Tryk \'detaljer\' for at se mere information om fejlene.</item>
<item quantity="other">Aegis kunne ikke importere %d tokens. Disse tokens vil blive sprunget over. Tryk \'detaljer\' for at se mere information om fejlene.</item>
</plurals>
<string name="unable_to_read_qrcode">Kan ikke læse og behandle QR-kode</string>
<string name="unable_to_generate_qrcode">Kan ikke generere QR-kode</string>
<string name="select_picture">Vælg billede</string>
<string name="select_icon">Vælg ikon</string>

View file

@ -251,7 +251,6 @@
<item quantity="one">Aegis konnte %d Token nicht importieren. Dieser Token wird übersprungen. Klicke auf Details, um weitere Informationen über die Fehler zu erhalten.</item>
<item quantity="other">Aegis konnte %d Token nicht importieren. Diese Token werden übersprungen. Klicke auf Details, um weitere Informationen über die Fehler zu erhalten.</item>
</plurals>
<string name="unable_to_read_qrcode">QR-Code kann nicht gelesen und verarbeitet werden</string>
<string name="unable_to_generate_qrcode">QR-Code kann nicht generiert werden</string>
<string name="select_picture">Bild auswählen</string>
<string name="select_icon">Symbol auswählen</string>

View file

@ -251,7 +251,6 @@
<item quantity="one">Το Aegis δεν μπόρεσε να εισαγάγει %d αναγνωριστικό. Αυτά θα παραληφθούν. Πατήστε \'Λεπτομέρειες\' για να δείτε περισσότερες πληροφορίες σχετικά με τα σφάλματα.</item>
<item quantity="other">Το Aegis δεν μπόρεσε να εισαγάγει %d αναγνωριστικά. Αυτά θα παραληφθούν. Πατήστε \'Λεπτομέρειες\' για να δείτε περισσότερες πληροφορίες σχετικά με τα σφάλματα.</item>
</plurals>
<string name="unable_to_read_qrcode">Αδυναμία ανάγνωσης και επεξεργασίας κωδικού QR</string>
<string name="unable_to_generate_qrcode">Δεν είναι δυνατή η δημιουργία κωδικού QR</string>
<string name="select_picture">Επιλέξτε εικόνα</string>
<string name="select_icon">Επιλέξτε εικονίδιο</string>

View file

@ -250,7 +250,6 @@
<item quantity="one">Aegis no pudo importar %d tokens. Estos tokens se omitirán. Pulse \'detalles\' para ver más información sobre los errores.</item>
<item quantity="other">Aegis no pudo importar %d tokens. Estos tokens se omitirán. Pulse \'detalles\' para ver más información sobre los errores.</item>
</plurals>
<string name="unable_to_read_qrcode">No se puede leer y procesar el código QR</string>
<string name="unable_to_generate_qrcode">No se puede generar el código QR</string>
<string name="select_picture">Seleccionar imagen</string>
<string name="select_icon">Seleccionar icono</string>

View file

@ -248,7 +248,6 @@
<item quantity="one">Aegisek ezin izan du %d token inportatu. Token hori kendu egingo da. Sakatu \'Xehetasunak\' akatsei buruzko informazio gehiago ikusteko.</item>
<item quantity="other">Aegisek ezin izan du %d token inportatu. Token horiek kendu egingo dira. Sakatu \'Xehetasunak\' akatsei buruzko informazio gehiago ikusteko.</item>
</plurals>
<string name="unable_to_read_qrcode">Ez da gai QR kodea irakurtzeko eta prozesatzeko</string>
<string name="unable_to_generate_qrcode">Ezin da QR kodea sortu</string>
<string name="select_picture">Aukeratu irudia</string>
<string name="select_icon">Aukeratu ikonoa</string>

View file

@ -222,7 +222,6 @@
<item quantity="one">%d توکن اضافه نشدند. برای اطلاعات بیشتر از دکمه جزییات استفاده نمایید.</item>
<item quantity="other">%d توکن اضافه نشدند. برای اطلاعات بیشتر از دکمه جزییات استفاده نمایید.</item>
</plurals>
<string name="unable_to_read_qrcode">عدم توانایی در خواندن و پردازش بارکد دوبعدی</string>
<string name="unable_to_generate_qrcode">عدم توانایی در ساخت بارکد دوبعدی</string>
<string name="select_picture">انتخاب تصویر</string>
<string name="select_icon">انتخاب آیکون</string>

View file

@ -239,7 +239,6 @@
<item quantity="one">Aegis ei voinut tuoda %d tunnusta. Nämä tunnukset ohitetaan. Paina \'tiedot\' nähdäksesi lisätietoa virheistä.</item>
<item quantity="other">Aegis ei voinut tuoda %d tunnusta. Nämä tunnukset ohitetaan. Paina \'tiedot\' nähdäksesi lisätietoa virheistä.</item>
</plurals>
<string name="unable_to_read_qrcode">QR-koodia ei voi lukea ja käsitellä</string>
<string name="unable_to_generate_qrcode">QR-koodia ei voitu luoda</string>
<string name="select_picture">Valitse kuva</string>
<string name="select_icon">Valitse kuvake</string>

View file

@ -251,7 +251,6 @@
<item quantity="one">Aegis n\'a pas pu importer %d jeton. Ce jeton sera ignoré. Appuyez sur « détails» pour voir plus d\'informations sur les erreurs.</item>
<item quantity="other">Aegis n\'a pas pu importer %d jetons. Ces jetons seront ignorés. Appuyez sur \'details\' pour voir plus d\'informations sur les erreurs.</item>
</plurals>
<string name="unable_to_read_qrcode">Impossible de lire et traiter le code QR</string>
<string name="unable_to_generate_qrcode">Impossible de générer le code QR</string>
<string name="select_picture">Sélectionner image</string>
<string name="select_icon">Sélectionner une icône</string>

View file

@ -188,7 +188,6 @@
<item quantity="one">Aegis %d टोकन आयात नहीं कर सका। इन टोकन को छोड़ दिया जाएगा। त्रुटियों के बारे में अधिक जानकारी देखने के लिए \'विवरण\' दबाएँ।</item>
<item quantity="other">Aegis %d टोकनों को आयात नहीं कर सका। इन टोकनों को छोड़ दिया जाएगा। त्रुटियों के बारे में अधिक जानकारी देखने के लिए \'विवरण\' दबाएँ।</item>
</plurals>
<string name="unable_to_read_qrcode">क्यूआर कोड को पढ़ने और प्रोसेस करने में असमर्थ</string>
<string name="unable_to_generate_qrcode">क्यूआर कोड उत्पन्न करने में असमर्थ</string>
<string name="select_picture">छवि चयन करें</string>
<string name="select_icon">आइकन चयन करें</string>

View file

@ -147,7 +147,6 @@
<string name="small_mode_title">Apró</string>
<string name="unknown_issuer">Ismeretlen kibocsátó</string>
<string name="unknown_account_name">Ismeretlen fióknév</string>
<string name="unable_to_read_qrcode">A QR-kód nem olvasható be és dolgozható fel</string>
<string name="select_picture">Válasszon képet</string>
<string name="toggle_checkboxes">Jelölőmezők be/ki</string>
<string name="search">Keresés</string>

View file

@ -243,7 +243,6 @@
<plurals name="import_error_dialog">
<item quantity="other">Aegis tidak dapat mengimpor %d token. Token ini akan dilewati. Tekan \'detail\' untuk melihat informasi lebih lanjut tentang kesalahan tersebut.</item>
</plurals>
<string name="unable_to_read_qrcode">Tidak dapat membaca dan memproses kode QR</string>
<string name="unable_to_generate_qrcode">Tidak dapat membuat kode QR</string>
<string name="select_picture">Pilih gambar</string>
<string name="select_icon">Pilih ikon</string>

View file

@ -251,7 +251,6 @@
<item quantity="one">Aegis non può importare %d token. Questi token verranno saltati. Premi \'dettagli\' per vedere ulteriori informazioni sugli errori.</item>
<item quantity="other">Aegis non può importare %d token. Questi token verranno saltati. Premi \'dettagli\' per vedere ulteriori informazioni sugli errori.</item>
</plurals>
<string name="unable_to_read_qrcode">Impossibile verificare il codice QR</string>
<string name="unable_to_generate_qrcode">Impossibile generare il codice QR</string>
<string name="select_picture">Seleziona immagine</string>
<string name="select_icon">Seleziona icona</string>

View file

@ -243,7 +243,6 @@
<plurals name="import_error_dialog">
<item quantity="other">Aegisは %d 個のトークンをインポートできませんでした。これらのトークンはスキップされます。エラーの詳細を見るには「詳細」を押してください。</item>
</plurals>
<string name="unable_to_read_qrcode">QRコードを読み取りできません</string>
<string name="unable_to_generate_qrcode">QRコードを生成できません</string>
<string name="select_picture">画像を選択</string>
<string name="select_icon">アイコンを選択</string>

View file

@ -101,7 +101,6 @@
<string name="small_mode_title">ಸಣ್ಣ</string>
<string name="unknown_issuer">ಗೊತ್ತಿಲ್ಲದಿರುವ ನೀಡುವವರು</string>
<string name="unknown_account_name">ಗೊತ್ತಿಲ್ಲದಿರುವ ಖಾತೆಯ ಹೆಸರು</string>
<string name="unable_to_read_qrcode">ಕ್ಯೂ ಆರ್ ಸಂಕೇತವನ್ನು ಓದವುದಕ್ಕೆ ಹಾಗು ಪ್ರಕ್ರಿಯಿಸುವುದಕ್ಕೆ ಸಾಧ್ಯವಾಗಲಿಲ್ಲ</string>
<string name="select_picture">ಚಿತ್ರವನ್ನು ಆಯ್ಕೆ ಮಾಡು</string>
<string name="toggle_checkboxes">ಚೆಕ್ಬಾಕ್ಸ್ಗಳನ್ನು ಟಾಗಲ್ ಮಾಡು</string>
<string name="search">ಹುಡುಕು</string>

View file

@ -179,7 +179,6 @@
<string name="compact_mode_title">Kompaktiška</string>
<string name="small_mode_title">Maža</string>
<string name="unknown_issuer">Nežinomas leidėjas</string>
<string name="unable_to_read_qrcode">Nepavyko perskaityti ir apdoroti QR kodo</string>
<string name="unable_to_generate_qrcode">Nepavyko sukurti QR kodo</string>
<string name="vault_unlocked_state">Slėptuvė atrakinta. Bakstelėkite čia, norėdami užrakinti.</string>
<string name="version">Versija</string>

View file

@ -256,7 +256,6 @@
<item quantity="one">Aegis nevarēja ievietot %d kodu. Tas tiks izlaists. Spiest uz \"Izklāsts\", lai redzētu vairāk informācijas par kļūdām.</item>
<item quantity="other">Aegis nevarēja ievietot %d kodus. Tie tiks izlaisti. Spiest uz \"Izklāsts\", lai redzētu vairāk informācijas par kļūdām.</item>
</plurals>
<string name="unable_to_read_qrcode">Nav iespējams nolasīt un apstrādāt kvadrātkodu</string>
<string name="unable_to_generate_qrcode">Nav iespējams izveidot kvadrātkodu</string>
<string name="select_picture">Atlasīt attēlu</string>
<string name="select_icon">Atlasīt ikonu</string>

View file

@ -251,7 +251,6 @@
<item quantity="one">Aegis kon %d token niet importeren. Dit token zal worden overgeslagen. Druk op \'details\' om meer informatie over de fouten te zien.</item>
<item quantity="other">Aegis kon %d tokens niet importeren. Deze tokens zullen worden overgeslagen. Druk op \'details\' om meer informatie over de fouten te zien.</item>
</plurals>
<string name="unable_to_read_qrcode">Kan QR-code niet lezen en verwerken</string>
<string name="unable_to_generate_qrcode">Kan de QR-code niet genereren</string>
<string name="select_picture">Foto selecteren</string>
<string name="select_icon">Icoon selecteren</string>

View file

@ -252,7 +252,6 @@
<item quantity="many">Aplikacja Aegis nie może zaimportować %d tokenów. Te tokeny zostaną pominięte. Kliknij \'Szczegóły\', aby zobaczyć więcej informacji o błędach.</item>
<item quantity="other">Aplikacja Aegis nie może zaimportować %d tokenów. Te tokeny zostaną pominięte. Kliknij \'Szczegóły\', aby zobaczyć więcej informacji o błędach.</item>
</plurals>
<string name="unable_to_read_qrcode">Nie można odczytać i przetworzyć kodu QR</string>
<string name="unable_to_generate_qrcode">Nie udało się wygenerować kodu QR</string>
<string name="select_picture">Wybierz obraz</string>
<string name="select_icon">Wybierz ikonę</string>

View file

@ -251,7 +251,6 @@
<item quantity="one">Aegis não pôde importar %d token. Esse token será ignorado. Pressione \'detalhes\' para ver mais informações sobre o erro.</item>
<item quantity="other">Aegis não pôde importar %d tokens. Esses tokens serão pulados. Pressione \'detalhes\' para ver mais informações sobre os erros.</item>
</plurals>
<string name="unable_to_read_qrcode">Não foi possível ler e processar o QR code</string>
<string name="unable_to_generate_qrcode">Não foi possível gerar o QR code</string>
<string name="select_picture">Selecionar imagem</string>
<string name="select_icon">Selecionar ícone</string>

View file

@ -197,7 +197,6 @@
<item quantity="one">Aegis não pôde importar %d token. Esse token será ignorado. Pressione \'detalhes\' para ver mais informações sobre o erro.</item>
<item quantity="other">Aegis não pôde importar %d tokens. Esses tokens serão pulados. Pressione \'detalhes\' para ver mais informações sobre os erros.</item>
</plurals>
<string name="unable_to_read_qrcode">Incapaz de ler e processar o código QR</string>
<string name="unable_to_generate_qrcode">Não foi possível gerar o código QR</string>
<string name="select_picture">Selecionar imagem</string>
<string name="select_icon">Selecionar ícone</string>

View file

@ -256,7 +256,6 @@
<item quantity="few">Aegis nu a putut importa %d token. Aceste token-uri vor fi sărite. Apăsați \'detalii\' pentru a vedea mai multe informații despre erori.</item>
<item quantity="other">Aegis nu a putut importa %d token-rui. Aceste token-uri vor fi sărite. Apăsați \'detalii\' pentru a vedea mai multe informații despre erori.</item>
</plurals>
<string name="unable_to_read_qrcode">Nu se poate citi și prelucra codul QR</string>
<string name="unable_to_generate_qrcode">Nu se poate genera codul QR</string>
<string name="select_picture">Selectare imagine</string>
<string name="select_icon">Selectare pictogramă</string>

View file

@ -261,7 +261,6 @@
<item quantity="many">Aegis не удалось импортировать %d ключей. Эти ключи будут пропущены. Нажмите «Подробнее», чтобы увидеть дополнительную информацию об ошибках.</item>
<item quantity="other">Aegis не удалось импортировать %d ключей. Эти ключи будут пропущены. Нажмите «Подробнее», чтобы увидеть дополнительную информацию об ошибках.</item>
</plurals>
<string name="unable_to_read_qrcode">Не удалось прочитать и обработать QR-код</string>
<string name="unable_to_generate_qrcode">Не удаётся сгенерировать QR-код</string>
<string name="select_picture">Выберите изображение</string>
<string name="select_icon">Выберите значок</string>

View file

@ -249,7 +249,6 @@
<item quantity="one">Aegis %d kodunu içe aktaramadı. Bu kodlar atlanacaktır. Hatalar hakkında daha detaylı bilgi için lütfen \'ayrıntılar\'a basınız.</item>
<item quantity="other">Aegis %d kodunu içe aktaramadı. Bu kodlar atlanacaktır. Hatalar hakkında daha detaylı bilgi için lütfen \'ayrıntılar\'a basınız.</item>
</plurals>
<string name="unable_to_read_qrcode">QR kod okunamadı ve işlenemedi</string>
<string name="unable_to_generate_qrcode">QR Kodu oluşturulamadı</string>
<string name="select_picture">Resim seç</string>
<string name="select_icon">Simge seç</string>

View file

@ -234,7 +234,6 @@
<item quantity="many">Aegis не вдалося імпортувати %d токенів. Ці токени буде пропущено. Натисніть \'деталі\', щоб побачити більше інформації про помилки.</item>
<item quantity="other">Aegis не вдалося імпортувати %d токенів. Ці токени буде пропущено. Натисніть \'деталі\', щоб побачити більше інформації про помилки.</item>
</plurals>
<string name="unable_to_read_qrcode">Не вдається прочитати і обробити QR-код</string>
<string name="unable_to_generate_qrcode">Не вдається згенерувати QR-код</string>
<string name="select_picture">Виберіть зображення</string>
<string name="select_icon">Виберіть іконку</string>

View file

@ -246,7 +246,6 @@
<plurals name="import_error_dialog">
<item quantity="other">Aegis không thể nhập %d token. Các token đó sẽ bị bỏ qua. Hãy nhấn \'Chi tiết\' để xem thêm thông tin về các lỗi.</item>
</plurals>
<string name="unable_to_read_qrcode">Không thể đọc và xử lý mã QR</string>
<string name="unable_to_generate_qrcode">Không thể tạo mã QR</string>
<string name="select_picture">Chọn ảnh</string>
<string name="select_icon">Chọn biểu tượng</string>

View file

@ -246,7 +246,6 @@
<plurals name="import_error_dialog">
<item quantity="other">Aegis 无法导入 %d 个令牌。这些令牌将被忽略。点击“详细信息”查看更多关于错误的信息。</item>
</plurals>
<string name="unable_to_read_qrcode">无法读取和处理二维码</string>
<string name="unable_to_generate_qrcode">无法生成二维码</string>
<string name="select_picture">选择图片</string>
<string name="select_icon">选择图标</string>

View file

@ -211,7 +211,6 @@
<plurals name="import_error_dialog">
<item quantity="other">Aegis 已忽略無法匯入的 %d 個憑證。如欲了解更詳細的錯誤訊息,請按「詳細內容」。</item>
</plurals>
<string name="unable_to_read_qrcode">無法讀取和處理QR碼</string>
<string name="unable_to_generate_qrcode">無法生成QR碼</string>
<string name="select_picture">選擇圖片</string>
<string name="select_icon">選擇圖示</string>

View file

@ -158,6 +158,11 @@
<string name="exporting_vault">Exporting the vault</string>
<string name="reading_file">Reading file</string>
<string name="analyzing_qr">Analyzing QR code</string>
<string name="analyzing_qr_multiple">Analyzing QR code %d/%d (%s)</string>
<plurals name="added_new_entries">
<item quantity="one">Added %d new entry to the vault</item>
<item quantity="other">Added %d new entries to the vault</item>
</plurals>
<string name="importing_icon_pack">Importing icon pack</string>
<string name="delete_entry">Delete entry</string>
<string name="delete_entry_description">Are you sure you want to delete this entry?</string>
@ -282,7 +287,9 @@
<item quantity="one">Aegis could not import %d token. These tokens will be skipped. Press \'details\' to see more information about the errors.</item>
<item quantity="other">Aegis could not import %d tokens. These tokens will be skipped. Press \'details\' to see more information about the errors.</item>
</plurals>
<string name="unable_to_read_qrcode">Unable to read and process QR code</string>
<string name="unable_to_process_deeplink">Unable to process deep link</string>
<string name="unable_to_read_qrcode_file">Unable to read and process QR code from file: %s.</string>
<string name="unable_to_read_qrcode_files">Unable to read and process some of the QR codes. Only %d/%d entries will be imported.</string>
<string name="unable_to_generate_qrcode">Unable to generate QR code</string>
<string name="select_picture">Select picture</string>
<string name="select_icon">Select icon</string>