diff --git a/.travis.yml b/.travis.yml index d2392afab..247e8fd9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ android: - '.+' before_script: - mkdir libs - - wget -O libs/libwebrtc-m83.aar http://gultsch.de/files/libwebrtc-m83.aar + - wget -O libs/libwebrtc-m84.aar http://gultsch.de/files/libwebrtc-m84.aar script: - ./gradlew assembleConversationsFreeSystemRelease - ./gradlew assembleQuicksyFreeCompatRelease diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cf05cfaa..a24790fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog +### Version 2.8.9 + +* add 'Return to chat' to audio call screen +* Improve keyboard shortcuts +* bug fixes + ### Version 2.8.8 + * Fixed notifications not showing up under certain conditions * Fixed compatibility issues and crashes related to A/V calls @@ -33,7 +40,7 @@ ### Version 2.8.2 -* Add button to switch camea during video call +* Add button to switch camera during video call * Fixed voice calls on tablets ### Version 2.8.1 diff --git a/build.gradle b/build.gradle index 1703a7854..958444bd3 100644 --- a/build.gradle +++ b/build.gradle @@ -96,8 +96,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 25 - versionCode 394 - versionName "2.8.8" + versionCode 395 + versionName "2.8.9" archivesBaseName += "-$versionName" applicationId "eu.sum7.conversations" resValue "string", "applicationId", applicationId @@ -232,7 +232,7 @@ android { } lintOptions { - disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity', 'AppCompatResource' + disable 'MissingTranslation', 'InvalidPackage', 'AppCompatResource' abortOnError false } diff --git a/metadata/en-US/changelogs/393.txt b/metadata/en-US/changelogs/393.txt index 82250ee87..18107a020 100644 --- a/metadata/en-US/changelogs/393.txt +++ b/metadata/en-US/changelogs/393.txt @@ -1,3 +1,3 @@ -* Show help button if A/V call fails -* Fixed some annoying crashes -* Fixed Jingle connections (file transfer + calls) with bare JIDs +• Show help button if A/V call fails +• Fixed some annoying crashes +• Fixed Jingle connections (file transfer + calls) with bare JIDs diff --git a/metadata/en-US/changelogs/395.txt b/metadata/en-US/changelogs/395.txt new file mode 100644 index 000000000..032fe93df --- /dev/null +++ b/metadata/en-US/changelogs/395.txt @@ -0,0 +1,3 @@ +• add 'Return to chat' to audio call screen +• Improve keyboard shortcuts +• bug fixes diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java index 8596b1c3f..e50179c8d 100644 --- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.services; +import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; @@ -10,9 +11,21 @@ import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.Binder; import android.os.IBinder; +import android.provider.OpenableColumns; import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; import android.util.Log; +import com.google.common.base.Charsets; +import com.google.common.io.CountingInputStream; + +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.io.CipherInputStream; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; + import java.io.BufferedReader; import java.io.DataInputStream; import java.io.File; @@ -33,10 +46,6 @@ import java.util.zip.GZIPInputStream; import java.util.zip.ZipException; import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -44,14 +53,9 @@ import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.ui.ManageAccountActivity; import eu.siacs.conversations.utils.BackupFileHeader; -import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.SerialSingleThreadExecutor; import eu.siacs.conversations.xmpp.Jid; -import static eu.siacs.conversations.services.ExportBackupService.CIPHERMODE; -import static eu.siacs.conversations.services.ExportBackupService.KEYTYPE; -import static eu.siacs.conversations.services.ExportBackupService.PROVIDER; - public class ImportBackupService extends Service { private static final int NOTIFICATION_ID = 21; @@ -117,9 +121,9 @@ public class ImportBackupService extends Service { return running.get(); } - public void loadBackupFiles(OnBackupFilesLoaded onBackupFilesLoaded) { + public void loadBackupFiles(final OnBackupFilesLoaded onBackupFilesLoaded) { executor.execute(() -> { - List accounts = mDatabaseBackend.getAccountJids(false); + final List accounts = mDatabaseBackend.getAccountJids(false); final ArrayList backupFiles = new ArrayList<>(); final Set apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name))); for (String app : apps) { @@ -128,7 +132,12 @@ public class ImportBackupService extends Service { Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath()); continue; } - for (File file : directory.listFiles()) { + final File[] files = directory.listFiles(); + if (files == null) { + onBackupFilesLoaded.onBackupFilesLoaded(backupFiles); + return; + } + for (final File file : files) { if (file.isFile() && file.getName().endsWith(".ceb")) { try { final BackupFile backupFile = BackupFile.read(file); @@ -149,24 +158,67 @@ public class ImportBackupService extends Service { } private void startForegroundService() { + startForeground(NOTIFICATION_ID, createImportBackupNotification(1, 0)); + } + + private void updateImportBackupNotification(final long total, final long current) { + final int max; + final int progress; + if (total == 0) { + max = 1; + progress = 0; + } else { + max = 100; + progress = (int) (current * 100 / total); + } + final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); + try { + notificationManager.notify(NOTIFICATION_ID, createImportBackupNotification(max, progress)); + } catch (final RuntimeException e) { + Log.d(Config.LOGTAG, "unable to make notification", e); + } + } + + private Notification createImportBackupNotification(final int max, final int progress) { NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup"); mBuilder.setContentTitle(getString(R.string.restoring_backup)) .setSmallIcon(R.drawable.ic_unarchive_white_24dp) - .setProgress(1, 0, true); - startForeground(NOTIFICATION_ID, mBuilder.build()); + .setProgress(max, progress, max == 1 && progress == 0); + return mBuilder.build(); } - private boolean importBackup(Uri uri, String password) { + private boolean importBackup(final Uri uri, final String password) { Log.d(Config.LOGTAG, "importing backup from " + uri); try { - SQLiteDatabase db = mDatabaseBackend.getWritableDatabase(); + final SQLiteDatabase db = mDatabaseBackend.getWritableDatabase(); final InputStream inputStream; - if ("file".equals(uri.getScheme())) { - inputStream = new FileInputStream(new File(uri.getPath())); + final String path = uri.getPath(); + final long fileSize; + if ("file".equals(uri.getScheme()) && path != null) { + final File file = new File(path); + inputStream = new FileInputStream(file); + fileSize = file.length(); } else { + final Cursor returnCursor = getContentResolver().query(uri, null, null, null, null); + if (returnCursor == null) { + fileSize = 0; + } else { + returnCursor.moveToFirst(); + fileSize = returnCursor.getLong(returnCursor.getColumnIndex(OpenableColumns.SIZE)); + returnCursor.close(); + } inputStream = getContentResolver().openInputStream(uri); } - final DataInputStream dataInputStream = new DataInputStream(inputStream); + if (inputStream == null) { + synchronized (mOnBackupProcessedListeners) { + for (final OnBackupProcessed l : mOnBackupProcessedListeners) { + l.onBackupRestoreFailed(); + } + } + return false; + } + final CountingInputStream countingInputStream = new CountingInputStream(inputStream); + final DataInputStream dataInputStream = new DataInputStream(countingInputStream); final BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream); Log.d(Config.LOGTAG, backupFileHeader.toString()); @@ -179,15 +231,14 @@ public class ImportBackupService extends Service { return false; } - final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); - byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt()); - SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); - IvParameterSpec ivSpec = new IvParameterSpec(backupFileHeader.getIv()); - cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); - CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher); + final byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt()); - GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream); - BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, "UTF-8")); + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(false, new AEADParameters(new KeyParameter(key), 128, backupFileHeader.getIv())); + final CipherInputStream cipherInputStream = new CipherInputStream(countingInputStream, cipher); + + final GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream); + BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, Charsets.UTF_8)); String line; StringBuilder multiLineQuery = null; while ((line = reader.readLine()) != null) { @@ -198,10 +249,12 @@ public class ImportBackupService extends Service { if (count % 2 == 1) { db.execSQL(multiLineQuery.toString()); multiLineQuery = null; + updateImportBackupNotification(fileSize, countingInputStream.getCount()); } } else { if (count % 2 == 0) { db.execSQL(line); + updateImportBackupNotification(fileSize, countingInputStream.getCount()); } else { multiLineQuery = new StringBuilder(line); } @@ -220,7 +273,7 @@ public class ImportBackupService extends Service { } } return true; - } catch (Exception e) { + } catch (final Exception e) { Throwable throwable = e.getCause(); final boolean reasonWasCrypto = throwable instanceof BadPaddingException || e instanceof ZipException; synchronized (mOnBackupProcessedListeners) { diff --git a/src/conversations/res/values-de/strings.xml b/src/conversations/res/values-de/strings.xml index 4de8877d6..a1c056a7d 100644 --- a/src/conversations/res/values-de/strings.xml +++ b/src/conversations/res/values-de/strings.xml @@ -8,4 +8,5 @@ Du wurdest zu %1$s eingeladen. Wir führen dich durch den Prozess der Kontoerstellung.\nWenn du %1$s als Provider wählst, kannst du mit Nutzern anderer Anbieter kommunizieren, indem du ihnen deine vollständige XMPP-Adresse gibst. Du wurdest zu %1$seingeladen. Ein Benutzername ist bereits für dich ausgewählt worden. Wir führen dich durch den Prozess der Kontoerstellung.\nDu kannst mit Nutzern anderer Anbieter kommunizieren, indem du ihnen deine vollständige XMPP-Adresse gibst. Deine Einladung für den Server + Falsch formatierter Provisionierungscode diff --git a/src/conversations/res/values-el/strings.xml b/src/conversations/res/values-el/strings.xml index dd9e2de9f..60f8c66ae 100644 --- a/src/conversations/res/values-el/strings.xml +++ b/src/conversations/res/values-el/strings.xml @@ -8,4 +8,4 @@ Έχετε προσκληθεί στο %1$s. Ένα όνομα χρήστη έχει ήδη επιλεγεί για εσάς. Θα σας καθοδηγήσουμε στη διαδικασία δημιουργίας λογαριασμού.\nΕπιλέγοντας τον %1$s ως πάροχο θα μπορείτε να επικοινωνείτε με χρήστες άλλων παρόχων δίνοντάς τους την πλήρη διεύθυνση XMPP σας. Έχετε προσκληθεί στο %1$s. Ένα όνομα χρήστη έχει ήδη επιλεγεί για εσάς. Θα σας καθοδηγήσουμε στη διαδικασία δημιουργίας λογαριασμού.\nΘα μπορείτε να επικοινωνείτε με χρήστες άλλων παρόχων δίνοντάς τους την πλήρη διεύθυνση XMPP σας. Η πρόσκλησή σας στον διακομιστή - + diff --git a/src/conversations/res/values-es/strings.xml b/src/conversations/res/values-es/strings.xml index c6a5371d4..577ca14bd 100644 --- a/src/conversations/res/values-es/strings.xml +++ b/src/conversations/res/values-es/strings.xml @@ -8,4 +8,5 @@ Has sido invitado a %1$s. Te guiaremos durante el proceso de creación de la cuenta.\nCuando selecciones %1$s como proveedor podrás comunicarte con usuarios de otros servidores proporcionándoles tu dirección XMPP completa. Has sido invitado a %1$s. Un nombre de usuario ya ha sido escogido para ti. Te guiaremos durante el proceso de creación de la cuenta.\nPodrás comunicarte con otros usuarios de otros servidores proporcionándoles tu dirección XMPP completa. Tu invitación al servidor + Código de abastecimiento formateado incorrectamente diff --git a/src/conversations/res/values-fr/strings.xml b/src/conversations/res/values-fr/strings.xml index 93271e735..4086a3c55 100644 --- a/src/conversations/res/values-fr/strings.xml +++ b/src/conversations/res/values-fr/strings.xml @@ -8,4 +8,5 @@ Vous avez été invité à %1$s. Nous allons vous guider à travers le processus de création d’un compte.\nEn choisissant %1$s comme fournisseur, vous pourrez communiquer avec les utilisateurs des autres fournisseurs en leur donnant votre adresse XMPP complète. Vous avez été invité à %1$s. Un nom d’utilisateur a déjà été choisi pour vous. Nous allons vous guider à travers le processus de création d’un compte.\nVous pourrez communiquer avec les utilisateurs des autres fournisseurs en leur donnant votre adresse XMPP complète. Votre invitation au serveur + Code de provisionnement mal formaté diff --git a/src/conversations/res/values-gl/strings.xml b/src/conversations/res/values-gl/strings.xml index 5401a977d..bd187d53b 100644 --- a/src/conversations/res/values-gl/strings.xml +++ b/src/conversations/res/values-gl/strings.xml @@ -8,4 +8,5 @@ Convidáronte a %1$s. Guiarémoste no proceso para crear unha conta.\nAo escoller %1$s como provedor poderás comunicarte con usuarias de outros provedores cando lles deas o teu enderezo XMPP completo. Convidáronte a %1$s. Escollemos un nome de usuaria por ti. Guiarémoste no proceso de crear unha conta.\nPoderás comunicarte con usuarias de outros provedores cando lles digas o teu enderezo XMPP completo. O convite do teu servidor + Código de aprovisionamento con formato non válido diff --git a/src/conversations/res/values-hu/strings.xml b/src/conversations/res/values-hu/strings.xml index acc0984f0..40641c62b 100644 --- a/src/conversations/res/values-hu/strings.xml +++ b/src/conversations/res/values-hu/strings.xml @@ -8,4 +8,4 @@ Meghívást kapott a(z) %1$s kiszolgálóra. Végig fogjuk vezetni egy fiók létrehozásának folyamatán.\nHa a(z) %1$s kiszolgálót választja szolgáltatóként, akkor képes lesz más szolgáltatók felhasználóival is kommunikálni, ha megadja nekik a teljes XMPP-címét. Meghívást kapott a(z) %1$s kiszolgálóra. Már kiválasztottak Önnek egy felhasználónevet. Végig fogjuk vezetni egy fiók létrehozásának folyamatán.\nKépes lesz más szolgáltatók felhasználóival is kommunikálni, ha megadja nekik a teljes XMPP-címét. Az Ön kiszolgálómeghívása - + diff --git a/src/conversations/res/values-it/strings.xml b/src/conversations/res/values-it/strings.xml index 06bf0ae0b..63a1c2379 100644 --- a/src/conversations/res/values-it/strings.xml +++ b/src/conversations/res/values-it/strings.xml @@ -10,4 +10,4 @@ In ogni caso per facilitare puoi creare facilmente un account su chat.sum7.eu, u Sei stato invitato su %1$s. Ti guideremo nel procedimento per creare un account.\nQuando scegli %1$s come fornitore sarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo. Sei stato invitato su %1$s. È già stato scelto un nome utente per te. Ti guideremo nel procedimento per creare un account.\nSarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo. Il tuo invito al server - + diff --git a/src/conversations/res/values-nl/strings.xml b/src/conversations/res/values-nl/strings.xml index ea7b00e82..ee1d0e01b 100644 --- a/src/conversations/res/values-nl/strings.xml +++ b/src/conversations/res/values-nl/strings.xml @@ -8,4 +8,4 @@ Je ontving een uitnodiging voor %1$s. We zullen je helpen een account aan te maken.\nWanneer je %1$s als je provider kiest kan je met gebruikers van andere providers communiceren door hen je volledige XMPP-adres te geven. Je ontving een uitnodiging voor %1$s. Er werd reeds een gebruikersnaam voor jou gekozen. We zullen je helpen een account aan te maken.\nJe zal met gebruikers van andere providers communiceren door hen je volledige XMPP-adres te geven. Je server uitnodiging - + diff --git a/src/conversations/res/values-pl/strings.xml b/src/conversations/res/values-pl/strings.xml index 3f03d437b..85a833b15 100644 --- a/src/conversations/res/values-pl/strings.xml +++ b/src/conversations/res/values-pl/strings.xml @@ -8,4 +8,4 @@ Zostałeś zaproszony do %1$s. Poprowadzimy ciebie przez proces tworzenia konta.\nWybierając %1$s jako dostawcę będziesz mógł komunikować się z innymi użytkownikami podając swój pełny adres XMPP. Zostałeś zaproszony do %1$s. Nazwa użytkownika została już dla ciebie wybrana. Poprowadzimy ciebie przez proces tworzenia konta.\nBęziesz mógł komunikować się z innymi użytkownikami podając swój adres XMPP. Zaproszenie twojego serwera - + diff --git a/src/conversations/res/values-pt-rBR/strings.xml b/src/conversations/res/values-pt-rBR/strings.xml index 2338c8361..8a9f8b405 100644 --- a/src/conversations/res/values-pt-rBR/strings.xml +++ b/src/conversations/res/values-pt-rBR/strings.xml @@ -8,4 +8,4 @@ Você foi convidado para %1$s. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nAo escolher %1$s como um provedor você conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo. Você foi convidado para %1$s. Um nome de usuário já foi escolhido para você. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nVocê conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo. Seu convite do servidor - + diff --git a/src/conversations/res/values-ro-rRO/strings.xml b/src/conversations/res/values-ro-rRO/strings.xml index e19a8da53..60a96740d 100644 --- a/src/conversations/res/values-ro-rRO/strings.xml +++ b/src/conversations/res/values-ro-rRO/strings.xml @@ -8,4 +8,5 @@ Ați fost invitați la %1$s. Vă vom ghida prin procesul de creare al unui cont.\nCând alegeți %1$s ca furnizor veți putea comunica cu utilizatorii altor furnizori oferindu-le adresa dumneavoastră completă XMPP. Ați fost invitați la %1$s. Un nume de utilizator a fost deja ales pentru dumneavoastră. Vă vom ghida prin procesul de creare al unui cont.\nVeți putea comunica cu utilizatorii altor furnizori oferindu-le adresa dumneavoastră completă XMPP. Invitația serverului dumneavoastră + Cod de acces formatat necorespunzător diff --git a/src/conversations/res/values-ru/strings.xml b/src/conversations/res/values-ru/strings.xml index 55b9679c6..906d279d4 100644 --- a/src/conversations/res/values-ru/strings.xml +++ b/src/conversations/res/values-ru/strings.xml @@ -4,5 +4,8 @@ Использовать chat.sum7.eu Создать новый аккаунт У вас есть аккаунт XMPP? Если вы использовали Conversations или другой XMPP-клиент в прошлом, то скорее всего, он у вас есть. Если у вас нет аккаунта, вы можете создать его прямо сейчас.\nНекоторые провайдеры электронной почты также регистрируют аккаунты XMPP. - XMPP - это независимая сеть обмена сообщениями. Conversations позволяет вам подключиться к любому XMPP-серверу на ваш выбор.\nЕсли у вас нет сервера, предлагаем вам зарегистрировать аккаунт на chat.sum7.eu, сервер, специально предназначеный для работы с приложением Conv6sations. - + XMPP - это независимая сеть обмена сообщениями. Conversations позволяет вам подключиться к любому XMPP-серверу на ваш выбор.\nЕсли у вас нет сервера, предлагаем вам зарегистрировать аккаунт на chat.sum7.eu, сервере, специально предназначенном для работы с Conversations. + Вас пригласили на %1$s. Мы проведём вас через процесс создания аккаунта. Аккаунт на %1$s позволит вам общаться с пользователями и на этом, и на других серверах, используя ваш полный XMPP-адрес. + Вас пригласили на %1$s. Вам уже назначили имя пользователя. Мы проведём вас через процесс создания аккаунта. Этот аккаунт позволит вам общаться с пользователями и на этом, и на других серверах, используя ваш полный XMPP-адрес. + Ваше приглашение + diff --git a/src/conversations/res/values-uk/strings.xml b/src/conversations/res/values-uk/strings.xml index f04ad6562..7023bf3ec 100644 --- a/src/conversations/res/values-uk/strings.xml +++ b/src/conversations/res/values-uk/strings.xml @@ -8,4 +8,5 @@ Вас запросили до %1$s. Ми проведемо вас крок за кроком, щоб створити обліковий запис.\nОбираючи %1$s в якості свого постачальника, ви зможете спілкуватися з користувачами інших постачальників, для цього повідомте їм свою повну адресу XMPP. Вас запросили до %1$s. Для вас створено ім\'я користувача. Ми проведемо вас крок за кроком, щоб створити обліковий запис.\nВи зможете спілкуватися з користувачами інших постачальників, для цього повідомите їм свою повну адресу XMPP. Ваше запрошення до сервера + Неправильно відформатований код забезпечення diff --git a/src/conversations/res/values-zh-rCN/strings.xml b/src/conversations/res/values-zh-rCN/strings.xml index 9f1227e4c..882190af6 100644 --- a/src/conversations/res/values-zh-rCN/strings.xml +++ b/src/conversations/res/values-zh-rCN/strings.xml @@ -8,4 +8,4 @@ 您已受邀参加%1$s。 我们将指导您完成创建帐户的过程。\n选择%1$s作为提供者后,您可以通过提供其他人的完整XMPP地址与其他提供者的用户进行交流。 您已受邀参加%1$s。 已经为您选择了一个用户名。 我们将指导您完成创建帐户的过程。\n您可以通过向其他提供商的用户提供完整的XMPP地址来与他们进行交流。 你的服务器邀请 - + diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index d96260957..68ad4d316 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -173,7 +173,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl public String findMostRecentRemoteDisplayableId() { final boolean multi = mode == Conversation.MODE_MULTI; synchronized (this.messages) { - for(final Message message : Lists.reverse(this.messages)) { + for (final Message message : Lists.reverse(this.messages)) { if (message.getStatus() == Message.STATUS_RECEIVED) { final String serverMsgId = message.getServerMsgId(); if (serverMsgId != null && multi) { @@ -186,6 +186,21 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl return null; } + public Message getLastEditableMessage() { + synchronized (this.messages) { + for (final Message message : Lists.reverse(this.messages)) { + if (message.isEditable()) { + if (message.isGeoUri() || message.getType() != Message.TYPE_TEXT) { + return null; + } + return message; + } + } + } + return null; + } + + public Message findUnsentMessageWithUuid(String uuid) { synchronized (this.messages) { for (final Message message : this.messages) { @@ -499,7 +514,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl @Override public int compareTo(@NonNull Conversation another) { return ComparisonChain.start() - .compareFalseFirst(another.getBooleanAttribute(ATTRIBUTE_PINNED_ON_TOP, false), getBooleanAttribute(ATTRIBUTE_PINNED_ON_TOP,false)) + .compareFalseFirst(another.getBooleanAttribute(ATTRIBUTE_PINNED_ON_TOP, false), getBooleanAttribute(ATTRIBUTE_PINNED_ON_TOP, false)) .compare(another.getSortableTime(), getSortableTime()) .result(); } diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 44f7588f8..90b2bdc61 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -15,7 +15,6 @@ import java.lang.ref.WeakReference; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -613,15 +612,15 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable public boolean isLastCorrectableMessage() { Message next = next(); while (next != null) { - if (next.isCorrectable()) { + if (next.isEditable()) { return false; } next = next.next(); } - return isCorrectable(); + return isEditable(); } - private boolean isCorrectable() { + public boolean isEditable() { return getStatus() != STATUS_RECEIVED && !isCarbon(); } diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 15190d08c..6c5496650 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -1,16 +1,20 @@ package eu.siacs.conversations.ui; +import android.Manifest; import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.databinding.DataBindingUtil; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.provider.ContactsContract.CommonDataKinds; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Intents; +import android.support.annotation.NonNull; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; import android.text.Spannable; @@ -57,15 +61,15 @@ import eu.siacs.conversations.utils.IrregularUnicodeDetector; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.XmppConnection; -import eu.siacs.conversations.xmpp.Jid; public class ContactDetailsActivity extends OmemoActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated, OnMediaLoaded { public static final String ACTION_VIEW_CONTACT = "view_contact"; + private final int REQUEST_SYNC_CONTACTS = 0x28cf; ActivityContactDetailsBinding binding; - private MediaAdapter mMediaAdapter; private Contact contact; @@ -110,47 +114,41 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp private boolean showInactiveOmemo = false; private String messageFingerprint; - private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() { + private void checkContactPermissionAndShowAddDialog() { + if (hasContactsPermission()) { + showAddToPhoneBookDialog(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS); + } + } - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); + private boolean hasContactsPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED; + } else { + return true; + } + } + + private void showAddToPhoneBookDialog() { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.action_add_phone_book)); + builder.setMessage(getString(R.string.add_phone_book_text, contact.getJid().toEscapedString())); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.add), (dialog, which) -> { + final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); intent.setType(Contacts.CONTENT_ITEM_TYPE); intent.putExtra(Intents.Insert.IM_HANDLE, contact.getJid().toEscapedString()); intent.putExtra(Intents.Insert.IM_PROTOCOL, CommonDataKinds.Im.PROTOCOL_JABBER); intent.putExtra("finishActivityOnSaveCompleted", true); try { - ContactDetailsActivity.this.startActivityForResult(intent, 0); + startActivityForResult(intent, 0); } catch (ActivityNotFoundException e) { Toast.makeText(ContactDetailsActivity.this, R.string.no_application_found_to_view_contact, Toast.LENGTH_SHORT).show(); } - } - }; - - private OnClickListener onBadgeClick = new OnClickListener() { - - @Override - public void onClick(View v) { - Uri systemAccount = contact.getSystemAccount(); - if (systemAccount == null) { - AlertDialog.Builder builder = new AlertDialog.Builder( - ContactDetailsActivity.this); - builder.setTitle(getString(R.string.action_add_phone_book)); - builder.setMessage(getString(R.string.add_phone_book_text, contact.getJid().toEscapedString())); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.add), addToPhonebook); - builder.create().show(); - } else { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(systemAccount); - try { - startActivity(intent); - } catch (ActivityNotFoundException e) { - Toast.makeText(ContactDetailsActivity.this, R.string.no_application_found_to_view_contact, Toast.LENGTH_SHORT).show(); - } - } - } - }; + }); + builder.create().show(); + } @Override public void onRosterUpdate() { @@ -233,6 +231,18 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp mMediaAdapter.setAttachments(Collections.emptyList()); } + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { + if (grantResults.length > 0) + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) { + showAddToPhoneBookDialog(); + xmppConnectionService.loadPhoneContacts(); + xmppConnectionService.startContactObserver(); + } + } + } + @Override public boolean onOptionsItemSelected(final MenuItem menuItem) { if (MenuDoubleTabUtil.shouldIgnoreTap()) { @@ -417,7 +427,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } binding.detailsAccount.setText(getString(R.string.using_account, account)); AvatarWorkerTask.loadAvatar(contact, binding.detailsContactBadge, R.dimen.avatar_on_details_screen_size); - binding.detailsContactBadge.setOnClickListener(this.onBadgeClick); + binding.detailsContactBadge.setOnClickListener(this::onBadgeClick); binding.detailsContactKeys.removeAllViews(); boolean hasKeys = false; @@ -496,6 +506,21 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } } + private void onBadgeClick(View view) { + final Uri systemAccount = contact.getSystemAccount(); + if (systemAccount == null) { + checkContactPermissionAndShowAddDialog(); + } else { + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(systemAccount); + try { + startActivity(intent); + } catch (final ActivityNotFoundException e) { + Toast.makeText(this, R.string.no_application_found_to_view_contact, Toast.LENGTH_SHORT).show(); + } + } + } + public void onBackendConnected() { if (accountJid != null && contactJid != null) { Account account = xmppConnectionService.findAccountByJid(accountJid); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 034ac1485..c027b56bf 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -32,6 +32,7 @@ import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -2733,15 +2734,26 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } @Override - public boolean onEnterPressed() { - SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(getActivity()); - final boolean enterIsSend = p.getBoolean("enter_is_send", getResources().getBoolean(R.bool.enter_is_send)); - if (enterIsSend) { + public boolean onEnterPressed(final boolean isCtrlPressed) { + if (isCtrlPressed || enterIsSend()) { sendMessage(); return true; - } else { - return false; } + return false; + } + + private boolean enterIsSend() { + final SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(getActivity()); + return p.getBoolean("enter_is_send", getResources().getBoolean(R.bool.enter_is_send)); + } + + public boolean onArrowUpCtrlPressed() { + final Message lastEditableMessage = conversation == null ? null : conversation.getLastEditableMessage(); + if (lastEditableMessage != null) { + correctMessage(lastEditableMessage); + return true; + } + return false; } @Override diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index 24e167e85..261907d5b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -49,6 +49,7 @@ import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; import android.util.Log; +import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; @@ -493,8 +494,19 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - Intent pendingIntent = pendingViewIntent.peek(); + public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { + if (keyCode == KeyEvent.KEYCODE_DPAD_UP && keyEvent.isCtrlPressed()) { + final ConversationFragment conversationFragment = ConversationFragment.get(this); + if (conversationFragment != null && conversationFragment.onArrowUpCtrlPressed()) { + return true; + } + } + return super.onKeyDown(keyCode, keyEvent); + } + + @Override + public void onSaveInstanceState(final Bundle savedInstanceState) { + final Intent pendingIntent = pendingViewIntent.peek(); savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent()); super.onSaveInstanceState(savedInstanceState); } diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 5db8dfc6d..33835119f 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -88,6 +88,10 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe RtpEndUserState.APPLICATION_ERROR, RtpEndUserState.CONNECTIVITY_ERROR ); + private static final List STATES_SHOWING_SWITCH_TO_CHAT = Arrays.asList( + RtpEndUserState.CONNECTING, + RtpEndUserState.CONNECTED + ); private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session"; private static final int REQUEST_ACCEPT_CALL = 0x1111; private WeakReference rtpConnectionReference; @@ -135,7 +139,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.activity_rtp_session, menu); final MenuItem help = menu.findItem(R.id.action_help); + final MenuItem gotoChat = menu.findItem(R.id.action_goto_chat); help.setVisible(isHelpButtonVisible()); + gotoChat.setVisible(isSwitchToConversationVisible()); return super.onCreateOptionsMenu(menu); } @@ -153,10 +159,25 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } } + private boolean isSwitchToConversationVisible() { + final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null; + return connection != null && STATES_SHOWING_SWITCH_TO_CHAT.contains(connection.getEndUserState()); + } + + private void switchToConversation() { + final Contact contact = getWith(); + final Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true); + switchToConversation(conversation); + } + public boolean onOptionsItemSelected(final MenuItem item) { - if (item.getItemId() == R.id.action_help) { - launchHelpInBrowser(); - return true; + switch (item.getItemId()) { + case R.id.action_help: + launchHelpInBrowser(); + break; + case R.id.action_goto_chat: + switchToConversation(); + break; } return super.onOptionsItemSelected(item); } @@ -308,6 +329,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe return; } final Account account = extractAccount(intent); + final String action = intent.getAction(); final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH)); final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID); if (sessionId != null) { @@ -320,6 +342,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe requestPermissionsAndAcceptCall(); resetIntent(intent.getExtras()); } + } else if (asList(ACTION_MAKE_VIDEO_CALL, ACTION_MAKE_VOICE_CALL).contains(action)) { + proposeJingleRtpSession(account, with, actionToMedia(action)); + binding.with.setText(account.getRoster().getContact(with).getDisplayName()); } else { throw new IllegalStateException("received onNewIntent without sessionId"); } @@ -437,7 +462,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe return; } } - retractSessionProposal(); + //TODO apparently this method is not getting called on Android 10 when using the task switcher + final boolean emptyReference = rtpConnectionReference == null || rtpConnectionReference.get() == null; + if (emptyReference && xmppConnectionService != null) { + retractSessionProposal(); + } } @RequiresApi(api = Build.VERSION_CODES.O) @@ -910,15 +939,17 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } private void disableMicrophone(View view) { - JingleRtpConnection rtpConnection = requireRtpConnection(); - rtpConnection.setMicrophoneEnabled(false); - updateInCallButtonConfiguration(); + final JingleRtpConnection rtpConnection = requireRtpConnection(); + if (rtpConnection.setMicrophoneEnabled(false)) { + updateInCallButtonConfiguration(); + } } private void enableMicrophone(View view) { - JingleRtpConnection rtpConnection = requireRtpConnection(); - rtpConnection.setMicrophoneEnabled(true); - updateInCallButtonConfiguration(); + final JingleRtpConnection rtpConnection = requireRtpConnection(); + if (rtpConnection.setMicrophoneEnabled(true)) { + updateInCallButtonConfiguration(); + } } private void switchToEarpiece(View view) { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index e9daee6df..ba171ce1f 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -553,7 +553,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.image.setVisibility(View.VISIBLE); final FileParams params = message.getFileParams(); - final double target = metrics.density * 288; + final float target = activity.getResources().getDimension(R.dimen.image_preview_width); final int scaledW; final int scaledH; if (Math.max(params.height, params.width) * metrics.density <= target) { @@ -569,7 +569,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie scaledW = (int) target; scaledH = (int) (params.height / ((double) params.width / target)); } - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scaledW, scaledH); + final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scaledW, scaledH); layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4)); viewHolder.image.setLayoutParams(layoutParams); activity.loadBitmap(message, viewHolder.image); diff --git a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java index 342867576..1a1327ec4 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java @@ -54,13 +54,14 @@ public class EditMessage extends EmojiWrapperEditText { } @Override - public boolean onKeyDown(int keyCode, KeyEvent e) { + public boolean onKeyDown(final int keyCode, final KeyEvent e) { + final boolean isCtrlPressed = e.isCtrlPressed(); if (keyCode == KeyEvent.KEYCODE_ENTER && !e.isShiftPressed()) { lastInputWasTab = false; - if (keyboardListener != null && keyboardListener.onEnterPressed()) { + if (keyboardListener != null && keyboardListener.onEnterPressed(isCtrlPressed)) { return true; } - } else if (keyCode == KeyEvent.KEYCODE_TAB && !e.isAltPressed() && !e.isCtrlPressed()) { + } else if (keyCode == KeyEvent.KEYCODE_TAB && !e.isAltPressed() && !isCtrlPressed) { if (keyboardListener != null && keyboardListener.onTabPressed(this.lastInputWasTab)) { lastInputWasTab = true; return true; @@ -191,7 +192,7 @@ public class EditMessage extends EmojiWrapperEditText { } public interface KeyboardListener { - boolean onEnterPressed(); + boolean onEnterPressed(boolean isCtrlPressed); void onTypingStarted(); diff --git a/src/main/java/eu/siacs/conversations/utils/Emoticons.java b/src/main/java/eu/siacs/conversations/utils/Emoticons.java index 6abcf9b02..26dc205b4 100644 --- a/src/main/java/eu/siacs/conversations/utils/Emoticons.java +++ b/src/main/java/eu/siacs/conversations/utils/Emoticons.java @@ -29,280 +29,281 @@ package eu.siacs.conversations.utils; +import android.support.annotation.NonNull; import android.util.LruCache; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.regex.Pattern; public class Emoticons { - private static final UnicodeRange MISC_SYMBOLS_AND_PICTOGRAPHS = new UnicodeRange(0x1F300,0x1F5FF); - private static final UnicodeRange SUPPLEMENTAL_SYMBOLS = new UnicodeRange(0x1F900,0x1F9FF); - private static final UnicodeRange EMOTICONS = new UnicodeRange(0x1F600,0x1F64F); - private static final UnicodeRange TRANSPORT_SYMBOLS = new UnicodeRange(0x1F680,0x1F6FF); - private static final UnicodeRange MISC_SYMBOLS = new UnicodeRange(0x2600,0x26FF); - private static final UnicodeRange DINGBATS = new UnicodeRange(0x2700,0x27BF); - private static final UnicodeRange ENCLOSED_ALPHANUMERIC_SUPPLEMENT = new UnicodeRange(0x1F100,0x1F1FF); - private static final UnicodeRange ENCLOSED_IDEOGRAPHIC_SUPPLEMENT = new UnicodeRange(0x1F200,0x1F2FF); - private static final UnicodeRange REGIONAL_INDICATORS = new UnicodeRange(0x1F1E6,0x1F1FF); - private static final UnicodeRange GEOMETRIC_SHAPES = new UnicodeRange(0x25A0,0x25FF); - private static final UnicodeRange LATIN_SUPPLEMENT = new UnicodeRange(0x80,0xFF); - private static final UnicodeRange MISC_TECHNICAL = new UnicodeRange(0x2300,0x23FF); - private static final UnicodeRange TAGS = new UnicodeRange(0xE0020,0xE007F); - private static final UnicodeList CYK_SYMBOLS_AND_PUNCTUATION = new UnicodeList(0x3030,0x303D); - private static final UnicodeList LETTERLIKE_SYMBOLS = new UnicodeList(0x2122,0x2139); + private static final UnicodeRange MISC_SYMBOLS_AND_PICTOGRAPHS = new UnicodeRange(0x1F300, 0x1F5FF); + private static final UnicodeRange SUPPLEMENTAL_SYMBOLS = new UnicodeRange(0x1F900, 0x1F9FF); + private static final UnicodeRange EMOTICONS = new UnicodeRange(0x1F600, 0x1F64F); + private static final UnicodeRange TRANSPORT_SYMBOLS = new UnicodeRange(0x1F680, 0x1F6FF); + private static final UnicodeRange MISC_SYMBOLS = new UnicodeRange(0x2600, 0x26FF); + private static final UnicodeRange DINGBATS = new UnicodeRange(0x2700, 0x27BF); + private static final UnicodeRange ENCLOSED_ALPHANUMERIC_SUPPLEMENT = new UnicodeRange(0x1F100, 0x1F1FF); + private static final UnicodeRange ENCLOSED_IDEOGRAPHIC_SUPPLEMENT = new UnicodeRange(0x1F200, 0x1F2FF); + private static final UnicodeRange REGIONAL_INDICATORS = new UnicodeRange(0x1F1E6, 0x1F1FF); + private static final UnicodeRange GEOMETRIC_SHAPES = new UnicodeRange(0x25A0, 0x25FF); + private static final UnicodeRange LATIN_SUPPLEMENT = new UnicodeRange(0x80, 0xFF); + private static final UnicodeRange MISC_TECHNICAL = new UnicodeRange(0x2300, 0x23FF); + private static final UnicodeRange TAGS = new UnicodeRange(0xE0020, 0xE007F); + private static final UnicodeList CYK_SYMBOLS_AND_PUNCTUATION = new UnicodeList(0x3030, 0x303D); + private static final UnicodeList LETTERLIKE_SYMBOLS = new UnicodeList(0x2122, 0x2139); - private static final UnicodeBlocks KEYCAP_COMBINEABLE = new UnicodeBlocks(new UnicodeList(0x23),new UnicodeList(0x2A),new UnicodeRange(0x30,0x39)); + private static final UnicodeBlocks KEYCAP_COMBINEABLE = new UnicodeBlocks(new UnicodeList(0x23), new UnicodeList(0x2A), new UnicodeRange(0x30, 0x39)); - private static final UnicodeBlocks SYMBOLIZE = new UnicodeBlocks( - GEOMETRIC_SHAPES, - LATIN_SUPPLEMENT, - CYK_SYMBOLS_AND_PUNCTUATION, - LETTERLIKE_SYMBOLS, - KEYCAP_COMBINEABLE); - private static final UnicodeBlocks EMOJIS = new UnicodeBlocks( - MISC_SYMBOLS_AND_PICTOGRAPHS, - SUPPLEMENTAL_SYMBOLS, - EMOTICONS, - TRANSPORT_SYMBOLS, - MISC_SYMBOLS, - DINGBATS, - ENCLOSED_ALPHANUMERIC_SUPPLEMENT, - ENCLOSED_IDEOGRAPHIC_SUPPLEMENT, - MISC_TECHNICAL); + private static final UnicodeBlocks SYMBOLIZE = new UnicodeBlocks( + GEOMETRIC_SHAPES, + LATIN_SUPPLEMENT, + CYK_SYMBOLS_AND_PUNCTUATION, + LETTERLIKE_SYMBOLS, + KEYCAP_COMBINEABLE); + private static final UnicodeBlocks EMOJIS = new UnicodeBlocks( + MISC_SYMBOLS_AND_PICTOGRAPHS, + SUPPLEMENTAL_SYMBOLS, + EMOTICONS, + TRANSPORT_SYMBOLS, + MISC_SYMBOLS, + DINGBATS, + ENCLOSED_ALPHANUMERIC_SUPPLEMENT, + ENCLOSED_IDEOGRAPHIC_SUPPLEMENT, + MISC_TECHNICAL); - private static final int MAX_EMOIJS = 42; + private static final int MAX_EMOIJS = 42; - private static final int ZWJ = 0x200D; - private static final int VARIATION_16 = 0xFE0F; - private static final int COMBINING_ENCLOSING_KEYCAP = 0x20E3; - private static final int BLACK_FLAG = 0x1F3F4; - private static final UnicodeRange FITZPATRICK = new UnicodeRange(0x1F3FB,0x1F3FF); + private static final int ZWJ = 0x200D; + private static final int VARIATION_16 = 0xFE0F; + private static final int COMBINING_ENCLOSING_KEYCAP = 0x20E3; + private static final int BLACK_FLAG = 0x1F3F4; + private static final UnicodeRange FITZPATRICK = new UnicodeRange(0x1F3FB, 0x1F3FF); - private static final LruCache CACHE = new LruCache<>(256); + private static final LruCache CACHE = new LruCache<>(256); - private static List parse(String input) { - List symbols = new ArrayList<>(); - Builder builder = new Builder(); - boolean needsFinalBuild = false; - for (int cp, i = 0; i < input.length(); i += Character.charCount(cp)) { - cp = input.codePointAt(i); - if (builder.offer(cp)) { - needsFinalBuild = true; - } else { - symbols.add(builder.build()); - builder = new Builder(); - if (builder.offer(cp)) { - needsFinalBuild = true; - } - } - } - if (needsFinalBuild) { - symbols.add(builder.build()); - } - return symbols; - } + private static List parse(String input) { + List symbols = new ArrayList<>(); + Builder builder = new Builder(); + boolean needsFinalBuild = false; + for (int cp, i = 0; i < input.length(); i += Character.charCount(cp)) { + cp = input.codePointAt(i); + if (builder.offer(cp)) { + needsFinalBuild = true; + } else { + symbols.add(builder.build()); + builder = new Builder(); + if (builder.offer(cp)) { + needsFinalBuild = true; + } + } + } + if (needsFinalBuild) { + symbols.add(builder.build()); + } + return symbols; + } - public static Pattern getEmojiPattern(CharSequence input) { - Pattern pattern = CACHE.get(input); - if (pattern == null) { - pattern = generatePattern(input); - CACHE.put(input, pattern); - } - return pattern; - } + public static Pattern getEmojiPattern(final CharSequence input) { + Pattern pattern = CACHE.get(input); + if (pattern == null) { + pattern = generatePattern(input); + CACHE.put(input, pattern); + } + return pattern; + } - private static Pattern generatePattern(CharSequence input) { - final HashSet emojis = new HashSet<>(); - int i = 0; - for(Symbol symbol : parse(input.toString())) { - if (symbol instanceof Emoji) { - emojis.add(symbol.toString()); - if (++i >= MAX_EMOIJS) { - return Pattern.compile(""); - } - } - } - final StringBuilder pattern = new StringBuilder(); - for(String emoji : emojis) { - if (pattern.length() != 0) { - pattern.append('|'); - } - pattern.append(Pattern.quote(emoji)); - } - return Pattern.compile(pattern.toString()); - } + private static Pattern generatePattern(CharSequence input) { + final HashSet emojis = new HashSet<>(); + int i = 0; + for (final Symbol symbol : parse(input.toString())) { + if (symbol instanceof Emoji) { + emojis.add(symbol.toString()); + if (++i >= MAX_EMOIJS) { + return Pattern.compile(""); + } + } + } + final StringBuilder pattern = new StringBuilder(); + for (String emoji : emojis) { + if (pattern.length() != 0) { + pattern.append('|'); + } + pattern.append(Pattern.quote(emoji)); + } + return Pattern.compile(pattern.toString()); + } - public static boolean isEmoji(String input) { - List symbols = parse(input); - return symbols.size() == 1 && symbols.get(0).isEmoji(); - } + public static boolean isEmoji(String input) { + List symbols = parse(input); + return symbols.size() == 1 && symbols.get(0).isEmoji(); + } - public static boolean isOnlyEmoji(String input) { - List symbols = parse(input); - for(Symbol symbol : symbols) { - if (!symbol.isEmoji()) { - return false; - } - } - return symbols.size() > 0; - } + public static boolean isOnlyEmoji(String input) { + List symbols = parse(input); + for (Symbol symbol : symbols) { + if (!symbol.isEmoji()) { + return false; + } + } + return symbols.size() > 0; + } - private static abstract class Symbol { + private static abstract class Symbol { - private final String value; + private final String value; - public Symbol(List codepoints) { - StringBuilder builder = new StringBuilder(); - for(Integer codepoint : codepoints) { - builder.appendCodePoint(codepoint); - } - this.value = builder.toString(); - } + Symbol(List codepoints) { + final StringBuilder builder = new StringBuilder(); + for (final Integer codepoint : codepoints) { + builder.appendCodePoint(codepoint); + } + this.value = builder.toString(); + } - abstract boolean isEmoji(); + abstract boolean isEmoji(); - @Override - public String toString() { - return value; - } - } + @NonNull + @Override + public String toString() { + return value; + } + } - public static class Emoji extends Symbol { + public static class Emoji extends Symbol { - public Emoji(List codepoints) { - super(codepoints); - } + Emoji(List codepoints) { + super(codepoints); + } - @Override - boolean isEmoji() { - return true; - } - } + @Override + boolean isEmoji() { + return true; + } + } - public static class Other extends Symbol { + public static class Other extends Symbol { - public Other(List codepoints) { - super(codepoints); - } + public Other(List codepoints) { + super(codepoints); + } - @Override - boolean isEmoji() { - return false; - } - } + @Override + boolean isEmoji() { + return false; + } + } - private static class Builder { - private final List codepoints = new ArrayList<>(); + private static class Builder { + private final List codepoints = new ArrayList<>(); - public boolean offer(int codepoint) { - boolean add = false; - if (this.codepoints.size() == 0) { - if (SYMBOLIZE.contains(codepoint)) { - add = true; - } else if (REGIONAL_INDICATORS.contains(codepoint)) { - add = true; - } else if (EMOJIS.contains(codepoint) && !FITZPATRICK.contains(codepoint) && codepoint != ZWJ) { - add = true; - } - } else { - int previous = codepoints.get(codepoints.size() -1); - if (codepoints.get(0) == BLACK_FLAG) { - add = TAGS.contains(codepoint); - } else if (COMBINING_ENCLOSING_KEYCAP == codepoint) { - add = KEYCAP_COMBINEABLE.contains(previous) || previous == VARIATION_16; - } else if (SYMBOLIZE.contains(previous)) { - add = codepoint == VARIATION_16; - } else if (REGIONAL_INDICATORS.contains(previous) && REGIONAL_INDICATORS.contains(codepoint)) { - add = codepoints.size() == 1; - } else if (previous == VARIATION_16) { - add = isMerger(codepoint); - } else if (FITZPATRICK.contains(previous)) { - add = codepoint == ZWJ; - } else if (ZWJ == previous) { - add = EMOJIS.contains(codepoint); - } else if (isMerger(codepoint)) { - add = true; - } else if (codepoint == VARIATION_16 && EMOJIS.contains(previous)) { - add = true; - } - } - if (add) { - codepoints.add(codepoint); - return true; - } else { - return false; - } - } + public boolean offer(int codepoint) { + boolean add = false; + if (this.codepoints.size() == 0) { + if (SYMBOLIZE.contains(codepoint)) { + add = true; + } else if (REGIONAL_INDICATORS.contains(codepoint)) { + add = true; + } else if (EMOJIS.contains(codepoint) && !FITZPATRICK.contains(codepoint) && codepoint != ZWJ) { + add = true; + } + } else { + int previous = codepoints.get(codepoints.size() - 1); + if (codepoints.get(0) == BLACK_FLAG) { + add = TAGS.contains(codepoint); + } else if (COMBINING_ENCLOSING_KEYCAP == codepoint) { + add = KEYCAP_COMBINEABLE.contains(previous) || previous == VARIATION_16; + } else if (SYMBOLIZE.contains(previous)) { + add = codepoint == VARIATION_16; + } else if (REGIONAL_INDICATORS.contains(previous) && REGIONAL_INDICATORS.contains(codepoint)) { + add = codepoints.size() == 1; + } else if (previous == VARIATION_16) { + add = isMerger(codepoint) || codepoint == VARIATION_16; + } else if (FITZPATRICK.contains(previous)) { + add = codepoint == ZWJ; + } else if (ZWJ == previous) { + add = EMOJIS.contains(codepoint); + } else if (isMerger(codepoint)) { + add = true; + } else if (codepoint == VARIATION_16 && EMOJIS.contains(previous)) { + add = true; + } + } + if (add) { + codepoints.add(codepoint); + return true; + } else { + return false; + } + } - private static boolean isMerger(int codepoint) { - return codepoint == ZWJ || FITZPATRICK.contains(codepoint); - } + private static boolean isMerger(int codepoint) { + return codepoint == ZWJ || FITZPATRICK.contains(codepoint); + } - public Symbol build() { - if (codepoints.size() > 0 && SYMBOLIZE.contains(codepoints.get(codepoints.size() - 1))) { - return new Other(codepoints); - } else if (codepoints.size() > 1 && KEYCAP_COMBINEABLE.contains(codepoints.get(0)) && codepoints.get(codepoints.size() - 1) != COMBINING_ENCLOSING_KEYCAP) { - return new Other(codepoints); - } - return codepoints.size() == 0 ? new Other(codepoints): new Emoji(codepoints); - } - } + public Symbol build() { + if (codepoints.size() > 0 && SYMBOLIZE.contains(codepoints.get(codepoints.size() - 1))) { + return new Other(codepoints); + } else if (codepoints.size() > 1 && KEYCAP_COMBINEABLE.contains(codepoints.get(0)) && codepoints.get(codepoints.size() - 1) != COMBINING_ENCLOSING_KEYCAP) { + return new Other(codepoints); + } + return codepoints.size() == 0 ? new Other(codepoints) : new Emoji(codepoints); + } + } - public static class UnicodeBlocks implements UnicodeSet { - final UnicodeSet[] unicodeSets; + public static class UnicodeBlocks implements UnicodeSet { + final UnicodeSet[] unicodeSets; - public UnicodeBlocks(UnicodeSet... sets) { - this.unicodeSets = sets; - } + UnicodeBlocks(final UnicodeSet... sets) { + this.unicodeSets = sets; + } - @Override - public boolean contains(int codepoint) { - for(UnicodeSet unicodeSet : unicodeSets) { - if (unicodeSet.contains(codepoint)) { - return true; - } - } - return false; - } - } + @Override + public boolean contains(int codepoint) { + for (UnicodeSet unicodeSet : unicodeSets) { + if (unicodeSet.contains(codepoint)) { + return true; + } + } + return false; + } + } - public interface UnicodeSet { - boolean contains(int codepoint); - } + public interface UnicodeSet { + boolean contains(int codepoint); + } - public static class UnicodeList implements UnicodeSet { + public static class UnicodeList implements UnicodeSet { - private final List list; + private final List list; - public UnicodeList(Integer... codes) { - this.list = Arrays.asList(codes); - } + UnicodeList(final Integer... codes) { + this.list = Arrays.asList(codes); + } - @Override - public boolean contains(int codepoint) { - return this.list.contains(codepoint); - } - } + @Override + public boolean contains(int codepoint) { + return this.list.contains(codepoint); + } + } - public static class UnicodeRange implements UnicodeSet { + public static class UnicodeRange implements UnicodeSet { - private final int lower; - private final int upper; + private final int lower; + private final int upper; - UnicodeRange(int lower, int upper) { - this.lower = lower; - this.upper = upper; - } + UnicodeRange(int lower, int upper) { + this.lower = lower; + this.upper = upper; + } - public boolean contains(int codePoint) { - return codePoint >= lower && codePoint <= upper; - } - } + public boolean contains(int codePoint) { + return codePoint >= lower && codePoint <= upper; + } + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 28f7c738b..830695831 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1085,8 +1085,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return webRTCWrapper.isMicrophoneEnabled(); } - public void setMicrophoneEnabled(final boolean enabled) { - webRTCWrapper.setMicrophoneEnabled(enabled); + public boolean setMicrophoneEnabled(final boolean enabled) { + return webRTCWrapper.setMicrophoneEnabled(enabled); } public boolean isVideoEnabled() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index ff7e6a677..58dfa4e5f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -370,12 +370,19 @@ public class WebRTCWrapper { } } - void setMicrophoneEnabled(final boolean enabled) { + boolean setMicrophoneEnabled(final boolean enabled) { final AudioTrack audioTrack = this.localAudioTrack; if (audioTrack == null) { throw new IllegalStateException("Local audio track does not exist (yet)"); } - audioTrack.setEnabled(enabled); + try { + audioTrack.setEnabled(enabled); + return true; + } catch (final IllegalStateException e) { + Log.d(Config.LOGTAG, "unable to toggle microphone", e); + //ignoring race condition in case MediaStreamTrack has been disposed + return false; + } } boolean isVideoEnabled() { @@ -502,7 +509,7 @@ public class WebRTCWrapper { final CameraEnumerator enumerator = getCameraEnumerator(); final Set deviceNames = ImmutableSet.copyOf(enumerator.getDeviceNames()); for (final String deviceName : deviceNames) { - if (enumerator.isFrontFacing(deviceName)) { + if (isFrontFacing(enumerator, deviceName)) { final CapturerChoice capturerChoice = of(enumerator, deviceName, deviceNames); if (capturerChoice == null) { return Optional.absent(); @@ -518,6 +525,14 @@ public class WebRTCWrapper { } } + private static boolean isFrontFacing(final CameraEnumerator cameraEnumerator, final String deviceName) { + try { + return cameraEnumerator.isFrontFacing(deviceName); + } catch (final NullPointerException e) { + return false; + } + } + public PeerConnection.PeerConnectionState getState() { return requirePeerConnection().connectionState(); } diff --git a/src/main/res/drawable-hdpi/ic_question_answer_white_24dp.png b/src/main/res/drawable-hdpi/ic_question_answer_white_24dp.png new file mode 100644 index 000000000..247b7f11d Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_question_answer_white_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_question_answer_white_24dp.png b/src/main/res/drawable-mdpi/ic_question_answer_white_24dp.png new file mode 100644 index 000000000..dad29526a Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_question_answer_white_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_question_answer_white_24dp.png b/src/main/res/drawable-xhdpi/ic_question_answer_white_24dp.png new file mode 100644 index 000000000..dbb131aab Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_question_answer_white_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_question_answer_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_question_answer_white_24dp.png new file mode 100644 index 000000000..4ece8d9fd Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_question_answer_white_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_question_answer_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_question_answer_white_24dp.png new file mode 100644 index 000000000..9b26c13a0 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_question_answer_white_24dp.png differ diff --git a/src/main/res/menu/activity_rtp_session.xml b/src/main/res/menu/activity_rtp_session.xml index 540a9def9..04756490a 100644 --- a/src/main/res/menu/activity_rtp_session.xml +++ b/src/main/res/menu/activity_rtp_session.xml @@ -1,11 +1,16 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> + app:showAsAction="always" /> + \ No newline at end of file diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml index 357841af6..aea2260b5 100644 --- a/src/main/res/values-ar/strings.xml +++ b/src/main/res/values-ar/strings.xml @@ -316,7 +316,6 @@ الرد إعتباره كمقروء أدخل للإرسال - استخدام مفتاح الدخول لإرسال رسالة عرض مفتاح الادخال تغيير مفتاح الرموز إلى مفتاح الدخول صوت diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml index f609d6561..8b9f2c6a4 100644 --- a/src/main/res/values-bg/strings.xml +++ b/src/main/res/values-bg/strings.xml @@ -316,7 +316,6 @@ Отбелязване като прочетено Въвеждане Enter изпраща - Натискането на клавиша Enter изпраща съобщението Показване на клавиша Enter Смяна на клавиша за емотикони с клавиша Enter аудио diff --git a/src/main/res/values-ca/strings.xml b/src/main/res/values-ca/strings.xml index 4780679cb..1f999f224 100644 --- a/src/main/res/values-ca/strings.xml +++ b/src/main/res/values-ca/strings.xml @@ -314,7 +314,6 @@ Marcar com llegit Entrada Entra per enviar - Utilitza el botó enter per enviar el missatge Mostra el botó enter Canviar la clau dels emoticones per un botó d\'entrada audio diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 545c29215..c231f9bee 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -264,7 +264,6 @@ Než opět změním Vstup Enter odesílá - Použít klávesu enter pro odesílání zpráv Zobrazit klávesu enter Změnit klávesu emotikon na klávesu enter audio diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index e68ea721a..eec74d269 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -400,10 +400,10 @@ Antworten Als gelesen markieren Eingabe - Eingabe-Taste (Enter) sendet Nachricht - Eingabe-Taste (Enter) zum Versenden einer Nachricht verwenden - Zeige Eingabe-Taste (Enter) - Emoji-Taste durch Eingabe-Taste ersetzen + Eingabetaste sendet Nachricht + Nutze die Eingabetaste zum Versenden einer Nachricht. Strg+Eingabetaste sendet die Nachricht unabhängig von dieser Einstellung. + Zeige Eingabetaste + Emoji-Taste durch Eingabetaste ersetzen Audio Video Bild @@ -916,6 +916,7 @@ Audioanruf Videoanruf Hilfe + Zur Unterhaltung wechseln Dein Mikrofon ist nicht verfügbar Du kannst immer nur einen Anruf zur gleichen Zeit machen. Zurück zum laufenden Aufruf diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml index 77a2279c4..c0be73378 100644 --- a/src/main/res/values-el/strings.xml +++ b/src/main/res/values-el/strings.xml @@ -347,7 +347,6 @@ Σημείωμα ως αναγνωσμένο Είσοδος Αποστολή με το πλήκτρο Enter - Χρήση του πλήκτρου Enter για την αποστολή μηνύματος Εμφάνιση του πλήκτρου Enter Αλλαγή του πλήκτρου emoticons σε πλήκτρο Enter ήχος diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 7bada1588..fa8f782cc 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -401,7 +401,7 @@ Marcar como leído Entrada Intro para enviar - Usar la tecla intro para enviar el mensaje + Utilizar la tecla Enter para enviar un mensaje. Siempre puedes usar Ctrl+Enter para enviar un mensaje, incluso si esta opción está deshabilitada. Mostrar tecla Intro Cambiar la tecla de emoticonos por la tecla Intro audio @@ -916,6 +916,7 @@ Audio llamada Video llamada Ayuda + Cambiar a conversación Tu micrófono no está disponible Solo puedes hacer una llamada a la vez Volver a la llamada en curso diff --git a/src/main/res/values-eu/strings.xml b/src/main/res/values-eu/strings.xml index c4e8b3d96..687db96db 100644 --- a/src/main/res/values-eu/strings.xml +++ b/src/main/res/values-eu/strings.xml @@ -344,7 +344,6 @@ Irakurrita bezala markatu Sarrera Sartu teklak bidaltzen du - Sartu tekla erabili mezua bidaltzeko Sartu tekla erakutsi Aurpegieren tekla sartu teklarekin aldatu audioa diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 58f1ac344..0ef628729 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -401,7 +401,6 @@ Marquer comme lu Saisie Touche Entrée pour envoyer - Utiliser la touche Entrée pour envoyer un message. Afficher la touche Entrée Remplacer la touche Émoticônes par une touche Entrée. audio diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 9a758c655..eafeb2478 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -401,7 +401,7 @@ Marcar como lido Entrada Enter envía - Utilizar a tecla Enter para enviar mensaxe + Usa a tecla Enter para enviar a mensaxe. Igualmente poderás usar Ctrl+Enter para enviar, incluso se esta opción está desactivada. Mostrar a tecla Enter Cambiar a chave de emoticonas pola tecla Enter son @@ -916,6 +916,7 @@ Chamada de audio Chamada de vídeo Axuda + Ir á conversa O micrófono non está dispoñible Só podes manter unha chamada en cada momento. Voltar á chamada activa diff --git a/src/main/res/values-hu/strings.xml b/src/main/res/values-hu/strings.xml index dd1f62576..055499efe 100644 --- a/src/main/res/values-hu/strings.xml +++ b/src/main/res/values-hu/strings.xml @@ -384,7 +384,6 @@ Megjelölés olvasottként Bevitel Küldés enterrel - Az enter billentyű használata az üzenet küldéséhez Enter billentyű megjelenítése Hangulatjelek billentyű megváltoztatása az enter billentyűre hang diff --git a/src/main/res/values-id/strings.xml b/src/main/res/values-id/strings.xml index 7700f2359..78fa9411f 100644 --- a/src/main/res/values-id/strings.xml +++ b/src/main/res/values-id/strings.xml @@ -226,7 +226,6 @@ Sampai pemberitahuan selanjutnya Masukan Enter untuk mengirim - Gunakan enter untuk mengrim pesan Tampilkan masukan kunci Mengubah kunci emoji untuk memasukan kunci audio diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index c2d1d7d88..7ea9504ca 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -401,7 +401,6 @@ Segna come già letto Input Invio spedisce - Il tasto invio spedisce il messaggio Mostra il tasto invio Cambia il tasto delle faccine nel tasto di invio audio diff --git a/src/main/res/values-iw/strings.xml b/src/main/res/values-iw/strings.xml index 532d5c187..2427c5098 100644 --- a/src/main/res/values-iw/strings.xml +++ b/src/main/res/values-iw/strings.xml @@ -220,7 +220,6 @@ לעולם לא עד אחרית הימים לחצן Enter שולח את ההודעה - השתמש בלחצן ה-Enter כלחצן השליחה הראה את לחצן ה Enter שנה את לחצן האימוג\'י ללחצן Enter קול diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 6936041e7..36f223084 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -316,7 +316,6 @@ 既読にする 入力 Enter は送信 - Enter キーをメッセージの送信に使用します Enter キーを表示 絵文字キーを Enter キーに変更 オーディオ diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml index 83c5053bb..f37f07a33 100644 --- a/src/main/res/values-ko/strings.xml +++ b/src/main/res/values-ko/strings.xml @@ -257,7 +257,6 @@ 나중에 알릴때까지 입력 엔터 키로 전송 - 엔터 키로 메세지를 보냅니다 엔터 키 표시 이모티콘 키를 엔터 키로 바꿉니다 오디오 diff --git a/src/main/res/values-nb-rNO/strings.xml b/src/main/res/values-nb-rNO/strings.xml index 9e9507e6a..4ecdbcbc5 100644 --- a/src/main/res/values-nb-rNO/strings.xml +++ b/src/main/res/values-nb-rNO/strings.xml @@ -285,7 +285,6 @@ Merk som lest Inndata Enter-forsendelsesknapp - Bruk enter for å sende en melding Vis enter-tast Endre smilefjas-tast til en enter-tast lyd diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index b4ddb26d9..02f5d2e06 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -356,7 +356,6 @@ Markeren als gelezen Invoer Enter is versturen - Gebruik de enter-toets om berichten te versturen Toon enter-toets Verander de emoticon-toets in een enter-toets audio diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 94ebf2f5f..bd17261a4 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -401,7 +401,6 @@ Oznacz jako przeczytane Ustawienia wprowadzania Enter wysyła - Używaj klawisza Enter do wysyłania wiadomości Pokaż klawisz Enter Zamień klawisz emotikon na klawisz Enter plik audio diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 1c943f0e2..7d3d968be 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -401,7 +401,6 @@ Marcar como lida Entrada Enter envia - Usa o botão Enter para enviar a mensagem. Exibir o botão Enter Altera o botão de emoticons para um botão Enter. áudio diff --git a/src/main/res/values-pt/strings.xml b/src/main/res/values-pt/strings.xml index 0e1415941..1a49ef2d9 100644 --- a/src/main/res/values-pt/strings.xml +++ b/src/main/res/values-pt/strings.xml @@ -279,7 +279,6 @@ Marcar como lida Introdução O enter envia - Use o enter para enviar a mensagem Exibir tecla enter Alterar a tecla dos emoticons para uma tecla enter áudio diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index be6cd1d42..8910ba10b 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -401,7 +401,7 @@ Marchează ca citit Opțiuni introducere text ENTER trimite - Apasă tasta ENTER pentru a trimite mesajul + Apasă tasta Enter pentru a trimite mesajul. Puteți mereu apăsa Ctrl-Enter pentru a trimite mesajul, chiar dacă această opțiune nu este activată. Arată tasta ENTER Preschimbă tasta emoticon în ENTER audio @@ -924,6 +924,7 @@ Apel audio Apel video Ajutor + Comutare la discuție Microfonul nu este disponibil Puteți avea un singur apel simultan. Reveniți la apelul în curs diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index a5e07707e..890d2d519 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -121,11 +121,15 @@ Звук уведомления о новых сообщениях Мелодия входящего звонка Грейс-период + Время, на которое уведомления будут отключены, когда вы пользуетесь аккаунтом на другом устройстве. Дополнительно Не отправлять отчёты об ошибках + Отправляя отчеты об ошибках, вы помогаете разработке этого приложения Отчёты о получении Позволяет вашим контактам видеть, когда вы получили и прочитали их сообщения Интерфейс + OpenKeychain вызвал ошибку. + Неподходящий ключ для шифрования. Принять Произошла ошибка Ошибка @@ -137,8 +141,10 @@ Сделать снимок Удовлетворять запросы на подписки Выбранный файл не является изображением + Не удалось конвертировать изображение Файл не найден Общая ошибка ввода/вывода. Возможно, на устройстве недостаточно свободного места? + У приложения, которым вы выбрали это изображение, недостаточно прав, чтобы прочитать этот файл.\n\nПожалуйста, используйте другой файловый менеджер, чтобы выбрать это изображение. Неизвестен Временно отключён В сети @@ -150,6 +156,8 @@ Регистрация не удалась Имя пользователя уже используется Регистрация завершена + Сервер не поддерживает возможность регистрации + Неправильный токен регистрации Не удалось согласовать TLS Нарушение правил Несовместимый сервер @@ -165,14 +173,17 @@ Анонсировать OpenPGP ключ Удалить открытый ключ OpenPGP Вы действительно хотите удалить ваш OpenPGP публичный ключ из опубликованных?\nВаши собеседники не смогут больше отправлять вам зашифрованные OpenPGP сообщения. + Публичный ключ OpenPGP опубликован. Включить аккаунт Вы уверены? + Удаление аккаунта также удалит всю историю вашей переписки Запись голоса XMPP-адрес Заблокировать XMPP-адрес username@example.com Пароль Недопустимый XMPP-адрес + Нехватка памяти. Изображение слишком большое Вы хотите добавить %s в вашу адресную книгу? Информация о сервере XEP-0313: Архив сообщений @@ -181,6 +192,7 @@ XEP-0191: Команда блокирования XEP-0237: Версии списков XEP-0198: Управление потоками + XEP-0215: Обнаружение внешних служб XEP-0163: PEP (Аватары / OMEMO) XEP-0363: Загрузка по HTTP XEP-0357: Push-уведомления @@ -188,9 +200,14 @@ недоступно Отсутствие анонсирования открытых ключей Присутствие: только что + Присутствие: одну минуту назад Присутствие: %d мин. назад + Присутствие: один час назад Присутствие: %d час. назад + Присутствие: один день назад Присутствие: %d дн. назад + Зашифрованное сообщение. Пожалуйста, установите OpenKeychain для дешифрования. + Найдены новые OpenPGP зашифрованые сообщения ID OpenPGP ключа OMEMO отпечаток v\\OMEMO отпечаток @@ -212,10 +229,14 @@ Выбрать Контакт уже существует Присоединиться + канал@конференция.пример.com/никнейм + канал@конференция.пример.com Сохранить закладку Удалить закладку Уничтожить конференцию Уничтожить канал + Вы уверены, что хотите распустить эту конференцию?\n\nПредупреждение:Конференция будет полностью удалена с сервера. + Вы уверены, что хотите закрыть этот публичный канал?\n\nПредупреждение: Канал будет полностью удален с сервера. Не удалось уничтожить конференцию Не удалось уничтожить канал Редактировать тему конференции @@ -378,7 +399,6 @@ Прочитано Ввод Отправить на \"Enter\" - Клавиша \"Enter\" отправляет сообщение Показывать клавишу ввода Поменять кнопку смайликов на кнопку ввода аудио @@ -393,7 +413,7 @@ Скрыть пользователей вне сети %s печатает… %s прекратил набор - %s печатают... + %s печатают… %s перестали печатать Оповещения о наборе Позволяет вашим контактам видеть, когда вы пишете им новое сообщение @@ -440,11 +460,11 @@ Повреждено Доступность \"Отошёл\" когда экран выключен - Отмечает ваш ресурс как «отошёл» когда экран выключен + Устанавливает статус \"Отошёл\", когда экран выключен \"Не беспокоить\" в беззвучном режиме - Помечать ресурс как \"Не беспокоить\", когда устройство в беззвучном режиме + Устанавливает статус \"Не беспокоить\", когда устройство в беззвучном режиме Не доступен в режиме вибрации - Помечать ресурс как \"Не беспокоить\", когда устройство на вибрации + Устанавливает статус \"Не беспокоить\", когда устройство на вибрации Расширенные настройки подключения Показывать имя сервера и порт в настройках аккаунтов xmpp.example.com @@ -674,6 +694,7 @@ Серверу не удалось аутентифицироваться в качестве \"%s\". Сертификат подходит только для: Вы все равно хотите подключиться? Детали сертификата: + Один раз Сканеру QR-кода необходим доступ к камере Прокручивать вниз Прокручивать вниз после отправки сообщения @@ -707,6 +728,7 @@ Открепить позицию Копировать местоположение Поделиться местоположением + Направления Поделиться местоположением Показать местоположение Поделиться @@ -782,8 +804,8 @@ Вы уверены, что хотите прервать процедуру регистрации? Да Нет - Подтверждение... - Запрос SMS... + Подтверждение… + Запрос SMS… Введенный вами код некорректен. Отправленный вам код просрочен. Неизвестная ошибка сети. @@ -799,6 +821,7 @@ У вас есть ограничение скорости Слишком много попыток Вы используете устаревшую версию приложения + Обновить Этот номер телефона в данный момент авторизирован на другом устройстве. Пожалуйста, введите ваше имя, чтобы другие люди, у которых нет вас в списке контактов, знали кто вы. Ваше имя @@ -811,7 +834,7 @@ Этот канал сделает ваш XMPP-адрес публичным Электронная книга Оригинал (без сжатия) - Открыть с помощью... + Открыть с помощью… Картинка профиля Conversations Выбрать аккаунт Восстановить из резервной копии @@ -831,7 +854,7 @@ Пожалуйста, предоставьте имя для канала Пожалуйста, предоставьте XMPP-адрес Это XMPP-адрес. Пожалуйста, предоставьте имя. - Создание публичного канала... + Создание публичного канала… Этот канал уже существует Вы присоединились к существующему каналу Не удалось сохранить настройки канала @@ -868,11 +891,12 @@ Эта учетная запись уже настроена Пожалуйста, введите пароль этой учетной записи Не удалось совершить это действие - Присоединиться к публичному каналу... + Присоединиться к публичному каналу… + Приложение для обмена не предоставило право доступа к этому файлу. jabber.network Локальный сервер - Большиству пользователей следует выбрать ‘jabber.network’ для лучших предложений от всей публичной экосистемы XMPP. + Большиству пользователей следует выбрать ‘jabber.network’ для получения наиболее подходящих предложений от всей публичной экосистемы XMPP. Способ поиска каналов Резервное копирование О @@ -890,6 +914,8 @@ Вызов Занято Не удалось установить соединение + Соединение потеряно + Вызов отклонен Ошибка приложения Завершить Активный звонок @@ -902,9 +928,18 @@ Пропущен вызов Аудиозвонок Видеозвонок + Помощь + Переключиться на беседу Микрофон недоступен + Нельзя одновременно совершать больше одного звонка. Вернуться к текущему звонку Не удалось переключить камеру Добавить в избранные Убрать из избранных + + Просмотр %1$d участника + Просмотр %1$d участников + Просмотр %1$d участников + Просмотр %1$d участников + diff --git a/src/main/res/values-sk/strings.xml b/src/main/res/values-sk/strings.xml index 2b1bc7a5f..cd8643ff1 100644 --- a/src/main/res/values-sk/strings.xml +++ b/src/main/res/values-sk/strings.xml @@ -216,7 +216,6 @@ Nikdy Až do odvolania Enter odosiela - Použiť klávesu enter na odoslanie správy Zobraziť klávesu enter Zmeniť klávesu s emotikonmi na klávesu enter audio diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml index 31f2d50aa..69102e87b 100644 --- a/src/main/res/values-sr/strings.xml +++ b/src/main/res/values-sr/strings.xml @@ -296,7 +296,6 @@ Означи прочитаним Унос Ентер шаље - Користи Ентер тастер за слање порука Прикажи Ентер тастер Промени тастер за емотиконе у ентер тастер звук diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index affc4780d..2929dba55 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -319,7 +319,6 @@ Läsmarkera Input Skicka med enter - Använd enter-knappen för att skicka meddelande Visa enter-knappen Byt ut emoticons-tangenten mot en enter-tangent ljud diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml index 06db19906..23421ad9e 100644 --- a/src/main/res/values-tr-rTR/strings.xml +++ b/src/main/res/values-tr-rTR/strings.xml @@ -271,7 +271,6 @@ İkinci bildirime kadar Girdi Enter=gönder - İleti göndermek için \"enter\" tuşunu kullan \"Enter\" tuşunu göster İfade simgesi tuşunu \"enter\" tuşu olarak değiştir ses diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 0d3da3030..2f41c4e8c 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -401,7 +401,6 @@ Позначити як прочитане Введення Enter для надсилання - Використовувати Enter для надсилання повідомлення Показувати кнопку Enter Змінити клавішу емоційок на кнопку Enter аудіо @@ -932,6 +931,7 @@ Голосовий виклик Відеовиклик Допомога + Перейти до розмови Недоступний мікрофон Водночас можливо здійснювати лише один виклик. Назад до активного виклику diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml index bc329d8cf..ff68350b9 100644 --- a/src/main/res/values-vi/strings.xml +++ b/src/main/res/values-vi/strings.xml @@ -246,7 +246,6 @@ Chưa từng Cho đến thông báo tiếp theo Bấm Enter để gửi - Bấm nút Enter để gửi tin nhắn Hiện nút Enter Đổi nút biểu tượng cảm xúc thành nút Enter âm thanh diff --git a/src/main/res/values-w384dp/dimens.xml b/src/main/res/values-w384dp/dimens.xml index 4a29d6f40..4f3550ab5 100644 --- a/src/main/res/values-w384dp/dimens.xml +++ b/src/main/res/values-w384dp/dimens.xml @@ -1,9 +1,10 @@ - - - 12sp - 288dp - 72dp - 64dp - 288dp + + + 12sp + 288dp + 288dp + 72dp + 64dp + 288dp diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index b94f7a156..27e8cd01c 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -401,7 +401,6 @@ 标记为已读 输入 点击回车发送 - 回车键发送消息 显示回车键 将表情键改为回车键 音频 diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index d026a66e0..f3447d8cc 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -260,7 +260,6 @@ 直到新的通知 輸入 回車是發送 - 用 Enter 鍵來發送訊息 顯示回車鍵 改變表情鍵為回車鍵 音訊 diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml index 1f78fb09b..e67517049 100644 --- a/src/main/res/values/attrs.xml +++ b/src/main/res/values/attrs.xml @@ -97,6 +97,7 @@ + diff --git a/src/main/res/values/dimens.xml b/src/main/res/values/dimens.xml index 215f11a14..069c19edc 100644 --- a/src/main/res/values/dimens.xml +++ b/src/main/res/values/dimens.xml @@ -10,6 +10,7 @@ 11sp 224dp + 224dp 16dp 80dp diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 30cb1ad91..f4cf32218 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -401,7 +401,7 @@ Mark as read Input Enter is send - Use enter key to send message + Use Enter key to send message. You can always use Ctrl+Enter to send message, even if this option is disabled. Show enter key Change the emoticons key to an enter key audio @@ -918,6 +918,7 @@ Audio call Video call Help + Switch to conversation Your microphone is unavailable You can only have one call at a time. Return to ongoing call diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index e66214615..d227f3eba 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -115,6 +115,7 @@ @drawable/ic_delete_black_24dp @drawable/ic_search_white_24dp @drawable/ic_help_white_24dp + @drawable/ic_question_answer_white_24dp @drawable/ic_lock_open_white_24dp @drawable/ic_settings_black_24dp @drawable/ic_share_white_24dp @@ -269,6 +270,7 @@ @drawable/ic_delete_white_24dp @drawable/ic_search_white_24dp @drawable/ic_help_white_24dp + @drawable/ic_question_answer_white_24dp @drawable/ic_lock_open_white_24dp @drawable/ic_settings_white_24dp @drawable/ic_share_white_24dp diff --git a/src/quicksy/res/values-ru/strings.xml b/src/quicksy/res/values-ru/strings.xml new file mode 100644 index 000000000..f36a8771a --- /dev/null +++ b/src/quicksy/res/values-ru/strings.xml @@ -0,0 +1,26 @@ + + + В Quicksy произошёл сбой + Отправляя отчёты об ошибках, вы помогаете исправить и улучшить Quicksy\nПредупреждение: Отчёт будет отправлен разработчику с вашего XMPP аккаунта. + Conversations использует стороннее приложение под названием OpenKeychain для шифровки и расшифровки сообщений и управления открытыми ключами.\nПрограмма OpenKeychain распространяется под лицензией GPLv3 и доступна через F-Droid или Google Play.\n\n(Пожалуйста, перезапустите Quicksy после установки.) + Quicksy не может зашифровать сообщение, потому что ваш собеседник не анонсирует свой открытый ключ.\n\nПожалуйста, попросите вашего собеседника настроить OpenPGP. + Quicksy не может зашифровать сообщение, потому что ваши собеседники не анонсируют их открытые ключи.\n\nПожалуйста, попросите ваших собеседников настроить OpenPGP. + Время, на которое уведомления от Quicksy будут отключены, когда вы пользуетесь аккаунтом на другом устройстве. + Отправляя отчёты об ошибках, вы помогаете в разработке Quicksy + Quicksy требуется доступ к основной файловой системе + Quicksy требуется доступ к камере + Ваше устройство использует агрессивную оптимизацию энергопотребления Quicksy, что может привести к задержке уведомлений и даже потере сообщений.\nРекомендуем ее отключить. + Ваше устройство использует агрессивную оптимизацию энергопотребления Quicksy, что может привести к задержке уведомлений и даже потере сообщений.\nСейчас появится предложение ее отключить. + Извещать собеседников, когда вы пользуетесь Quicksy + Ваша операционная система не позволяет Quicksy доступ в Интернет в фоновом режиме. Для получения уведомлений вы должны разрешить Quicksy неограниченный доступ в Интернет в режиме экономии трафика.\nQuicksy будет использовать настолько мало трафика, насколько это возможно. + Ваше устройство не позволяет отключить режим экономии трафика для Quicksy. + Чтобы продолжать получать уведомления, даже если экран выключен, вам необходимо добавить Quicksy в список защищенных приложений. + Quicksy не удалось отправить зашифрованные сообщения для %1$s. Причиной этому может быть использование получателем устаревшего сервера или приложения, которые не поддерживают OMEMO. + Quicksy требуется доступ к микрофону + Эта категория уведомлений используется для отображения постоянного уведомления о том что Quicksy работает. + Аватар для Quicksy + Quicksy не доступно в вашем регионе + Не удалось подтвердить сервер. + Неизвестная ошибка безопасности. + Время ожидания подключения к серверу вышло. + diff --git a/src/quicksy/res/values-uk/strings.xml b/src/quicksy/res/values-uk/strings.xml index d9069b63c..da4251850 100644 --- a/src/quicksy/res/values-uk/strings.xml +++ b/src/quicksy/res/values-uk/strings.xml @@ -1,26 +1,26 @@ - Програма дала збій - Надсилаючи траси стеків виклику Ви допомагаєте розробці цієї програми\nУвага: Це використовуватиме ваш обліковий запис XMPP, щоб надсилати траси стеків виклику розробнику. - Ця програма використовує сторонній додаток, який називається OpenKeychain, для шифрування повідомлень і впорядкування ваших публічних ключів.\n\nOpenKeychain поширюється на умова ліцензії GPLv3 й доступна в F-Droid та Google Play.\n\n(Будь ласка, перезапустіть цю програму після цього.) - Ця програма не змогла зашифрувати повідомлення, оскільки публічний ключ адресата не опубліковано.\n\nБудь ласка, попросіть адресата налаштувати OpenPGP. - Ця програма не змогла зашифрувати повідомлення, оскільки публічні ключі адресатів не опубліковано.\n\nБудь ласка, попросіть їх налаштувати OpenPGP. - Час, протягом якого ця програма дотримується тиші після активності на іншому пристрої. - Надсилаючи траси стеків виклику, ви допомагаєте розробці цієї програми. - Ця програма потребує доступу до зовнішнього сховища - Ця програма потребує доступу до камери + Відмова застосунку + Надсилаючи траси стеків виклику, ви допомагаєте у розробці цього застосунку.\nУвага: Це використовуватиме ваш обліковий запис XMPP, щоб надсилати траси стеків виклику розробнику. + Цей застосунок використовує сторонній застосунок, який називається OpenKeychain, для шифрування повідомлень і впорядкування ваших публічних ключів.\n\nOpenKeychain поширюється на умова ліцензії GPLv3 й доступна в F-Droid та Google Play.\n\n(Будь ласка, перезапустіть цю програму після цього.) + Застосунок не зміг зашифрувати повідомлення, оскільки публічний ключ адресата не опубліковано.\n\nБудь ласка, попросіть адресата налаштувати OpenPGP. + Застосунок не зміг зашифрувати повідомлення, оскільки публічні ключі адресатів не опубліковано.\n\nБудь ласка, попросіть їх налаштувати OpenPGP. + Час, протягом якого застосунок дотримується тиші після активності на іншому пристрої. + Надсилаючи траси стеків виклику, ви допомагаєте розробці цього застосунку. + Цей застосунок потребує доступу до зовнішнього сховища + Цей застосунок потребує доступу до камери Ваш пристрій використовує посилену оптимізацію батареї для Quicksy, що може призвести до затримок у отриманні сповіщень або навіть втраті повідомлень.\nlt Рекомендується вимкнути цю функцію. Ваш пристрій використовує посилену оптимізацію батареї для Quicksy, що може призвести до затримок у отриманні сповіщень або навіть втраті повідомлень.\n\n Ми вас попросимо вимкнути цю функцію. Дозволити всім вашим контактам знати, коли ви використовуєте цю програму. Операційна система обмежує цю програму в доступі до Інтернету, коли вона не на екрані. Щоб отримувати сповіщення про нові повідомлення, слід дозволити цій програмі доступ, коли ввімкнено заощадження трафіку.\nЦя програма все одно намагатиметься заощадити трафік, коли можливо. Цей пристрій не надає можливості зробити виняток щодо заощадження трафіку для цієї програми. Щоб продовжувати отримувати сповіщення, навіть коли екран вимкнуто, потрібно додати цю програму до списку захищених. - Програма не може надіслати зашифроване повідомлення до %1$s. Можливо, це обумовлено тим, що адресат користується застарілим сервером або програмою, яка не підтримує OMEMO. - Програма потребує доступу до мікрофона + Застосунок не може надіслати зашифроване повідомлення до %1$s. Можливо, це обумовлено тим, що адресат користується застарілим сервером або програмою, яка не підтримує OMEMO. + Цей застосунок потребує доступу до мікрофона Цей вид сповіщень показує постійне сповіщення про те, що ця програма працює. Зображення профілю для Quicksy - Ця програма не доступна у Вашій країні. - Автентичність сервера не підтверджено + Цей застосунок не доступний у вашій країні. + Автентичність сервера не підтверджено. Невідома помилка безпеки. Вичерпано час для встановлення з\'єднання із сервером.