...
 
Commits (19)
......@@ -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
......
# 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
......
......@@ -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
}
......
* 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
• add 'Return to chat' to audio call screen
• Improve keyboard shortcuts
• bug fixes
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<Jid> accounts = mDatabaseBackend.getAccountJids(false);
final List<Jid> accounts = mDatabaseBackend.getAccountJids(false);
final ArrayList<BackupFile> backupFiles = new ArrayList<>();
final Set<String> 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());
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
cipher.init(false, new AEADParameters(new KeyParameter(key), 128, backupFileHeader.getIv()));
final CipherInputStream cipherInputStream = new CipherInputStream(countingInputStream, cipher);
GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream);
BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, "UTF-8"));
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) {
......
......@@ -8,4 +8,5 @@
<string name="magic_create_text_on_x">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.</string>
<string name="magic_create_text_fixed">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.</string>
<string name="your_server_invitation">Deine Einladung für den Server</string>
<string name="improperly_formatted_provisioning">Falsch formatierter Provisionierungscode</string>
</resources>
......@@ -8,4 +8,4 @@
<string name="magic_create_text_on_x">Έχετε προσκληθεί στο %1$s. Ένα όνομα χρήστη έχει ήδη επιλεγεί για εσάς. Θα σας καθοδηγήσουμε στη διαδικασία δημιουργίας λογαριασμού.\nΕπιλέγοντας τον %1$s ως πάροχο θα μπορείτε να επικοινωνείτε με χρήστες άλλων παρόχων δίνοντάς τους την πλήρη διεύθυνση XMPP σας.</string>
<string name="magic_create_text_fixed">Έχετε προσκληθεί στο %1$s. Ένα όνομα χρήστη έχει ήδη επιλεγεί για εσάς. Θα σας καθοδηγήσουμε στη διαδικασία δημιουργίας λογαριασμού.\nΘα μπορείτε να επικοινωνείτε με χρήστες άλλων παρόχων δίνοντάς τους την πλήρη διεύθυνση XMPP σας.</string>
<string name="your_server_invitation">Η πρόσκλησή σας στον διακομιστή</string>
</resources>
</resources>
......@@ -8,4 +8,5 @@
<string name="magic_create_text_on_x">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. </string>
<string name="magic_create_text_fixed">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. </string>
<string name="your_server_invitation">Tu invitación al servidor</string>
<string name="improperly_formatted_provisioning">Código de abastecimiento formateado incorrectamente</string>
</resources>
......@@ -8,4 +8,5 @@
<string name="magic_create_text_on_x">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.</string>
<string name="magic_create_text_fixed">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.</string>
<string name="your_server_invitation">Votre invitation au serveur</string>
<string name="improperly_formatted_provisioning">Code de provisionnement mal formaté</string>
</resources>
......@@ -8,4 +8,5 @@
<string name="magic_create_text_on_x">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.</string>
<string name="magic_create_text_fixed">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.</string>
<string name="your_server_invitation">O convite do teu servidor</string>
<string name="improperly_formatted_provisioning">Código de aprovisionamento con formato non válido</string>
</resources>
......@@ -8,4 +8,4 @@
<string name="magic_create_text_on_x">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.</string>
<string name="magic_create_text_fixed">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.</string>
<string name="your_server_invitation">Az Ön kiszolgálómeghívása</string>
</resources>
</resources>
......@@ -10,4 +10,4 @@ In ogni caso per facilitare puoi creare facilmente un account su chat.sum7.eu, u
<string name="magic_create_text_on_x">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.</string>
<string name="magic_create_text_fixed">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.</string>
<string name="your_server_invitation">Il tuo invito al server</string>
</resources>
</resources>
......@@ -8,4 +8,4 @@
<string name="magic_create_text_on_x">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.</string>
<string name="magic_create_text_fixed">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.</string>
<string name="your_server_invitation">Je server uitnodiging</string>
</resources>
</resources>
......@@ -8,4 +8,4 @@
<string name="magic_create_text_on_x">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.</string>
<string name="magic_create_text_fixed">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.</string>
<string name="your_server_invitation">Zaproszenie twojego serwera</string>
</resources>
</resources>
......@@ -8,4 +8,4 @@
<string name="magic_create_text_on_x">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.</string>
<string name="magic_create_text_fixed">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.</string>
<string name="your_server_invitation">Seu convite do servidor</string>
</resources>
</resources>
......@@ -8,4 +8,5 @@
<string name="magic_create_text_on_x">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.</string>
<string name="magic_create_text_fixed">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.</string>
<string name="your_server_invitation">Invitația serverului dumneavoastră</string>
<string name="improperly_formatted_provisioning">Cod de acces formatat necorespunzător</string>
</resources>
......@@ -4,5 +4,8 @@
<string name="use_chat.sum7.eu">Использовать chat.sum7.eu</string>
<string name="create_new_account">Создать новый аккаунт</string>
<string name="do_you_have_an_account">У вас есть аккаунт XMPP? Если вы использовали Conversations или другой XMPP-клиент в прошлом, то скорее всего, он у вас есть. Если у вас нет аккаунта, вы можете создать его прямо сейчас.\nНекоторые провайдеры электронной почты также регистрируют аккаунты XMPP. </string>
<string name="server_select_text">XMPP - это независимая сеть обмена сообщениями. Conversations позволяет вам подключиться к любому XMPP-серверу на ваш выбор.\nЕсли у вас нет сервера, предлагаем вам зарегистрировать аккаунт на chat.sum7.eu, сервер, специально предназначеный для работы с приложением Conv6sations.</string>
</resources>
<string name="server_select_text">XMPP - это независимая сеть обмена сообщениями. Conversations позволяет вам подключиться к любому XMPP-серверу на ваш выбор.\nЕсли у вас нет сервера, предлагаем вам зарегистрировать аккаунт на chat.sum7.eu, сервере, специально предназначенном для работы с Conversations.</string>
<string name="magic_create_text_on_x">Вас пригласили на %1$s. Мы проведём вас через процесс создания аккаунта. Аккаунт на %1$s позволит вам общаться с пользователями и на этом, и на других серверах, используя ваш полный XMPP-адрес.</string>
<string name="magic_create_text_fixed">Вас пригласили на %1$s. Вам уже назначили имя пользователя. Мы проведём вас через процесс создания аккаунта. Этот аккаунт позволит вам общаться с пользователями и на этом, и на других серверах, используя ваш полный XMPP-адрес.</string>
<string name="your_server_invitation">Ваше приглашение</string>
</resources>
......@@ -8,4 +8,5 @@
<string name="magic_create_text_on_x">Вас запросили до %1$s. Ми проведемо вас крок за кроком, щоб створити обліковий запис.\nОбираючи %1$s в якості свого постачальника, ви зможете спілкуватися з користувачами інших постачальників, для цього повідомте їм свою повну адресу XMPP.</string>
<string name="magic_create_text_fixed">Вас запросили до %1$s. Для вас створено ім\'я користувача. Ми проведемо вас крок за кроком, щоб створити обліковий запис.\nВи зможете спілкуватися з користувачами інших постачальників, для цього повідомите їм свою повну адресу XMPP.</string>
<string name="your_server_invitation">Ваше запрошення до сервера</string>
<string name="improperly_formatted_provisioning">Неправильно відформатований код забезпечення</string>
</resources>
......@@ -8,4 +8,4 @@
<string name="magic_create_text_on_x">您已受邀参加%1$s。 我们将指导您完成创建帐户的过程。\n选择%1$s作为提供者后,您可以通过提供其他人的完整XMPP地址与其他提供者的用户进行交流。</string>
<string name="magic_create_text_fixed">您已受邀参加%1$s。 已经为您选择了一个用户名。 我们将指导您完成创建帐户的过程。\n您可以通过向其他提供商的用户提供完整的XMPP地址来与他们进行交流。</string>
<string name="your_server_invitation">你的服务器邀请</string>
</resources>
</resources>
......@@ -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();
}
......
......@@ -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();
}
......
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);
......
......@@ -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
......
......@@ -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);
}
......
......@@ -88,6 +88,10 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
RtpEndUserState.APPLICATION_ERROR,
RtpEndUserState.CONNECTIVITY_ERROR
);
private static final List<RtpEndUserState> 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<JingleRtpConnection> 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) {
......
......@@ -553,7 +553,7 @@ public class MessageAdapter extends ArrayAdapter<Message> 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<Message> 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);
......
......@@ -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();
......
......@@ -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() {
......
......@@ -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<String> 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();
}
......
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_help"
android:icon="?attr/icon_help"
android:title="@string/help"
app:showAsAction="always"/>
app:showAsAction="always" />
<item
android:id="@+id/action_goto_chat"
android:icon="?attr/icon_goto_chat"
android:title="@string/switch_to_conversation"
app:showAsAction="always" />
</menu>
\ No newline at end of file
......@@ -316,7 +316,6 @@
<string name="reply">الرد</string>
<string name="mark_as_read">إعتباره كمقروء</string>
<string name="pref_enter_is_send">أدخل للإرسال</string>
<string name="pref_enter_is_send_summary">استخدام مفتاح الدخول لإرسال رسالة</string>
<string name="pref_display_enter_key">عرض مفتاح الادخال</string>
<string name="pref_display_enter_key_summary">تغيير مفتاح الرموز إلى مفتاح الدخول</string>
<string name="audio">صوت</string>
......
......@@ -316,7 +316,6 @@
<string name="mark_as_read">Отбелязване като прочетено</string>
<string name="pref_input_options">Въвеждане</string>
<string name="pref_enter_is_send">Enter изпраща</string>
<string name="pref_enter_is_send_summary">Натискането на клавиша Enter изпраща съобщението</string>
<string name="pref_display_enter_key">Показване на клавиша Enter</string>
<string name="pref_display_enter_key_summary">Смяна на клавиша за емотикони с клавиша Enter</string>
<string name="audio">аудио</string>
......
......@@ -314,7 +314,6 @@
<string name="mark_as_read">Marcar com llegit</string>
<string name="pref_input_options">Entrada</string>
<string name="pref_enter_is_send">Entra per enviar</string>
<string name="pref_enter_is_send_summary">Utilitza el botó enter per enviar el missatge</string>
<string name="pref_display_enter_key">Mostra el botó enter</string>
<string name="pref_display_enter_key_summary">Canviar la clau dels emoticones per un botó d\'entrada </string>
<string name="audio">audio</string>
......
......@@ -264,7 +264,6 @@
<string name="until_further_notice">Než opět změním</string>
<string name="pref_input_options">Vstup</string>
<string name="pref_enter_is_send">Enter odesílá</string>
<string name="pref_enter_is_send_summary">Použít klávesu enter pro odesílání zpráv</string>
<string name="pref_display_enter_key">Zobrazit klávesu enter</string>
<string name="pref_display_enter_key_summary">Změnit klávesu emotikon na klávesu enter</string>
<string name="audio">audio</string>
......
......@@ -400,10 +400,10 @@
<string name="reply">Antworten</string>
<string name="mark_as_read">Als gelesen markieren</string>
<string name="pref_input_options">Eingabe</string>
<string name="pref_enter_is_send">Eingabe-Taste (Enter) sendet Nachricht</string>
<string name="pref_enter_is_send_summary">Eingabe-Taste (Enter) zum Versenden einer Nachricht verwenden</string>
<string name="pref_display_enter_key">Zeige Eingabe-Taste (Enter)</string>
<string name="pref_display_enter_key_summary">Emoji-Taste durch Eingabe-Taste ersetzen</string>
<string name="pref_enter_is_send">Eingabetaste sendet Nachricht</string>
<string name="pref_enter_is_send_summary">Nutze die Eingabetaste zum Versenden einer Nachricht. Strg+Eingabetaste sendet die Nachricht unabhängig von dieser Einstellung.</string>
<string name="pref_display_enter_key">Zeige Eingabetaste</string>
<string name="pref_display_enter_key_summary">Emoji-Taste durch Eingabetaste ersetzen</string>
<string name="audio">Audio</string>
<string name="video">Video</string>
<string name="image">Bild</string>
......@@ -916,6 +916,7 @@
<string name="audio_call">Audioanruf</string>
<string name="video_call">Videoanruf</string>
<string name="help">Hilfe</string>
<string name="switch_to_conversation">Zur Unterhaltung wechseln</string>
<string name="microphone_unavailable">Dein Mikrofon ist nicht verfügbar</string>
<string name="only_one_call_at_a_time">Du kannst immer nur einen Anruf zur gleichen Zeit machen.</string>
<string name="return_to_ongoing_call">Zurück zum laufenden Aufruf</string>
......
......@@ -347,7 +347,6 @@
<string name="mark_as_read">Σημείωμα ως αναγνωσμένο</string>
<string name="pref_input_options">Είσοδος</string>
<string name="pref_enter_is_send">Αποστολή με το πλήκτρο Enter</string>
<string name="pref_enter_is_send_summary">Χρήση του πλήκτρου Enter για την αποστολή μηνύματος</string>
<string name="pref_display_enter_key">Εμφάνιση του πλήκτρου Enter</string>
<string name="pref_display_enter_key_summary">Αλλαγή του πλήκτρου emoticons σε πλήκτρο Enter</string>
<string name="audio">ήχος</string>
......
......@@ -401,7 +401,7 @@
<string name="mark_as_read">Marcar como leído</string>
<string name="pref_input_options">Entrada</string>
<string name="pref_enter_is_send">Intro para enviar</string>
<string name="pref_enter_is_send_summary">Usar la tecla intro para enviar el mensaje</string>
<string name="pref_enter_is_send_summary">Utilizar la tecla Enter para enviar un mensaje. Siempre puedes usar Ctrl+Enter para enviar un mensaje, incluso si esta opción está deshabilitada.</string>
<string name="pref_display_enter_key">Mostrar tecla Intro</string>
<string name="pref_display_enter_key_summary">Cambiar la tecla de emoticonos por la tecla Intro</string>
<string name="audio">audio</string>
......@@ -916,6 +916,7 @@
<string name="audio_call">Audio llamada</string>
<string name="video_call">Video llamada</string>
<string name="help">Ayuda</string>
<string name="switch_to_conversation">Cambiar a conversación</string>
<string name="microphone_unavailable">Tu micrófono no está disponible</string>
<string name="only_one_call_at_a_time">Solo puedes hacer una llamada a la vez</string>
<string name="return_to_ongoing_call">Volver a la llamada en curso</string>
......
......@@ -344,7 +344,6 @@
<string name="mark_as_read">Irakurrita bezala markatu</string>
<string name="pref_input_options">Sarrera</string>
<string name="pref_enter_is_send">Sartu teklak bidaltzen du</string>
<string name="pref_enter_is_send_summary">Sartu tekla erabili mezua bidaltzeko</string>
<string name="pref_display_enter_key">Sartu tekla erakutsi</string>
<string name="pref_display_enter_key_summary">Aurpegieren tekla sartu teklarekin aldatu</string>
<string name="audio">audioa</string>
......
......@@ -401,7 +401,6 @@
<string name="mark_as_read">Marquer comme lu</string>
<string name="pref_input_options">Saisie</string>
<string name="pref_enter_is_send">Touche Entrée pour envoyer</string>
<string name="pref_enter_is_send_summary">Utiliser la touche Entrée pour envoyer un message.</string>
<string name="pref_display_enter_key">Afficher la touche Entrée</string>
<string name="pref_display_enter_key_summary">Remplacer la touche Émoticônes par une touche Entrée.</string>
<string name="audio">audio</string>
......
......@@ -401,7 +401,7 @@
<string name="mark_as_read">Marcar como lido</string>
<string name="pref_input_options">Entrada</string>
<string name="pref_enter_is_send">Enter envía</string>
<string name="pref_enter_is_send_summary">Utilizar a tecla Enter para enviar mensaxe</string>
<string name="pref_enter_is_send_summary">Usa a tecla Enter para enviar a mensaxe. Igualmente poderás usar Ctrl+Enter para enviar, incluso se esta opción está desactivada.</string>
<string name="pref_display_enter_key">Mostrar a tecla Enter</string>
<string name="pref_display_enter_key_summary">Cambiar a chave de emoticonas pola tecla Enter</string>
<string name="audio">son</string>
......@@ -916,6 +916,7 @@
<string name="audio_call">Chamada de audio</string>
<string name="video_call">Chamada de vídeo</string>
<string name="help">Axuda</string>
<string name="switch_to_conversation">Ir á conversa</string>
<string name="microphone_unavailable">O micrófono non está dispoñible</string>
<string name="only_one_call_at_a_time">Só podes manter unha chamada en cada momento.</string>
<string name="return_to_ongoing_call">Voltar á chamada activa</string>
......
......@@ -384,7 +384,6 @@
<string name="mark_as_read">Megjelölés olvasottként</string>
<string name="pref_input_options">Bevitel</string>
<string name="pref_enter_is_send">Küldés enterrel</string>
<string name="pref_enter_is_send_summary">Az enter billentyű használata az üzenet küldéséhez</string>
<string name="pref_display_enter_key">Enter billentyű megjelenítése</string>
<string name="pref_display_enter_key_summary">Hangulatjelek billentyű megváltoztatása az enter billentyűre</string>
<string name="audio">hang</string>
......
......@@ -226,7 +226,6 @@
<string name="until_further_notice">Sampai pemberitahuan selanjutnya</string>
<string name="pref_input_options">Masukan</string>
<string name="pref_enter_is_send">Enter untuk mengirim</string>
<string name="pref_enter_is_send_summary">Gunakan enter untuk mengrim pesan</string>
<string name="pref_display_enter_key">Tampilkan masukan kunci</string>
<string name="pref_display_enter_key_summary">Mengubah kunci emoji untuk memasukan kunci</string>
<string name="audio">audio</string>
......
......@@ -401,7 +401,6 @@
<string name="mark_as_read">Segna come già letto</string>
<string name="pref_input_options">Input</string>
<string name="pref_enter_is_send">Invio spedisce</string>
<string name="pref_enter_is_send_summary">Il tasto invio spedisce il messaggio</string>
<string name="pref_display_enter_key">Mostra il tasto invio</string>
<string name="pref_display_enter_key_summary">Cambia il tasto delle faccine nel tasto di invio</string>
<string name="audio">audio</string>
......
......@@ -220,7 +220,6 @@
<string name="never">לעולם לא</string>
<string name="until_further_notice">עד אחרית הימים</string>
<string name="pref_enter_is_send">לחצן Enter שולח את ההודעה</string>
<string name="pref_enter_is_send_summary">השתמש בלחצן ה-Enter כלחצן השליחה</string>
<string name="pref_display_enter_key">הראה את לחצן ה Enter</string>
<string name="pref_display_enter_key_summary">שנה את לחצן האימוג\'י ללחצן Enter</string>
<string name="audio">קול</string>
......
......@@ -316,7 +316,6 @@
<string name="mark_as_read">既読にする</string>
<string name="pref_input_options">入力</string>
<string name="pref_enter_is_send">Enter は送信</string>
<string name="pref_enter_is_send_summary">Enter キーをメッセージの送信に使用します</string>
<string name="pref_display_enter_key">Enter キーを表示</string>
<string name="pref_display_enter_key_summary">絵文字キーを Enter キーに変更</string>
<string name="audio">オーディオ</string>
......
......@@ -257,7 +257,6 @@
<string name="until_further_notice">나중에 알릴때까지 </string>
<string name="pref_input_options">입력</string>
<string name="pref_enter_is_send">엔터 키로 전송 </string>
<string name="pref_enter_is_send_summary">엔터 키로 메세지를 보냅니다 </string>
<string name="pref_display_enter_key">엔터 키 표시 </string>
<string name="pref_display_enter_key_summary">이모티콘 키를 엔터 키로 바꿉니다 </string>
<string name="audio">오디오 </string>
......
......@@ -285,7 +285,6 @@
<string name="mark_as_read">Merk som lest</string>
<string name="pref_input_options">Inndata</string>
<string name="pref_enter_is_send">Enter-forsendelsesknapp</string>
<string name="pref_enter_is_send_summary">Bruk enter for å sende en melding</string>
<string name="pref_display_enter_key">Vis enter-tast</string>
<string name="pref_display_enter_key_summary">Endre smilefjas-tast til en enter-tast</string>
<string name="audio">lyd</string>
......
......@@ -356,7 +356,6 @@
<string name="mark_as_read">Markeren als gelezen</string>
<string name="pref_input_options">Invoer</string>
<string name="pref_enter_is_send">Enter is versturen</string>
<string name="pref_enter_is_send_summary">Gebruik de enter-toets om berichten te versturen</string>
<string name="pref_display_enter_key">Toon enter-toets</string>
<string name="pref_display_enter_key_summary">Verander de emoticon-toets in een enter-toets</string>
<string name="audio">audio</string>
......
......@@ -401,7 +401,6 @@
<string name="mark_as_read">Oznacz jako przeczytane</string>
<string name="pref_input_options">Ustawienia wprowadzania</string>
<string name="pref_enter_is_send">Enter wysyła</string>
<string name="pref_enter_is_send_summary">Używaj klawisza Enter do wysyłania wiadomości</string>
<string name="pref_display_enter_key">Pokaż klawisz Enter</string>
<string name="pref_display_enter_key_summary">Zamień klawisz emotikon na klawisz Enter</string>
<string name="audio">plik audio</string>
......
......@@ -401,7 +401,6 @@
<string name="mark_as_read">Marcar como lida</string>
<string name="pref_input_options">Entrada</string>
<string name="pref_enter_is_send">Enter envia</string>
<string name="pref_enter_is_send_summary">Usa o botão Enter para enviar a mensagem.</string>
<string name="pref_display_enter_key">Exibir o botão Enter</string>
<string name="pref_display_enter_key_summary">Altera o botão de emoticons para um botão Enter.</string>
<string name="audio">áudio</string>
......
......@@ -279,7 +279,6 @@
<string name="mark_as_read">Marcar como lida</string>
<string name="pref_input_options">Introdução</string>
<string name="pref_enter_is_send">O enter envia</string>
<string name="pref_enter_is_send_summary">Use o enter para enviar a mensagem</string>
<string name="pref_display_enter_key">Exibir tecla enter</string>
<string name="pref_display_enter_key_summary">Alterar a tecla dos emoticons para uma tecla enter</string>
<string name="audio">áudio</string>
......
......@@ -401,7 +401,7 @@
<string name="mark_as_read">Marchează ca citit</string>
<string name="pref_input_options">Opțiuni introducere text</string>
<string name="pref_enter_is_send">ENTER trimite</string>
<string name="pref_enter_is_send_summary">Apasă tasta ENTER pentru a trimite mesajul</string>
<string name="pref_enter_is_send_summary">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ă.</string>
<string name="pref_display_enter_key">Arată tasta ENTER</string>
<string name="pref_display_enter_key_summary">Preschimbă tasta emoticon în ENTER</string>
<string name="audio">audio</string>
......@@ -924,6 +924,7 @@
<string name="audio_call">Apel audio</string>
<string name="video_call">Apel video</string>
<string name="help">Ajutor</string>
<string name="switch_to_conversation">Comutare la discuție</string>
<string name="microphone_unavailable">Microfonul nu este disponibil</string>
<string name="only_one_call_at_a_time">Puteți avea un singur apel simultan.</string>
<string name="return_to_ongoing_call">Reveniți la apelul în curs</string>
......
This diff is collapsed.
......@@ -216,7 +216,6 @@
<string name="never">Nikdy</string>
<string name="until_further_notice">Až do odvolania</string>
<string name="pref_enter_is_send">Enter odosiela</string>
<string name="pref_enter_is_send_summary">Použiť klávesu enter na odoslanie správy</string>
<string name="pref_display_enter_key">Zobraziť klávesu enter</string>
<string name="pref_display_enter_key_summary">Zmeniť klávesu s emotikonmi na klávesu enter</string>
<string name="audio">audio</string>
......
......@@ -296,7 +296,6 @@
<string name="mark_as_read">Означи прочитаним</string>
<string name="pref_input_options">Унос</string>
<string name="pref_enter_is_send">Ентер шаље</string>
<string name="pref_enter_is_send_summary">Користи Ентер тастер за слање порука</string>
<string name="pref_display_enter_key">Прикажи Ентер тастер</string>
<string name="pref_display_enter_key_summary">Промени тастер за емотиконе у ентер тастер</string>
<string name="audio">звук</string>
......
......@@ -319,7 +319,6 @@
<string name="mark_as_read">Läsmarkera</string>
<string name="pref_input_options">Input</string>
<string name="pref_enter_is_send">Skicka med enter</string>
<string name="pref_enter_is_send_summary">Använd enter-knappen för att skicka meddelande</string>
<string name="pref_display_enter_key">Visa enter-knappen</string>
<string name="pref_display_enter_key_summary">Byt ut emoticons-tangenten mot en enter-tangent</string>
<string name="audio">ljud</string>
......
......@@ -271,7 +271,6 @@
<string name="until_further_notice">İkinci bildirime kadar</string>
<string name="pref_input_options">Girdi</string>
<string name="pref_enter_is_send">Enter=gönder</string>
<string name="pref_enter_is_send_summary">İleti göndermek için \"enter\" tuşunu kullan</string>
<string name="pref_display_enter_key">\"Enter\" tuşunu göster</string>
<string name="pref_display_enter_key_summary">İfade simgesi tuşunu \"enter\" tuşu olarak değiştir</string>
<string name="audio">ses</string>
......
......@@ -401,7 +401,6 @@
<string name="mark_as_read">Позначити як прочитане</string>
<string name="pref_input_options">Введення</string>
<string name="pref_enter_is_send">Enter для надсилання</string>
<string name="pref_enter_is_send_summary">Використовувати Enter для надсилання повідомлення</string>
<string name="pref_display_enter_key">Показувати кнопку Enter</string>
<string name="pref_display_enter_key_summary">Змінити клавішу емоційок на кнопку Enter</string>
<string name="audio">аудіо</string>
......@@ -932,6 +931,7 @@
<string name="audio_call">Голосовий виклик</string>
<string name="video_call">Відеовиклик</string>
<string name="help">Допомога</string>
<string name="switch_to_conversation">Перейти до розмови</string>
<string name="microphone_unavailable">Недоступний мікрофон</string>
<string name="only_one_call_at_a_time">Водночас можливо здійснювати лише один виклик.</string>
<string name="return_to_ongoing_call">Назад до активного виклику</string>
......
......@@ -246,7 +246,6 @@
<string name="never">Chưa từng</string>
<string name="until_further_notice">Cho đến thông báo tiếp theo</string>
<string name="pref_enter_is_send">Bấm Enter để gửi</string>
<string name="pref_enter_is_send_summary">Bấm nút Enter để gửi tin nhắn</string>
<string name="pref_display_enter_key">Hiện nút Enter</string>
<string name="pref_display_enter_key_summary">Đổi nút biểu tượng cảm xúc thành nút Enter</string>
<string name="audio">âm thanh</string>
......
<resources>
<!-- 384dp is the screen width of the Nexus 4. Something like a Moto G is smaller but a Nexus 5X is larger -->
<!-- https://material.io/devices/ -->
<dimen name="fineprint_size">12sp</dimen>
<dimen name="audio_player_width">288dp</dimen>
<dimen name="avatar_on_details_screen_size">72dp</dimen>
<dimen name="media_size">64dp</dimen> <!-- ideally not larger than avatar_on_details_screen -->
<dimen name="sd_label_max_width">288dp</dimen>
<!-- 384dp is the screen width of the Nexus 4. Something like a Moto G is smaller but a Nexus 5X is larger -->
<!-- https://material.io/devices/ -->
<dimen name="fineprint_size">12sp</dimen>
<dimen name="audio_player_width">288dp</dimen>
<dimen name="image_preview_width">288dp</dimen>
<dimen name="avatar_on_details_screen_size">72dp</dimen>
<dimen name="media_size">64dp</dimen> <!-- ideally not larger than avatar_on_details_screen -->
<dimen name="sd_label_max_width">288dp</dimen>
</resources>
......@@ -401,7 +401,6 @@
<string name="mark_as_read">标记为已读</string>
<string name="pref_input_options">输入</string>
<string name="pref_enter_is_send">点击回车发送</string>
<string name="pref_enter_is_send_summary">回车键发送消息</string>
<string name="pref_display_enter_key">显示回车键</string>
<string name="pref_display_enter_key_summary">将表情键改为回车键</string>
<string name="audio">音频</string>
......
......@@ -260,7 +260,6 @@
<string name="until_further_notice">直到新的通知</string>
<string name="pref_input_options">輸入</string>
<string name="pref_enter_is_send">回車是發送</string>
<string name="pref_enter_is_send_summary">用 Enter 鍵來發送訊息</string>
<string name="pref_display_enter_key">顯示回車鍵</string>
<string name="pref_display_enter_key_summary">改變表情鍵為回車鍵</string>
<string name="audio">音訊</string>
......
......@@ -97,6 +97,7 @@
<attr name="icon_remove" format="reference" />
<attr name="icon_search" format="reference" />
<attr name="icon_help" format="reference" />
<attr name="icon_goto_chat" format="reference" />
<attr name="icon_secure" format="reference" />
<attr name="icon_settings" format="reference" />
<attr name="icon_share" format="reference" />
......
......@@ -10,6 +10,7 @@
</dimen> <!-- icon width (24dp) + 2 * image button padding -->
<dimen name="fineprint_size">11sp</dimen>
<dimen name="audio_player_width">224dp</dimen>
<dimen name="image_preview_width">224dp</dimen>