aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsteckbrief <steckbrief@chefmail.de>2015-03-30 22:12:49 +0200
committersteckbrief <steckbrief@chefmail.de>2015-03-30 22:12:49 +0200
commit75c5275f7e9a27019b9517770603ca4c77f1fbd9 (patch)
tree567d10bd9e559089487f5627c436000e2a1a8fc8
parentc8fe93cf0a99481bfe7a30bbc1cd98383205bcaa (diff)
parent7eabdfd80f50634282307f45c5f99ab46181805d (diff)
Merge tag '1.2.0' into trz/master
Conflicts: src/main/java/eu/siacs/conversations/parser/AbstractParser.java src/main/java/eu/siacs/conversations/ui/SettingsActivity.java src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java src/main/res/values-de/strings.xml src/main/res/xml/preferences.xml
-rw-r--r--CHANGELOG.md25
-rw-r--r--build.gradle11
-rw-r--r--docs/XEPs.md2
-rw-r--r--src/main/java/eu/siacs/conversations/Config.java22
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/OtrEngine.java32
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Account.java2
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Contact.java2
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Conversation.java60
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Message.java33
-rw-r--r--src/main/java/eu/siacs/conversations/entities/MucOptions.java17
-rw-r--r--src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java25
-rw-r--r--src/main/java/eu/siacs/conversations/generator/IqGenerator.java13
-rw-r--r--src/main/java/eu/siacs/conversations/generator/MessageGenerator.java36
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpConnection.java2
-rw-r--r--src/main/java/eu/siacs/conversations/parser/AbstractParser.java5
-rw-r--r--src/main/java/eu/siacs/conversations/parser/IqParser.java8
-rw-r--r--src/main/java/eu/siacs/conversations/parser/MessageParser.java59
-rw-r--r--src/main/java/eu/siacs/conversations/parser/PresenceParser.java8
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java2
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/FileBackend.java60
-rw-r--r--src/main/java/eu/siacs/conversations/services/AvatarService.java40
-rw-r--r--src/main/java/eu/siacs/conversations/services/NotificationService.java55
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java178
-rw-r--r--src/main/java/eu/siacs/conversations/ui/AboutPreference.java14
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java94
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java34
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java22
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java221
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationFragment.java343
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java23
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditMessage.java56
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java15
-rw-r--r--src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java12
-rw-r--r--src/main/java/eu/siacs/conversations/ui/SettingsActivity.java2
-rw-r--r--src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java35
-rw-r--r--src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java12
-rw-r--r--src/main/java/eu/siacs/conversations/ui/XmppActivity.java62
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java95
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java96
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java36
-rw-r--r--src/main/java/eu/siacs/conversations/utils/CryptoHelper.java28
-rw-r--r--src/main/java/eu/siacs/conversations/utils/DNSHelper.java7
-rw-r--r--src/main/java/eu/siacs/conversations/utils/GeoHelper.java71
-rw-r--r--src/main/java/eu/siacs/conversations/utils/PhoneHelper.java14
-rw-r--r--src/main/java/eu/siacs/conversations/utils/UIHelper.java10
-rw-r--r--src/main/java/eu/siacs/conversations/utils/XmppUri.java6
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java89
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/chatstate/ChatState.java32
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jid/InvalidJidException.java1
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java32
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java5
-rw-r--r--src/main/res/drawable-hdpi/ic_autorenew_white_24dp.pngbin0 -> 489 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_block_white_24dp.pngbin0 -> 606 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_room_white_24dp.pngbin0 -> 494 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_autorenew_white_24dp.pngbin0 -> 353 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_block_white_24dp.pngbin0 -> 428 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_room_white_24dp.pngbin0 -> 362 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_autorenew_white_24dp.pngbin0 -> 604 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_block_white_24dp.pngbin0 -> 796 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_room_white_24dp.pngbin0 -> 587 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_autorenew_white_24dp.pngbin0 -> 869 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_block_white_24dp.pngbin0 -> 1194 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_room_white_24dp.pngbin0 -> 861 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_autorenew_white_24dp.pngbin0 -> 1114 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_block_white_24dp.pngbin0 -> 1497 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_room_white_24dp.pngbin0 -> 1093 bytes
-rw-r--r--src/main/res/menu/attachment_choices.xml17
-rw-r--r--src/main/res/menu/select_multiple.xml9
-rw-r--r--src/main/res/values-ar-rEG/strings.xml116
-rw-r--r--src/main/res/values-bg/strings.xml434
-rw-r--r--src/main/res/values-ca/strings.xml53
-rw-r--r--src/main/res/values-cs/strings.xml18
-rw-r--r--src/main/res/values-de/strings.xml98
-rw-r--r--src/main/res/values-el/strings.xml430
-rw-r--r--src/main/res/values-es/strings.xml10
-rw-r--r--src/main/res/values-eu/strings.xml11
-rw-r--r--src/main/res/values-fa/strings.xml2
-rw-r--r--src/main/res/values-fr/strings.xml23
-rw-r--r--src/main/res/values-nl/strings.xml14
-rw-r--r--src/main/res/values-pl/strings.xml89
-rw-r--r--src/main/res/values-sk/strings.xml5
-rw-r--r--src/main/res/values-sr/strings.xml24
-rw-r--r--src/main/res/values-sv/strings.xml48
-rw-r--r--src/main/res/values/strings.xml14
-rw-r--r--src/main/res/xml/preferences.xml6
-rw-r--r--todo.md23
86 files changed, 2991 insertions, 617 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98657b21..28bf2765 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,30 @@
###Changelog
+####Version 1.2.0
+* Send current location. (requires [plugin](https://play.google.com/store/apps/details?id=eu.siacs.conversations.sharelocation))
+* Invite multiple contacts at once
+* performance improvements
+* bug fixes
+
+####Version 1.1.0
+* Typing notifications (must be turned on in settings)
+* Various UI performance improvements
+* bug fixes
+
+####Version 1.0.4
+* load avatars asynchronously on start up
+* support for XEP-0092: Software Version
+
+####Version 1.0.3
+* load messages asynchronously on start up
+* bug fixes
+
+####Version 1.0.2
+* skipped
+
+####Version 1.0.1
+* accept more ciphers
+
####Version 1.0
* MUC controls (Affiliaton changes)
* Added download button to notification
diff --git a/build.gradle b/build.gradle
index 4b695b0d..8f7bb914 100644
--- a/build.gradle
+++ b/build.gradle
@@ -20,9 +20,6 @@ allprojects {
apply plugin: 'com.android.application'
repositories {
- maven {
- url "http://jitsi.github.com/otr4j/repository/"
- }
jcenter()
mavenCentral()
}
@@ -31,8 +28,8 @@ dependencies {
compile project(':libs:openpgp-api-lib')
compile project(':libs:MemorizingTrustManager')
compile 'com.android.support:support-v13:21.0.3'
- compile 'org.bouncycastle:bcprov-jdk15on:1.50'
- compile 'net.java:otr4j:0.22'
+ compile 'org.bouncycastle:bcprov-jdk15on:1.51'
+ compile 'org.jitsi:org.otr4j:0.22'
compile 'org.gnu.inet:libidn:1.15'
compile 'com.google.zxing:core:3.1.0'
compile 'com.google.zxing:android-integration:3.1.0'
@@ -46,8 +43,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 21
- versionCode 46
- versionName "1.0.1"
+ versionCode 56
+ versionName "1.2.0"
}
compileOptions {
diff --git a/docs/XEPs.md b/docs/XEPs.md
index c64f1f57..1b9ea6af 100644
--- a/docs/XEPs.md
+++ b/docs/XEPs.md
@@ -2,6 +2,8 @@
* XEP-0030: Service Discovery
* XEP-0045: Multi-User Chat
* XEP-0048: Bookmarks
+* XEP-0085: Chat State Notifications
+* XEP-0092: Software Version
* XEP-0115: Entity Capabilities
* XEP-0163: Personal Eventing Protocol (avatars and nicks)
* XEP-0166: Jingle (only used for file transfer)
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
index 81a68008..fa9345a2 100644
--- a/src/main/java/eu/siacs/conversations/Config.java
+++ b/src/main/java/eu/siacs/conversations/Config.java
@@ -2,6 +2,8 @@ package eu.siacs.conversations;
import android.graphics.Bitmap;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
+
public final class Config {
public static final String LOGTAG = "conversations";
@@ -23,21 +25,26 @@ public final class Config {
public static final int PAGE_SIZE = 50;
public static final int MAX_NUM_PAGES = 3;
- public static final int PROGRESS_UI_UPDATE_INTERVAL = 750;
+ public static final int PROGRESS_UI_UPDATE_INTERVAL = 750;
+ public static final int REFRESH_UI_INTERVAL = 500;
public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb
+ public static final boolean DISABLE_STRING_PREP = false; // setting to true might increase startup performance
public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2;
public static final int MAM_MAX_MESSAGES = 500;
+ public static final ChatState DEFAULT_CHATSTATE = ChatState.ACTIVE;
+ public static final int TYPING_TIMEOUT = 8;
+
public static final String ENABLED_CIPHERS[] = {
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
- "TLS_ECDHE_RSA_AES_128_SHA",
- "TLS_ECDHE_RSA_AES_256_SHA",
+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
+ "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA384",
@@ -59,6 +66,15 @@ public final class Config {
"TLS_RSA_WITH_AES_256_CBC_SHA",
};
+ public static final String WEAK_CIPHER_PATTERNS[] = {
+ "_NULL_",
+ "_EXPORT_",
+ "_anon_",
+ "_RC4_",
+ "_DES_",
+ "_MD5",
+ };
+
private Config() {
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java b/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java
index d5c45465..20427d7b 100644
--- a/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java
+++ b/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java
@@ -21,6 +21,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
@@ -182,14 +183,39 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
packet.addChild("private", "urn:xmpp:carbons:2");
packet.addChild("no-copy", "urn:xmpp:hints");
packet.addChild("no-store", "urn:xmpp:hints");
+
+ try {
+ Jid jid = Jid.fromSessionID(session);
+ Conversation conversation = mXmppConnectionService.find(account,jid);
+ if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
+ if (mXmppConnectionService.sendChatStates()) {
+ packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
+ }
+ }
+ } catch (final InvalidJidException ignored) {
+
+ }
+
packet.setType(MessagePacket.TYPE_CHAT);
account.getXmppConnection().sendMessagePacket(packet);
}
@Override
- public void messageFromAnotherInstanceReceived(SessionID id) {
- Log.d(Config.LOGTAG,
- "unreadable message received from " + id.getAccountID());
+ public void messageFromAnotherInstanceReceived(SessionID session) {
+ try {
+ Jid jid = Jid.fromSessionID(session);
+ Conversation conversation = mXmppConnectionService.find(account, jid);
+ String id = conversation == null ? null : conversation.getLastReceivedOtrMessageId();
+ if (id != null) {
+ MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateOtrError(jid,id);
+ packet.setFrom(account.getJid());
+ mXmppConnectionService.sendMessagePacket(account,packet);
+ Log.d(Config.LOGTAG,packet.toString());
+ Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": unreadable OTR message in "+conversation.getName());
+ }
+ } catch (InvalidJidException e) {
+ return;
+ }
}
@Override
diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java
index b0cde62c..2bc2c954 100644
--- a/src/main/java/eu/siacs/conversations/entities/Account.java
+++ b/src/main/java/eu/siacs/conversations/entities/Account.java
@@ -148,7 +148,7 @@ public class Account extends AbstractEntity {
try {
this.keys = new JSONObject(keys);
} catch (final JSONException ignored) {
-
+ this.keys = new JSONObject();
}
this.avatar = avatar;
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java
index 698e0322..cef03ebe 100644
--- a/src/main/java/eu/siacs/conversations/entities/Contact.java
+++ b/src/main/java/eu/siacs/conversations/entities/Contact.java
@@ -80,7 +80,7 @@ public class Contact implements ListItem, Blockable {
cursor.getLong(cursor.getColumnIndex(LAST_TIME)));
final Jid jid;
try {
- jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(JID)));
+ jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(JID)), true);
} catch (final InvalidJidException e) {
// TODO: Borked DB... handle this somehow?
return null;
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index 036acf63..bfee5007 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -2,10 +2,8 @@ package eu.siacs.conversations.entities;
import android.content.ContentValues;
import android.database.Cursor;
-import android.os.SystemClock;
import net.java.otr4j.OtrException;
-import net.java.otr4j.crypto.OtrCryptoEngineImpl;
import net.java.otr4j.crypto.OtrCryptoException;
import net.java.otr4j.session.SessionID;
import net.java.otr4j.session.SessionImpl;
@@ -21,6 +19,7 @@ import java.util.Comparator;
import java.util.List;
import eu.siacs.conversations.Config;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -77,6 +76,9 @@ public class Conversation extends AbstractEntity implements Blockable {
private Bookmark bookmark;
private boolean messagesLeftOnServer = true;
+ private ChatState mOutgoingChatState = Config.DEFAULT_CHATSTATE;
+ private ChatState mIncomingChatState = Config.DEFAULT_CHATSTATE;
+ private String mLastReceivedOtrMessageId = null;
public boolean hasMessagesLeftOnServer() {
return messagesLeftOnServer;
@@ -138,6 +140,34 @@ public class Conversation extends AbstractEntity implements Blockable {
}
}
+ public boolean setIncomingChatState(ChatState state) {
+ if (this.mIncomingChatState == state) {
+ return false;
+ }
+ this.mIncomingChatState = state;
+ return true;
+ }
+
+ public ChatState getIncomingChatState() {
+ return this.mIncomingChatState;
+ }
+
+ public boolean setOutgoingChatState(ChatState state) {
+ if (mode == MODE_MULTI) {
+ return false;
+ }
+ if (this.mOutgoingChatState != state) {
+ this.mOutgoingChatState = state;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public ChatState getOutgoingChatState() {
+ return this.mOutgoingChatState;
+ }
+
public void trim() {
synchronized (this.messages) {
final int size = messages.size();
@@ -205,6 +235,14 @@ public class Conversation extends AbstractEntity implements Blockable {
return getContact().getBlockedJid();
}
+ public String getLastReceivedOtrMessageId() {
+ return this.mLastReceivedOtrMessageId;
+ }
+
+ public void setLastReceivedOtrMessageId(String id) {
+ this.mLastReceivedOtrMessageId = id;
+ }
+
public interface OnMessageFound {
public void onMessageFound(final Message message);
@@ -340,7 +378,7 @@ public class Conversation extends AbstractEntity implements Blockable {
public static Conversation fromCursor(Cursor cursor) {
Jid jid;
try {
- jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(CONTACTJID)));
+ jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(CONTACTJID)), true);
} catch (final InvalidJidException e) {
// Borked DB..
jid = null;
@@ -626,8 +664,7 @@ public class Conversation extends AbstractEntity implements Blockable {
}
public boolean isMuted() {
- return SystemClock.elapsedRealtime() < this.getLongAttribute(
- ATTRIBUTE_MUTED_TILL, 0);
+ return System.currentTimeMillis() < this.getLongAttribute(ATTRIBUTE_MUTED_TILL, 0);
}
public boolean setAttribute(String key, String value) {
@@ -706,6 +743,19 @@ public class Conversation extends AbstractEntity implements Blockable {
}
}
+ public int unreadCount() {
+ synchronized (this.messages) {
+ int count = 0;
+ for(int i = this.messages.size() - 1; i >= 0; --i) {
+ if (this.messages.get(i).isRead()) {
+ return count;
+ }
+ ++count;
+ }
+ return count;
+ }
+ }
+
public class Smp {
public static final int STATUS_NONE = 0;
public static final int STATUS_CONTACT_REQUESTED = 1;
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index 8112f5de..8015eead 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -8,6 +8,7 @@ import java.net.URL;
import java.util.Arrays;
import eu.siacs.conversations.Config;
+import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -49,6 +50,7 @@ public class Message extends AbstractEntity {
public static final String RELATIVE_FILE_PATH = "relativeFilePath";
public static final String ME_COMMAND = "/me ";
+
public boolean markable = false;
protected String conversationUuid;
protected Jid counterpart;
@@ -115,7 +117,7 @@ public class Message extends AbstractEntity {
try {
String value = cursor.getString(cursor.getColumnIndex(COUNTERPART));
if (value != null) {
- jid = Jid.fromString(value);
+ jid = Jid.fromString(value, true);
} else {
jid = null;
}
@@ -126,7 +128,7 @@ public class Message extends AbstractEntity {
try {
String value = cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART));
if (value != null) {
- trueCounterpart = Jid.fromString(value);
+ trueCounterpart = Jid.fromString(value, true);
} else {
trueCounterpart = null;
}
@@ -147,10 +149,11 @@ public class Message extends AbstractEntity {
cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)));
}
- public static Message createStatusMessage(Conversation conversation) {
+ public static Message createStatusMessage(Conversation conversation, String body) {
Message message = new Message();
message.setType(Message.TYPE_STATUS);
message.setConversation(conversation);
+ message.setBody(body);
return message;
}
@@ -361,11 +364,14 @@ public class Message extends AbstractEntity {
message.getDownloadable() == null &&
message.getEncryption() != Message.ENCRYPTION_PGP &&
this.getType() == message.getType() &&
- this.getStatus() == message.getStatus() &&
+ //this.getStatus() == message.getStatus() &&
+ isStatusMergeable(this.getStatus(),message.getStatus()) &&
this.getEncryption() == message.getEncryption() &&
this.getCounterpart() != null &&
this.getCounterpart().equals(message.getCounterpart()) &&
(message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) &&
+ !GeoHelper.isGeoUri(message.getBody()) &&
+ !GeoHelper.isGeoUri(this.body) &&
!message.bodyContainsDownloadable() &&
!this.bodyContainsDownloadable() &&
!message.getBody().startsWith(ME_COMMAND) &&
@@ -373,12 +379,23 @@ public class Message extends AbstractEntity {
);
}
+ private static boolean isStatusMergeable(int a, int b) {
+ return a == b || (
+ ( a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_UNSEND)
+ || (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_SEND)
+ || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND)
+ || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND_RECEIVED)
+ || (a == Message.STATUS_SEND && b == Message.STATUS_UNSEND)
+ || (a == Message.STATUS_SEND && b == Message.STATUS_SEND_RECEIVED)
+ );
+ }
+
public String getMergedBody() {
final Message next = this.next();
if (this.mergeable(next)) {
- return getBody() + '\n' + next.getMergedBody();
+ return getBody().trim() + '\n' + next.getMergedBody();
}
- return getBody();
+ return getBody().trim();
}
public boolean hasMeCommand() {
@@ -386,6 +403,10 @@ public class Message extends AbstractEntity {
}
public int getMergedStatus() {
+ final Message next = this.next();
+ if (this.mergeable(next)) {
+ return next.getStatus();
+ }
return getStatus();
}
diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
index 27821c65..addee8db 100644
--- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java
+++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
@@ -150,6 +150,21 @@ public class MucOptions {
}
}
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (!(other instanceof User)) {
+ return false;
+ } else {
+ User o = (User) other;
+ return name != null && name.equals(o.name)
+ && jid != null && jid.equals(o.jid)
+ && affiliation == o.affiliation
+ && role == o.role;
+ }
+ }
+
public Affiliation getAffiliation() {
return this.affiliation;
}
@@ -260,7 +275,7 @@ public class MucOptions {
User user = new User();
if (x != null) {
Element item = x.findChild("item");
- if (item != null) {
+ if (item != null && name != null) {
user.setName(name);
user.setAffiliation(item.getAttribute("affiliation"));
user.setRole(item.getAttribute("role"));
diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
index 526e5b19..79626511 100644
--- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
@@ -13,6 +13,7 @@ import java.util.Locale;
import java.util.TimeZone;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.PhoneHelper;
public abstract class AbstractGenerator {
private final String[] FEATURES = {
@@ -25,15 +26,18 @@ public abstract class AbstractGenerator {
"http://jabber.org/protocol/caps",
"http://jabber.org/protocol/disco#info",
"urn:xmpp:avatar:metadata+notify",
- "urn:xmpp:ping"};
+ "urn:xmpp:ping",
+ "jabber:iq:version",
+ "http://jabber.org/protocol/chatstates"};
private final String[] MESSAGE_CONFIRMATION_FEATURES = {
"urn:xmpp:chat-markers:0",
"urn:xmpp:receipts"
};
- public final String IDENTITY_NAME = "Conversations 1.0";
+ private String mVersion = null;
+ public final String IDENTITY_NAME = "Conversations";
public final String IDENTITY_TYPE = "phone";
- private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
+ private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
protected XmppConnectionService mXmppConnectionService;
@@ -41,10 +45,21 @@ public abstract class AbstractGenerator {
this.mXmppConnectionService = service;
}
+ protected String getIdentityVersion() {
+ if (mVersion == null) {
+ this.mVersion = PhoneHelper.getVersionName(mXmppConnectionService);
+ }
+ return this.mVersion;
+ }
+
+ protected String getIdentityName() {
+ return IDENTITY_NAME + " " + getIdentityVersion();
+ }
+
public String getCapHash() {
StringBuilder s = new StringBuilder();
- s.append("client/" + IDENTITY_TYPE + "//" + IDENTITY_NAME + "<");
- MessageDigest md = null;
+ s.append("client/" + IDENTITY_TYPE + "//" + getIdentityName() + "<");
+ MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
index 161e6f89..6bc629b5 100644
--- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
@@ -8,6 +8,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.forms.Data;
@@ -30,14 +31,22 @@ public class IqGenerator extends AbstractGenerator {
query.setAttribute("node", request.query().getAttribute("node"));
final Element identity = query.addChild("identity");
identity.setAttribute("category", "client");
- identity.setAttribute("type", this.IDENTITY_TYPE);
- identity.setAttribute("name", IDENTITY_NAME);
+ identity.setAttribute("type", IDENTITY_TYPE);
+ identity.setAttribute("name", getIdentityName());
for (final String feature : getFeatures()) {
query.addChild("feature").setAttribute("var", feature);
}
return packet;
}
+ public IqPacket versionResponse(final IqPacket request) {
+ final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
+ Element query = packet.query("jabber:iq:version");
+ query.addChild("name").setContent(IDENTITY_NAME);
+ query.addChild("version").setContent(getIdentityVersion());
+ return packet;
+ }
+
protected IqPacket publish(final String node, final Element item) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
final Element pubsub = packet.addChild("pubsub",
diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
index 2ee636b5..8f6a90b9 100644
--- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
@@ -12,6 +12,7 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
@@ -34,6 +35,9 @@ public class MessageGenerator extends AbstractGenerator {
} else if (message.getType() == Message.TYPE_PRIVATE) {
packet.setTo(message.getCounterpart());
packet.setType(MessagePacket.TYPE_CHAT);
+ if (this.mXmppConnectionService.indicateReceived()) {
+ packet.addChild("request", "urn:xmpp:receipts");
+ }
} else {
packet.setTo(message.getCounterpart().toBareJid());
packet.setType(MessagePacket.TYPE_GROUPCHAT);
@@ -102,21 +106,12 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket generateNotAcceptable(MessagePacket origin) {
- MessagePacket packet = generateError(origin);
- Element error = packet.addChild("error");
- error.setAttribute("type", "modify");
- error.setAttribute("code", "406");
- error.addChild("not-acceptable");
- return packet;
- }
-
- private MessagePacket generateError(MessagePacket origin) {
+ public MessagePacket generateChatState(Conversation conversation) {
+ final Account account = conversation.getAccount();
MessagePacket packet = new MessagePacket();
- packet.setId(origin.getId());
- packet.setTo(origin.getFrom());
- packet.setBody(origin.getBody());
- packet.setType(MessagePacket.TYPE_ERROR);
+ packet.setTo(conversation.getJid().toBareJid());
+ packet.setFrom(account.getJid());
+ packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
return packet;
}
@@ -176,4 +171,17 @@ public class MessageGenerator extends AbstractGenerator {
received.setAttribute("id", originalMessage.getId());
return receivedPacket;
}
+
+ public MessagePacket generateOtrError(Jid to, String id) {
+ MessagePacket packet = new MessagePacket();
+ packet.setType(MessagePacket.TYPE_ERROR);
+ packet.setAttribute("id",id);
+ packet.setTo(to);
+ Element error = packet.addChild("error");
+ error.setAttribute("code","406");
+ error.setAttribute("type","modify");
+ error.addChild("not-acceptable","urn:ietf:params:xml:ns:xmpp-stanzas");
+ error.addChild("text").setContent("unreadable OTR message received");
+ return packet;
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnection.java b/src/main/java/eu/siacs/conversations/http/HttpConnection.java
index 4bff5251..e7d30919 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpConnection.java
@@ -148,7 +148,7 @@ public class HttpConnection implements Downloadable {
mXmppConnectionService.getRNG());
final SSLSocketFactory sf = sc.getSocketFactory();
- final String[] cipherSuites = CryptoHelper.getSupportedCipherSuites(
+ final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
sf.getSupportedCipherSuites());
if (cipherSuites.length > 0) {
sc.getDefaultSSLParameters().setCipherSuites(cipherSuites);
diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
index 9b3e239c..977062e2 100644
--- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
@@ -64,6 +64,11 @@ public abstract class AbstractParser {
protected void updateLastseen(final Element packet, final Account account,
final boolean presenceOverwrite) {
final Jid from = packet.getAttributeAsJid("from");
+ updateLastseen(packet, account, from, presenceOverwrite);
+ }
+
+ protected void updateLastseen(final Element packet, final Account account, final Jid from,
+ final boolean presenceOverwrite) {
final String presence = from == null || from.isBareJid() ? "" : from.getResourcepart();
final Contact contact = account.getRoster().getContact(from);
final long timestamp = getTimestamp(packet);
diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java
index 6430c296..6039d395 100644
--- a/src/main/java/eu/siacs/conversations/parser/IqParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java
@@ -134,9 +134,11 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
mXmppConnectionService.getJingleConnectionManager()
.deliverIbbPacket(account, packet);
} else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) {
- final IqPacket response = mXmppConnectionService.getIqGenerator()
- .discoResponse(packet);
- account.getXmppConnection().sendIqPacket(response, null);
+ final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(packet);
+ mXmppConnectionService.sendIqPacket(account, response, null);
+ } else if (packet.hasChild("query","jabber:iq:version")) {
+ final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
+ mXmppConnectionService.sendIqPacket(account,response,null);
} else if (packet.hasChild("ping", "urn:xmpp:ping")) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null);
diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index 49efb004..8ae9b642 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -14,6 +14,7 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
@@ -24,13 +25,27 @@ public class MessageParser extends AbstractParser implements
super(service);
}
+ private boolean extractChatState(Conversation conversation, final Element element) {
+ ChatState state = ChatState.parse(element);
+ if (state != null && conversation != null) {
+ final Account account = conversation.getAccount();
+ Jid from = element.getAttributeAsJid("from");
+ if (from != null && from.toBareJid().equals(account.getJid().toBareJid())) {
+ conversation.setOutgoingChatState(state);
+ return false;
+ } else {
+ return conversation.setIncomingChatState(state);
+ }
+ }
+ return false;
+ }
+
private Message parseChat(MessagePacket packet, Account account) {
- final Jid jid = packet.getFrom();
+ final Jid jid = packet.getFrom();
if (jid == null) {
return null;
}
Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid.toBareJid(), false);
- updateLastseen(packet, account, true);
String pgpBody = getPgpBody(packet);
Message finishedMessage;
if (pgpBody != null) {
@@ -45,16 +60,22 @@ public class MessageParser extends AbstractParser implements
finishedMessage.markable = isMarkable(packet);
if (conversation.getMode() == Conversation.MODE_MULTI
&& !jid.isBareJid()) {
+ final Jid trueCounterpart = conversation.getMucOptions()
+ .getTrueCounterpart(jid.getResourcepart());
+ if (trueCounterpart != null) {
+ updateLastseen(packet, account, trueCounterpart, false);
+ }
finishedMessage.setType(Message.TYPE_PRIVATE);
- finishedMessage.setTrueCounterpart(conversation.getMucOptions()
- .getTrueCounterpart(jid.getResourcepart()));
+ finishedMessage.setTrueCounterpart(trueCounterpart);
if (conversation.hasDuplicateMessage(finishedMessage)) {
return null;
}
-
+ } else {
+ updateLastseen(packet, account, true);
}
finishedMessage.setCounterpart(jid);
finishedMessage.setTime(getTimestamp(packet));
+ extractChatState(conversation,packet);
return finishedMessage;
}
@@ -69,10 +90,11 @@ public class MessageParser extends AbstractParser implements
.findOrCreateConversation(account, from.toBareJid(), false);
String presence;
if (from.isBareJid()) {
- presence = "";
+ presence = "";
} else {
presence = from.getResourcepart();
}
+ extractChatState(conversation, packet);
updateLastseen(packet, account, true);
String body = packet.getBody();
if (body.matches("^\\?OTRv\\d{1,2}\\?.*")) {
@@ -97,6 +119,7 @@ public class MessageParser extends AbstractParser implements
}
}
try {
+ conversation.setLastReceivedOtrMessageId(packet.getId());
Session otrSession = conversation.getOtrSession();
SessionStatus before = otrSession.getSessionStatus();
body = otrSession.transformReceiving(body);
@@ -123,6 +146,7 @@ public class MessageParser extends AbstractParser implements
finishedMessage.setRemoteMsgId(packet.getId());
finishedMessage.markable = isMarkable(packet);
finishedMessage.setCounterpart(from);
+ conversation.setLastReceivedOtrMessageId(null);
return finishedMessage;
} catch (Exception e) {
conversation.resetOtrSession();
@@ -132,7 +156,7 @@ public class MessageParser extends AbstractParser implements
private Message parseGroupchat(MessagePacket packet, Account account) {
int status;
- final Jid from = packet.getFrom();
+ final Jid from = packet.getFrom();
if (from == null) {
return null;
}
@@ -142,6 +166,10 @@ public class MessageParser extends AbstractParser implements
}
Conversation conversation = mXmppConnectionService
.findOrCreateConversation(account, from.toBareJid(), true);
+ final Jid trueCounterpart = conversation.getMucOptions().getTrueCounterpart(from.getResourcepart());
+ if (trueCounterpart != null) {
+ updateLastseen(packet, account, trueCounterpart, false);
+ }
if (packet.hasChild("subject")) {
conversation.setHasMessagesLeftOnServer(true);
conversation.getMucOptions().setSubject(packet.findChild("subject").getContent());
@@ -275,6 +303,7 @@ public class MessageParser extends AbstractParser implements
finishedMessage = new Message(conversation, body,
Message.ENCRYPTION_NONE, status);
}
+ extractChatState(conversation,message);
finishedMessage.setTime(getTimestamp(message));
finishedMessage.setRemoteMsgId(message.getAttribute("id"));
finishedMessage.markable = isMarkable(message);
@@ -362,6 +391,9 @@ public class MessageParser extends AbstractParser implements
private void parseNonMessage(Element packet, Account account) {
final Jid from = packet.getAttributeAsJid("from");
+ if (extractChatState(from == null ? null : mXmppConnectionService.find(account,from), packet)) {
+ mXmppConnectionService.updateConversationUi();
+ }
Element invite = extractInvite(packet);
if (invite != null) {
Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, from, true);
@@ -377,14 +409,19 @@ public class MessageParser extends AbstractParser implements
Element event = packet.findChild("event",
"http://jabber.org/protocol/pubsub#event");
parseEvent(event, from, account);
- } else if (from != null
- && packet.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
+ } else if (from != null && packet.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
String id = packet
.findChild("displayed", "urn:xmpp:chat-markers:0")
.getAttribute("id");
updateLastseen(packet, account, true);
- mXmppConnectionService.markMessage(account, from.toBareJid(),
- id, Message.STATUS_SEND_DISPLAYED);
+ final Message displayedMessage = mXmppConnectionService.markMessage(account, from.toBareJid(), id, Message.STATUS_SEND_DISPLAYED);
+ Message message = displayedMessage == null ? null :displayedMessage.prev();
+ while (message != null
+ && message.getStatus() == Message.STATUS_SEND_RECEIVED
+ && message.getTimeSent() < displayedMessage.getTimeSent()) {
+ mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED);
+ message = message.prev();
+ }
} else if (from != null
&& packet.hasChild("received", "urn:xmpp:chat-markers:0")) {
String id = packet.findChild("received", "urn:xmpp:chat-markers:0")
diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
index accb56ea..7505b091 100644
--- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
@@ -1,5 +1,7 @@
package eu.siacs.conversations.parser;
+import java.util.ArrayList;
+
import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
@@ -27,8 +29,12 @@ public class PresenceParser extends AbstractParser implements
final MucOptions mucOptions = conversation.getMucOptions();
boolean before = mucOptions.online();
int count = mucOptions.getUsers().size();
+ final ArrayList<MucOptions.User> tileUserBefore = new ArrayList<>(mucOptions.getUsers().subList(0,Math.min(mucOptions.getUsers().size(),5)));
mucOptions.processPacket(packet, mPgpEngine);
- mXmppConnectionService.getAvatarService().clear(conversation);
+ final ArrayList<MucOptions.User> tileUserAfter = new ArrayList<>(mucOptions.getUsers().subList(0,Math.min(mucOptions.getUsers().size(),5)));
+ if (!tileUserAfter.equals(tileUserBefore)) {
+ mXmppConnectionService.getAvatarService().clear(conversation);
+ }
if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUsers().size())) {
mXmppConnectionService.updateConversationUi();
} else if (mucOptions.online()) {
diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
index 3ae3356d..28e1c47e 100644
--- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -278,6 +278,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
return (count > 0);
} catch (SQLiteCantOpenDatabaseException e) {
return true; // better safe than sorry
+ } catch (RuntimeException e) {
+ return true; // better safe than sorry
}
}
diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
index 62987aaa..c499d499 100644
--- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
@@ -318,39 +318,41 @@ public class FileBackend {
}
public boolean save(Avatar avatar) {
+ File file;
if (isAvatarCached(avatar)) {
- return true;
- }
- String filename = getAvatarPath(avatar.getFilename());
- File file = new File(filename + ".tmp");
- file.getParentFile().mkdirs();
- try {
- file.createNewFile();
- FileOutputStream mFileOutputStream = new FileOutputStream(file);
- MessageDigest digest = MessageDigest.getInstance("SHA-1");
- digest.reset();
- DigestOutputStream mDigestOutputStream = new DigestOutputStream(
- mFileOutputStream, digest);
- mDigestOutputStream.write(avatar.getImageAsBytes());
- mDigestOutputStream.flush();
- mDigestOutputStream.close();
- avatar.size = file.length();
- String sha1sum = CryptoHelper.bytesToHex(digest.digest());
- if (sha1sum.equals(avatar.sha1sum)) {
- file.renameTo(new File(filename));
- return true;
- } else {
- Log.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner);
- file.delete();
+ file = new File(getAvatarPath(avatar.getFilename()));
+ } else {
+ String filename = getAvatarPath(avatar.getFilename());
+ file = new File(filename + ".tmp");
+ file.getParentFile().mkdirs();
+ try {
+ file.createNewFile();
+ FileOutputStream mFileOutputStream = new FileOutputStream(file);
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ digest.reset();
+ DigestOutputStream mDigestOutputStream = new DigestOutputStream(
+ mFileOutputStream, digest);
+ mDigestOutputStream.write(avatar.getImageAsBytes());
+ mDigestOutputStream.flush();
+ mDigestOutputStream.close();
+ String sha1sum = CryptoHelper.bytesToHex(digest.digest());
+ if (sha1sum.equals(avatar.sha1sum)) {
+ file.renameTo(new File(filename));
+ } else {
+ Log.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner);
+ file.delete();
+ return false;
+ }
+ } catch (FileNotFoundException e) {
+ return false;
+ } catch (IOException e) {
+ return false;
+ } catch (NoSuchAlgorithmException e) {
return false;
}
- } catch (FileNotFoundException e) {
- return false;
- } catch (IOException e) {
- return false;
- } catch (NoSuchAlgorithmException e) {
- return false;
}
+ avatar.size = file.length();
+ return true;
}
public String getAvatarPath(String avatar) {
diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java
index f28dc24e..7412eb93 100644
--- a/src/main/java/eu/siacs/conversations/services/AvatarService.java
+++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java
@@ -38,10 +38,10 @@ public class AvatarService {
this.mXmppConnectionService = service;
}
- public Bitmap get(final Contact contact, final int size) {
+ private Bitmap get(final Contact contact, final int size, boolean cachedOnly) {
final String KEY = key(contact, size);
Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY);
- if (avatar != null) {
+ if (avatar != null || cachedOnly) {
return avatar;
}
if (contact.getProfilePhoto() != null) {
@@ -51,7 +51,7 @@ public class AvatarService {
avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatar(), size);
}
if (avatar == null) {
- avatar = get(contact.getDisplayName(), size);
+ avatar = get(contact.getDisplayName(), size, cachedOnly);
}
this.mXmppConnectionService.getBitmapCache().put(KEY, avatar);
return avatar;
@@ -77,25 +77,33 @@ public class AvatarService {
}
public Bitmap get(ListItem item, int size) {
+ return get(item,size,false);
+ }
+
+ public Bitmap get(ListItem item, int size, boolean cachedOnly) {
if (item instanceof Contact) {
- return get((Contact) item, size);
+ return get((Contact) item, size,cachedOnly);
} else if (item instanceof Bookmark) {
Bookmark bookmark = (Bookmark) item;
if (bookmark.getConversation() != null) {
- return get(bookmark.getConversation(), size);
+ return get(bookmark.getConversation(), size, cachedOnly);
} else {
- return get(bookmark.getDisplayName(), size);
+ return get(bookmark.getDisplayName(), size, cachedOnly);
}
} else {
- return get(item.getDisplayName(), size);
+ return get(item.getDisplayName(), size, cachedOnly);
}
}
public Bitmap get(Conversation conversation, int size) {
+ return get(conversation,size,false);
+ }
+
+ public Bitmap get(Conversation conversation, int size, boolean cachedOnly) {
if (conversation.getMode() == Conversation.MODE_SINGLE) {
- return get(conversation.getContact(), size);
+ return get(conversation.getContact(), size, cachedOnly);
} else {
- return get(conversation.getMucOptions(), size);
+ return get(conversation.getMucOptions(), size, cachedOnly);
}
}
@@ -107,10 +115,10 @@ public class AvatarService {
}
}
- public Bitmap get(MucOptions mucOptions, int size) {
+ private Bitmap get(MucOptions mucOptions, int size, boolean cachedOnly) {
final String KEY = key(mucOptions, size);
Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY);
- if (bitmap != null) {
+ if (bitmap != null || cachedOnly) {
return bitmap;
}
final List<MucOptions.User> users = new ArrayList<>(mucOptions.getUsers());
@@ -179,7 +187,7 @@ public class AvatarService {
avatar = mXmppConnectionService.getFileBackend().getAvatar(
account.getAvatar(), size);
if (avatar == null) {
- avatar = get(account.getJid().toBareJid().toString(), size);
+ avatar = get(account.getJid().toBareJid().toString(), size,false);
}
mXmppConnectionService.getBitmapCache().put(KEY, avatar);
return avatar;
@@ -204,10 +212,14 @@ public class AvatarService {
+ String.valueOf(size);
}
- public Bitmap get(final String name, final int size) {
+ public Bitmap get(String name, int size) {
+ return get(name,size,false);
+ }
+
+ public Bitmap get(final String name, final int size, boolean cachedOnly) {
final String KEY = key(name, size);
Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY);
- if (bitmap != null) {
+ if (bitmap != null || cachedOnly) {
return bitmap;
}
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index 2ea0904f..7269a559 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -40,7 +40,9 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.ui.ManageAccountActivity;
import eu.siacs.conversations.ui.TimePreference;
+import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.xmpp.XmppConnection;
public class NotificationService {
@@ -210,9 +212,10 @@ public class NotificationService {
mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
}
setNotificationColor(mBuilder);
+ mBuilder.setDefaults(0);
mBuilder.setSmallIcon(R.drawable.ic_notification);
mBuilder.setDeleteIntent(createDeleteIntent());
- mBuilder.setLights(0xffffffff, 2000, 4000);
+ mBuilder.setLights(0xff00FF00, 2000, 3000);
final Notification notification = mBuilder.build();
notificationManager.notify(NOTIFICATION_ID, notification);
}
@@ -277,6 +280,11 @@ public class NotificationService {
createDownloadIntent(message)
);
}
+ if ((message = getFirstLocationMessage(messages)) != null) {
+ mBuilder.addAction(R.drawable.ic_room_white_24dp,
+ mXmppConnectionService.getString(R.string.show_location),
+ createShowLocationIntent(message));
+ }
mBuilder.setContentIntent(createContentIntent(conversation));
}
return mBuilder;
@@ -340,6 +348,15 @@ public class NotificationService {
return null;
}
+ private Message getFirstLocationMessage(final Iterable<Message> messages) {
+ for(final Message message : messages) {
+ if (GeoHelper.isGeoUri(message.getBody())) {
+ return message;
+ }
+ }
+ return null;
+ }
+
private CharSequence getMergedBodies(final ArrayList<Message> messages) {
final StringBuilder text = new StringBuilder();
for (int i = 0; i < messages.size(); ++i) {
@@ -351,6 +368,16 @@ public class NotificationService {
return text.toString();
}
+ private PendingIntent createShowLocationIntent(final Message message) {
+ Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(message);
+ for(Intent intent : intents) {
+ if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) {
+ return PendingIntent.getActivity(mXmppConnectionService,18,intent,PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+ }
+ return createOpenConversationsIntent();
+ }
+
private PendingIntent createContentIntent(final String conversationUuid, final String downloadMessageUuid) {
final TaskStackBuilder stackBuilder = TaskStackBuilder
.create(mXmppConnectionService);
@@ -395,7 +422,20 @@ public class NotificationService {
final Intent intent = new Intent(mXmppConnectionService,
XmppConnectionService.class);
intent.setAction(XmppConnectionService.ACTION_DISABLE_FOREGROUND);
- return PendingIntent.getService(mXmppConnectionService, 0, intent, 0);
+ return PendingIntent.getService(mXmppConnectionService, 34, intent, 0);
+ }
+
+ private PendingIntent createTryAgainIntent() {
+ final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
+ intent.setAction(XmppConnectionService.ACTION_TRY_AGAIN);
+ return PendingIntent.getService(mXmppConnectionService, 45, intent, 0);
+ }
+
+ private PendingIntent createDisableAccountIntent(final Account account) {
+ final Intent intent = new Intent(mXmppConnectionService,XmppConnectionService.class);
+ intent.setAction(XmppConnectionService.ACTION_DISABLE_ACCOUNT);
+ intent.putExtra("account",account.getJid().toBareJid().toString());
+ return PendingIntent.getService(mXmppConnectionService,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);
}
private boolean wasHighlightedOrPrivate(final Message message) {
@@ -423,9 +463,6 @@ public class NotificationService {
}
public void setIsInForeground(final boolean foreground) {
- if (foreground != this.mIsInForeground) {
- Log.d(Config.LOGTAG,"setIsInForeground("+Boolean.toString(foreground)+")");
- }
this.mIsInForeground = foreground;
}
@@ -492,6 +529,14 @@ public class NotificationService {
mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.problem_connecting_to_accounts));
mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_fix));
}
+ mBuilder.addAction(R.drawable.ic_autorenew_white_24dp,
+ mXmppConnectionService.getString(R.string.try_again),
+ createTryAgainIntent());
+ if (errors.size() == 1) {
+ mBuilder.addAction(R.drawable.ic_block_white_24dp,
+ mXmppConnectionService.getString(R.string.disable_account),
+ createDisableAccountIntent(errors.get(0)));
+ }
mBuilder.setOngoing(true);
//mBuilder.setLights(0xffffffff, 2000, 4000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index e34f9bd7..ca182867 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -16,6 +16,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.FileObserver;
import android.os.IBinder;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
@@ -85,6 +86,7 @@ import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
import eu.siacs.conversations.xmpp.OnStatusChanged;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import eu.siacs.conversations.xmpp.XmppConnection;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.forms.Field;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
@@ -102,6 +104,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public static final String ACTION_CLEAR_NOTIFICATION = "clear_notification";
public static final String ACTION_DISABLE_FOREGROUND = "disable_foreground";
private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
+ public static final String ACTION_TRY_AGAIN = "try_again";
+ public static final String ACTION_DISABLE_ACCOUNT = "disable_account";
private ContentObserver contactObserver = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
@@ -275,6 +279,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private LruCache<String, Bitmap> mBitmapCache;
private Thread mPhoneContactMergerThread;
+ private boolean mRestoredFromDatabase = false;
+ public boolean areMessagesInitialized() {
+ return this.mRestoredFromDatabase;
+ }
+
public PgpEngine getPgpEngine() {
if (pgpServiceConnection.isBound()) {
if (this.mPgpEngine == null) {
@@ -297,6 +306,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return this.mAvatarService;
}
+ public void attachLocationToConversation(final Conversation conversation,
+ final Uri uri,
+ final UiCallback<Message> callback) {
+ int encryption = conversation.getNextEncryption(forceEncryption());
+ if (encryption == Message.ENCRYPTION_PGP) {
+ encryption = Message.ENCRYPTION_DECRYPTED;
+ }
+ Message message = new Message(conversation,uri.toString(),encryption);
+ if (conversation.getNextCounterpart() != null) {
+ message.setCounterpart(conversation.getNextCounterpart());
+ }
+ if (encryption == Message.ENCRYPTION_DECRYPTED) {
+ getPgpEngine().encrypt(message,callback);
+ } else {
+ callback.success(message);
+ }
+ }
+
public void attachFileToConversation(final Conversation conversation,
final Uri uri,
final UiCallback<Message> callback) {
@@ -386,7 +413,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (action != null) {
switch (action) {
case ACTION_MERGE_PHONE_CONTACTS:
- PhoneHelper.loadPhoneContacts(getApplicationContext(), new CopyOnWriteArrayList<Bundle>(), this);
+ if (mRestoredFromDatabase) {
+ PhoneHelper.loadPhoneContacts(getApplicationContext(),
+ new CopyOnWriteArrayList<Bundle>(),
+ this);
+ }
return START_STICKY;
case Intent.ACTION_SHUTDOWN:
logoutAndSave();
@@ -398,6 +429,28 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
getPreferences().edit().putBoolean("keep_foreground_service",false).commit();
toggleForegroundService();
break;
+ case ACTION_TRY_AGAIN:
+ for(Account account : accounts) {
+ if (account.hasErrorStatus()) {
+ final XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.resetAttemptCount();
+ }
+ }
+ }
+ break;
+ case ACTION_DISABLE_ACCOUNT:
+ try {
+ String jid = intent.getStringExtra("account");
+ Account account = jid == null ? null : findAccountByJid(Jid.fromString(jid));
+ if (account != null) {
+ account.setOption(Account.OPTION_DISABLED,true);
+ updateAccount(account);
+ }
+ } catch (final InvalidJidException ignored) {
+ break;
+ }
+ break;
}
}
this.wakeLock.acquire();
@@ -432,10 +485,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode());
}
} else if (account.getStatus() == Account.State.OFFLINE) {
- if (account.getXmppConnection() == null) {
- account.setXmppConnection(this.createConnection(account));
- }
- new Thread(account.getXmppConnection()).start();
+ reconnectAccount(account,true);
} else if (account.getStatus() == Account.State.CONNECTING) {
long timeout = Config.CONNECT_TIMEOUT - ((SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000);
if (timeout < 0) {
@@ -499,10 +549,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
for (final Account account : this.accounts) {
account.initOtrEngine(this);
- this.databaseBackend.readRoster(account.getRoster());
}
- initConversations();
- PhoneHelper.loadPhoneContacts(getApplicationContext(),new CopyOnWriteArrayList<Bundle>(), this);
+ restoreFromDatabase();
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
this.fileObserver.startWatching();
@@ -574,6 +622,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return connection;
}
+ public void sendChatState(Conversation conversation) {
+ if (sendChatStates()) {
+ MessagePacket packet = mMessageGenerator.generateChatState(conversation);
+ sendMessagePacket(conversation.getAccount(), packet);
+ }
+ }
+
public void sendMessage(final Message message) {
final Account account = message.getConversation().getAccount();
account.deactivateGracePeriod();
@@ -674,6 +729,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
if ((send) && (packet != null)) {
+ if (conv.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
+ if (this.sendChatStates()) {
+ packet.addChild(ChatState.toElement(conv.getOutgoingChatState()));
+ }
+ }
sendMessagePacket(account, packet);
}
updateConversationUi();
@@ -755,6 +815,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} else {
markMessage(message, Message.STATUS_UNSEND);
}
+ if (message.getConversation().setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
+ if (this.sendChatStates()) {
+ packet.addChild(ChatState.toElement(message.getConversation().getOutgoingChatState()));
+ }
+ }
sendMessagePacket(account, packet);
}
}
@@ -855,7 +920,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mPhoneContactMergerThread.start();
}
- private void initConversations() {
+ private void restoreFromDatabase() {
synchronized (this.conversations) {
final Map<String, Account> accountLookupTable = new Hashtable<>();
for (Account account : this.accounts) {
@@ -865,9 +930,29 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
for (Conversation conversation : this.conversations) {
Account account = accountLookupTable.get(conversation.getAccountUuid());
conversation.setAccount(account);
- conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE));
- checkDeletedFiles(conversation);
}
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(Config.LOGTAG,"restoring roster");
+ for(Account account : accounts) {
+ databaseBackend.readRoster(account.getRoster());
+ }
+ getBitmapCache().evictAll();
+ Looper.prepare();
+ PhoneHelper.loadPhoneContacts(getApplicationContext(),
+ new CopyOnWriteArrayList<Bundle>(),
+ XmppConnectionService.this);
+ Log.d(Config.LOGTAG,"restoring messages");
+ for (Conversation conversation : conversations) {
+ conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE));
+ checkDeletedFiles(conversation);
+ }
+ mRestoredFromDatabase = true;
+ Log.d(Config.LOGTAG,"restored all messages");
+ updateConversationUi();
+ }
+ }).start();
}
}
@@ -1069,7 +1154,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
account.initOtrEngine(this);
databaseBackend.createAccount(account);
this.accounts.add(account);
- this.reconnectAccount(account, false);
+ this.reconnectAccountInBackground(account);
updateAccountUi();
}
@@ -1274,6 +1359,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
}
+ for(Conversation conversation : getConversations()) {
+ conversation.setIncomingChatState(ChatState.ACTIVE);
+ }
this.mNotificationService.setIsInForeground(false);
Log.d(Config.LOGTAG, "app switched into background");
}
@@ -1902,24 +1990,29 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void reconnectAccount(final Account account, final boolean force) {
- new Thread(new Runnable() {
+ synchronized (account) {
+ if (account.getXmppConnection() != null) {
+ disconnect(account, force);
+ }
+ if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ if (account.getXmppConnection() == null) {
+ account.setXmppConnection(createConnection(account));
+ }
+ Thread thread = new Thread(account.getXmppConnection());
+ thread.start();
+ scheduleWakeUpCall(Config.CONNECT_TIMEOUT, account.getUuid().hashCode());
+ } else {
+ account.getRoster().clearPresences();
+ account.setXmppConnection(null);
+ }
+ }
+ }
+ public void reconnectAccountInBackground(final Account account) {
+ new Thread(new Runnable() {
@Override
public void run() {
- if (account.getXmppConnection() != null) {
- disconnect(account, force);
- }
- if (!account.isOptionSet(Account.OPTION_DISABLED)) {
- if (account.getXmppConnection() == null) {
- account.setXmppConnection(createConnection(account));
- }
- Thread thread = new Thread(account.getXmppConnection());
- thread.start();
- scheduleWakeUpCall(Config.CONNECT_TIMEOUT, account.getUuid().hashCode());
- } else {
- account.getRoster().clearPresences();
- account.setXmppConnection(null);
- }
+ reconnectAccount(account,false);
}
}).start();
}
@@ -1943,19 +2036,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
- public boolean markMessage(final Account account, final Jid recipient, final String uuid,
- final int status) {
+ public Message markMessage(final Account account, final Jid recipient, final String uuid, final int status) {
if (uuid == null) {
- return false;
- } else {
- for (Conversation conversation : getConversations()) {
- if (conversation.getJid().equals(recipient)
- && conversation.getAccount().equals(account)) {
- return markMessage(conversation, uuid, status);
+ return null;
+ }
+ for (Conversation conversation : getConversations()) {
+ if (conversation.getJid().toBareJid().equals(recipient) && conversation.getAccount() == account) {
+ final Message message = conversation.findSentMessageWithUuid(uuid);
+ if (message != null) {
+ markMessage(message, status);
}
+ return message;
}
- return false;
}
+ return null;
}
public boolean markMessage(Conversation conversation, String uuid,
@@ -1997,6 +2091,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return getPreferences().getBoolean("confirm_messages", true);
}
+ public boolean sendChatStates() {
+ return getPreferences().getBoolean("chat_states", false);
+ }
+
public boolean saveEncryptedMessages() {
return !getPreferences().getBoolean("dont_save_encrypted", false);
}
@@ -2005,6 +2103,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return getPreferences().getBoolean("indicate_received", false);
}
+ public int unreadCount() {
+ int count = 0;
+ for(Conversation conversation : getConversations()) {
+ count += conversation.unreadCount();
+ }
+ return count;
+ }
+
public void updateConversationUi() {
if (mOnConversationUpdate != null) {
mOnConversationUpdate.onConversationUpdate();
diff --git a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
index 804b4e23..a57e1b89 100644
--- a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
+++ b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
@@ -6,6 +6,8 @@ import android.content.pm.PackageManager;
import android.preference.Preference;
import android.util.AttributeSet;
+import eu.siacs.conversations.utils.PhoneHelper;
+
public class AboutPreference extends Preference {
public AboutPreference(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
@@ -25,17 +27,7 @@ public class AboutPreference extends Preference {
}
private void setSummary() {
- if (getContext() != null && getContext().getPackageManager() != null) {
- final String packageName = getContext().getPackageName();
- final String versionName;
- try {
- versionName = getContext().getPackageManager().getPackageInfo(packageName, 0).versionName;
- setSummary("Conversations " + versionName);
- } catch (final PackageManager.NameNotFoundException e) {
- // Using try/catch as part of the logic is sort of like this:
- // https://xkcd.com/292/
- }
- }
+ setSummary("Conversations " + PhoneHelper.getVersionName(getContext()));
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java
index 70b353c6..c9e99ce5 100644
--- a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java
@@ -3,20 +3,100 @@ package eu.siacs.conversations.ui;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
+import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
+import android.widget.ListView;
+import java.util.Set;
+import java.util.HashSet;
import java.util.Collections;
+import java.util.List;
+import java.util.ArrayList;
+import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem;
public class ChooseContactActivity extends AbstractSearchableListItemActivity {
+
+ private Set<Contact> selected;
+ private Set<String> filterContacts;
+
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ filterContacts = new HashSet<>();
+ String[] contacts = getIntent().getStringArrayExtra("filter_contacts");
+ if (contacts != null) {
+ Collections.addAll(filterContacts, contacts);
+ }
+
+ if (getIntent().getBooleanExtra("multiple", false)) {
+ getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+ getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(),
+ InputMethodManager.HIDE_IMPLICIT_ONLY);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.select_multiple, menu);
+ selected = new HashSet<Contact>();
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.selection_submit:
+ final Intent request = getIntent();
+ final Intent data = new Intent();
+ data.putExtra("conversation",
+ request.getStringExtra("conversation"));
+ String[] selection = getSelectedContactJids();
+ data.putExtra("contacts", selection);
+ data.putExtra("multiple", true);
+ setResult(RESULT_OK, data);
+ finish();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+ Contact item = (Contact) getListItems().get(position);
+ if (checked) {
+ selected.add(item);
+ } else {
+ selected.remove(item);
+ }
+ int numSelected = selected.size();
+ MenuItem selectButton = mode.getMenu().findItem(R.id.selection_submit);
+ String buttonText = getResources().getQuantityString(R.plurals.select_contact,
+ numSelected, numSelected);
+ selectButton.setTitle(buttonText);
+ }
+ });
+ }
+
getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
@@ -36,6 +116,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity {
data.putExtra("account", account);
data.putExtra("conversation",
request.getStringExtra("conversation"));
+ data.putExtra("multiple", false);
setResult(RESULT_OK, data);
finish();
}
@@ -48,7 +129,9 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity {
for (final Account account : xmppConnectionService.getAccounts()) {
if (account.getStatus() != Account.State.DISABLED) {
for (final Contact contact : account.getRoster().getContacts()) {
- if (contact.showInRoster() && contact.match(needle)) {
+ if (contact.showInRoster() &&
+ !filterContacts.contains(contact.getJid().toBareJid().toString())
+ && contact.match(needle)) {
getListItems().add(contact);
}
}
@@ -57,4 +140,13 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity {
Collections.sort(getListItems());
getListItemAdapter().notifyDataSetChanged();
}
+
+ private String[] getSelectedContactJids() {
+ List<String> result = new ArrayList<>();
+ for (Contact contact : selected) {
+ result.add(contact.getJid().toString());
+ }
+ return result.toArray(new String[result.size()]);
+ }
+
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
index 399d9fdf..e4bfd6ff 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
@@ -25,6 +25,8 @@ import android.widget.Toast;
import org.openintents.openpgp.util.OpenPgpUtils;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import eu.siacs.conversations.R;
@@ -142,24 +144,17 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
@Override
public void onConversationUpdate() {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- updateView();
- }
- });
+ refreshUi();
}
@Override
public void onMucRosterUpdate() {
- runOnUiThread(new Runnable() {
+ refreshUi();
+ }
- @Override
- public void run() {
- updateView();
- }
- });
+ @Override
+ protected void refreshUiReal() {
+ updateView();
}
@Override
@@ -431,9 +426,16 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
membersView.removeAllViews();
- for (final User user : mConversation.getMucOptions().getUsers()) {
- View view = inflater.inflate(R.layout.contact, membersView,
- false);
+ final ArrayList<User> users = new ArrayList<>();
+ users.addAll(mConversation.getMucOptions().getUsers());
+ Collections.sort(users,new Comparator<User>() {
+ @Override
+ public int compare(User lhs, User rhs) {
+ return lhs.getName().compareToIgnoreCase(rhs.getName());
+ }
+ });
+ for (final User user : users) {
+ View view = inflater.inflate(R.layout.contact, membersView,false);
this.setListItemBackgroundOnView(view);
view.setOnClickListener(new OnClickListener() {
@Override
diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index fda0c617..40a4587c 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -139,26 +139,18 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
@Override
public void onRosterUpdate() {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- invalidateOptionsMenu();
- populateView();
- }
- });
+ refreshUi();
}
@Override
public void onAccountUpdate() {
- runOnUiThread(new Runnable() {
+ refreshUi();
+ }
- @Override
- public void run() {
- invalidateOptionsMenu();
- populateView();
- }
- });
+ @Override
+ protected void refreshUiReal() {
+ invalidateOptionsMenu();
+ populateView();
}
@Override
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index 0a55c6b5..82afda07 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -12,7 +12,6 @@ import android.content.IntentSender.SendIntentException;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.SystemClock;
import android.provider.MediaStore;
import android.support.v4.widget.SlidingPaneLayout;
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
@@ -34,7 +33,6 @@ import java.util.ArrayList;
import java.util.List;
import eu.siacs.conversations.R;
-import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Blockable;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
@@ -64,6 +62,7 @@ public class ConversationActivity extends XmppActivity
private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
private static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303;
private static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304;
+ private static final int ATTACHMENT_CHOICE_LOCATION = 0x0305;
private static final String STATE_OPEN_CONVERSATION = "state_open_conversation";
private static final String STATE_PANEL_OPEN = "state_panel_open";
private static final String STATE_PENDING_URI = "state_pending_uri";
@@ -72,6 +71,7 @@ public class ConversationActivity extends XmppActivity
private boolean mPanelOpen = true;
private Uri mPendingImageUri = null;
private Uri mPendingFileUri = null;
+ private Uri mPendingGeoUri = null;
private View mContentView;
@@ -85,6 +85,7 @@ public class ConversationActivity extends XmppActivity
private Toast prepareFileToast;
private boolean mActivityPaused = false;
+ private boolean mRedirected = true;
public Conversation getSelectedConversation() {
return this.mSelectedConversation;
@@ -313,7 +314,6 @@ public class ConversationActivity extends XmppActivity
menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite());
} else {
menuMucDetails.setVisible(false);
- final Account account = this.getSelectedConversation().getAccount();
}
if (this.getSelectedConversation().isMuted()) {
menuMute.setVisible(false);
@@ -325,50 +325,60 @@ public class ConversationActivity extends XmppActivity
return true;
}
- private void selectPresenceToAttachFile(final int attachmentChoice) {
- selectPresence(getSelectedConversation(), new OnPresenceSelected() {
+ private void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) {
+ if (attachmentChoice == ATTACHMENT_CHOICE_LOCATION && encryption != Message.ENCRYPTION_OTR) {
+ getSelectedConversation().setNextCounterpart(null);
+ Intent intent = new Intent("eu.siacs.conversations.location.request");
+ startActivityForResult(intent,attachmentChoice);
+ } else {
+ selectPresence(getSelectedConversation(), new OnPresenceSelected() {
- @Override
- public void onPresenceSelected() {
- Intent intent = new Intent();
- boolean chooser = false;
- switch (attachmentChoice) {
- case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
- intent.setAction(Intent.ACTION_GET_CONTENT);
- intent.setType("image/*");
- chooser = true;
- break;
- case ATTACHMENT_CHOICE_TAKE_PHOTO:
- mPendingImageUri = xmppConnectionService.getFileBackend().getTakePhotoUri();
- intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
- intent.putExtra(MediaStore.EXTRA_OUTPUT,mPendingImageUri);
- break;
- case ATTACHMENT_CHOICE_CHOOSE_FILE:
- chooser = true;
- intent.setType("*/*");
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setAction(Intent.ACTION_GET_CONTENT);
- break;
- case ATTACHMENT_CHOICE_RECORD_VOICE:
- intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
- break;
- }
- if (intent.resolveActivity(getPackageManager()) != null) {
- if (chooser) {
- startActivityForResult(
- Intent.createChooser(intent,getString(R.string.perform_action_with)),
- attachmentChoice);
- } else {
- startActivityForResult(intent, attachmentChoice);
+ @Override
+ public void onPresenceSelected() {
+ Intent intent = new Intent();
+ boolean chooser = false;
+ switch (attachmentChoice) {
+ case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ chooser = true;
+ break;
+ case ATTACHMENT_CHOICE_TAKE_PHOTO:
+ mPendingImageUri = xmppConnectionService.getFileBackend().getTakePhotoUri();
+ intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, mPendingImageUri);
+ break;
+ case ATTACHMENT_CHOICE_CHOOSE_FILE:
+ chooser = true;
+ intent.setType("*/*");
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ break;
+ case ATTACHMENT_CHOICE_RECORD_VOICE:
+ intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
+ break;
+ case ATTACHMENT_CHOICE_LOCATION:
+ intent.setAction("eu.siacs.conversations.location.request");
+ break;
+ }
+ if (intent.resolveActivity(getPackageManager()) != null) {
+ if (chooser) {
+ startActivityForResult(
+ Intent.createChooser(intent, getString(R.string.perform_action_with)),
+ attachmentChoice);
+ } else {
+ startActivityForResult(intent, attachmentChoice);
+ }
}
}
- }
- });
+ });
+ }
}
private void attachFile(final int attachmentChoice) {
final Conversation conversation = getSelectedConversation();
- if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
+ final int encryption = conversation.getNextEncryption(forceEncryption());
+ if (encryption == Message.ENCRYPTION_PGP) {
if (hasPgp()) {
if (conversation.getContact().getPgpKeyId() != 0) {
xmppConnectionService.getPgpEngine().hasKey(
@@ -378,13 +388,12 @@ public class ConversationActivity extends XmppActivity
@Override
public void userInputRequried(PendingIntent pi,
Contact contact) {
- ConversationActivity.this.runIntent(pi,
- attachmentChoice);
+ ConversationActivity.this.runIntent(pi,attachmentChoice);
}
@Override
public void success(Contact contact) {
- selectPresenceToAttachFile(attachmentChoice);
+ selectPresenceToAttachFile(attachmentChoice,encryption);
}
@Override
@@ -406,7 +415,7 @@ public class ConversationActivity extends XmppActivity
.setNextEncryption(Message.ENCRYPTION_NONE);
xmppConnectionService.databaseBackend
.updateConversation(conversation);
- selectPresenceToAttachFile(attachmentChoice);
+ selectPresenceToAttachFile(attachmentChoice,Message.ENCRYPTION_NONE);
}
});
}
@@ -414,11 +423,8 @@ public class ConversationActivity extends XmppActivity
} else {
showInstallPgpDialog();
}
- } else if (getSelectedConversation().getNextEncryption(
- forceEncryption()) == Message.ENCRYPTION_NONE) {
- selectPresenceToAttachFile(attachmentChoice);
} else {
- selectPresenceToAttachFile(attachmentChoice);
+ selectPresenceToAttachFile(attachmentChoice,encryption);
}
}
@@ -526,6 +532,9 @@ public class ConversationActivity extends XmppActivity
if (new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(getPackageManager()) == null) {
attachFilePopup.getMenu().findItem(R.id.attach_record_voice).setVisible(false);
}
+ if (new Intent("eu.siacs.conversations.location.request").resolveActivity(getPackageManager()) == null) {
+ attachFilePopup.getMenu().findItem(R.id.attach_location).setVisible(false);
+ }
attachFilePopup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
@@ -543,6 +552,9 @@ public class ConversationActivity extends XmppActivity
case R.id.attach_record_voice:
attachFile(ATTACHMENT_CHOICE_RECORD_VOICE);
break;
+ case R.id.attach_location:
+ attachFile(ATTACHMENT_CHOICE_LOCATION);
+ break;
}
return false;
}
@@ -677,8 +689,7 @@ public class ConversationActivity extends XmppActivity
if (durations[which] == -1) {
till = Long.MAX_VALUE;
} else {
- till = SystemClock.elapsedRealtime()
- + (durations[which] * 1000);
+ till = System.currentTimeMillis() + (durations[which] * 1000);
}
conversation.setMutedTill(till);
ConversationActivity.this.xmppConnectionService.databaseBackend
@@ -722,6 +733,7 @@ public class ConversationActivity extends XmppActivity
@Override
public void onStart() {
super.onStart();
+ this.mRedirected = false;
if (this.xmppConnectionServiceBound) {
this.onBackendConnected();
}
@@ -778,10 +790,19 @@ public class ConversationActivity extends XmppActivity
this.xmppConnectionService.getNotificationService().setIsInForeground(true);
updateConversationList();
if (xmppConnectionService.getAccounts().size() == 0) {
- startActivity(new Intent(this, EditAccountActivity.class));
+ if (!mRedirected) {
+ this.mRedirected = true;
+ startActivity(new Intent(this, EditAccountActivity.class));
+ finish();
+ }
} else if (conversationList.size() <= 0) {
- startActivity(new Intent(this, StartConversationActivity.class));
- finish();
+ if (!mRedirected) {
+ this.mRedirected = true;
+ Intent intent = new Intent(this, StartConversationActivity.class);
+ intent.putExtra("init",true);
+ startActivity(intent);
+ finish();
+ }
} else if (getIntent() != null && VIEW_CONVERSATION.equals(getIntent().getType())) {
handleViewConversationIntent(getIntent());
} else if (selectConversationByUuid(mOpenConverstaion)) {
@@ -795,11 +816,12 @@ public class ConversationActivity extends XmppActivity
this.mConversationFragment.reInit(getSelectedConversation());
mOpenConverstaion = null;
} else if (getSelectedConversation() != null) {
- this.mConversationFragment.updateMessages();
+ this.mConversationFragment.reInit(getSelectedConversation());
} else {
showConversationsOverview();
mPendingImageUri = null;
mPendingFileUri = null;
+ mPendingGeoUri = null;
setSelectedConversation(conversationList.get(0));
this.mConversationFragment.reInit(getSelectedConversation());
}
@@ -810,6 +832,9 @@ public class ConversationActivity extends XmppActivity
} else if (mPendingFileUri != null) {
attachFileToConversation(getSelectedConversation(),mPendingFileUri);
mPendingFileUri = null;
+ } else if (mPendingGeoUri != null) {
+ attachLocationToConversation(getSelectedConversation(),mPendingGeoUri);
+ mPendingGeoUri = null;
}
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
setIntent(new Intent());
@@ -888,6 +913,14 @@ public class ConversationActivity extends XmppActivity
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(mPendingImageUri);
sendBroadcast(intent);
+ } else if (requestCode == ATTACHMENT_CHOICE_LOCATION) {
+ double latitude = data.getDoubleExtra("latitude",0);
+ double longitude = data.getDoubleExtra("longitude",0);
+ this.mPendingGeoUri = Uri.parse("geo:"+String.valueOf(latitude)+","+String.valueOf(longitude));
+ if (xmppConnectionServiceBound) {
+ attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
+ this.mPendingGeoUri = null;
+ }
}
} else {
if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
@@ -896,6 +929,26 @@ public class ConversationActivity extends XmppActivity
}
}
+ private void attachLocationToConversation(Conversation conversation, Uri uri) {
+ xmppConnectionService.attachLocationToConversation(conversation,uri, new UiCallback<Message>() {
+
+ @Override
+ public void success(Message message) {
+ xmppConnectionService.sendMessage(message);
+ }
+
+ @Override
+ public void error(int errorCode, Message object) {
+
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Message object) {
+
+ }
+ });
+ }
+
private void attachFileToConversation(Conversation conversation, Uri uri) {
prepareFileToast = Toast.makeText(getApplicationContext(),
getText(R.string.preparing_file), Toast.LENGTH_LONG);
@@ -1008,56 +1061,50 @@ public class ConversationActivity extends XmppActivity
}
@Override
- public void onAccountUpdate() {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- updateConversationList();
- ConversationActivity.this.mConversationFragment.updateMessages();
- updateActionBarTitle();
+ protected void refreshUiReal() {
+ updateConversationList();
+ if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 0) {
+ if (!mRedirected) {
+ this.mRedirected = true;
+ startActivity(new Intent(this, EditAccountActivity.class));
+ finish();
}
- });
+ } else if (conversationList.size() == 0) {
+ if (!mRedirected) {
+ this.mRedirected = true;
+ Intent intent = new Intent(this, StartConversationActivity.class);
+ intent.putExtra("init",true);
+ startActivity(intent);
+ finish();
+ }
+ } else {
+ ConversationActivity.this.mConversationFragment.updateMessages();
+ updateActionBarTitle();
+ }
}
@Override
- public void onConversationUpdate() {
- runOnUiThread(new Runnable() {
+ public void onAccountUpdate() {
+ this.refreshUi();
+ }
- @Override
- public void run() {
- updateConversationList();
- if (conversationList.size() == 0) {
- startActivity(new Intent(getApplicationContext(),
- StartConversationActivity.class));
- finish();
- }
- ConversationActivity.this.mConversationFragment.updateMessages();
- updateActionBarTitle();
- }
- });
+ @Override
+ public void onConversationUpdate() {
+ this.refreshUi();
}
@Override
public void onRosterUpdate() {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- updateConversationList();
- ConversationActivity.this.mConversationFragment.updateMessages();
- updateActionBarTitle();
- }
- });
+ this.refreshUi();
}
@Override
public void OnUpdateBlocklist(Status status) {
+ this.refreshUi();
runOnUiThread(new Runnable() {
@Override
public void run() {
invalidateOptionsMenu();
- ConversationActivity.this.mConversationFragment.updateMessages();
}
});
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 80ac9da1..d5f20e41 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -8,6 +8,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
+import android.net.Uri;
import android.os.Bundle;
import android.text.InputType;
import android.view.ContextMenu;
@@ -40,6 +41,7 @@ import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentLinkedQueue;
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.entities.Account;
@@ -52,15 +54,16 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.ui.EditMessage.OnEnterPressed;
import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected;
import eu.siacs.conversations.ui.XmppActivity.OnValueEdited;
import eu.siacs.conversations.ui.adapter.MessageAdapter;
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked;
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked;
+import eu.siacs.conversations.utils.GeoHelper;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.Jid;
-public class ConversationFragment extends Fragment {
+public class ConversationFragment extends Fragment implements EditMessage.KeyboardListener {
protected Conversation conversation;
private OnClickListener leaveMuc = new OnClickListener() {
@@ -99,7 +102,6 @@ public class ConversationFragment extends Fragment {
protected ListView messagesView;
final protected List<Message> messageList = new ArrayList<>();
protected MessageAdapter messageListAdapter;
- protected Contact contact;
private EditMessage mEditMessage;
private ImageButton mSendButton;
private RelativeLayout snackbar;
@@ -196,6 +198,7 @@ public class ConversationFragment extends Fragment {
askForPassphraseIntent,
ConversationActivity.REQUEST_DECRYPT_PGP, null, 0,
0, 0);
+ askForPassphraseIntent = null;
} catch (SendIntentException e) {
//
}
@@ -321,22 +324,12 @@ public class ConversationFragment extends Fragment {
@Override
public void onClick(View v) {
- activity.hideConversationsOverview();
- }
- });
- mEditMessage.setOnEditorActionListener(mEditorActionListener);
- mEditMessage.setOnEnterPressedListener(new OnEnterPressed() {
-
- @Override
- public boolean onEnterPressed() {
- if (activity.enterIsSend()) {
- sendMessage();
- return true;
- } else {
- return false;
+ if (activity != null) {
+ activity.hideConversationsOverview();
}
}
});
+ mEditMessage.setOnEditorActionListener(mEditorActionListener);
mSendButton = (ImageButton) view.findViewById(R.id.textSendButton);
mSendButton.setOnClickListener(this.mSendButtonListener);
@@ -418,19 +411,21 @@ public class ConversationFragment extends Fragment {
MenuItem copyUrl = menu.findItem(R.id.copy_url);
MenuItem downloadImage = menu.findItem(R.id.download_image);
MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission);
- if (m.getType() != Message.TYPE_TEXT || m.getDownloadable() != null) {
+ if ((m.getType() != Message.TYPE_TEXT && m.getType() != Message.TYPE_PRIVATE)
+ || m.getDownloadable() != null || GeoHelper.isGeoUri(m.getBody())) {
copyText.setVisible(false);
}
- if (m.getType() == Message.TYPE_TEXT
+ if ((m.getType() == Message.TYPE_TEXT
|| m.getType() == Message.TYPE_PRIVATE
- || m.getDownloadable() != null) {
+ || m.getDownloadable() != null)
+ && (!GeoHelper.isGeoUri(m.getBody()))) {
shareWith.setVisible(false);
- }
+ }
if (m.getStatus() != Message.STATUS_SEND_FAILED) {
sendAgain.setVisible(false);
}
- if ((m.getType() != Message.TYPE_IMAGE && m.getDownloadable() == null)
- || m.getImageParams().url == null) {
+ if (((m.getType() != Message.TYPE_IMAGE && m.getDownloadable() == null)
+ || m.getImageParams().url == null) && !GeoHelper.isGeoUri(m.getBody())) {
copyUrl.setVisible(false);
}
if (m.getType() != Message.TYPE_TEXT
@@ -475,16 +470,21 @@ public class ConversationFragment extends Fragment {
private void shareWith(Message message) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
- shareIntent.putExtra(Intent.EXTRA_STREAM,
- activity.xmppConnectionService.getFileBackend()
- .getJingleFileUri(message));
- shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- String path = message.getRelativeFilePath();
- String mime = path == null ? null :URLConnection.guessContentTypeFromName(path);
- if (mime == null) {
- mime = "image/webp";
- }
- shareIntent.setType(mime);
+ if (GeoHelper.isGeoUri(message.getBody())) {
+ shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody());
+ shareIntent.setType("text/plain");
+ } else {
+ shareIntent.putExtra(Intent.EXTRA_STREAM,
+ activity.xmppConnectionService.getFileBackend()
+ .getJingleFileUri(message));
+ shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ String path = message.getRelativeFilePath();
+ String mime = path == null ? null : URLConnection.guessContentTypeFromName(path);
+ if (mime == null) {
+ mime = "image/webp";
+ }
+ shareIntent.setType(mime);
+ }
activity.startActivity(Intent.createChooser(shareIntent,getText(R.string.share_with)));
}
@@ -509,8 +509,16 @@ public class ConversationFragment extends Fragment {
}
private void copyUrl(Message message) {
- if (activity.copyTextToClipboard(
- message.getImageParams().url.toString(), R.string.image_url)) {
+ final String url;
+ final int resId;
+ if (GeoHelper.isGeoUri(message.getBody())) {
+ resId = R.string.location;
+ url = message.getBody();
+ } else {
+ resId = R.string.image_url;
+ url = message.getImageParams().url.toString();
+ }
+ if (activity.copyTextToClipboard(url, resId)) {
Toast.makeText(activity, R.string.url_copied_to_clipboard,
Toast.LENGTH_SHORT).show();
}
@@ -555,7 +563,17 @@ public class ConversationFragment extends Fragment {
mDecryptJobRunning = false;
super.onStop();
if (this.conversation != null) {
- this.conversation.setNextMessage(mEditMessage.getText().toString());
+ final String msg = mEditMessage.getText().toString();
+ this.conversation.setNextMessage(msg);
+ updateChatState(this.conversation,msg);
+ }
+ }
+
+ private void updateChatState(final Conversation conversation, final String msg) {
+ ChatState state = msg.length() == 0 ? Config.DEFAULT_CHATSTATE : ChatState.PAUSED;
+ Account.State status = conversation.getAccount().getStatus();
+ if (status == Account.State.ONLINE && conversation.setOutgoingChatState(state)) {
+ activity.xmppConnectionService.sendChatState(conversation);
}
}
@@ -563,20 +581,30 @@ public class ConversationFragment extends Fragment {
if (conversation == null) {
return;
}
+
+ this.activity = (ConversationActivity) getActivity();
+
if (this.conversation != null) {
- this.conversation.setNextMessage(mEditMessage.getText().toString());
+ final String msg = mEditMessage.getText().toString();
+ this.conversation.setNextMessage(msg);
+ if (this.conversation != conversation) {
+ updateChatState(this.conversation,msg);
+ }
this.conversation.trim();
}
- this.activity = (ConversationActivity) getActivity();
+
+ this.askForPassphraseIntent = null;
this.conversation = conversation;
this.mDecryptJobRunning = false;
this.mEncryptedMessages.clear();
if (this.conversation.getMode() == Conversation.MODE_MULTI) {
this.conversation.setNextCounterpart(null);
}
+ this.mEditMessage.setKeyboardListener(null);
this.mEditMessage.setText("");
this.mEditMessage.append(this.conversation.getNextMessage());
- this.messagesView.invalidateViews();
+ this.mEditMessage.setKeyboardListener(this);
+ this.messagesView.setAdapter(messageListAdapter);
updateMessages();
this.messagesLoaded = true;
int size = this.messageList.size();
@@ -585,88 +613,127 @@ public class ConversationFragment extends Fragment {
}
}
+ private OnClickListener mUnblockClickListener = new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ v.post(new Runnable() {
+ @Override
+ public void run() {
+ v.setVisibility(View.INVISIBLE);
+ }
+ });
+ if (conversation.isDomainBlocked()) {
+ BlockContactDialog.show(activity, activity.xmppConnectionService, conversation);
+ } else {
+ activity.unblockConversation(conversation);
+ }
+ }
+ };
+
+ private OnClickListener mAddBackClickListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ final Contact contact = conversation == null ? null :conversation.getContact();
+ if (contact != null) {
+ activity.xmppConnectionService.createContact(contact);
+ activity.switchToContactDetails(contact);
+ }
+ }
+ };
+
+ private OnClickListener mUnmuteClickListener = new OnClickListener() {
+
+ @Override
+ public void onClick(final View v) {
+ activity.unmuteConversation(conversation);
+ }
+ };
+
+ private OnClickListener mAnswerSmpClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(activity, VerifyOTRActivity.class);
+ intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT);
+ intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString());
+ intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString());
+ intent.putExtra("mode",VerifyOTRActivity.MODE_ANSWER_QUESTION);
+ startActivity(intent);
+ }
+ };
+
+ private void updateSnackBar(final Conversation conversation) {
+ final Account account = conversation.getAccount();
+ final Contact contact = conversation.getContact();
+ final int mode = conversation.getMode();
+ if (conversation.isBlocked()) {
+ showSnackbar(R.string.contact_blocked, R.string.unblock,this.mUnblockClickListener);
+ } else if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
+ showSnackbar(R.string.contact_added_you, R.string.add_back,this.mAddBackClickListener);
+ } else if (mode == Conversation.MODE_MULTI
+ &&!conversation.getMucOptions().online()
+ && account.getStatus() == Account.State.ONLINE) {
+ switch (conversation.getMucOptions().getError()) {
+ case MucOptions.ERROR_NICK_IN_USE:
+ showSnackbar(R.string.nick_in_use, R.string.edit, clickToMuc);
+ break;
+ case MucOptions.ERROR_UNKNOWN:
+ showSnackbar(R.string.conference_not_found, R.string.leave, leaveMuc);
+ break;
+ case MucOptions.ERROR_PASSWORD_REQUIRED:
+ showSnackbar(R.string.conference_requires_password, R.string.enter_password, enterPassword);
+ break;
+ case MucOptions.ERROR_BANNED:
+ showSnackbar(R.string.conference_banned, R.string.leave, leaveMuc);
+ break;
+ case MucOptions.ERROR_MEMBERS_ONLY:
+ showSnackbar(R.string.conference_members_only, R.string.leave, leaveMuc);
+ break;
+ case MucOptions.KICKED_FROM_ROOM:
+ showSnackbar(R.string.conference_kicked, R.string.join, joinMuc);
+ break;
+ default:
+ break;
+ }
+ } else if (askForPassphraseIntent != null ) {
+ showSnackbar(R.string.openpgp_messages_found,R.string.decrypt, clickToDecryptListener);
+ } else if (mode == Conversation.MODE_SINGLE
+ && conversation.smpRequested()) {
+ showSnackbar(R.string.smp_requested, R.string.verify,this.mAnswerSmpClickListener);
+ } else if (mode == Conversation.MODE_SINGLE
+ &&conversation.hasValidOtrSession()
+ && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED)
+ && (!conversation.isOtrFingerprintVerified())) {
+ showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify);
+ } else if (conversation.isMuted()) {
+ showSnackbar(R.string.notifications_disabled, R.string.enable,this.mUnmuteClickListener);
+ } else {
+ hideSnackbar();
+ }
+ }
+
public void updateMessages() {
synchronized (this.messageList) {
if (getView() == null) {
return;
}
- hideSnackbar();
final ConversationActivity activity = (ConversationActivity) getActivity();
if (this.conversation != null) {
+ updateSnackBar(this.conversation);
final Contact contact = this.conversation.getContact();
if (this.conversation.isBlocked()) {
- showSnackbar(R.string.contact_blocked, R.string.unblock,
- new OnClickListener() {
- @Override
- public void onClick(final View v) {
- v.post(new Runnable() {
- @Override
- public void run() {
- v.setVisibility(View.INVISIBLE);
- }
- });
- if (conversation.isDomainBlocked()) {
- BlockContactDialog.show(getActivity(), ((ConversationActivity) getActivity()).xmppConnectionService, conversation);
- } else {
- ((ConversationActivity) getActivity()).unblockConversation(conversation);
- }
- }
- });
+
} else if (!contact.showInRoster()
&& contact
.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
- showSnackbar(R.string.contact_added_you, R.string.add_back,
- new OnClickListener() {
- @Override
- public void onClick(View v) {
- activity.xmppConnectionService
- .createContact(contact);
- activity.switchToContactDetails(contact);
- }
- });
} else if (conversation.getMode() == Conversation.MODE_SINGLE) {
makeFingerprintWarning();
} else if (!conversation.getMucOptions().online()
&& conversation.getAccount().getStatus() == Account.State.ONLINE) {
- int error = conversation.getMucOptions().getError();
- switch (error) {
- case MucOptions.ERROR_NICK_IN_USE:
- showSnackbar(R.string.nick_in_use, R.string.edit,
- clickToMuc);
- break;
- case MucOptions.ERROR_UNKNOWN:
- showSnackbar(R.string.conference_not_found,
- R.string.leave, leaveMuc);
- break;
- case MucOptions.ERROR_PASSWORD_REQUIRED:
- showSnackbar(R.string.conference_requires_password,
- R.string.enter_password, enterPassword);
- break;
- case MucOptions.ERROR_BANNED:
- showSnackbar(R.string.conference_banned,
- R.string.leave, leaveMuc);
- break;
- case MucOptions.ERROR_MEMBERS_ONLY:
- showSnackbar(R.string.conference_members_only,
- R.string.leave, leaveMuc);
- break;
- case MucOptions.KICKED_FROM_ROOM:
- showSnackbar(R.string.conference_kicked, R.string.join,
- joinMuc);
- break;
- default:
- break;
- }
+
} else if (this.conversation.isMuted()) {
- showSnackbar(R.string.notifications_disabled, R.string.enable,
- new OnClickListener() {
- @Override
- public void onClick(final View v) {
- activity.unmuteConversation(conversation);
- }
- });
}
conversation.populateWithMessages(ConversationFragment.this.messageList);
for (final Message message : this.messageList) {
@@ -703,8 +770,7 @@ public class ConversationFragment extends Fragment {
public void userInputRequried(PendingIntent pi, Message message) {
mDecryptJobRunning = false;
askForPassphraseIntent = pi.getIntentSender();
- showSnackbar(R.string.openpgp_messages_found,
- R.string.decrypt, clickToDecryptListener);
+ updateSnackBar(conversation);
}
@Override
@@ -792,13 +858,21 @@ public class ConversationFragment extends Fragment {
protected void updateStatusMessages() {
synchronized (this.messageList) {
if (conversation.getMode() == Conversation.MODE_SINGLE) {
- for (int i = this.messageList.size() - 1; i >= 0; --i) {
- if (this.messageList.get(i).getStatus() == Message.STATUS_RECEIVED) {
- return;
- } else {
- if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
- this.messageList.add(i + 1,Message.createStatusMessage(conversation));
+ ChatState state = conversation.getIncomingChatState();
+ if (state == ChatState.COMPOSING) {
+ this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_is_typing, conversation.getName())));
+ } else if (state == ChatState.PAUSED) {
+ this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_has_stopped_typing, conversation.getName())));
+ } else {
+ for (int i = this.messageList.size() - 1; i >= 0; --i) {
+ if (this.messageList.get(i).getStatus() == Message.STATUS_RECEIVED) {
return;
+ } else {
+ if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
+ this.messageList.add(i + 1,
+ Message.createStatusMessage(conversation, getString(R.string.contact_has_read_up_to_this_point, conversation.getName())));
+ return;
+ }
}
}
}
@@ -807,22 +881,7 @@ public class ConversationFragment extends Fragment {
}
protected void makeFingerprintWarning() {
- if (conversation.smpRequested()) {
- showSnackbar(R.string.smp_requested, R.string.verify, new OnClickListener() {
- @Override
- public void onClick(View view) {
- Intent intent = new Intent(activity, VerifyOTRActivity.class);
- intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT);
- intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString());
- intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString());
- intent.putExtra("mode",VerifyOTRActivity.MODE_ANSWER_QUESTION);
- startActivity(intent);
- }
- });
- } else if (conversation.hasValidOtrSession() && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED)
- && (!conversation.isOtrFingerprintVerified())) {
- showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify);
- }
+
}
protected void showSnackbar(final int message, final int action,
@@ -968,4 +1027,38 @@ public class ConversationFragment extends Fragment {
this.mEditMessage.append(text);
}
+ @Override
+ public boolean onEnterPressed() {
+ if (activity.enterIsSend()) {
+ sendMessage();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onTypingStarted() {
+ Account.State status = conversation.getAccount().getStatus();
+ if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) {
+ activity.xmppConnectionService.sendChatState(conversation);
+ }
+ }
+
+ @Override
+ public void onTypingStopped() {
+ Account.State status = conversation.getAccount().getStatus();
+ if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.PAUSED)) {
+ activity.xmppConnectionService.sendChatState(conversation);
+ }
+ }
+
+ @Override
+ public void onTextDeleted() {
+ Account.State status = conversation.getAccount().getStatus();
+ if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
+ activity.xmppConnectionService.sendChatState(conversation);
+ }
+ }
+
}
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index ea45b75e..27dfc492 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -102,6 +102,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} catch (final InvalidJidException ignored) {
return;
}
+ mAccountJid.setError(null);
+ mPasswordConfirm.setError(null);
mAccount.setPassword(password);
mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
xmppConnectionService.updateAccount(mAccount);
@@ -221,6 +223,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if (avatar != null) {
intent = new Intent(getApplicationContext(),
StartConversationActivity.class);
+ intent.putExtra("init",true);
} else {
intent = new Intent(getApplicationContext(),
PublishProfilePictureActivity.class);
@@ -234,7 +237,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
protected void updateSaveButton() {
- if (mAccount != null && mAccount.getStatus() == Account.State.CONNECTING) {
+ if (mAccount != null && (mAccount.getStatus() == Account.State.CONNECTING || mFetchingAvatar)) {
this.mSaveButton.setEnabled(false);
this.mSaveButton.setTextColor(getSecondaryTextColor());
this.mSaveButton.setText(R.string.account_status_connecting);
@@ -329,17 +332,18 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list);
final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
- if (mAccount == null) {
+ if (mAccount != null && mAccount.isOnlineAndConnected()) {
+ if (!mAccount.getXmppConnection().getFeatures().blocking()) {
+ showBlocklist.setVisible(false);
+ }
+ if (!mAccount.getXmppConnection().getFeatures().register()) {
+ changePassword.setVisible(false);
+ }
+ } else {
showQrCode.setVisible(false);
showBlocklist.setVisible(false);
showMoreInfo.setVisible(false);
changePassword.setVisible(false);
- } else if (mAccount.getStatus() != Account.State.ONLINE) {
- showBlocklist.setVisible(false);
- showMoreInfo.setVisible(false);
- changePassword.setVisible(false);
- } else if (!mAccount.getXmppConnection().getFeatures().blocking()) {
- showBlocklist.setVisible(false);
}
return true;
}
@@ -379,6 +383,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if (getActionBar() != null) {
getActionBar().setDisplayHomeAsUpEnabled(false);
getActionBar().setDisplayShowHomeEnabled(false);
+ getActionBar().setHomeButtonEnabled(false);
}
this.mCancelButton.setEnabled(false);
this.mCancelButton.setTextColor(getSecondaryTextColor());
@@ -491,6 +496,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if (this.mAccount.errorStatus()) {
this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId()));
this.mAccountJid.requestFocus();
+ } else {
+ this.mAccountJid.setError(null);
}
this.mStats.setVisibility(View.GONE);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/EditMessage.java
index 5090bbf5..a58cf2b8 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditMessage.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditMessage.java
@@ -1,10 +1,13 @@
package eu.siacs.conversations.ui;
import android.content.Context;
+import android.os.Handler;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.EditText;
+import eu.siacs.conversations.Config;
+
public class EditMessage extends EditText {
public EditMessage(Context context, AttributeSet attrs) {
@@ -15,28 +18,61 @@ public class EditMessage extends EditText {
super(context);
}
- protected OnEnterPressed mOnEnterPressed;
+ protected Handler mTypingHandler = new Handler();
+
+ protected Runnable mTypingTimeout = new Runnable() {
+ @Override
+ public void run() {
+ if (isUserTyping && keyboardListener != null) {
+ keyboardListener.onTypingStopped();
+ isUserTyping = false;
+ }
+ }
+ };
+
+ private boolean isUserTyping = false;
+
+ protected KeyboardListener keyboardListener;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
- if (mOnEnterPressed != null) {
- if (mOnEnterPressed.onEnterPressed()) {
- return true;
- } else {
- return super.onKeyDown(keyCode, event);
- }
+ if (keyboardListener != null && keyboardListener.onEnterPressed()) {
+ return true;
}
}
return super.onKeyDown(keyCode, event);
}
- public void setOnEnterPressedListener(OnEnterPressed listener) {
- this.mOnEnterPressed = listener;
+ @Override
+ public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
+ super.onTextChanged(text,start,lengthBefore,lengthAfter);
+ if (this.mTypingHandler != null && this.keyboardListener != null) {
+ this.mTypingHandler.removeCallbacks(mTypingTimeout);
+ this.mTypingHandler.postDelayed(mTypingTimeout, Config.TYPING_TIMEOUT * 1000);
+ final int length = text.length();
+ if (!isUserTyping && length > 0) {
+ this.isUserTyping = true;
+ this.keyboardListener.onTypingStarted();
+ } else if (length == 0) {
+ this.isUserTyping = false;
+ this.keyboardListener.onTextDeleted();
+ }
+ }
+ }
+
+ public void setKeyboardListener(KeyboardListener listener) {
+ this.keyboardListener = listener;
+ if (listener != null) {
+ this.isUserTyping = false;
+ }
}
- public interface OnEnterPressed {
+ public interface KeyboardListener {
public boolean onEnterPressed();
+ public void onTypingStarted();
+ public void onTypingStopped();
+ public void onTextDeleted();
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
index b3ab5ee6..b2d5ddfd 100644
--- a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
@@ -32,18 +32,17 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
@Override
public void onAccountUpdate() {
+ refreshUi();
+ }
+
+ @Override
+ protected void refreshUiReal() {
synchronized (this.accountList) {
accountList.clear();
accountList.addAll(xmppConnectionService.getAccounts());
}
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- invalidateOptionsMenu();
- mAccountAdapter.notifyDataSetChanged();
- }
- });
+ invalidateOptionsMenu();
+ mAccountAdapter.notifyDataSetChanged();
}
@Override
diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
index 2ba0b090..3f72b723 100644
--- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
@@ -54,8 +54,10 @@ public class PublishProfilePictureActivity extends XmppActivity {
@Override
public void run() {
if (mInitialAccountSetup) {
- startActivity(new Intent(getApplicationContext(),
- StartConversationActivity.class));
+ Intent intent = new Intent(getApplicationContext(),
+ StartConversationActivity.class);
+ intent.putExtra("init",true);
+ startActivity(intent);
}
Toast.makeText(PublishProfilePictureActivity.this,
R.string.avatar_has_been_published,
@@ -112,8 +114,10 @@ public class PublishProfilePictureActivity extends XmppActivity {
@Override
public void onClick(View v) {
if (mInitialAccountSetup) {
- startActivity(new Intent(getApplicationContext(),
- StartConversationActivity.class));
+ Intent intent = new Intent(getApplicationContext(),
+ StartConversationActivity.class);
+ intent.putExtra("init",true);
+ startActivity(intent);
}
finish();
}
diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
index 136108ef..1c1ff3b9 100644
--- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
@@ -67,7 +67,7 @@ public class SettingsActivity extends XmppActivity implements
for (Account account : xmppConnectionService.getAccounts()) {
account.setResource(resource);
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
- xmppConnectionService.reconnectAccount(account, false);
+ xmppConnectionService.reconnectAccountInBackground(account);
}
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
index ff46ffd8..a556b8b7 100644
--- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
@@ -176,15 +176,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
@Override
public void onRosterUpdate() {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- if (mSearchEditText != null) {
- filter(mSearchEditText.getText().toString());
- }
- }
- });
+ this.refreshUi();
}
@Override
@@ -582,9 +574,15 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
this.mActivatedAccounts.add(account.getJid().toBareJid().toString());
}
}
+ final Intent intent = getIntent();
+ final ActionBar ab = getActionBar();
+ if (intent != null && intent.getBooleanExtra("init",false) && ab != null) {
+ ab.setDisplayShowHomeEnabled(false);
+ ab.setDisplayHomeAsUpEnabled(false);
+ ab.setHomeButtonEnabled(false);
+ }
this.mKnownHosts = xmppConnectionService.getKnownHosts();
- this.mKnownConferenceHosts = xmppConnectionService
- .getKnownConferenceHosts();
+ this.mKnownConferenceHosts = xmppConnectionService.getKnownConferenceHosts();
if (this.mPendingInvite != null) {
mPendingInvite.invite();
this.mPendingInvite = null;
@@ -711,15 +709,14 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
@Override
public void OnUpdateBlocklist(final Status status) {
- runOnUiThread(new Runnable() {
+ refreshUi();
+ }
- @Override
- public void run() {
- if (mSearchEditText != null) {
- filter(mSearchEditText.getText().toString());
- }
- }
- });
+ @Override
+ protected void refreshUiReal() {
+ if (mSearchEditText != null) {
+ filter(mSearchEditText.getText().toString());
+ }
}
public static class MyListFragment extends ListFragment {
diff --git a/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java b/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java
index c33decd8..ec9d59e1 100644
--- a/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java
@@ -436,11 +436,11 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer
}
public void onConversationUpdate() {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- updateView();
- }
- });
+ refreshUi();
+ }
+
+ @Override
+ protected void refreshUiReal() {
+ updateView();
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index 1f1af09c..62f62b9a 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -33,7 +33,9 @@ import android.nfc.NfcEvent;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.util.DisplayMetrics;
@@ -68,6 +70,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService;
@@ -100,6 +103,33 @@ public abstract class XmppActivity extends Activity {
protected int mTheme;
protected boolean mUsingEnterKey = false;
+ private long mLastUiRefresh = 0;
+ private Handler mRefreshUiHandler = new Handler();
+ private Runnable mRefreshUiRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mLastUiRefresh = SystemClock.elapsedRealtime();
+ refreshUiReal();
+ }
+ };
+
+
+ protected void refreshUi() {
+ final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh;
+ if (diff > Config.REFRESH_UI_INTERVAL) {
+ mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
+ runOnUiThread(mRefreshUiRunnable);
+ } else {
+ final long next = Config.REFRESH_UI_INTERVAL - diff;
+ mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
+ mRefreshUiHandler.postDelayed(mRefreshUiRunnable,next);
+ }
+ }
+
+ protected void refreshUiReal() {
+
+ };
+
protected interface OnValueEdited {
public void onValueEdited(String value);
}
@@ -384,7 +414,20 @@ public abstract class XmppActivity extends Activity {
protected void inviteToConversation(Conversation conversation) {
Intent intent = new Intent(getApplicationContext(),
ChooseContactActivity.class);
+ List<String> contacts = new ArrayList<>();
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ for (MucOptions.User user : conversation.getMucOptions().getUsers()) {
+ Jid jid = user.getJid();
+ if (jid != null) {
+ contacts.add(jid.toBareJid().toString());
+ }
+ }
+ } else {
+ contacts.add(conversation.getJid().toBareJid().toString());
+ }
+ intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()]));
intent.putExtra("conversation", conversation.getUuid());
+ intent.putExtra("multiple", true);
startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION);
}
@@ -627,22 +670,31 @@ public abstract class XmppActivity extends Activity {
if (requestCode == REQUEST_INVITE_TO_CONVERSATION
&& resultCode == RESULT_OK) {
try {
- Jid jid = Jid.fromString(data.getStringExtra("contact"));
String conversationUuid = data.getStringExtra("conversation");
Conversation conversation = xmppConnectionService
.findConversationByUuid(conversationUuid);
+ List<Jid> jids = new ArrayList<Jid>();
+ if (data.getBooleanExtra("multiple", false)) {
+ String[] toAdd = data.getStringArrayExtra("contacts");
+ for (String item : toAdd) {
+ jids.add(Jid.fromString(item));
+ }
+ } else {
+ jids.add(Jid.fromString(data.getStringExtra("contact")));
+ }
+
if (conversation.getMode() == Conversation.MODE_MULTI) {
- xmppConnectionService.invite(conversation, jid);
+ for (Jid jid : jids) {
+ xmppConnectionService.invite(conversation, jid);
+ }
} else {
- List<Jid> jids = new ArrayList<Jid>();
jids.add(conversation.getJid().toBareJid());
- jids.add(jid);
xmppConnectionService.createAdhocConference(conversation.getAccount(), jids, adhocCallback);
}
} catch (final InvalidJidException ignored) {
}
- }
+ }
}
private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
index e62aaf96..c4a446e8 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
@@ -1,8 +1,13 @@
package eu.siacs.conversations.ui.adapter;
import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
@@ -11,14 +16,15 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
+import java.lang.ref.WeakReference;
import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
import de.tzur.conversations.Settings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Downloadable;
-import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.ui.ConversationActivity;
@@ -132,8 +138,91 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
mTimestamp.setText(UIHelper.readableTimeDifference(activity,conversation.getLatestMessage().getTimeSent()));
ImageView profilePicture = (ImageView) view.findViewById(R.id.conversation_image);
- profilePicture.setImageBitmap(activity.avatarService().get(conversation, activity.getPixel(56)));
+ loadAvatar(conversation,profilePicture);
return view;
}
-}
+
+ class BitmapWorkerTask extends AsyncTask<Conversation, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private Conversation conversation = null;
+
+ public BitmapWorkerTask(ImageView imageView) {
+ imageViewReference = new WeakReference<>(imageView);
+ }
+
+ @Override
+ protected Bitmap doInBackground(Conversation... params) {
+ return activity.avatarService().get(params[0], activity.getPixel(56));
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+
+ public void loadAvatar(Conversation conversation, ImageView imageView) {
+ if (cancelPotentialWork(conversation, imageView)) {
+ final Bitmap bm = activity.avatarService().get(conversation, activity.getPixel(56), true);
+ if (bm != null) {
+ imageView.setImageBitmap(bm);
+ imageView.setBackgroundColor(0x00000000);
+ } else {
+ imageView.setBackgroundColor(UIHelper.getColorForName(conversation.getName()));
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(conversation);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+ }
+
+ public static boolean cancelPotentialWork(Conversation conversation, ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Conversation oldConversation = bitmapWorkerTask.conversation;
+ if (oldConversation == null || conversation != oldConversation) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+ }
+
+ public BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
index 91fb021c..7b20b55f 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
@@ -1,17 +1,23 @@
package eu.siacs.conversations.ui.adapter;
+import java.lang.ref.WeakReference;
import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
-import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.ui.XmppActivity;
+import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.jid.Jid;
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
import android.preference.PreferenceManager;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -77,8 +83,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
tvJid.setText("");
}
tvName.setText(item.getDisplayName());
- picture.setImageBitmap(activity.avatarService().get(item,
- activity.getPixel(48)));
+ loadAvatar(item,picture);
return view;
}
@@ -90,4 +95,87 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
public void onTagClicked(String tag);
}
+ class BitmapWorkerTask extends AsyncTask<ListItem, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private ListItem item = null;
+
+ public BitmapWorkerTask(ImageView imageView) {
+ imageViewReference = new WeakReference<>(imageView);
+ }
+
+ @Override
+ protected Bitmap doInBackground(ListItem... params) {
+ return activity.avatarService().get(params[0], activity.getPixel(48));
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+
+ public void loadAvatar(ListItem item, ImageView imageView) {
+ if (cancelPotentialWork(item, imageView)) {
+ final Bitmap bm = activity.avatarService().get(item,activity.getPixel(48),true);
+ if (bm != null) {
+ imageView.setImageBitmap(bm);
+ imageView.setBackgroundColor(0x00000000);
+ } else {
+ imageView.setBackgroundColor(UIHelper.getColorForName(item.getDisplayName()));
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(item);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+ }
+
+ public static boolean cancelPotentialWork(ListItem item, ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final ListItem oldItem = bitmapWorkerTask.item;
+ if (oldItem == null || item != oldItem) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+ }
+
+ public BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+
}
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 7fa05050..c3736f0b 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
@@ -36,6 +36,7 @@ import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Message.ImageParams;
import eu.siacs.conversations.ui.ConversationActivity;
+import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
public class MessageAdapter extends ArrayAdapter<Message> {
@@ -305,6 +306,21 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.download_button.setOnLongClickListener(openContextMenu);
}
+ private void displayLocationMessage(ViewHolder viewHolder, final Message message) {
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.GONE);
+ viewHolder.download_button.setVisibility(View.VISIBLE);
+ viewHolder.download_button.setText(R.string.show_location);
+ viewHolder.download_button.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ showLocation(message);
+ }
+ });
+ viewHolder.download_button.setOnLongClickListener(openContextMenu);
+ }
+
private void displayImageMessage(ViewHolder viewHolder,
final Message message) {
if (viewHolder.download_button != null) {
@@ -416,9 +432,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
.avatarService().get(conversation.getContact(),
activity.getPixel(32)));
viewHolder.contact_picture.setAlpha(0.5f);
- viewHolder.status_message.setText(
- activity.getString(R.string.contact_has_read_up_to_this_point, conversation.getName()));
-
+ viewHolder.status_message.setText(message.getBody());
}
return view;
} else if (type == NULL) {
@@ -517,7 +531,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
displayDecryptionFailed(viewHolder);
} else {
- displayTextMessage(viewHolder, message);
+ if (GeoHelper.isGeoUri(message.getBody())) {
+ displayLocationMessage(viewHolder,message);
+ } else {
+ displayTextMessage(viewHolder, message);
+ }
}
displayStatus(viewHolder, message);
@@ -552,6 +570,16 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
+ public void showLocation(Message message) {
+ for(Intent intent : GeoHelper.createGeoIntentsFromMessage(message)) {
+ if (intent.resolveActivity(getContext().getPackageManager()) != null) {
+ getContext().startActivity(intent);
+ return;
+ }
+ }
+ Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show();
+ }
+
public interface OnContactPictureClicked {
public void onContactPictureClicked(Message message);
}
diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
index fc21acbc..eb7e2c3c 100644
--- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
@@ -4,7 +4,9 @@ import java.security.SecureRandom;
import java.text.Normalizer;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Iterator;
import java.util.LinkedHashSet;
+import java.util.List;
import eu.siacs.conversations.Config;
@@ -97,10 +99,26 @@ public final class CryptoHelper {
return builder.toString();
}
- public static String[] getSupportedCipherSuites(final String[] platformSupportedCipherSuites) {
- //final Collection<String> cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS));
- //cipherSuites.retainAll(Arrays.asList(platformSupportedCipherSuites));
- //return cipherSuites.toArray(new String[cipherSuites.size()]);
- return platformSupportedCipherSuites;
+ public static String[] getOrderedCipherSuites(final String[] platformSupportedCipherSuites) {
+ final Collection<String> cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS));
+ final List<String> platformCiphers = Arrays.asList(platformSupportedCipherSuites);
+ cipherSuites.retainAll(platformCiphers);
+ cipherSuites.addAll(platformCiphers);
+ filterWeakCipherSuites(cipherSuites);
+ return cipherSuites.toArray(new String[cipherSuites.size()]);
+ }
+
+ private static void filterWeakCipherSuites(final Collection<String> cipherSuites) {
+ final Iterator<String> it = cipherSuites.iterator();
+ while (it.hasNext()) {
+ String cipherName = it.next();
+ // remove all ciphers with no or very weak encryption or no authentication
+ for (String weakCipherPattern : Config.WEAK_CIPHER_PATTERNS) {
+ if (cipherName.contains(weakCipherPattern)) {
+ it.remove();
+ break;
+ }
+ }
+ }
}
}
diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
index a09b4d0f..bcb2ca44 100644
--- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
@@ -140,18 +140,13 @@ public class DNSHelper {
}
ArrayList<Bundle> values = new ArrayList<>();
for (SRV srv : result) {
- boolean added = false;
if (ips6.containsKey(srv.getName())) {
values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6));
- added = true;
}
if (ips4.containsKey(srv.getName())) {
values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4));
- added = true;
- }
- if (!added) {
- values.add(createNamePortBundle(srv.getName(),srv.getPort(),null));
}
+ values.add(createNamePortBundle(srv.getName(),srv.getPort(),null));
}
bundle.putParcelableArrayList("values", values);
} catch (SocketTimeoutException e) {
diff --git a/src/main/java/eu/siacs/conversations/utils/GeoHelper.java b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java
new file mode 100644
index 00000000..f7dda936
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java
@@ -0,0 +1,71 @@
+package eu.siacs.conversations.utils;
+
+import android.content.Intent;
+import android.net.Uri;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+
+public class GeoHelper {
+ private static Pattern GEO_URI = Pattern.compile("geo:([\\-0-9.]+),([\\-0-9.]+)(?:,([\\-0-9.]+))?(?:\\?(.*))?", Pattern.CASE_INSENSITIVE);
+
+ public static boolean isGeoUri(String body) {
+ return body != null && GEO_URI.matcher(body).matches();
+ }
+
+ public static ArrayList<Intent> createGeoIntentsFromMessage(Message message) {
+ final ArrayList<Intent> intents = new ArrayList();
+ Matcher matcher = GEO_URI.matcher(message.getBody());
+ if (!matcher.matches()) {
+ return intents;
+ }
+ double latitude;
+ double longitude;
+ try {
+ latitude = Double.parseDouble(matcher.group(1));
+ if (latitude > 90.0 || latitude < -90.0) {
+ return intents;
+ }
+ longitude = Double.parseDouble(matcher.group(2));
+ if (longitude > 180.0 || longitude < -180.0) {
+ return intents;
+ }
+ } catch (NumberFormatException nfe) {
+ return intents;
+ }
+ final Conversation conversation = message.getConversation();
+ String label;
+ if (conversation.getMode() == Conversation.MODE_SINGLE && message.getStatus() == Message.STATUS_RECEIVED) {
+ try {
+ label = "(" + URLEncoder.encode(message.getConversation().getName(), "UTF-8") + ")";
+ } catch (UnsupportedEncodingException e) {
+ label = "";
+ }
+ } else {
+ label = "";
+ }
+
+ Intent locationPluginIntent = new Intent("eu.siacs.conversations.location.show");
+ locationPluginIntent.putExtra("latitude",latitude);
+ locationPluginIntent.putExtra("longitude",longitude);
+ if (conversation.getMode() == Conversation.MODE_SINGLE && message.getStatus() == Message.STATUS_RECEIVED) {
+ locationPluginIntent.putExtra("name",conversation.getName());
+ }
+ intents.add(locationPluginIntent);
+
+ Intent geoIntent = new Intent(Intent.ACTION_VIEW);
+ geoIntent.setData(Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude) + "?q=" + String.valueOf(latitude) + "," + String.valueOf(longitude) + label));
+ intents.add(geoIntent);
+
+ Intent httpIntent = new Intent(Intent.ACTION_VIEW);
+ httpIntent.setData(Uri.parse("https://maps.google.com/maps?q=loc:"+String.valueOf(latitude) + "," + String.valueOf(longitude) +label));
+ intents.add(httpIntent);
+ return intents;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java
index 9a5cbaaf..99e8ebb8 100644
--- a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java
@@ -8,6 +8,7 @@ import android.content.Context;
import android.content.CursorLoader;
import android.content.Loader;
import android.content.Loader.OnLoadCompleteListener;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
@@ -91,4 +92,17 @@ public class PhoneHelper {
}
}
}
+
+ public static String getVersionName(Context context) {
+ final String packageName = context == null ? null : context.getPackageName();
+ if (packageName != null) {
+ try {
+ return context.getPackageManager().getPackageInfo(packageName, 0).versionName;
+ } catch (final PackageManager.NameNotFoundException e) {
+ return "unknown";
+ }
+ } else {
+ return "unknown";
+ }
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
index 333f6e27..0ddf606f 100644
--- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
@@ -295,8 +295,14 @@ public class UIHelper {
if (message.getBody().startsWith(Message.ME_COMMAND)) {
return new Pair<>(message.getBody().replaceAll("^" + Message.ME_COMMAND,
UIHelper.getMessageDisplayName(message) + " "), false);
- } else {
- return new Pair<>(message.getBody(), false);
+ } else if (GeoHelper.isGeoUri(message.getBody())) {
+ if (message.getStatus() == Message.STATUS_RECEIVED) {
+ return new Pair<>(context.getString(R.string.received_location),true);
+ } else {
+ return new Pair<>(context.getString(R.string.location), true);
+ }
+ } else{
+ return new Pair<>(message.getBody().trim(), false);
}
}
}
diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java
index 0f1b18c3..92c0241e 100644
--- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java
+++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java
@@ -32,7 +32,7 @@ public class XmppUri {
protected void parse(Uri uri) {
String scheme = uri.getScheme();
- if ("xmpp".equals(scheme)) {
+ if ("xmpp".equalsIgnoreCase(scheme)) {
// sample: xmpp:jid@foo.com
muc = "join".equalsIgnoreCase(uri.getQuery());
if (uri.getAuthority() != null) {
@@ -41,7 +41,7 @@ public class XmppUri {
jid = uri.getSchemeSpecificPart().split("\\?")[0];
}
fingerprint = parseFingerprint(uri.getQuery());
- } else if ("imto".equals(scheme)) {
+ } else if ("imto".equalsIgnoreCase(scheme)) {
// sample: imto://xmpp/jid@foo.com
try {
jid = URLDecoder.decode(uri.getEncodedPath(), "UTF-8").split("/")[1];
@@ -73,7 +73,7 @@ public class XmppUri {
public Jid getJid() {
try {
- return this.jid == null ? null :Jid.fromString(this.jid);
+ return this.jid == null ? null :Jid.fromString(this.jid.toLowerCase());
} catch (InvalidJidException e) {
return null;
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index 19e271b2..48dc2150 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -242,6 +242,13 @@ public class XmppConnection implements Runnable {
@Override
public void run() {
+ try {
+ if (socket != null) {
+ socket.close();
+ }
+ } catch (final IOException ignored) {
+
+ }
connect();
}
@@ -515,8 +522,9 @@ public class XmppConnection implements Runnable {
sslSocket.setEnabledProtocols(supportProtocols);
- final String[] cipherSuites = CryptoHelper.getSupportedCipherSuites(
+ final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
sslSocket.getSupportedCipherSuites());
+ //Log.d(Config.LOGTAG, "Using ciphers: " + Arrays.toString(cipherSuites));
if (cipherSuites.length > 0) {
sslSocket.setEnabledCipherSuites(cipherSuites);
}
@@ -658,6 +666,12 @@ public class XmppConnection implements Runnable {
}
private void sendBindRequest() {
+ while(!mXmppConnectionService.areMessagesInitialized()) {
+ try {
+ Thread.sleep(500);
+ } catch (final InterruptedException ignored) {
+ }
+ }
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind")
.addChild("resource").setContent(account.getResource());
@@ -673,28 +687,11 @@ public class XmppConnection implements Runnable {
} catch (final InvalidJidException e) {
// TODO: Handle the case where an external JID is technically invalid?
}
- if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) {
- smVersion = 3;
- final EnablePacket enable = new EnablePacket(smVersion);
- tagWriter.writeStanzaAsync(enable);
- stanzasSent = 0;
- messageReceipts.clear();
- } else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) {
- smVersion = 2;
- final EnablePacket enable = new EnablePacket(smVersion);
- tagWriter.writeStanzaAsync(enable);
- stanzasSent = 0;
- messageReceipts.clear();
- }
- features.carbonsEnabled = false;
- features.blockListRequested = false;
- disco.clear();
- sendServiceDiscoveryInfo(account.getServer());
- sendServiceDiscoveryItems(account.getServer());
- if (bindListener != null) {
- bindListener.onBind(account);
+ if (streamFeatures.hasChild("session")) {
+ sendStartSession();
+ } else {
+ sendPostBindInitialization();
}
- sendInitialPing();
} else {
disconnect(true);
}
@@ -703,12 +700,45 @@ public class XmppConnection implements Runnable {
}
}
});
- if (this.streamFeatures.hasChild("session")) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending deprecated session");
- final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET);
- startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session");
- this.sendUnmodifiedIqPacket(startSession, null);
+ }
+
+ private void sendStartSession() {
+ final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET);
+ startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session");
+ this.sendUnmodifiedIqPacket(startSession, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ sendPostBindInitialization();
+ } else {
+ disconnect(true);
+ }
+ }
+ });
+ }
+
+ private void sendPostBindInitialization() {
+ smVersion = 0;
+ if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) {
+ smVersion = 3;
+ } else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) {
+ smVersion = 2;
+ }
+ if (smVersion != 0) {
+ final EnablePacket enable = new EnablePacket(smVersion);
+ tagWriter.writeStanzaAsync(enable);
+ stanzasSent = 0;
+ messageReceipts.clear();
}
+ features.carbonsEnabled = false;
+ features.blockListRequested = false;
+ disco.clear();
+ sendServiceDiscoveryInfo(account.getServer());
+ sendServiceDiscoveryItems(account.getServer());
+ if (bindListener != null) {
+ bindListener.onBind(account);
+ }
+ sendInitialPing();
}
private void sendServiceDiscoveryInfo(final Jid server) {
@@ -1027,6 +1057,11 @@ public class XmppConnection implements Runnable {
this.sendPacket(new InactivePacket());
}
+ public void resetAttemptCount() {
+ this.attempt = 0;
+ this.lastConnect = 0;
+ }
+
public class Features {
XmppConnection connection;
private boolean carbonsEnabled = false;
diff --git a/src/main/java/eu/siacs/conversations/xmpp/chatstate/ChatState.java b/src/main/java/eu/siacs/conversations/xmpp/chatstate/ChatState.java
new file mode 100644
index 00000000..f85efbdb
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/xmpp/chatstate/ChatState.java
@@ -0,0 +1,32 @@
+package eu.siacs.conversations.xmpp.chatstate;
+
+import eu.siacs.conversations.xml.Element;
+
+public enum ChatState {
+
+ ACTIVE, INACTIVE, GONE, COMPOSING, PAUSED, mIncomingChatState;
+
+ public static ChatState parse(Element element) {
+ final String NAMESPACE = "http://jabber.org/protocol/chatstates";
+ if (element.hasChild("active",NAMESPACE)) {
+ return ACTIVE;
+ } else if (element.hasChild("inactive",NAMESPACE)) {
+ return INACTIVE;
+ } else if (element.hasChild("composing",NAMESPACE)) {
+ return COMPOSING;
+ } else if (element.hasChild("gone",NAMESPACE)) {
+ return GONE;
+ } else if (element.hasChild("paused",NAMESPACE)) {
+ return PAUSED;
+ } else {
+ return null;
+ }
+ }
+
+ public static Element toElement(ChatState state) {
+ final String NAMESPACE = "http://jabber.org/protocol/chatstates";
+ final Element element = new Element(state.toString().toLowerCase());
+ element.setAttribute("xmlns",NAMESPACE);
+ return element;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jid/InvalidJidException.java b/src/main/java/eu/siacs/conversations/xmpp/jid/InvalidJidException.java
index f1855263..164e8849 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jid/InvalidJidException.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jid/InvalidJidException.java
@@ -8,6 +8,7 @@ public class InvalidJidException extends Exception {
public final static String INVALID_PART_LENGTH = "JID part must be between 0 and 1023 characters";
public final static String INVALID_CHARACTER = "JID contains an invalid character";
public final static String STRINGPREP_FAIL = "The STRINGPREP operation has failed for the given JID";
+ public final static String IS_NULL = "JID can not be NULL";
/**
* Constructs a new {@code Exception} that includes the current stack trace.
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java
index a35ea37c..295e067a 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java
@@ -1,9 +1,12 @@
package eu.siacs.conversations.xmpp.jid;
+import android.util.LruCache;
+
import net.java.otr4j.session.SessionID;
import java.net.IDN;
+import eu.siacs.conversations.Config;
import gnu.inet.encoding.Stringprep;
import gnu.inet.encoding.StringprepException;
@@ -12,6 +15,8 @@ import gnu.inet.encoding.StringprepException;
*/
public final class Jid {
+ private static LruCache<String,Jid> cache = new LruCache<>(1024);
+
private final String localpart;
private final String domainpart;
private final String resourcepart;
@@ -41,7 +46,11 @@ public final class Jid {
}
public static Jid fromString(final String jid) throws InvalidJidException {
- return new Jid(jid);
+ return Jid.fromString(jid, false);
+ }
+
+ public static Jid fromString(final String jid, final boolean safe) throws InvalidJidException {
+ return new Jid(jid, safe);
}
public static Jid fromParts(final String localpart,
@@ -56,10 +65,21 @@ public final class Jid {
if (resourcepart != null && !resourcepart.isEmpty()) {
out = out + "/" + resourcepart;
}
- return new Jid(out);
+ return new Jid(out, false);
}
- private Jid(final String jid) throws InvalidJidException {
+ private Jid(final String jid, final boolean safe) throws InvalidJidException {
+ if (jid == null) throw new InvalidJidException(InvalidJidException.IS_NULL);
+
+ Jid fromCache = Jid.cache.get(jid);
+ if (fromCache != null) {
+ displayjid = fromCache.displayjid;
+ localpart = fromCache.localpart;
+ domainpart = fromCache.domainpart;
+ resourcepart = fromCache.resourcepart;
+ return;
+ }
+
// Hackish Android way to count the number of chars in a string... should work everywhere.
final int atCount = jid.length() - jid.replace("@", "").length();
final int slashCount = jid.length() - jid.replace("/", "").length();
@@ -88,7 +108,7 @@ public final class Jid {
} else {
final String lp = jid.substring(0, atLoc);
try {
- localpart = Stringprep.nodeprep(lp);
+ localpart = Config.DISABLE_STRING_PREP || safe ? lp : Stringprep.nodeprep(lp);
} catch (final StringprepException e) {
throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
}
@@ -103,7 +123,7 @@ public final class Jid {
if (slashCount > 0) {
final String rp = jid.substring(slashLoc + 1, jid.length());
try {
- resourcepart = Stringprep.resourceprep(rp);
+ resourcepart = Config.DISABLE_STRING_PREP || safe ? rp : Stringprep.resourceprep(rp);
} catch (final StringprepException e) {
throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
}
@@ -139,6 +159,8 @@ public final class Jid {
throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
}
+ Jid.cache.put(jid, this);
+
this.displayjid = finaljid;
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java
index 4e7b532b..93aaa68c 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java
@@ -26,7 +26,7 @@ public class MessagePacket extends AbstractStanza {
this.children.remove(findChild("body"));
Element body = new Element("body");
body.setContent(text);
- this.children.add(body);
+ this.children.add(0, body);
}
public void setType(int type) {
@@ -39,6 +39,9 @@ public class MessagePacket extends AbstractStanza {
break;
case TYPE_NORMAL:
break;
+ case TYPE_ERROR:
+ this.setAttribute("type","error");
+ break;
default:
this.setAttribute("type", "chat");
break;
diff --git a/src/main/res/drawable-hdpi/ic_autorenew_white_24dp.png b/src/main/res/drawable-hdpi/ic_autorenew_white_24dp.png
new file mode 100644
index 00000000..b1ee2974
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_autorenew_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_block_white_24dp.png b/src/main/res/drawable-hdpi/ic_block_white_24dp.png
new file mode 100644
index 00000000..13f716d7
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_block_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_room_white_24dp.png b/src/main/res/drawable-hdpi/ic_room_white_24dp.png
new file mode 100644
index 00000000..c2ccd510
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_room_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_autorenew_white_24dp.png b/src/main/res/drawable-mdpi/ic_autorenew_white_24dp.png
new file mode 100644
index 00000000..86b71938
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_autorenew_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_block_white_24dp.png b/src/main/res/drawable-mdpi/ic_block_white_24dp.png
new file mode 100644
index 00000000..4e5093f7
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_block_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_room_white_24dp.png b/src/main/res/drawable-mdpi/ic_room_white_24dp.png
new file mode 100644
index 00000000..a4fcd770
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_room_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_autorenew_white_24dp.png b/src/main/res/drawable-xhdpi/ic_autorenew_white_24dp.png
new file mode 100644
index 00000000..e8462223
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_autorenew_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_block_white_24dp.png b/src/main/res/drawable-xhdpi/ic_block_white_24dp.png
new file mode 100644
index 00000000..b86e6c47
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_block_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_room_white_24dp.png b/src/main/res/drawable-xhdpi/ic_room_white_24dp.png
new file mode 100644
index 00000000..e1e60a5c
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_room_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_autorenew_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_autorenew_white_24dp.png
new file mode 100644
index 00000000..b871381a
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_autorenew_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_block_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_block_white_24dp.png
new file mode 100644
index 00000000..d2be2d61
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_block_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_room_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_room_white_24dp.png
new file mode 100644
index 00000000..00b15508
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_room_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_autorenew_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_autorenew_white_24dp.png
new file mode 100644
index 00000000..569bafe2
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_autorenew_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_block_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_block_white_24dp.png
new file mode 100644
index 00000000..b538af61
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_block_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_room_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_room_white_24dp.png
new file mode 100644
index 00000000..a5dde3b9
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_room_white_24dp.png
Binary files differ
diff --git a/src/main/res/menu/attachment_choices.xml b/src/main/res/menu/attachment_choices.xml
index 5139272c..6e8fc51d 100644
--- a/src/main/res/menu/attachment_choices.xml
+++ b/src/main/res/menu/attachment_choices.xml
@@ -2,14 +2,21 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:id="@+id/attach_choose_picture"
- android:title="@string/attach_choose_picture"/>
+ android:id="@+id/attach_location"
+ android:title="@string/send_location"/>
+
+ <item
+ android:id="@+id/attach_record_voice"
+ android:title="@string/attach_record_voice"/>
+
<item
android:id="@+id/attach_take_picture"
android:title="@string/attach_take_picture"/>
- <item
- android:id="@+id/attach_record_voice"
- android:title="@string/attach_record_voice"/>
+
+ <item
+ android:id="@+id/attach_choose_picture"
+ android:title="@string/attach_choose_picture"/>
+
<item
android:id="@+id/attach_choose_file"
android:title="@string/choose_file"/>
diff --git a/src/main/res/menu/select_multiple.xml b/src/main/res/menu/select_multiple.xml
new file mode 100644
index 00000000..4240849f
--- /dev/null
+++ b/src/main/res/menu/select_multiple.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:id="@+id/selection_submit"
+ android:title="@string/invite_contact"
+ android:showAsAction="always" />
+
+</menu>
diff --git a/src/main/res/values-ar-rEG/strings.xml b/src/main/res/values-ar-rEG/strings.xml
index f81a2ee7..3acf8e54 100644
--- a/src/main/res/values-ar-rEG/strings.xml
+++ b/src/main/res/values-ar-rEG/strings.xml
@@ -83,12 +83,16 @@
<string name="download_image">تنزيل الصورة</string>
<string name="send_unencrypted">إرسال بدون تشفير</string>
<string name="decryption_failed">فشل فك التشفير. ربما لم يكن لديك المفتاح الخاص الصحيح.</string>
+ <string name="openkeychain_required">OpenKeychain</string>
+ <string name="openkeychain_required_long">Conversations :: يستخدم تطبيق آخر يسمى <b> OpenKeychain </b> لتشفير وفك تشفير الرسائل وإدارة المفاتيح العامة الخاصة بك \n\nOpenKeychain تحت الرخصة GPLv3 و لتحميل التطبيق من جوجل بلاي \n\n <small>(وأعد تشغيل التطبيق مرة أخرى)</small></string>
<string name="restart">اعادة تشغيل</string>
<string name="install">تثبيت</string>
<string name="offering">عرض ..</string>
<string name="waiting">انتظار ..</string>
<string name="no_pgp_key">OpenPGP-لايوجد مفتاح</string>
+ <string name="contact_has_no_pgp_key">Conversations::لا يستطيع تشفير الرساله\n\n<small>من فضلك أخبر صديقك بتنصيب تطبيق OpenPGP.</small></string>
<string name="no_pgp_keys">OpenPGP-لايوجد مفاتيح</string>
+ <string name="contacts_have_no_pgp_keys">Conversations::لا يستطيع تشفير الرساله\n\n<small>من فضلك أخبر صديقك بتنصيب تطبيق OpenPGP.</small></string>
<string name="encrypted_message_received"><i>تلقيت رساله مشفّرة .. لمسه بأناملك لعرضها.</i></string>
<string name="pref_general">عام</string>
<string name="pref_xmpp_resource">الريسورس</string>
@@ -105,6 +109,7 @@
<string name="pref_advanced_options">اعدادات متقدمّة</string>
<string name="pref_never_send_crash">لا ترسل تقارير أخطاء</string>
<string name="pref_never_send_crash_summary">الغاء ارسال تقارير الأخطاء يقلل من فرص حل المشكلة سريعا فكن متعاون</string>
+ <string name="pref_confirm_messages">تأكيد الرسالة</string>
<string name="accept">قبول</string>
<string name="error">حدث خطأ ما</string>
<string name="pref_grant_presence_updates">منح تحديثات الظهور</string>
@@ -112,6 +117,115 @@
<string name="your_account">حسابك</string>
<string name="keys">مفاتيح</string>
<string name="send_presence_updates">ارسال تحديثات الظهور</string>
+ <string name="attach_choose_picture">اختيار صورة</string>
+ <string name="attach_take_picture">التقاط صورة</string>
+ <string name="error_file_not_found">الملف غير موجود</string>
+ <string name="account_status_unknown">غير معروف</string>
+ <string name="account_status_online">متصل</string>
+ <string name="account_status_offline">غير متصل</string>
+ <string name="account_status_unauthorized">غير مصرح له</string>
+ <string name="account_status_not_found">لا يمكن الاتصال بالسرفر</string>
+ <string name="account_status_no_internet">تحقق من اتصالك بالانترنت</string>
+ <string name="account_status_regis_fail">فشل تسجيل حساب بالسيرفر</string>
+ <string name="account_status_regis_conflict">اسم المستخدم مستخدم من قبل</string>
+ <string name="account_status_regis_success">تم تسجيل حسابك بنجاح</string>
+ <string name="account_status_regis_not_sup">تسجيل الحسابات غير متاح على هذا السرفر</string>
+ <string name="encryption_choice_none">رساله عادية</string>
+ <string name="encryption_choice_otr">OTRرسالة مشفرة عبر</string>
+ <string name="encryption_choice_pgp">OpenPGPرسالة مشفرة عبر</string>
+ <string name="mgmt_account_edit">تعديل الحساب</string>
+ <string name="mgmt_account_delete">حذف الحساب</string>
+ <string name="mgmt_account_publish_avatar">نشر الصورة الرمزية</string>
+ <string name="mgmt_account_enable">تفعيل الحساب</string>
+ <string name="mgmt_account_are_you_sure">هل أنت متأكد ؟</string>
+ <string name="mgmt_account_delete_confirm_text">اذا مسحت حسابك ستفقد جميع الرسائل المحفوظه !!</string>
+ <string name="attach_record_voice">تسجيل صوت</string>
+ <string name="account_settings_jabber_id">حساب جابر</string>
+ <string name="account_settings_password">كلمة السر</string>
+ <string name="account_settings_example_jabber_id">username@example.com</string>
+ <string name="account_settings_confirm_password">تأكيد كلمة السر</string>
+ <string name="password">كلمة السر</string>
+ <string name="confirm_password">تأكيد كلمة السر</string>
+ <string name="passwords_do_not_match">الكلمتان غير متطابقتان</string>
+ <string name="invalid_jid">حساب جابر غير صالح</string>
+ <string name="contact_status_online">متصل</string>
+ <string name="contact_status_free_to_chat">متاح للدردشة</string>
+ <string name="contact_status_away">بعيد</string>
+ <string name="contact_status_extended_away">بعيد جدا</string>
+ <string name="contact_status_do_not_disturb">مشغول</string>
+ <string name="contact_status_offline">غير متصل</string>
+ <string name="muc_details_conference">الغرف</string>
+ <string name="muc_details_other_members">المشتركين</string>
+ <string name="server_info_show_more">معلومات السرفر</string>
+ <string name="server_info_available">متاح</string>
+ <string name="server_info_unavailable">غير متاح</string>
+ <string name="last_seen_now">آخر ظهور الآن</string>
+ <string name="last_seen_min">آخر ظهور منذ 1 دقيقة</string>
+ <string name="last_seen_mins">آخر ظهور منذ %d دقيقة</string>
+ <string name="last_seen_hour">آخر ظهور منذ 1 ساعة</string>
+ <string name="last_seen_hours">آخر ظهور منذ %d ساعة</string>
+ <string name="last_seen_day">آخر ظهور منذ 1 يوم</string>
+ <string name="last_seen_days">آخر ظهور منذ %d يوم</string>
+ <string name="never_seen">لم يظهر متصلا حتى الآن</string>
+ <string name="verify">تأكيد</string>
+ <string name="conferences">الغرف</string>
+ <string name="search">بحث</string>
+ <string name="create_contact">اضافة جهة اتصال</string>
+ <string name="join_conference">دخول الغرف</string>
+ <string name="delete_contact">حذف جهة اتصال</string>
+ <string name="view_contact_details">عرض بيانات جهة الاتصال</string>
+ <string name="block_contact">حجب جهة اتصال</string>
+ <string name="unblock_contact">الغاء حجب جهة اتصال</string>
+ <string name="create">أضف</string>
+ <string name="contact_already_exists">جهة الاتصال موجودة لديك مسبقا</string>
+ <string name="join">دخول</string>
+ <string name="conference_address">اسم الغرفة كامل</string>
+ <string name="conference_address_example">room@conference.example.com</string>
+ <string name="save_as_bookmark">حفظ بالمفضلة</string>
+ <string name="delete_bookmark">حذف من المفضلة</string>
+ <string name="bookmark_already_exists">موجوده بالمفضلة سابقا</string>
+ <string name="you">انت</string>
+ <string name="action_edit_subject">تعديل موضوع الغرفة</string>
+ <string name="conference_not_found">الغرفة غير متاحه .. تأكد من عنوان الغرفة</string>
+ <string name="leave">غادر</string>
+ <string name="contact_added_you">جهة اتصال أضافتك </string>
+ <string name="publish">نشر</string>
+ <string name="publishing">نشر ...</string>
+ <string name="private_message_to">الى %s</string>
+ <string name="send_private_message_to">ارسال رسالة خاصة الى %s</string>
+ <string name="connect">اتصال</string>
+ <string name="account_already_exists">الحساب موجود من قبل</string>
+ <string name="next">التالي</string>
+ <string name="additional_information">معلومات اضافية</string>
+ <string name="skip">تجاهل</string>
+ <string name="disable_notifications">ايقاف التنبيهات</string>
+ <string name="disable_notifications_for_this_conversation">ايقاف التنبيهات لتلك المحادثة</string>
+ <string name="notifications_disabled">التنبيهات غير فعاله</string>
+ <string name="enable">تفعيل</string>
+ <string name="conference_requires_password">الغرفة تطلب كلمة مرور</string>
+ <string name="enter_password">أدخل كلمة المرور</string>
+ <string name="request_now">اطلب الآن</string>
+ <string name="ignore">تجاهل</string>
+ <string name="pref_expert_options_other">أخرى</string>
+ <string name="pref_conference_name">اسم الغرفة</string>
+ <string name="conference_banned">أنت مفصول في هذه الغرفة</string>
+ <string name="conference_members_only">الغرفة للأعضاء فقط</string>
+ <string name="conference_kicked">تم طردك من الغرفة</string>
+ <string name="not_connected_try_again">انقطع الإتصال .. حاول مرة أخرى</string>
+ <string name="check_image_filesize">فحص حجم الصورة</string>
+ <string name="message_options">خيارات الرساله</string>
+ <string name="copy_text">نسخ النص</string>
+ <string name="message_text">نص الرسالة</string>
+ <string name="account_details">تفاصيل الحساب</string>
+ <string name="scan">سكان</string>
+ <string name="confirm">تأكيد</string>
+ <string name="in_progress">جاري الاتصال</string>
+ <string name="failed">فشل الاتصال</string>
+ <string name="try_again">حاول مرة أخرى</string>
+ <string name="finish">انهاء</string>
+ <string name="verified">تأكيد!</string>
+ <string name="conversations_foreground_service">Conversations</string>
+ <string name="pref_keep_foreground_service">احتفظ بالتطبيق يعمل في المقدمة</string>
<string name="pref_keep_foreground_service_summary">منع نظام التشغيل من انهاء اتصالك</string>
<string name="choose_file">اختيار ملف</string>
<string name="receiving_x_file">اكتمل الإستلام %1$s (%2$d%% بنسبة)</string>
@@ -203,4 +317,6 @@
<string name="avatar_has_been_published">تم نشر الصورة!</string>
<string name="sending_x_file">ارسال %s</string>
<string name="offering_x_file">عرض %s</string>
+ <string name="hide_offline">اخفاء غير المتصلين</string>
+ <string name="disable_account">ايقاف الحساب</string>
</resources>
diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml
new file mode 100644
index 00000000..2f89f3ae
--- /dev/null
+++ b/src/main/res/values-bg/strings.xml
@@ -0,0 +1,434 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <string name="action_settings">Настройки</string>
+ <string name="action_add">Нов разговор</string>
+ <string name="action_accounts">Управление на профилите</string>
+ <string name="action_end_conversation">Край на този разговор</string>
+ <string name="action_contact_details">Подробности за контакта</string>
+ <string name="action_muc_details">Подробности за беседата</string>
+ <string name="action_secure">Защитен разговор</string>
+ <string name="action_add_account">Добавяне на профил</string>
+ <string name="action_edit_contact">Редактиране на името</string>
+ <string name="action_add_phone_book">Добавяне към списъка с телефонни номера</string>
+ <string name="action_delete_contact">Изтриване от списъка</string>
+ <string name="action_block_contact">Блокиране на контакта</string>
+ <string name="action_unblock_contact">Деблокиране на контакта</string>
+ <string name="action_block_domain">Блокиране на домейна</string>
+ <string name="action_unblock_domain">Деблокиране на домейна</string>
+ <string name="title_activity_manage_accounts">Управление на профилите</string>
+ <string name="title_activity_settings">Настройки</string>
+ <string name="title_activity_conference_details">Подробности за беседата</string>
+ <string name="title_activity_contact_details">Подробности за контакта</string>
+ <string name="title_activity_sharewith">Споделяне в разговора</string>
+ <string name="title_activity_start_conversation">Започване на разговор</string>
+ <string name="title_activity_choose_contact">Изберете контакт</string>
+ <string name="title_activity_block_list">Списък с блокирани</string>
+ <string name="just_now">току-що</string>
+ <string name="minute_ago">преди 1 минута</string>
+ <string name="minutes_ago">преди %d минути</string>
+ <string name="unread_conversations">непрочетени разговори</string>
+ <string name="sending">изпращане...</string>
+ <string name="encrypted_message">Дешифроване на съобщението. Моля, изчакайте...</string>
+ <string name="nick_in_use">Псевдонимът вече се използва</string>
+ <string name="admin">Администратор</string>
+ <string name="owner">Собственик</string>
+ <string name="moderator">Модератор</string>
+ <string name="participant">Участник</string>
+ <string name="visitor">Посетител</string>
+ <string name="remove_contact_text">Искате ли да премахнете %s от списъка си? Разговорът с него няма да бъде премахнат.</string>
+ <string name="block_contact_text">Искате ли да блокирате %s, така че да не може да Ви праща съобщения?</string>
+ <string name="unblock_contact_text">Искате ли да деблокирате %s, така че отново да може да Ви праща съобщения?</string>
+ <string name="block_domain_text">Блокиране на всички контакти от %s?</string>
+ <string name="unblock_domain_text">Деблокиране на всички контакти от %s?</string>
+ <string name="contact_blocked">Контактът е блокиран</string>
+ <string name="remove_bookmark_text">Искате ли да премахнете отметката за %s? Разговорът, свързан с тази отметка, няма да бъде премахнат.</string>
+ <string name="register_account">Регистриране на нов профил на сървъра</string>
+ <string name="change_password_on_server">Промяна на паролата в сървъра</string>
+ <string name="share_with">Споделяне с...</string>
+ <string name="start_conversation">Започване на разговор</string>
+ <string name="invite_contact">Канене на контакт</string>
+ <string name="contacts">Контакти</string>
+ <string name="cancel">Отказ</string>
+ <string name="set">Задаване</string>
+ <string name="add">Добавяне</string>
+ <string name="edit">Редактиране</string>
+ <string name="delete">Изтриване</string>
+ <string name="block">Блокиране</string>
+ <string name="unblock">Деблокиране</string>
+ <string name="save">Запазване</string>
+ <string name="ok">Добре</string>
+ <string name="crash_report_title">Conversations се срина</string>
+ <string name="crash_report_message">Изпращайки ни проследявания на стека, Вие помагате за непрекъснатото развитие на Conversations\n<b>Внимание:</b> Това ще използва Вашия XMPP профил, за да изпраща проследяването на стека до разработчика.</string>
+ <string name="send_now">Изпращане сега</string>
+ <string name="send_never">Не ме питайте повече</string>
+ <string name="problem_connecting_to_account">Неуспешно свързване с профила</string>
+ <string name="problem_connecting_to_accounts">Неуспешно свързване с няколко профила</string>
+ <string name="touch_to_fix">Докоснете тук за управление на Вашите профили</string>
+ <string name="attach_file">Прикачане на файл</string>
+ <string name="not_in_roster">Този контакт не е в списъка Ви. Искате ли да го добавите?</string>
+ <string name="add_contact">Добавяне на контакт</string>
+ <string name="send_failed">доставянето се провали</string>
+ <string name="send_rejected">отказано</string>
+ <string name="preparing_image">Подготовка на изображението за изпращане</string>
+ <string name="action_clear_history">Изчистване на историята</string>
+ <string name="clear_conversation_history">Изчистване на историята на разговорите</string>
+ <string name="clear_histor_msg">Искате ли да изтриете всички съобщения от този разговор?\n\n<b>Внимание:</b> Това няма да изтрие съобщенията, съхранявани на други устройства или на сървърите.</string>
+ <string name="delete_messages">Изтриване на съобщенията</string>
+ <string name="also_end_conversation">Този разговор да приключи след това</string>
+ <string name="choose_presence">Изберете присъствие за контакта</string>
+ <string name="send_plain_text_message">Изпращане на обикновено текстово съобщение</string>
+ <string name="send_otr_message">Изпращане на съобщение, шифровано чрез OTP</string>
+ <string name="send_pgp_message">Изпращане на съобщение, шифровано чрез OpenPGP</string>
+ <string name="your_nick_has_been_changed">Псевдонимът Ви беше променен</string>
+ <string name="download_image">Изтегляне на изображението</string>
+ <string name="send_unencrypted">Изпращане нешифровано</string>
+ <string name="decryption_failed">Неуспешно дешифроване. Възможно е да нямате правилния частен ключ.</string>
+ <string name="openkeychain_required">OpenKeychain</string>
+ <string name="openkeychain_required_long">Conversations използва външно приложение с име <b>OpenKeychain</b>, за да шифрова и дешифрова съобщенията и да управлява публичните Ви ключове.\n\nOpenKeychain е лицензирано под условията на GPLv3 и е налично в F-Droid и Google Play.\n\n<small>(Моля, рестартирайте Conversations след това.)</small></string>
+ <string name="restart">Рестартиране</string>
+ <string name="install">Инсталиране</string>
+ <string name="offering">предлагане...</string>
+ <string name="waiting">изчакване...</string>
+ <string name="no_pgp_key">Не е открит OpenPGP ключ</string>
+ <string name="contact_has_no_pgp_key">Conversations не може да шифрова съобщенията Ви, тъй като Вашият контакт не обявява публичния си ключ.\n\n<small>Моля, помолете го/я да инсталира и настрои OpenPGP.</small></string>
+ <string name="no_pgp_keys">Не са открити OpenPGP ключове</string>
+ <string name="contacts_have_no_pgp_keys">Conversations не може да шифрова съобщенията Ви, тъй като Вашите контакти не обявяват публичните си ключове.\n\n<small>Моля, помолете го да инсталират и настроят OpenPGP.</small></string>
+ <string name="encrypted_message_received"><i>Получено е шифровано съобщение. Докоснете, за да го прегледате и дешифровате.</i></string>
+ <string name="pref_general">Общи</string>
+ <string name="pref_xmpp_resource">XMPP ресурс</string>
+ <string name="pref_xmpp_resource_summary">Името, с което се определя този клиент</string>
+ <string name="pref_accept_files">Приемане на файлове</string>
+ <string name="pref_accept_files_summary">Автоматично приемане на файлове с размер, по-малък от...</string>
+ <string name="pref_notification_settings">Настройки за известията</string>
+ <string name="pref_notifications">Известия</string>
+ <string name="pref_notifications_summary">Известяване при получаване на ново съобщение</string>
+ <string name="pref_vibrate">Вибрация</string>
+ <string name="pref_vibrate_summary">Също така да има и вибрация при получаване на ново съобщение</string>
+ <string name="pref_sound">Звук</string>
+ <string name="pref_sound_summary">Изпълнение на звук с известието</string>
+ <string name="pref_conference_notifications">Известия за беседите</string>
+ <string name="pref_conference_notifications_summary">Известяване винаги, когато пристигне ново съобщение в беседа, а не само когато тя е отбелязана</string>
+ <string name="pref_notification_grace_period">Продължителност на отсрочване на известията</string>
+ <string name="pref_notification_grace_period_summary">Изключва известията за кратко, след като бъде получено копие на съобщение</string>
+ <string name="pref_advanced_options">Разширени настройки</string>
+ <string name="pref_never_send_crash">Никога да не се изпращат доклади за сривове</string>
+ <string name="pref_never_send_crash_summary">Изпращайки проследявания на стека, Вие помагате за непрекъснатото развитие на Conversations</string>
+ <string name="pref_confirm_messages">Потвърждаване на съобщенията</string>
+ <string name="pref_confirm_messages_summary">Уведомява контакта Ви, че сте приели и прочели съобщението му</string>
+ <string name="pref_ui_options">Настройки на интерфейса</string>
+ <string name="openpgp_error">OpenKeychain докладва за грешка</string>
+ <string name="error_decrypting_file">В/И грешка при дешифроването на файла</string>
+ <string name="accept">Приемане</string>
+ <string name="error">Възникна грешка</string>
+ <string name="pref_grant_presence_updates">Позволяване на актуализации на присъствието</string>
+ <string name="pref_grant_presence_updates_summary">Предварително позволяване и изискване на абониране за актуализации на присъствието за контакти, създадено от Вас</string>
+ <string name="subscriptions">Абонаменти</string>
+ <string name="your_account">Вашият профил</string>
+ <string name="keys">Ключове</string>
+ <string name="send_presence_updates">Изпращане на актуализации за присъствието</string>
+ <string name="receive_presence_updates">Получаване на актуализации за присъствието</string>
+ <string name="ask_for_presence_updates">Питане за актуализации за присъствието</string>
+ <string name="attach_choose_picture">Изберете снимка</string>
+ <string name="attach_take_picture">Заснемане</string>
+ <string name="preemptively_grant">Предварително позволяване на абониране при заявка</string>
+ <string name="error_not_an_image_file">Избраният файл не е изображение</string>
+ <string name="error_compressing_image">Грешка при преобразуването на изображението</string>
+ <string name="error_file_not_found">Файлът не е открит</string>
+ <string name="error_io_exception">Обща В/И грешка. Може би нямате достатъчно свободно място?</string>
+ <string name="error_security_exception_during_image_copy">Приложението, което използвахте, за да изберете това изображение, не ни осигури нужните права за прочитането му.\n\n<small>Използвайте друг диспечер на файлове, за да изберете изображение.</small></string>
+ <string name="account_status_unknown">Непознат</string>
+ <string name="account_status_disabled">Временно деактивиран</string>
+ <string name="account_status_online">На линия</string>
+ <string name="account_status_connecting">Свързване\u2026</string>
+ <string name="account_status_offline">Извън линия</string>
+ <string name="account_status_unauthorized">Неупълномощен</string>
+ <string name="account_status_not_found">Сървърът не е открит</string>
+ <string name="account_status_no_internet">Няма връзка</string>
+ <string name="account_status_regis_fail">Неуспешна регистрация</string>
+ <string name="account_status_regis_conflict">Потребителското име е заето</string>
+ <string name="account_status_regis_success">Регистрацията е завършена</string>
+ <string name="account_status_regis_not_sup">Сървърът не поддържа регистриране</string>
+ <string name="account_status_security_error">Грешка в сигурността</string>
+ <string name="account_status_incompatible_server">Несъвместим сървър</string>
+ <string name="encryption_choice_none">Обикновен текст</string>
+ <string name="encryption_choice_otr">OTR</string>
+ <string name="encryption_choice_pgp">OpenPGP</string>
+ <string name="mgmt_account_edit">Редактиране на профила</string>
+ <string name="mgmt_account_delete">Изтриване на профила</string>
+ <string name="mgmt_account_disable">Временно деактивиране</string>
+ <string name="mgmt_account_publish_avatar">Публикуване на аватар</string>
+ <string name="mgmt_account_publish_pgp">Публикуване на публичния OpenPGP ключ</string>
+ <string name="mgmt_account_enable">Активиране на профила</string>
+ <string name="mgmt_account_are_you_sure">Сигурни ли сте?</string>
+ <string name="mgmt_account_delete_confirm_text">Ако изтриете профила, цялата история на разговорите Ви ще бъде изтрита.</string>
+ <string name="attach_record_voice">Запис на глас</string>
+ <string name="account_settings_jabber_id">Jabber идентификатор</string>
+ <string name="account_settings_password">Парола</string>
+ <string name="account_settings_example_jabber_id">username@example.com</string>
+ <string name="account_settings_confirm_password">Потвърдете паролата</string>
+ <string name="password">Парола</string>
+ <string name="confirm_password">Потвърдете паролата</string>
+ <string name="passwords_do_not_match">Паролите са различни</string>
+ <string name="invalid_jid">Това не е правилен Jabber идентификатор</string>
+ <string name="error_out_of_memory">Няма достатъчно памет. Изображението е твърде голямо.</string>
+ <string name="add_phone_book_text">Искате ли да добавите %s в списъка си от телефонни контакти?</string>
+ <string name="contact_status_online">на линия</string>
+ <string name="contact_status_free_to_chat">свободен за разговор</string>
+ <string name="contact_status_away">отсъстващ</string>
+ <string name="contact_status_extended_away">разширено отсъстващ</string>
+ <string name="contact_status_do_not_disturb">отпочиващ</string>
+ <string name="contact_status_offline">извън линия</string>
+ <string name="muc_details_conference">Беседа</string>
+ <string name="muc_details_other_members">Други членове</string>
+ <string name="server_info_show_more">Инф. за сървъра</string>
+ <string name="server_info_mam">XEP-0313: Управление на архива на съобщенията</string>
+ <string name="server_info_carbon_messages">XEP-0280: Копия на съобщенията</string>
+ <string name="server_info_csi">XEP-0352: Показания за състоянието на клиента</string>
+ <string name="server_info_blocking">XEP-0191: Команда за блокиране</string>
+ <string name="server_info_roster_version">XEP-0237: Поддържане на версия на списъка</string>
+ <string name="server_info_stream_management">XEP-0198: Управление на потоците</string>
+ <string name="server_info_pep">XEP-0163: PEP (Аватари)</string>
+ <string name="server_info_available">налично</string>
+ <string name="server_info_unavailable">не е налично</string>
+ <string name="missing_public_keys">Липсват обявления за публичен ключ</string>
+ <string name="last_seen_now">последно видян току-що</string>
+ <string name="last_seen_min">последно видян преди 1 минута</string>
+ <string name="last_seen_mins">последно видян преди %d минути</string>
+ <string name="last_seen_hour">последно видян преди 1 час</string>
+ <string name="last_seen_hours">последно видян преди %d часа</string>
+ <string name="last_seen_day">последно видян преди 1 ден</string>
+ <string name="last_seen_days">последно видян преди %d дни</string>
+ <string name="never_seen">не е виждан никога</string>
+ <string name="install_openkeychain">Шифровано съобщение. Моля, инсталирайте OpenKeychain, за да го дешифровате.</string>
+ <string name="unknown_otr_fingerprint">Непознат OTR отпечатък</string>
+ <string name="openpgp_messages_found">Открити са съобщения, шифровани чрез OpenPGP</string>
+ <string name="reception_failed">Неуспешно получаване</string>
+ <string name="your_fingerprint">Вашият отпечатък</string>
+ <string name="otr_fingerprint">OTR отпечатък</string>
+ <string name="verify">Потвърждаване</string>
+ <string name="decrypt">Дешифроване</string>
+ <string name="conferences">Беседи</string>
+ <string name="search">Търсене</string>
+ <string name="create_contact">Създаване на контакт</string>
+ <string name="join_conference">Присъединяване към беседа</string>
+ <string name="delete_contact">Изтриване на контакт</string>
+ <string name="view_contact_details">Преглед на подр. за контакта</string>
+ <string name="block_contact">Блокиране на контакт</string>
+ <string name="unblock_contact">Деблокиране на контакт</string>
+ <string name="create">Създаване</string>
+ <string name="contact_already_exists">Контактът вече съществува</string>
+ <string name="join">Присъединяване</string>
+ <string name="conference_address">Адрес на беседата</string>
+ <string name="conference_address_example">room@conference.example.com</string>
+ <string name="save_as_bookmark">Запазване като отметка</string>
+ <string name="delete_bookmark">Изтриване на отметка</string>
+ <string name="bookmark_already_exists">Вече съществува такава отметка</string>
+ <string name="you">Вие</string>
+ <string name="action_edit_subject">Редактиране на темата на беседата</string>
+ <string name="conference_not_found">Беседата не е открита</string>
+ <string name="leave">Напускане</string>
+ <string name="contact_added_you">Контактът е добавен във Вашия списък от контакти</string>
+ <string name="add_back">Добавяне обратно</string>
+ <string name="contact_has_read_up_to_this_point">%s е прочел до тук</string>
+ <string name="publish">Публикуване</string>
+ <string name="touch_to_choose_picture">Докоснете аватара, за да изберете изображение от галерията</string>
+ <string name="publish_avatar_explanation">Забележка: Всеки, абониран за актуализации на присъствието Ви, ще може да вижда тази снимка.</string>
+ <string name="publishing">Публикуване...</string>
+ <string name="error_publish_avatar_server_reject">Сървърът отказа Вашето публикуване</string>
+ <string name="error_publish_avatar_converting">Нещо се обърка при преобразуването на снимката Ви</string>
+ <string name="error_saving_avatar">Неуспешно запазване на аватара на диска</string>
+ <string name="or_long_press_for_default">(Или задръжте, за да върнете началното)</string>
+ <string name="error_publish_avatar_no_server_support">Сървърът Ви не поддържа публикуване на аватари</string>
+ <string name="private_message">прошепна</string>
+ <string name="private_message_to">на %s</string>
+ <string name="send_private_message_to">Изпращане на лично съобщение до %s</string>
+ <string name="connect">Свързване</string>
+ <string name="account_already_exists">Този профил вече съществува</string>
+ <string name="next">Следващо</string>
+ <string name="server_info_session_established">Установена е текуща сесия</string>
+ <string name="additional_information">Допълнителна информация</string>
+ <string name="skip">Пропускане</string>
+ <string name="disable_notifications">Изключване на известията</string>
+ <string name="disable_notifications_for_this_conversation">Изключване на известията за този разговор</string>
+ <string name="notifications_disabled">Известията са изключени</string>
+ <string name="enable">Включване</string>
+ <string name="conference_requires_password">Беседата изисква парола</string>
+ <string name="enter_password">Въведете парола</string>
+ <string name="missing_presence_updates">Липсват актуализации за присъствието на контакта</string>
+ <string name="request_presence_updates">Моля, първо помолете контакта за актуализации на присъствието му.\n\n<small>Това ще бъде използвано, за да се провери какъв клиент (или клиенти) използва контакта.</small></string>
+ <string name="request_now">Поискване сега</string>
+ <string name="delete_fingerprint">Изтриване на отпечатъка</string>
+ <string name="sure_delete_fingerprint">Сигурни ли сте, че искате да изтриете този отпечатък?</string>
+ <string name="ignore">Пренебрегване</string>
+ <string name="without_mutual_presence_updates"><b>Внимание:</b> Изпращането на това без съвместни актуализации на присъствието може да доведе до неочаквани проблеми.\n\n<small>Погледнете подробностите за контакта, за да проверите дали сте абониран за актуализации на присъствието.</small></string>
+ <string name="pref_encryption_settings">Настройки за шифроване</string>
+ <string name="pref_force_encryption">Налагане на шифроване в двете посоки</string>
+ <string name="pref_force_encryption_summary">Съобщенията да се изпращат винаги шифровани (освен в беседите)</string>
+ <string name="pref_dont_save_encrypted">Шифрованите съобщения да не се запазват</string>
+ <string name="pref_dont_save_encrypted_summary">Внимание: Това може да доведе до загуба на съобщения</string>
+ <string name="pref_expert_options">Настройки за напреднали</string>
+ <string name="pref_expert_options_summary">Моля, бъдете внимателни с тези</string>
+ <string name="title_activity_about">Относно Conversations</string>
+ <string name="pref_about_conversations_summary">Информация за версията и лицензите</string>
+ <string name="title_pref_quiet_hours">Тихи часове</string>
+ <string name="title_pref_quiet_hours_start_time">Начало</string>
+ <string name="title_pref_quiet_hours_end_time">Край</string>
+ <string name="title_pref_enable_quiet_hours">Включване на тихите часове</string>
+ <string name="pref_quiet_hours_summary">Известията ще бъдат заглушени по време на тихите часове</string>
+ <string name="pref_use_larger_font">Голям размер на шрифта</string>
+ <string name="pref_use_larger_font_summary">Използване на по-голям размер на шрифтовете в цялото приложение</string>
+ <string name="pref_use_send_button_to_indicate_status">Бутонът за изпращане показва състоянието</string>
+ <string name="pref_use_indicate_received">Изискване на отчет за съобщенията</string>
+ <string name="pref_use_indicate_received_summary">Получените съобщения ще бъдат отбелязани със зелена отметка, ако това се поддържа</string>
+ <string name="pref_use_send_button_to_indicate_status_summary">Оцветяване на бутона за изпращане в зависимост от състоянието на контакта</string>
+ <string name="pref_expert_options_other">Други</string>
+ <string name="pref_conference_name">Име на беседата</string>
+ <string name="pref_conference_name_summary">Използване на темата на стаята вместо JID идентификатора за беседите</string>
+ <string name="toast_message_otr_fingerprint">OTR отпечатъкът е копиран!</string>
+ <string name="conference_banned">Достъпът Ви до тази беседа беше забранен</string>
+ <string name="conference_members_only">Тази беседа е само за членове</string>
+ <string name="conference_kicked">Бяхте изритан от тази конференция</string>
+ <string name="using_account">използвайки профила %s</string>
+ <string name="checking_image">Проверяване на изображението на HTTP сървъра</string>
+ <string name="image_file_deleted">Изображението е изтрито</string>
+ <string name="not_connected_try_again">Не сте свързани. Опитайте отново по-късно</string>
+ <string name="check_image_filesize">Проверка на размера на файла с изображението</string>
+ <string name="message_options">Настройки за съобщенята</string>
+ <string name="copy_text">Копиране на текста</string>
+ <string name="copy_original_url">Копиране на оригиналния адрес</string>
+ <string name="send_again">Повторно изпращане</string>
+ <string name="image_url">Адрес на изображението</string>
+ <string name="message_text">Текст на съобщението</string>
+ <string name="url_copied_to_clipboard">Адресът е копиран</string>
+ <string name="message_copied_to_clipboard">Съобщението е копирано</string>
+ <string name="image_transmission_failed">Неуспешно прехвърляне на изображението</string>
+ <string name="scan_qr_code">Сканиране на QR кода</string>
+ <string name="show_qr_code">Показване на QR кода</string>
+ <string name="show_block_list">Показване на списъка с блокирани</string>
+ <string name="account_details">Подробности за профила</string>
+ <string name="verify_otr">Проверка на OTR</string>
+ <string name="remote_fingerprint">Отдалечен отпечатък</string>
+ <string name="scan">сканиране</string>
+ <string name="or_touch_phones">(или докоснете телефоните)</string>
+ <string name="smp">Протокол „Socialist Millionaire“</string>
+ <string name="shared_secret_hint">Подсказка или въпрос</string>
+ <string name="shared_secret_secret">Обща тайна</string>
+ <string name="confirm">Потвърждаване</string>
+ <string name="in_progress">В процес</string>
+ <string name="respond">Отговаряне</string>
+ <string name="failed">Неуспешно</string>
+ <string name="secrets_do_not_match">Тайната информация не съвпада</string>
+ <string name="try_again">Повторен опит</string>
+ <string name="finish">Край</string>
+ <string name="verified">Проверено!</string>
+ <string name="smp_requested">Контактът изиска SMP проверка</string>
+ <string name="no_otr_session_found">Няма открита OTR сесия!</string>
+ <string name="conversations_foreground_service">Conversations</string>
+ <string name="pref_keep_foreground_service">Услугата да е на преден план</string>
+ <string name="pref_keep_foreground_service_summary">Предотвратява прекъсването на връзката Ви от операционната система</string>
+ <string name="choose_file">Изберете файл</string>
+ <string name="receiving_x_file">Получаване на %1$s (%2$d%% завършено)</string>
+ <string name="download_x_file">Изтегляне на %s</string>
+ <string name="file">файл</string>
+ <string name="open_x_file">Отваряне на %s</string>
+ <string name="sending_file">изпращане (%1$d%% завършено)</string>
+ <string name="preparing_file">Подготовка на файла за прехвърляне</string>
+ <string name="x_file_offered_for_download">%s е предложен за сваляне</string>
+ <string name="cancel_transmission">Отказ на прехвърлянето</string>
+ <string name="file_transmission_failed">неуспешно прехвърляне на файл</string>
+ <string name="file_deleted">Файлът беше изтрит</string>
+ <string name="no_application_found_to_open_file">Няма намерено приложение за отваряне на файла</string>
+ <string name="could_not_verify_fingerprint">Неуспешна проверка на отпечатъка</string>
+ <string name="manually_verify">Ръчна проверка</string>
+ <string name="are_you_sure_verify_fingerprint">Сигурни ли сте, че искате да проверите OTR отпечатъка на контактите си?</string>
+ <string name="pref_show_dynamic_tags">Динамични етикети</string>
+ <string name="pref_show_dynamic_tags_summary">Показване на етикети, предназначени само за четене под контактите</string>
+ <string name="enable_notifications">Включване на известията</string>
+ <string name="conference_with">Започване на беседа с...</string>
+ <string name="no_conference_server_found">Не е открит сървър за беседата</string>
+ <string name="conference_creation_failed">Неуспешно създаване на беседа!</string>
+ <string name="conference_created">Беседата беше създадена!</string>
+ <string name="secret_accepted">Тайната е приета!</string>
+ <string name="reset">Възстановяване</string>
+ <string name="account_image_description">Аватар на профила</string>
+ <string name="copy_otr_clipboard_description">Копиране на OTR отпечатъка</string>
+ <string name="fetching_history_from_server">Получаване на историята от сървъра</string>
+ <string name="no_more_history_on_server">Няма повече история на сървъра</string>
+ <string name="updating">Актуализиране...</string>
+ <string name="password_changed">Паролата е променена!</string>
+ <string name="could_not_change_password">Неуспешна промяна на паролата</string>
+ <string name="otr_session_not_started">Изпратете съобщение, за да започнете нешифрован разговор</string>
+ <string name="ask_question">Задаване на въпрос</string>
+ <string name="smp_explain_question">Ако Вие и контакта Ви имате някаква тайна информация, която никой друг не знае (като някаква шега или пък просто какво сте обядвали, когато сте се срещнали за последно), можете да я използвате, за да проверите отпечатъците си един на друг.\n\nМожете да подсигурите подсказка или въпрос, на който контакта Ви да отговори, като има предвид, че главните и малките букви се броят за различни.</string>
+ <string name="smp_explain_answer">Вашият контакт би искал да провери отпечатъка Ви, като Ви попита за обща тайна информация. Контактът Ви предостави следната подсказка или въпрос, който да Ви насочи към тази тайна.</string>
+ <string name="shared_secret_hint_should_not_be_empty">Подсказката Ви не трябва да е празна</string>
+ <string name="shared_secret_can_not_be_empty">Общата Ви тайна не може да е празна</string>
+ <string name="manual_verification_explanation">Внимателно сравнете отпечатъка по-долу с този на Вашия контакт.\nМожете да използвате всякакъв начин на сигурна комуникация, като шифрована е-поща или телефонен разговор, за да размените отпечатъците.</string>
+ <string name="change_password">Промяна на паролата</string>
+ <string name="current_password">Текуща парола</string>
+ <string name="new_password">Нова парола</string>
+ <string name="password_should_not_be_empty">Паролата не трябва да е празна</string>
+ <string name="enable_all_accounts">Активиране на всички профили</string>
+ <string name="disable_all_accounts">Деактивиране на всички профили</string>
+ <string name="perform_action_with">Изпълнение на действието с</string>
+ <string name="no_affiliation">Няма принадлежност</string>
+ <string name="no_role">Няма роля</string>
+ <string name="outcast">Отхвърлен</string>
+ <string name="member">Член</string>
+ <string name="advanced_mode">Разширен режим</string>
+ <string name="grant_membership">Даване на членство</string>
+ <string name="remove_membership">Отмяна на членството</string>
+ <string name="grant_admin_privileges">Даване на администраторски права</string>
+ <string name="remove_admin_privileges">Отмяна на администраторските права</string>
+ <string name="remove_from_room">Премахване от беседата</string>
+ <string name="could_not_change_affiliation">Неуспешна промяна на принадлежността на %s</string>
+ <string name="ban_from_conference">Забраняване на достъпа до беседата</string>
+ <string name="removing_from_public_conference">Вие се опитвате да премахнете %s от публична беседа. Единственият начин да направите това, е да забраните достъпа на този потребител завинаги.</string>
+ <string name="ban_now">Забраняване на достъпа сега</string>
+ <string name="could_not_change_role">Неуспешна промяна на ролята на %s</string>
+ <string name="public_conference">Публично достъпни беседи</string>
+ <string name="private_conference">Частни беседи, само за членове</string>
+ <string name="conference_options">Настройки на беседата</string>
+ <string name="members_only">Частна (само за членове)</string>
+ <string name="non_anonymous">Не-анонимна</string>
+ <string name="modified_conference_options">Настройките на беседата бяха променени!</string>
+ <string name="could_not_modify_conference_options">Неуспешна промяна на настройките на беседата</string>
+ <string name="never">Никога</string>
+ <string name="thirty_minutes">30 минути</string>
+ <string name="one_hour">1 час</string>
+ <string name="two_hours">2 часа</string>
+ <string name="eight_hours">8 часа</string>
+ <string name="until_further_notice">До отмяна</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>
+ <string name="video">видео</string>
+ <string name="image">изображение</string>
+ <string name="pdf_document">PDF документ</string>
+ <string name="apk">Приложение за Андроид</string>
+ <string name="vcard">Контакт</string>
+ <string name="received_x_file">Получи %s</string>
+ <string name="disable_foreground_service">Изключване на услугата на преден план</string>
+ <string name="touch_to_open_conversations">Докоснете, за да отворите Conversations</string>
+ <string name="avatar_has_been_published">Аватарът беше публикуван!</string>
+ <string name="sending_x_file">Изпращане на %s</string>
+ <string name="offering_x_file">Предлагане на %s</string>
+ <string name="hide_offline">Скриване на тези извън линия</string>
+ <string name="disable_account">Деактивиране на профила</string>
+ <string name="contact_is_typing">%s пише...</string>
+ <string name="contact_has_stopped_typing">%s спря да пише</string>
+ <string name="pref_chat_states">Известия за писането</string>
+ <string name="pref_chat_states_summary">Позволяване на контакта Ви да вижда, когато пишете ново съобщение</string>
+ <string name="send_location">Изпращане на местоположението</string>
+ <string name="show_location">Показване на местоположението</string>
+ <string name="no_application_found_to_display_location">Няма намерено приложение за показване на местоположението</string>
+ <string name="location">Местоположение</string>
+ <string name="received_location">Получено местоположение</string>
+ <plurals name="select_contact">
+ <item quantity="one">Изберете %d контакт</item>
+ <item quantity="other">Изберете %d контакта</item>
+ </plurals>
+</resources>
diff --git a/src/main/res/values-ca/strings.xml b/src/main/res/values-ca/strings.xml
index c178283e..e50fa740 100644
--- a/src/main/res/values-ca/strings.xml
+++ b/src/main/res/values-ca/strings.xml
@@ -17,13 +17,13 @@
<string name="action_unblock_domain">Desbloqueja aquest domini</string>
<string name="title_activity_manage_accounts">Administrar comptes</string>
<string name="title_activity_settings">Configuració</string>
- <string name="title_activity_conference_details">Detalls de la conferència o Detalls de la sala</string>
+ <string name="title_activity_conference_details">Detalls de la conferència de conversació</string>
<string name="title_activity_contact_details">Detalls del contacte</string>
<string name="title_activity_sharewith">Compartir amb converses</string>
<string name="title_activity_start_conversation">Començar una conversa</string>
<string name="title_activity_choose_contact">Escollir un contacte</string>
<string name="title_activity_block_list">LLista bloqueix</string>
- <string name="just_now">ara</string>
+ <string name="just_now">Ara</string>
<string name="minute_ago">1 min avans</string>
<string name="minutes_ago">%de minuts avans</string>
<string name="unread_conversations">Converses sense llegir o no llegides</string>
@@ -112,9 +112,9 @@
<string name="pref_notification_grace_period_summary">Desactiva les notificacions durant un breu termini després de rebre una còpia de missatges carbon</string>
<string name="pref_advanced_options">Opcions avançades</string>
<string name="pref_never_send_crash">Mai enviïs informes d\'errors</string>
- <string name="pref_never_send_crash_summary">Enviant traces d\'execució ajudes al futur desenvolupament del Conversations.</string>
+ <string name="pref_never_send_crash_summary">Enviant traces d\'execució d\'ajudes al futur desenvolupament del Conversations.</string>
<string name="pref_confirm_messages">Confirmant missatges</string>
- <string name="pref_confirm_messages_summary">Deixeu que el seu contacte sápiga quan heu rebut i llegit un missatge</string>
+ <string name="pref_confirm_messages_summary">Deixeu que el seu contacte sàpiga quan heu rebut i llegit un missatge</string>
<string name="pref_ui_options">Opcions de UI</string>
<string name="openpgp_error">OpenKeychain ha reportat un error</string>
<string name="error_decrypting_file">I/O Error al desxifrar un arxiu</string>
@@ -123,9 +123,9 @@
<string name="pref_grant_presence_updates">Concedir actualitzacions</string>
<string name="pref_grant_presence_updates_summary">Preventivament atorgar i preguntar per les subscripcions als contactes creats</string>
<string name="subscriptions">Subscripcions</string>
- <string name="your_account">La teva compte</string>
+ <string name="your_account">El teu compte</string>
<string name="keys">Claus</string>
- <string name="send_presence_updates">Enviar actualitzacions</string>
+ <string name="send_presence_updates">Enviar actualitzacions de presència</string>
<string name="receive_presence_updates">Rebre actualitzacions</string>
<string name="ask_for_presence_updates">Preguntar per les actualizacions</string>
<string name="attach_choose_picture">Escollir una imatge</string>
@@ -133,7 +133,7 @@
<string name="preemptively_grant">Preventivament otorgar una petició a la subscripció</string>
<string name="error_not_an_image_file">L\'arxiu que has seleccionat no és una imatge</string>
<string name="error_compressing_image">Error mentrés s\'intentaba convertir l\'imatge de l\'arxiu</string>
- <string name="error_file_not_found">Arxiu no trobat</string>
+ <string name="error_file_not_found">L\'arxiu no s\'ha trobat</string>
<string name="error_io_exception">Error general I/O. Potser és troba sense espai d\'emmagatzematge?</string>
<string name="error_security_exception_during_image_copy">L\'aplicació què está utilitzan per seleccionar l\'imatge no conté els suficients permissos per llegir l\'arxiu.\n\n<small> Utilitzeu un gestor de fitxers diferent per escollir una imatge.</small></string>
<string name="account_status_unknown">Desconegut</string>
@@ -178,7 +178,7 @@
<string name="contact_status_extended_away">Lluny</string>
<string name="contact_status_do_not_disturb">No molestar</string>
<string name="contact_status_offline">Fora de línia</string>
- <string name="muc_details_conference">Sala</string>
+ <string name="muc_details_conference">Conferència de conversació</string>
<string name="muc_details_other_members">Altres membres</string>
<string name="server_info_show_more">Informació del servidor</string>
<string name="server_info_mam">XEP-0313:MAM</string>
@@ -203,11 +203,11 @@
<string name="unknown_otr_fingerprint">Empremta dactilar OTR desconeguda</string>
<string name="openpgp_messages_found">Missatges xifrats OpenPGP trobats</string>
<string name="reception_failed">Recepció fallida</string>
- <string name="your_fingerprint">La teva empremta dactilar</string>
- <string name="otr_fingerprint">Empremta dactilar OTR</string>
+ <string name="your_fingerprint">La teva empremta digital</string>
+ <string name="otr_fingerprint">Empremta digital OTR</string>
<string name="verify">Verificar</string>
<string name="decrypt">Desxifrar</string>
- <string name="conferences">Sales</string>
+ <string name="conferences">Conferencies de les conversacions</string>
<string name="search">Cercar</string>
<string name="create_contact">Crear contacte</string>
<string name="join_conference">Unir-se a la sala</string>
@@ -257,10 +257,10 @@
<string name="missing_presence_updates">Perdut les actualitzacions del contacte</string>
<string name="request_presence_updates">Si us plau, sol.liciteu les actualitzacions de presència del primer contacte.\n\n<small>.S\'utlitza per determinar quins client(s) ésta utilitzant el vostre contacte.</small></string>
<string name="request_now">Sol.licita ara</string>
- <string name="delete_fingerprint">Eliminar l\'empremta dactilar</string>
- <string name="sure_delete_fingerprint">Estàs segur que t\'agradaria eliminar l\'empremta dactilar?</string>
+ <string name="delete_fingerprint">Eliminar l\'empremta digital</string>
+ <string name="sure_delete_fingerprint">Estàs segur que t\'agradaria eliminar l\'empremta digital?</string>
<string name="ignore">Ignorar</string>
- <string name="without_mutual_presence_updates"><b>Perill:</b>L\'ennviament d\'aquest sense actualitzacions de presència podria causar problemes inesperats.\n\n<small> Ves als detalls del contacte per verificar les subscripcions de presència.</small></string>
+ <string name="without_mutual_presence_updates"><b>Perill:</b>L\'enviament d\'aquest sense actualitzacions de presència podria causar problemes inesperats.\n\n<small> Ves als detalls del contacte per verificar les subscripcions de presència.</small></string>
<string name="pref_encryption_settings">Configuració del xifratge</string>
<string name="pref_force_encryption">Força d\'extrema extrem del xifrat</string>
<string name="pref_force_encryption_summary">Enviar sempre missatges xifrat( Excepte per les sales)</string>
@@ -275,16 +275,16 @@
<string name="title_pref_quiet_hours_end_time">Hora de finalització</string>
<string name="title_pref_enable_quiet_hours">Habilitar hores de silenci</string>
<string name="pref_quiet_hours_summary">Les notificacions seràn silenciades a les hores de silenci</string>
- <string name="pref_use_larger_font">Augmentar el tamany de la lletra</string>
+ <string name="pref_use_larger_font">Augmentar el tamany de la font.</string>
<string name="pref_use_larger_font_summary">Utilitzar la mida més gran de les lletres per a tota l\'aplicació</string>
<string name="pref_use_send_button_to_indicate_status">Botó d\'indicació de l\'estatus enviar</string>
- <string name="pref_use_indicate_received">Rebuts de sol.licituds de missatges</string>
+ <string name="pref_use_indicate_received">Rebuts de sol.licituds dels missatges</string>
<string name="pref_use_indicate_received_summary">Els missatges rebuts seràn marcats amb uns ticks verds si ho admet</string>
<string name="pref_use_send_button_to_indicate_status_summary">Pintar el botó d\'enviament per indicar l\'estatus del contacte</string>
<string name="pref_expert_options_other">Altres</string>
<string name="pref_conference_name">Nom de la sala</string>
<string name="pref_conference_name_summary">Utilitzar el tema de la sala en menys de la identificació de Jabber per identificar les sales</string>
- <string name="toast_message_otr_fingerprint">Empremta dactilar OTR copiada al portapapers</string>
+ <string name="toast_message_otr_fingerprint">Empremta digital OTR copiada al portapapers</string>
<string name="conference_banned">La teva admissió en aquesta sala ha sigut bloquejada</string>
<string name="conference_members_only">La sala es nomès per membres</string>
<string name="conference_kicked">Estàs expulsat d\'aquesta sala</string>
@@ -307,7 +307,7 @@
<string name="show_block_list">Mostra la llista de bloqueig</string>
<string name="account_details">Detalls del compte</string>
<string name="verify_otr">Verificar OTR</string>
- <string name="remote_fingerprint">Empremta dactilar remota</string>
+ <string name="remote_fingerprint">Empremta digital remota</string>
<string name="scan">Escanejar</string>
<string name="or_touch_phones">( o toca altres mòbils)</string>
<string name="smp">Protocol de socialistes millionaris</string>
@@ -340,18 +340,18 @@
<string name="no_application_found_to_open_file">Cap aplicació trobada a l\'obrir l\'arxiu</string>
<string name="could_not_verify_fingerprint">No s\'ha pogut verificar l\'empremta dactilar</string>
<string name="manually_verify">Verificat manualment</string>
- <string name="are_you_sure_verify_fingerprint">Estàs segur que vols verificar l\'empremta dactilar OTR dels teus contactes?</string>
+ <string name="are_you_sure_verify_fingerprint">Estàs segur que vols verificar l\'empremta digital OTR dels teus contactes?</string>
<string name="pref_show_dynamic_tags">Mostrar etiquetes dinàmiques</string>
<string name="pref_show_dynamic_tags_summary">Mostra etiquetes de nomès lectura per sota dels noms dels contactes</string>
<string name="enable_notifications">Habilitar notificació</string>
- <string name="conference_with">Crear una sala amb ...</string>
- <string name="no_conference_server_found">Servidor de la sala no trobat</string>
- <string name="conference_creation_failed">Creació de la sala fallat</string>
- <string name="conference_created">Sala creada</string>
+ <string name="conference_with">Crear una conferència de conversació amb ...</string>
+ <string name="no_conference_server_found">Servidor de la conferència de conversació no trobat</string>
+ <string name="conference_creation_failed">la creació de la conferència de conversació ha fallat</string>
+ <string name="conference_created">Conferència de la conversació creada</string>
<string name="secret_accepted">Aceptació del secret</string>
<string name="reset">Reset</string>
<string name="account_image_description">Avatar del compte</string>
- <string name="copy_otr_clipboard_description">Copiar l\'empremta dactilar OTR al portapapers</string>
+ <string name="copy_otr_clipboard_description">Copiar l\'empremta digital OTR al portapapers</string>
<string name="fetching_history_from_server">Anar a cercar la història als servidors</string>
<string name="no_more_history_on_server">No hi ha més histories al servidor</string>
<string name="updating">Actualitzant</string>
@@ -386,8 +386,8 @@
<string name="removing_from_public_conference">Estàs intentant eliminar %s des d\'una sala pùblica. L\'unica manera per fer-ho és eliminar a l\'usuari per sempre</string>
<string name="ban_now">Banejat ara</string>
<string name="could_not_change_role">No s\'ha pogut canviar les regles de %s</string>
- <string name="public_conference">Sala d\'accés pùblica</string>
- <string name="private_conference">Privada, únicament membres de la sala</string>
+ <string name="public_conference">Comferència de la conversació d\'accés pùblic</string>
+ <string name="private_conference">Privada, únicament els membres de la conferència de conversació</string>
<string name="conference_options">Opcions de la sala</string>
<string name="members_only">Privat( Nomès membres)</string>
<string name="non_anonymous">Sense anonimat</string>
@@ -416,4 +416,5 @@
<string name="avatar_has_been_published">L\'avatar ha sigut publicat!</string>
<string name="sending_x_file">Enviant %s</string>
<string name="offering_x_file">Oferint %s</string>
+ <string name="hide_offline">Amaga el fora de línia</string>
</resources>
diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml
index a98b00f2..0c08fa7a 100644
--- a/src/main/res/values-cs/strings.xml
+++ b/src/main/res/values-cs/strings.xml
@@ -114,7 +114,7 @@
<string name="pref_never_send_crash">Neodesílat detaily o pádu aplikace</string>
<string name="pref_never_send_crash_summary">Zasláním detailů o důvodu selhání pomůžete dalšímu vývoji aplikace Konverzace</string>
<string name="pref_confirm_messages">Potvrzovat zprávy</string>
- <string name="pref_confirm_messages_summary">Dá vědět kontaktům, že zpráva byla přijata a přečtena</string>
+ <string name="pref_confirm_messages_summary">Oznamovat kontaktům, že zpráva byla přijata a přečtena</string>
<string name="pref_ui_options">Nastavení UI</string>
<string name="openpgp_error">OpenKeychain nahlásil chybu</string>
<string name="error_decrypting_file">I/O chyba dešifrování souboru</string>
@@ -416,4 +416,20 @@
<string name="avatar_has_been_published">Avatar byl zveřejněn!</string>
<string name="sending_x_file">Odesílám %s</string>
<string name="offering_x_file">Nabízím %s</string>
+ <string name="hide_offline">Skrýt offline</string>
+ <string name="disable_account">Vypnout účet</string>
+ <string name="contact_is_typing">%s píše...</string>
+ <string name="contact_has_stopped_typing">%s přestal(a) psát</string>
+ <string name="pref_chat_states">Upozornění při psaní</string>
+ <string name="pref_chat_states_summary">Oznamovat kontaktům že píšete novou zprávu</string>
+ <string name="send_location">Poslat pozici</string>
+ <string name="show_location">Zobrazit pozici</string>
+ <string name="no_application_found_to_display_location">Nebyla nalezena aplikace pro zobrazení pozice</string>
+ <string name="location">Pozice</string>
+ <string name="received_location">Přijmout pozici</string>
+ <plurals name="select_contact">
+ <item quantity="one">Vybrat %d kontakt</item>
+ <item quantity="few">Vybrat %d kontakty</item>
+ <item quantity="other">Vybrat %d kontaktů</item>
+ </plurals>
</resources>
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index 8cd837d2..44a7efb2 100644
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -4,11 +4,11 @@
<string name="action_add">Neue Unterhaltung</string>
<string name="action_accounts">Konten verwalten</string>
<string name="action_end_conversation">Unterhaltung beenden</string>
- <string name="action_contact_details">Kontaktdetails</string>
+ <string name="action_contact_details">Kontakt-Details</string>
<string name="action_muc_details">Konferenz-Details</string>
<string name="action_secure">Verschlüsselte Unterhaltung</string>
<string name="action_add_account">Konto hinzufügen</string>
- <string name="action_edit_contact">Name bearbeiten</string>
+ <string name="action_edit_contact">Namen bearbeiten</string>
<string name="action_add_phone_book">Zum Telefonbuch hinzufügen</string>
<string name="action_delete_contact">Aus Kontaktliste entfernen</string>
<string name="action_block_contact">Kontakt sperren</string>
@@ -18,7 +18,7 @@
<string name="title_activity_manage_accounts">Konten verwalten</string>
<string name="title_activity_settings">Einstellungen</string>
<string name="title_activity_conference_details">Konferenz-Details</string>
- <string name="title_activity_contact_details">Kontaktdetails</string>
+ <string name="title_activity_contact_details">Kontakt-Details</string>
<string name="title_activity_sharewith">Mit Unterhaltung teilen</string>
<string name="title_activity_start_conversation">Beginne Unterhaltung</string>
<string name="title_activity_choose_contact">Kontakt auswählen</string>
@@ -27,7 +27,7 @@
<string name="minute_ago">vor einer Minute</string>
<string name="minutes_ago">vor %d Minuten</string>
<string name="unread_conversations">ungelesene Unterhaltungen</string>
- <string name="sending">Senden…</string>
+ <string name="sending">senden…</string>
<string name="encrypted_message">Entschlüssele Nachricht. Bitte warten…</string>
<string name="nick_in_use">Nickname wird bereits verwendet</string>
<string name="admin">Administrator</string>
@@ -41,7 +41,7 @@
<string name="block_domain_text">Sperre alle Kontakte von %s?</string>
<string name="unblock_domain_text">Entsperre alle Kontakte %s?</string>
<string name="contact_blocked">Kontakt gesperrt</string>
- <string name="remove_bookmark_text">Möchtest du das Lesezeichen %s entfernen? Die Unterhaltung mit diesem Lesezeichen wird dabei nicht entfernt.</string>
+ <string name="remove_bookmark_text">Möchtest du %s von deiner Kontaktliste entfernen? Die Unterhaltung mit dieser Konferenz wird dabei nicht entfernt.</string>
<string name="register_account">Neues Konto auf dem Server erstellen</string>
<string name="change_password_on_server">Passwort ändern</string>
<string name="share_with">Teile mit…</string>
@@ -66,34 +66,34 @@
<string name="touch_to_fix">Drücke hier, um das Konto zu verwalten</string>
<string name="attach_file">Datei anfügen</string>
<string name="not_in_roster">Der Kontakt ist nicht in deiner Kontaktliste. Möchtest du ihn hinzufügen?</string>
- <string name="add_contact">Kontakt hinzufügen</string>
+ <string name="add_contact">Zur Kontaktliste hinzufügen</string>
<string name="send_failed">Zustellung nicht erfolgreich</string>
<string name="send_rejected">abgelehnt</string>
<string name="preparing_image">Bereite Bild für die Übertragung vor</string>
<string name="action_clear_history">Verlauf löschen</string>
- <string name="clear_conversation_history">Unterhaltungsverlauf löschen</string>
+ <string name="clear_conversation_history">Verlauf löschen</string>
<string name="clear_histor_msg">Möchtest du alle Nachrichten in dieser Unterhaltung löschen?\n\n<b>Achtung:</b> Dies beeinflusst nicht Nachrichten, die auf anderen Geräten oder Servern gespeichert sind.</string>
<string name="delete_messages">Nachrichten löschen</string>
<string name="also_end_conversation">Diese Unterhaltung danach beenden</string>
<string name="choose_presence">Ressource des Kontakts auswählen</string>
- <string name="send_plain_text_message">Unverschlüsselt schreiben</string>
- <string name="send_otr_message">OTR-verschlüsselt schreiben</string>
- <string name="send_pgp_message">OpenPGP-verschlüsselt schreiben</string>
+ <string name="send_plain_text_message">Normal schreiben…</string>
+ <string name="send_otr_message">OTR-verschlüsselt schreiben…</string>
+ <string name="send_pgp_message">OpenPGP-verschlüsselt schreiben…</string>
<string name="your_nick_has_been_changed">Dein Nickname wurde geändert</string>
<string name="download_image">Bild herunterladen</string>
- <string name="send_unencrypted">Unverschlüsselt verschicken</string>
+ <string name="send_unencrypted">Normal verschicken</string>
<string name="decryption_failed">Entschlüsselung fehlgeschlagen. Vielleicht hast du nicht den richtigen privaten Schlüssel.</string>
<string name="openkeychain_required">OpenKeychain</string>
<string name="openkeychain_required_long">Conversations benutzt eine Drittanwendung namens <b>OpenKeychain</b>, um Nachrichten zu ver- und entschlüsseln und um deine Schlüssel zu verwalten.\n\nOpenKeychain ist GPLv3-lizenziert und kann über F-Droid oder Google Play bezogen werden.\n\n<small>(Bitte starte Conversations danach neu.)</small></string>
- <string name="restart">Neustarten</string>
+ <string name="restart">Neu starten</string>
<string name="install">Installieren</string>
<string name="offering">angeboten…</string>
<string name="waiting">warten…</string>
<string name="no_pgp_key">Kein OpenPGP-Schlüssel gefunden</string>
<string name="contact_has_no_pgp_key">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil dein Kontakt seinen oder ihren Schlüssel nicht preisgibt.\n\n<small>Bitte sag deinem Kontakt, er oder sie möge OpenPGP einrichten.</small></string>
<string name="no_pgp_keys">Keine OpenPGP-Schlüssel gefunden</string>
- <string name="contacts_have_no_pgp_keys">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil deine Kontakte ihre Schlüssel nicht preisgeben.\n\n<small>Bitte sag deinen Kontakten, sie mögen OpenPGP einrichten.</small></string>
- <string name="encrypted_message_received"><i>Verschlüsselte Nachricht erhalten. Drücke hier, um sie anzuzeigen und zu entschlüsseln.</i></string>
+ <string name="contacts_have_no_pgp_keys">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil deine Kontakte ihre Schlüssel nicht preisgeben.\n\n<small>Bitte sage deinen Kontakten, sie mögen OpenPGP einrichten.</small></string>
+ <string name="encrypted_message_received"><i>Verschlüsselte Nachricht erhalten. Drücke hier, um sie zu entschlüsseln und anzuzeigen.</i></string>
<string name="pref_general">Allgemeines</string>
<string name="pref_xmpp_resource">XMPP-Ressource</string>
<string name="pref_xmpp_resource_summary">Der Name, mit dem sich der Client selbst identifiziert</string>
@@ -113,7 +113,7 @@
<string name="pref_advanced_options">Erweiterte Optionen</string>
<string name="pref_never_send_crash">Sende niemals Absturzberichte</string>
<string name="pref_never_send_crash_summary">Wenn du Absturzberichte einschickst, hilfst du Conversations stetig zu verbessern</string>
- <string name="pref_confirm_messages">Lesebestätigung senden</string>
+ <string name="pref_confirm_messages">Lese- und Empfangsbestätigung senden</string>
<string name="pref_confirm_messages_summary">Informiere deine Kontakte, wenn du eine Nachricht empfangen und gelesen hast</string>
<string name="pref_ui_options">Benutzeroberfläche</string>
<string name="pref_parse_emoticons">Smilies ersetzen</string>
@@ -123,7 +123,7 @@
<string name="accept">Annehmen</string>
<string name="error">Ein unbekannter Fehler ist aufgetreten</string>
<string name="pref_grant_presence_updates">Online-Status</string>
- <string name="pref_grant_presence_updates_summary">Erlaube Kontakten, die von dir erstellt wurden, deinen Status zu sehen und frage um Erlaubnis, ihren sehen zu dürfen</string>
+ <string name="pref_grant_presence_updates_summary">Erlaube neu hinzugefügten Kontakten deinen online-Status zu sehen und frage um Erlaubnis, ihren sehen zu dürfen</string>
<string name="subscriptions">Abonnements</string>
<string name="your_account">Dein Konto</string>
<string name="keys">Schlüssel</string>
@@ -141,7 +141,7 @@
<string name="account_status_unknown">Unbekannt</string>
<string name="account_status_disabled">Vorübergehend abgeschaltet</string>
<string name="account_status_online">Online</string>
- <string name="account_status_connecting">Verbinde\u2026</string>
+ <string name="account_status_connecting">Verbinde…</string>
<string name="account_status_offline">Offline</string>
<string name="account_status_unauthorized">Ungültige Zugangsdaten</string>
<string name="account_status_not_found">Server nicht gefunden</string>
@@ -164,8 +164,8 @@
<string name="mgmt_account_are_you_sure">Bist du dir sicher?</string>
<string name="mgmt_account_delete_confirm_text">Wenn du dein Konto löschst, gehen alle Gesprächsverläufe verloren</string>
<string name="attach_record_voice">Sprache aufzeichnen</string>
- <string name="account_settings_jabber_id">Jabber-ID:</string>
- <string name="account_settings_password">Passwort:</string>
+ <string name="account_settings_jabber_id">Jabber-ID</string>
+ <string name="account_settings_password">Passwort</string>
<string name="account_settings_example_jabber_id">benutzer@domain.de</string>
<string name="account_settings_confirm_password">Passwort bestätigen</string>
<string name="password">Passwort</string>
@@ -174,7 +174,7 @@
<string name="invalid_jid">Ungültige Jabber-ID</string>
<string name="error_out_of_memory">Zu wenig Speicher vorhanden. Das Bild ist zu groß</string>
<string name="add_phone_book_text">Möchtest du %s zum Telefonbuch hinzufügen?</string>
- <string name="contact_status_online">Online</string>
+ <string name="contact_status_online">online</string>
<string name="contact_status_free_to_chat">Bereit</string>
<string name="contact_status_away">Abwesend</string>
<string name="contact_status_extended_away">Abwesend (erweitert)</string>
@@ -214,17 +214,17 @@
<string name="create_contact">Kontakt erstellen</string>
<string name="join_conference">Konferenz betreten</string>
<string name="delete_contact">Kontakt löschen</string>
- <string name="view_contact_details">Kontaktdetails anzeigen</string>
+ <string name="view_contact_details">Kontakt-Details anzeigen</string>
<string name="block_contact">Kontakt sperren</string>
<string name="unblock_contact">Kontakt entsperren</string>
<string name="create">Erstellen</string>
<string name="contact_already_exists">Der Kontakt existiert bereits</string>
<string name="join">Beitreten</string>
<string name="conference_address">Konferenz-Adresse</string>
- <string name="conference_address_example">raum@conference.example.com</string>
- <string name="save_as_bookmark">Als Lesezeichen speichern</string>
- <string name="delete_bookmark">Lesezeichen löschen</string>
- <string name="bookmark_already_exists">Das Lesezeichen existiert bereits</string>
+ <string name="conference_address_example">raum@conference.domain.de</string>
+ <string name="save_as_bookmark">Zur Kontaktliste hinzufügen</string>
+ <string name="delete_bookmark">Von Kontaktliste entfernen</string>
+ <string name="bookmark_already_exists">Die Konferenz befindet sich bereits auf deiner Kontaktliste</string>
<string name="you">Du</string>
<string name="action_edit_subject">Konferenz-Thema anpassen</string>
<string name="conference_not_found">Konferenz nicht gefunden</string>
@@ -241,9 +241,9 @@
<string name="error_saving_avatar">Kann Avatar nicht speichern.</string>
<string name="or_long_press_for_default">(Oder klicke lange, um Standard wiederherzustellen)</string>
<string name="error_publish_avatar_no_server_support">Dein Server unterstützt die Veröffentlichung von Avataren nicht.</string>
- <string name="private_message">private Nachricht</string>
- <string name="private_message_to">an %s:</string>
- <string name="send_private_message_to">Sende private Nachricht an %s</string>
+ <string name="private_message">private Nachricht:</string>
+ <string name="private_message_to">privat an %s:</string>
+ <string name="send_private_message_to">Sende private Nachricht an %s…</string>
<string name="connect">Verbinden</string>
<string name="account_already_exists">Das Konto existiert bereits</string>
<string name="next">Weiter</string>
@@ -280,7 +280,7 @@
<string name="pref_use_larger_font">Schrift vergrößern</string>
<string name="pref_use_larger_font_summary">Größere Schrift verwenden</string>
<string name="pref_use_send_button_to_indicate_status">Absende-Knopf zeigt Online-Status an</string>
- <string name="pref_use_indicate_received">Anfrage für Nachrichten Empfang</string>
+ <string name="pref_use_indicate_received">Anfrage für Nachrichtenempfang</string>
<string name="pref_use_indicate_received_summary">Empfangene Nachrichten werden mit einem grünen Häkchen markiert. Bitte beachte, dass dies nicht in allen Fällen funktioniert.</string>
<string name="pref_use_send_button_to_indicate_status_summary">Absende-Knopf einfärben, um den Online-Status des Kontakts zu signalisieren</string>
<string name="pref_expert_options_other">Sonstiges</string>
@@ -307,7 +307,7 @@
<string name="scan_qr_code">Scanne QR-Code</string>
<string name="show_qr_code">Zeige QR-Code</string>
<string name="show_block_list">Zeige Sperrliste</string>
- <string name="account_details">Konto Details</string>
+ <string name="account_details">Konto-Details</string>
<string name="verify_otr">Prüfe OTR</string>
<string name="remote_fingerprint">Fingerabdruck der Gegenseite</string>
<string name="scan">Scanne</string>
@@ -327,7 +327,7 @@
<string name="no_otr_session_found">Keine gültige OTR Sitzung gefunden!</string>
<string name="conversations_foreground_service">Conversations</string>
<string name="pref_keep_foreground_service">Den Dienst im Vordergrund ausführen.</string>
- <string name="pref_keep_foreground_service_summary">Verhindert, dass Android die Verbindung unterbricht</string>
+ <string name="pref_keep_foreground_service_summary">Verhindert, dass Android Conversations beendet und die Verbindung unterbricht</string>
<string name="choose_file">Datei auswählen</string>
<string name="receiving_x_file">Empfange %1$s (%2$d%% abgeschlossen)</string>
<string name="download_x_file">Lade %s herunter</string>
@@ -346,15 +346,15 @@
<string name="pref_show_dynamic_tags">Dynamische Tags anzeigen</string>
<string name="pref_show_dynamic_tags_summary">Zeige schreibgeschützte Tags unterhalb der Kontakte</string>
<string name="enable_notifications">Aktiviere Benachrichtigungen</string>
- <string name="conference_with">Starte Konferenz mit…</string>
+ <string name="conference_with">Beginne Konferenz mit…</string>
<string name="no_conference_server_found">Konferenz-Server kann nicht gefunden werden</string>
- <string name="conference_creation_failed">Starten der Konferenz fehlgeschlagen!</string>
+ <string name="conference_creation_failed">Beginnen der Konferenz fehlgeschlagen!</string>
<string name="conference_created">Konferenz erstellt!</string>
<string name="secret_accepted">Schlüssel akzeptiert!</string>
<string name="reset">Zurücksetzen</string>
<string name="account_image_description">Konto-Avatar</string>
<string name="copy_otr_clipboard_description">OTR-Fingerabdruck in Zwischenablage kopieren</string>
- <string name="fetching_history_from_server">Lade Chatverlauf</string>
+ <string name="fetching_history_from_server">Lade Chatverlauf…</string>
<string name="no_more_history_on_server">Keine weiteren Nachrichten vorhanden</string>
<string name="updating">Aktualisiere…</string>
<string name="password_changed">Passwort geändert.</string>
@@ -390,22 +390,22 @@
<string name="could_not_change_role">Rolle von %s konnte nicht geändert werden</string>
<string name="public_conference">Öffentlich zugängliche Konferenz</string>
<string name="private_conference">Private Konferenz nur für Mitglieder</string>
- <string name="conference_options">Konferenzoptionen</string>
+ <string name="conference_options">Konferenz-Optionen</string>
<string name="members_only">Privat (Nur für Mitglieder)</string>
<string name="non_anonymous">De-anonymisiert</string>
- <string name="modified_conference_options">Konferenzoptionen wurden modifiziert!</string>
- <string name="could_not_modify_conference_options">Konferenzoptionen konnten nicht modifiziert werden</string>
+ <string name="modified_conference_options">Konferenz-Optionen wurden modifiziert!</string>
+ <string name="could_not_modify_conference_options">Konferenz-Optionen konnten nicht modifiziert werden</string>
<string name="never">Niemals</string>
<string name="thirty_minutes">30 Minuten</string>
<string name="one_hour">1 Stunde</string>
<string name="two_hours">2 Stunden</string>
<string name="eight_hours">8 Stunden</string>
<string name="until_further_notice">Bis auf weiters</string>
- <string name="pref_input_options">Eingabeoptionen</string>
- <string name="pref_enter_is_send">Eingabetaste sendet Nachricht</string>
- <string name="pref_enter_is_send_summary">Benutze die Eingabetaste zum Senden einer Nachricht</string>
- <string name="pref_display_enter_key">Zeige Enter-Taste</string>
- <string name="pref_display_enter_key_summary">Zeige die Enter-Taste anstelle der Smiley-Taste</string>
+ <string name="pref_input_options">Eingabe-Optionen</string>
+ <string name="pref_enter_is_send">Eingabe-Taste (Enter) sendet Nachricht</string>
+ <string name="pref_enter_is_send_summary">Benutze die Eingabe-Taste (Enter) zum Senden einer Nachricht</string>
+ <string name="pref_display_enter_key">Zeige Eingabe-Taste (Enter)</string>
+ <string name="pref_display_enter_key_summary">Zeige die Eingabe-Taste (Enter) anstelle der Smiley-Taste</string>
<string name="audio">Audio</string>
<string name="video">Video</string>
<string name="image">Bild</string>
@@ -419,4 +419,18 @@
<string name="sending_x_file">Sende %s</string>
<string name="offering_x_file">%s angeboten</string>
<string name="hide_offline">verstecke offline</string>
+ <string name="disable_account">Konto abschalten</string>
+ <string name="contact_is_typing">%s schreibt…</string>
+ <string name="contact_has_stopped_typing">%s schreibt nicht mehr</string>
+ <string name="pref_chat_states">Tipp-Benachrichtigung</string>
+ <string name="pref_chat_states_summary">Informiere deine Kontakte, wenn du eine Nachricht eintippst.</string>
+ <string name="send_location">Standort senden</string>
+ <string name="show_location">Standort anzeigen</string>
+ <string name="no_application_found_to_display_location">Keine App für die Standort-Anzeige gefunden</string>
+ <string name="location">Standort</string>
+ <string name="received_location">Standort empfangen</string>
+ <plurals name="select_contact">
+ <item quantity="one">%d Kontakt ausgewählt</item>
+ <item quantity="other">%d Kontakte ausgewählt</item>
+ </plurals>
</resources>
diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml
index c757504a..48dc4034 100644
--- a/src/main/res/values-el/strings.xml
+++ b/src/main/res/values-el/strings.xml
@@ -1,2 +1,430 @@
<?xml version='1.0' encoding='UTF-8'?>
-<resources/>
+<resources>
+ <string name="action_settings">Ρυθμίσεις</string>
+ <string name="action_add">Νέα συζήτηση</string>
+ <string name="action_accounts">Διαχείριση λογαριασμών</string>
+ <string name="action_end_conversation">Λήξη συζήτησης</string>
+ <string name="action_contact_details">Λεπτομέρειες επαφής</string>
+ <string name="action_muc_details">Λεπτομέρειες συνδιάσκεψης</string>
+ <string name="action_secure">Ασφαλής συζήτηση</string>
+ <string name="action_add_account">Προσθήκη λογαριασμού</string>
+ <string name="action_edit_contact">Επεξεργασία ονόματος</string>
+ <string name="action_add_phone_book">Προσθήκη στον τηλεφωνικό κατάλογο</string>
+ <string name="action_delete_contact">Διαγραφή από τη λίστα επαφών</string>
+ <string name="action_block_contact">Αποκλεισμός επαφής</string>
+ <string name="action_unblock_contact">Άρση αποκλεισμού επαφής</string>
+ <string name="action_block_domain">Αποκλεισμός τομέα</string>
+ <string name="action_unblock_domain">Άρση αποκλεισμού τομέα</string>
+ <string name="title_activity_manage_accounts">Διαχείριση Λογαριασμών</string>
+ <string name="title_activity_settings">Ρυθμίσεις</string>
+ <string name="title_activity_conference_details">Λεπτομέρειες Συνδιάσκεψης</string>
+ <string name="title_activity_contact_details">Λεπτομέρειες Επαφής</string>
+ <string name="title_activity_sharewith">Διαμοιρασμός με Συζήτηση</string>
+ <string name="title_activity_start_conversation">Έναρξη Συζήτησης</string>
+ <string name="title_activity_choose_contact">Επιλογή επαφής</string>
+ <string name="title_activity_block_list">Αποκλεισμός λίστας</string>
+ <string name="just_now">μόλις τώρα</string>
+ <string name="minute_ago">πριν από 1 λεπτό</string>
+ <string name="minutes_ago">πριν από %d λεπτά</string>
+ <string name="unread_conversations">μη αναγνωσμένες Συζητήσεις</string>
+ <string name="sending">αποστολή...</string>
+ <string name="encrypted_message">Αποκρυπτογράφηση μηνύματος. Παρακαλώ περιμένετε...</string>
+ <string name="nick_in_use">Το ψευδώνυμο είναι ήδη σε χρήση</string>
+ <string name="admin">Διαχειριστής</string>
+ <string name="owner">Κάτοχος</string>
+ <string name="moderator">Συντονιστής</string>
+ <string name="participant">Συμμετέχων</string>
+ <string name="visitor">Επισκέπτης</string>
+ <string name="remove_contact_text">Θέλετε να αφαιρέσετε την επαφή %s από τη λίστα επαφών; Η συζήτηση που σχετίζεται με αυτή την επαφή δεν θα αφαιρεθεί.</string>
+ <string name="block_contact_text">Θέλετε να αποκλείσετε την επαφή %s από το να σας στέλνει μηνύματα;</string>
+ <string name="unblock_contact_text">Θέλετε να επιτρέψετε στην επαφή %s να σας στέλνει μηνύματα;</string>
+ <string name="block_domain_text">Αποκλεισμός όλων των επαφών από το %s;</string>
+ <string name="unblock_domain_text">Άρση αποκλεισμού όλων των επαφών από το %s;</string>
+ <string name="contact_blocked">Η επαφή αποκλείστηκε</string>
+ <string name="remove_bookmark_text">Θέλετε να αφαιρέσετε το %s ως σελιδοδείκτη; Η συζήτηση που σχετίζεται με αυτό τον σελιδοδείκτη δεν θα αφαιρεθεί.</string>
+ <string name="register_account">Εγγραφή νέου λογαριασμού στον διακομιστή</string>
+ <string name="change_password_on_server">Αλλαγή συνθηματικού στον διακομιστή</string>
+ <string name="share_with">Διαμοιρασμός με...</string>
+ <string name="start_conversation">Έναρξη Συζήτησης</string>
+ <string name="invite_contact">Πρόσκληση Επαφής</string>
+ <string name="contacts">Επαφές</string>
+ <string name="cancel">Ακύρωση</string>
+ <string name="set">Ορισμός</string>
+ <string name="add">Προσθήκη</string>
+ <string name="edit">Επεξεργασία</string>
+ <string name="delete">Αφαίρεση</string>
+ <string name="block">Αποκλεισμός</string>
+ <string name="unblock">Άρση αποκλεισμού</string>
+ <string name="save">Αποθήκευση</string>
+ <string name="ok">Εντάξει</string>
+ <string name="crash_report_title">Το Conversations έκλεισε απροσδόκητα</string>
+ <string name="crash_report_message">Στέλνοντας ίχνη στοίβας προωθείτε την συνεχή ανάπτυξη του Conversations\n<b>Προειδοποίηση:</b> Για την αποστολή του ίχνους στοίβας θα χρησιμοποιηθεί ο XMPP λογαριασμός σας.</string>
+ <string name="send_now">Αποστολή τώρα</string>
+ <string name="send_never">Χωρίς ερώτηση την επόμενη φορά</string>
+ <string name="problem_connecting_to_account">Αδύνατη η σύνδεση στον λογαριασμό</string>
+ <string name="problem_connecting_to_accounts">Αδύνατη η σύνδεση σε πολλαπλούς λογαριασμούς</string>
+ <string name="touch_to_fix">Επιλέξτε εδώ για να διαχειριστείτε τους λογαριασμούς σας</string>
+ <string name="attach_file">Επισύναψη αρχείου</string>
+ <string name="not_in_roster">Η επαφή δεν είναι στη λίστα επαφών σας. Θέλετε να την προσθέσετε;</string>
+ <string name="add_contact">Προσθήκη επαφής</string>
+ <string name="send_failed">η αποστολή απέτυχε</string>
+ <string name="send_rejected">απορρίφθηκε</string>
+ <string name="preparing_image">Προετοιμασία εικόνας για μετάδοση</string>
+ <string name="action_clear_history">Καθαρισμός ιστορικού</string>
+ <string name="clear_conversation_history">Καθαρισμός ιστορικού Συζήτησης</string>
+ <string name="clear_histor_msg">Θέλετε να σβήσετε όλα τα μηνύματα αυτής της Συζήτησης;\n\n<b>Προειδοποίηση:</b> Αυτό δεν θα επηρεάσει τα μηνύματα που είναι αποθηκευμένα σε άλλες συσκευές ή άλλους διακομιστές.</string>
+ <string name="delete_messages">Διαγραφή μηνυμάτων</string>
+ <string name="also_end_conversation">Τερματισμός αυτής της συζήτησης αμέσως μετά</string>
+ <string name="choose_presence">Επιλέξτε παρουσία για επικοινωνία</string>
+ <string name="send_plain_text_message">Αποστολή απλού μηνύματος κειμένου</string>
+ <string name="send_otr_message">Αποστολή κρυπτογραφημένου μηνύματος OTR</string>
+ <string name="send_pgp_message">Αποστολή κρυπτογραφημένου μηνύματος OpenPGP</string>
+ <string name="your_nick_has_been_changed">Το ψευδώνυμό σας έχει αλλάξει</string>
+ <string name="download_image">Μεταφόρτωση εικόνας</string>
+ <string name="send_unencrypted">Αποστολή χωρίς κρυπτογράφηση</string>
+ <string name="decryption_failed">Η αποκρυπτογράφηση απέτυχε. Ίσως δεν κατέχετε το σωστό ιδιωτικό κλειδί.</string>
+ <string name="openkeychain_required">OpenKeychain</string>
+ <string name="openkeychain_required_long">Το Conversations χρησιμοποιεί μια τρίτη εφαρμογή που ονομάζεται <b>OpenKeychain</b> για να κρυπτογραφεί και να αποκρυπτογραφεί μηνύματα και να διαχειρίζεται τα δημόσια κλειδιά σας.\n\nΤο OpenKeychain δημοσιεύεται με την άδεια GPLv3 και είναι διαθέσιμο στο F-Droid και το Google Play.\n\n<small>(Παρακαλώ επανεκκινήστε το Conversations αμέσως μετά.)</small></string>
+ <string name="restart">Επανεκκίνηση</string>
+ <string name="install">Εγκατάσταση</string>
+ <string name="offering">προσφορά...</string>
+ <string name="waiting">αναμονή...</string>
+ <string name="no_pgp_key">Δεν βρέθηκε κλειδί OpenPGP</string>
+ <string name="contact_has_no_pgp_key">Το Conversations αδυνατεί να κρυπτογραφήσει τα μηνύματά σας γιατί η επαφή σας δεν ανακοινώνει το δημόσιο κλειδί της.\n\n<small>Παρακαλώ ζητήστε από την επαφή σας να εγκαταστήσει το OpenPGP.</small></string>
+ <string name="no_pgp_keys">Δεν βρέθηκαν κλειδιά OpenPGP</string>
+ <string name="contacts_have_no_pgp_keys">Το Conversations αδυνατεί να κρυπτογραφήσει τα μηνύματά σας γιατί οι επαφές σας δεν ανακοινώνουν το δημόσιο κλειδί τους.\n\n<small>Παρακαλώ ζητήστε από τις επαφές σας να εγκαταστήσουν το OpenPGP.</small></string>
+ <string name="encrypted_message_received"><i>Λήψη κρυπτογραφημένου μηνύματος. Επιλέξτε για ανάγνωση και αποκρυπτογράφηση.</i></string>
+ <string name="pref_general">Γενικά</string>
+ <string name="pref_xmpp_resource">πόρος XMPP</string>
+ <string name="pref_xmpp_resource_summary">Το όνομα με το οποίο ταυτοποιείται αυτό το πρόγραμμα-πελάτης</string>
+ <string name="pref_accept_files">Αποδοχή αρχείων</string>
+ <string name="pref_accept_files_summary">Αυτόματη αποδοχή αρχείων μικρότερα από...</string>
+ <string name="pref_notification_settings">Επιλογές ειδοποιήσεων</string>
+ <string name="pref_notifications">Ειδοποιήσεις</string>
+ <string name="pref_notifications_summary">Ειδοποίηση όταν λαμβάνεται ένα νέο μήνυμα</string>
+ <string name="pref_vibrate">Δόνηση</string>
+ <string name="pref_vibrate_summary">Επίσης δόνηση όταν έρχεται ένα νέο μήνυμα</string>
+ <string name="pref_sound">Ήχος</string>
+ <string name="pref_sound_summary">Αναπαραγωγή ήχου κλήσης με την ειδοποίηση</string>
+ <string name="pref_conference_notifications">Ειδοποιήσεις συνδιασκέψεων</string>
+ <string name="pref_conference_notifications_summary">Ειδοποίηση πάντα όταν ένα νέο μήνυμα συνδιάσκεψης λαμβάνεται αντί για μόνο όταν τονίζεται</string>
+ <string name="pref_notification_grace_period">Περίοδος χάριτος ειδοποιήσεων</string>
+ <string name="pref_notification_grace_period_summary">Απενεργοποίηση ειδοποιήσεων για λίγο χρόνο μετά από τη λήψη ακριβούς αντιγράφου</string>
+ <string name="pref_advanced_options">Προχωρημένες επιλογές</string>
+ <string name="pref_never_send_crash">Να μην αποστέλλονται αναφορές λαθών</string>
+ <string name="pref_never_send_crash_summary">Στέλνοντας ίχνη στοίβας βοηθάτε την συνεχόμενη ανάπτυξη του Conversations</string>
+ <string name="pref_confirm_messages">Επιβεβαίωση μηνυμάτων</string>
+ <string name="pref_confirm_messages_summary">Επιτρέψτε στην επαφή σας να ειδοποιείται όταν έχετε λάβει και διαβάσει ένα μήνυμα</string>
+ <string name="pref_ui_options">Επιλογές διεπαφής χρήστη</string>
+ <string name="openpgp_error">Το OpenKeychain ανέφερε κάποιο σφάλμα</string>
+ <string name="error_decrypting_file">Σφάλμα εισόδου/εξόδου κατά την αποκρυπτογράφηση αρχείου</string>
+ <string name="accept">Αποδοχή</string>
+ <string name="error">Έχει συμβεί κάποιο σφάλμα</string>
+ <string name="pref_grant_presence_updates">Χορήγηση ενημερώσεων παρουσίας</string>
+ <string name="pref_grant_presence_updates_summary">Ερήμην χορήγηση και παράκληση για συνδρομή παρουσίας στις επαφές που δημιουργείτε</string>
+ <string name="subscriptions">Συνδρομές</string>
+ <string name="your_account">Ο λογαριασμός σας</string>
+ <string name="keys">Κλειδιά</string>
+ <string name="send_presence_updates">Αποστολή ενημερώσεων παρουσίας</string>
+ <string name="receive_presence_updates">Λήψη ενημερώσεων παρουσίας</string>
+ <string name="ask_for_presence_updates">Παράκληση για ενημερώσεις παρουσίας</string>
+ <string name="attach_choose_picture">Επιλογή εικόνας</string>
+ <string name="attach_take_picture">Λήψη εικόνας</string>
+ <string name="preemptively_grant">Ερήμην χορήγηση αίτησης συνδρομής</string>
+ <string name="error_not_an_image_file">Το αρχείο που επιλέξατε δεν είναι εικόνα</string>
+ <string name="error_compressing_image">Σφάλμα κατά τη μετατροπή του αρχείου εικόνας</string>
+ <string name="error_file_not_found">Το αρχείο δεν βρέθηκε</string>
+ <string name="error_io_exception">Γενικό σφάλμα εισόδου/εξόδου. Ίσως δεν έχετε ελεύθερο χώρο αποθήκευσης;</string>
+ <string name="error_security_exception_during_image_copy">Η εφαρμογή που χρησιμοποιήσατε για να επιλέξετε αυτή την εικόνα δεν μας παραχώρησε αρκετά δικαιώματα για την ανάγνωση του αρχείου.\n\n<small>Χρησιμοποιήστε διαφορετικό διαχειριστή αρχείων για να επιλέξετε μια εικόνα</small></string>
+ <string name="account_status_unknown">Άγνωστο</string>
+ <string name="account_status_disabled">Προσωρινά απενεργοποιημένο</string>
+ <string name="account_status_online">Συνδεμένος</string>
+ <string name="account_status_connecting">Σύνδεση\u2026</string>
+ <string name="account_status_offline">Εκτός σύνδεσης</string>
+ <string name="account_status_unauthorized">Χωρίς εξουσιοδότηση</string>
+ <string name="account_status_not_found">Δεν βρέθηκε ο διακομιστής</string>
+ <string name="account_status_no_internet">Χωρίς σύνδεση</string>
+ <string name="account_status_regis_fail">Η εγγραφή απέτυχε</string>
+ <string name="account_status_regis_conflict">Το όνομα χρησιμοποιείται ήδη</string>
+ <string name="account_status_regis_success">Ολοκλήρωση εγγραφής</string>
+ <string name="account_status_regis_not_sup">Ο διακομιστής δεν υποστηρίζει εγγραφή</string>
+ <string name="account_status_security_error">Σφάλμα ασφάλειας</string>
+ <string name="account_status_incompatible_server">Μη συμβατός διακομιστής</string>
+ <string name="encryption_choice_none">Απλό κείμενο</string>
+ <string name="encryption_choice_otr">OTR</string>
+ <string name="encryption_choice_pgp">OpenPGP</string>
+ <string name="mgmt_account_edit">Επεξεργασία λογαριασμού</string>
+ <string name="mgmt_account_delete">Διαγραφή λογαριασμού</string>
+ <string name="mgmt_account_disable">Προσωρινά μη διαθέσιμο</string>
+ <string name="mgmt_account_publish_avatar">Δημοσίευση εικόνας προφίλ</string>
+ <string name="mgmt_account_publish_pgp">Δημοσίευση του δημόσιου κλειδιού OpenPGP</string>
+ <string name="mgmt_account_enable">Ενεργοποίηση λογαριασμού</string>
+ <string name="mgmt_account_are_you_sure">Είστε βέβαιοι;</string>
+ <string name="mgmt_account_delete_confirm_text">Αν διαγράψετε τον λογαριασμό σας, θα χαθεί όλο το ιστορικό συζητήσεών σας</string>
+ <string name="attach_record_voice">Εγγραφή φωνής</string>
+ <string name="account_settings_jabber_id">ταυτότητα Jabber</string>
+ <string name="account_settings_password">Συνθηματικό</string>
+ <string name="account_settings_example_jabber_id">username@example.com</string>
+ <string name="account_settings_confirm_password">Επιβεβαίωση συνθηματικού</string>
+ <string name="password">Συνθηματικό</string>
+ <string name="confirm_password">Επιβεβαίωση συνθηματικού</string>
+ <string name="passwords_do_not_match">Τα συνθηματικά δεν ταιριάζουν</string>
+ <string name="invalid_jid">Αυτή δεν είναι έγκυρη ταυτότητα Jabber</string>
+ <string name="error_out_of_memory">Πλήρης μνήμη. Η εικόνα είναι πολύ μεγάλη</string>
+ <string name="add_phone_book_text">Θέλετε να προσθέσετε την επαφή %s στον τηλεφωνικό κατάλογο του τηλεφώνου σας;</string>
+ <string name="contact_status_online">συνδεμένος</string>
+ <string name="contact_status_free_to_chat">ελεύθερος για συνομιλία</string>
+ <string name="contact_status_away">λείπω</string>
+ <string name="contact_status_extended_away">παρατεταμένη απουσία</string>
+ <string name="contact_status_do_not_disturb">μην ενοχλείτε</string>
+ <string name="contact_status_offline">εκτός σύνδεσης</string>
+ <string name="muc_details_conference">Συνδιάσκεψη</string>
+ <string name="muc_details_other_members">Άλλα μέλη</string>
+ <string name="server_info_show_more">Πληροφορίες διακομιστή</string>
+ <string name="server_info_mam">XEP-0313: Διαχείριση αρχείου μηνυμάτων</string>
+ <string name="server_info_carbon_messages">XEP-0280: Αντίγραφα μηνυμάτων</string>
+ <string name="server_info_csi">XEP-0352: Ένδειξη κατάστασης πελάτη</string>
+ <string name="server_info_blocking">XEP-0191: Εντολή αποκλεισμού</string>
+ <string name="server_info_roster_version">XEP-0237: Διατήρηση εκδόσεων λίστας επαφών</string>
+ <string name="server_info_stream_management">XEP-0198: Διαχείριση ροών</string>
+ <string name="server_info_pep">XEP-0163: Πρωτόκολλο προσωπικών συμβάντων (εικόνες προφίλ)</string>
+ <string name="server_info_available">διαθέσιμος</string>
+ <string name="server_info_unavailable">μη διαθέσιμος</string>
+ <string name="missing_public_keys">Ελλειπείς ανακοινώσεις δημοσίων κλειδιών</string>
+ <string name="last_seen_now">συνδέθηκε τελευταία φορά μόλις τώρα</string>
+ <string name="last_seen_min">τελευταία σύνδεση πριν από 1 λεπτό</string>
+ <string name="last_seen_mins">τελευταία σύνδεση πριν από %d λεπτά</string>
+ <string name="last_seen_hour">τελευταία σύνδεση πριν από 1 ώρα</string>
+ <string name="last_seen_hours">τελευταία σύνδεση πριν από %d ώρες</string>
+ <string name="last_seen_day">τελευταία σύνδεσ πριν από 1 μέρα</string>
+ <string name="last_seen_days">τελευταία σύνδεση πριν από %d μέρες</string>
+ <string name="never_seen">δεν έχει συνδεθεί ποτέ</string>
+ <string name="install_openkeychain">Κρυπτογραφημένο μήνυμα. Παρακαλώ εγκαταστήστε το OpenKeychain για αποκρυπτογράφηση</string>
+ <string name="unknown_otr_fingerprint">Άγνωστο αποτύπωμα OTR</string>
+ <string name="openpgp_messages_found">Βρέθηκαν μηνύματα κρυπτογραφημένα με OpenPGP</string>
+ <string name="reception_failed">Η λήψη απέτυχε</string>
+ <string name="your_fingerprint">Το αποτύπωμά σας</string>
+ <string name="otr_fingerprint">Αποτύπωμα OTR</string>
+ <string name="verify">Επαλήθευση</string>
+ <string name="decrypt">Αποκρυπτογράφηση</string>
+ <string name="conferences">Συνδιασκέψεις</string>
+ <string name="search">Αναζήτηση</string>
+ <string name="create_contact">Δημιουργία επαφής</string>
+ <string name="join_conference">Συμμετοχή σε συνδιάσκεψη</string>
+ <string name="delete_contact">Διαγραφή επαφής</string>
+ <string name="view_contact_details">Λεπτομέρειες επαφής</string>
+ <string name="block_contact">Αποκλεισμός επαφής</string>
+ <string name="unblock_contact">Άρση αποκλεισμού επαφής</string>
+ <string name="create">Δημιουργία</string>
+ <string name="contact_already_exists">Η επαφή υπάρχει ήδη</string>
+ <string name="join">Συμμετοχή</string>
+ <string name="conference_address">Διεύθυνση συνδιάσκεψης</string>
+ <string name="conference_address_example">room@conference.example.com</string>
+ <string name="save_as_bookmark">Αποθήκευση σαν σελιδοδείκτη</string>
+ <string name="delete_bookmark">Διαγραφή σελιδοδείκτη</string>
+ <string name="bookmark_already_exists">Αυτός ο σελιδοδείκτης υπάρχει ήδη</string>
+ <string name="you">Εσείς</string>
+ <string name="action_edit_subject">Επεξεργασία θέματος συνδιάσκεψης</string>
+ <string name="conference_not_found">Η συνδιάσκεψη δεν βρέθηκε</string>
+ <string name="leave">Έξοδος</string>
+ <string name="contact_added_you">Η επαφή σας πρόσθεσε στην λίστα επαφών</string>
+ <string name="add_back">Προσθήκη επίσης</string>
+ <string name="contact_has_read_up_to_this_point">Η επαφή %s έχει διαβάσει μέχρι αυτό το σημείο</string>
+ <string name="publish">Δημοσίευση</string>
+ <string name="touch_to_choose_picture">Επιλέξτε την εικόνα προφίλ για να διαλέξετε εικόνα από την έκθεση</string>
+ <string name="publish_avatar_explanation">Παρακαλώ σημειώστε: Όλοι όσοι είναι συνδρομητές στις ενημερώσεις παρουσίας σας θα έχουν την δυνατότητα να δουν αυτή την εικόνα.</string>
+ <string name="publishing">Δημοσίευση...</string>
+ <string name="error_publish_avatar_server_reject">Ο διακομιστής απέρριψε την δημοσίευσή σας</string>
+ <string name="error_publish_avatar_converting">Κάτι πήγε στραβά κατά τη μετατροπή της εικόνας σας</string>
+ <string name="error_saving_avatar">Αδύνατη η αποθήκευση της εικόνας προφίλ στο δίσκο</string>
+ <string name="or_long_press_for_default">(Ή πατήστε παρατεταμένα για να επιστρέψετε στο αρχικό)</string>
+ <string name="error_publish_avatar_no_server_support">Ο διακομιστής σας δεν υποστηρίζει την δημοσίευση εικονών προφίλ</string>
+ <string name="private_message">ψιθύρισε</string>
+ <string name="private_message_to">στο %s</string>
+ <string name="send_private_message_to">Αποστολή ιδιωτικού μηνύματος στην επαφή %s</string>
+ <string name="connect">Σύνδεση</string>
+ <string name="account_already_exists">Αυτός ο λογαριασμός υπάρχει ήδη</string>
+ <string name="next">Επόμενος</string>
+ <string name="server_info_session_established">Σύσταση συνεδρίας</string>
+ <string name="additional_information">Επιπλέον πληροφορίες</string>
+ <string name="skip">Παράλειψη</string>
+ <string name="disable_notifications">Απενεργοποίηση ειδοποιήσεων</string>
+ <string name="disable_notifications_for_this_conversation">Απενεργοποίηση ειδοποιήσεων για αυτή την συζήτηση</string>
+ <string name="notifications_disabled">Οι ειδοποιήσεις είναι απενεργοποιημένες</string>
+ <string name="enable">Ενεργοποίηση</string>
+ <string name="conference_requires_password">Η συζήτηση απαιτεί συνθηματικό</string>
+ <string name="enter_password">Εισαγωγή συνθηματικού</string>
+ <string name="missing_presence_updates">Ελλειπείς ενημερώσεις παρουσίας για την επαφή</string>
+ <string name="request_presence_updates">Παρακαλώ αιτηθείτε ενημερώσεις παρουσίας από την επαφή σας πρώτα.\n\n<small>Αυτό θα χρησιμοποιηθεί για να ταυτοποιηθεί το πρόγραμμα-πελάτης που χρησιμοποιεί η επαφή σας.</small></string>
+ <string name="request_now">Αίτηση τώρα</string>
+ <string name="delete_fingerprint">Διαγραφή αποτυπώματος</string>
+ <string name="sure_delete_fingerprint">Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το αποτύπωμα;</string>
+ <string name="ignore">Αγνόηση</string>
+ <string name="without_mutual_presence_updates"><b>Προειδοποίηση:</b> Η αποστολή αυτού χωρίς αμφίδρομες ενημερώσεις παρουσίας μπορεί να προκαλέσει απροσδόκητα προβλήματα.\n\n<small>Πηγαίνετε στις λεπτομέρειες επαφής για να επαληθεύσετε τις συνδρομές παρουσίας σας.</small></string>
+ <string name="pref_encryption_settings">Ρυθμίσεις κρυπτογράφησης</string>
+ <string name="pref_force_encryption">Επιβολή κρυπτογράφησης από άκρη σε άκρη</string>
+ <string name="pref_force_encryption_summary">Πάντα αποστολή κρυπτογραφημένων μηνυμάτων (εκτός από συνδιασκέψεις)</string>
+ <string name="pref_dont_save_encrypted">Χωρίς αποθήκευση κρυπτογραφημένων μηνυμάτων</string>
+ <string name="pref_dont_save_encrypted_summary">Προειδοποίηση: Αυτό μπορεί να οδηγήσει σε απώλεια μηνυμάτων</string>
+ <string name="pref_expert_options">Επιλογές για προχωρημένους</string>
+ <string name="pref_expert_options_summary">Παρακαλώ να είστε προσεκτικοί με αυτά</string>
+ <string name="title_activity_about">Σχετικά με το Conversations</string>
+ <string name="pref_about_conversations_summary">Πληροφορίες δημιουργίας και αδειών</string>
+ <string name="title_pref_quiet_hours">Ώρες ησυχίας</string>
+ <string name="title_pref_quiet_hours_start_time">Ώρα έναρξης</string>
+ <string name="title_pref_quiet_hours_end_time">Ώρα λήξης</string>
+ <string name="title_pref_enable_quiet_hours">Ενεργοποίηση ωρών ησυχίας</string>
+ <string name="pref_quiet_hours_summary">Οι ειδοποιήσεις θα σιγαστούν κατά τις ώρες ησυχίας</string>
+ <string name="pref_use_larger_font">Αύξηση μεγέθους γραμματοσειράς</string>
+ <string name="pref_use_larger_font_summary">Χρήση μεγαλύτερου μεγέθους γραμματοσειράς για όλη την εφαρμογή</string>
+ <string name="pref_use_send_button_to_indicate_status">Το κουμπί αποστολής υποδηλώνει την κατάσταση</string>
+ <string name="pref_use_indicate_received">Αίτηση για αποδείξεις μηνυμάτων</string>
+ <string name="pref_use_indicate_received_summary">Τα ληφθέντα μηνύματα θα σημειώνονται με ένα πράσινο τικ, αν αυτό υποστηρίζεται</string>
+ <string name="pref_use_send_button_to_indicate_status_summary">Χρωματισμός του κουμπιού αποστολής ως ένδειξη κατάστασης επαφής</string>
+ <string name="pref_expert_options_other">Άλλο</string>
+ <string name="pref_conference_name">Όνομα συνδιάσκεψης</string>
+ <string name="pref_conference_name_summary">Χρήση του θέματος δωματίου αντί για την ταυτότητα Jabber για την ταυτοποίηση συνδιασκέψεων</string>
+ <string name="toast_message_otr_fingerprint">Το αποτύπωμα OTR αντιγράφηκε στο πρόχειρο!</string>
+ <string name="conference_banned">Είστε αποκλεισμένοι από αυτή τη συνδιάσκεψη</string>
+ <string name="conference_members_only">Αυτή η συνδιάσκεψη είναι μόνο για μέλη</string>
+ <string name="conference_kicked">Έχετε διωχθει από αυτή την συνδιάσκεψη</string>
+ <string name="using_account">χρήση λογαριασμού %s</string>
+ <string name="checking_image">Έλεγχος εικόνας στον διακομιστή HTTP</string>
+ <string name="image_file_deleted">Το αρχείο εικόνας έχει διαγραφεί</string>
+ <string name="not_connected_try_again">Δεν είστε συνδεμένοι. Δοκιμάστε ξανά αργότερα</string>
+ <string name="check_image_filesize">Ελέγξτε το μέγεθος του αρχείου εικόνας</string>
+ <string name="message_options">Επιλογές μηνυμάτων</string>
+ <string name="copy_text">Αντιγραφή κειμένου</string>
+ <string name="copy_original_url">Αντιγραφή αρχικής διεύθυνσης URL</string>
+ <string name="send_again">Αποστολή ξανά</string>
+ <string name="image_url">Διεύθυνση URL εικόνας</string>
+ <string name="message_text">Κείμενο μηνύματος</string>
+ <string name="url_copied_to_clipboard">Η διεύθυνση URL αντιγράφηκε στο πρόχειρο</string>
+ <string name="message_copied_to_clipboard">Το μήνυμα αντιγράφηκε στο πρόχειρο</string>
+ <string name="image_transmission_failed">Η μετάδοση της εικόνας απέτυχε</string>
+ <string name="scan_qr_code">Σάρωση κωδικού QR</string>
+ <string name="show_qr_code">Εμφάνιση κωδικού QR</string>
+ <string name="show_block_list">Εμφάνιση λίστας αποκλεισμένων</string>
+ <string name="account_details">Λεπτομέρειες λογαριασμού</string>
+ <string name="verify_otr">Επαλήθευση OTR</string>
+ <string name="remote_fingerprint">Απομακρυσμένο αποτύπωμα</string>
+ <string name="scan">σάρωση</string>
+ <string name="or_touch_phones">(ή τηλέφωνα επαφής)</string>
+ <string name="smp">Πρωτόκολλο Socialist Millionaire</string>
+ <string name="shared_secret_hint">Υπαινιγμός ή ερώτηση</string>
+ <string name="shared_secret_secret">Κοινό μυστικό</string>
+ <string name="confirm">Επιβεβαίωση</string>
+ <string name="in_progress">Σε εξέλιξη</string>
+ <string name="respond">Απάντηση</string>
+ <string name="failed">Αποτυχία</string>
+ <string name="secrets_do_not_match">Τα μυστικά δεν ταιριάζουν</string>
+ <string name="try_again">Επανάληψη</string>
+ <string name="finish">Τέλος</string>
+ <string name="verified">Επαληθέυτηκε!</string>
+ <string name="smp_requested">Η επαφή ζήτησε επαλήθευση SMP</string>
+ <string name="no_otr_session_found">Δεν βρέθηκε έγκυρη συνεδρία OTR!</string>
+ <string name="conversations_foreground_service">Συζητήσεις</string>
+ <string name="pref_keep_foreground_service">Διατήρηση της υπηρεσίας στο προσκήνιο</string>
+ <string name="pref_keep_foreground_service_summary">Αποτρέπει τον τερματισμό της σύνδεσης από το λειτουργικό σύστημα</string>
+ <string name="choose_file">Επιλογή αρχείου</string>
+ <string name="receiving_x_file">Λήψη %1$s (ολοκληρώθηκε %2$d%%)</string>
+ <string name="download_x_file">Μεταφόρτωση του %s</string>
+ <string name="file">αρχείο</string>
+ <string name="open_x_file">Άνοιγμα του %s</string>
+ <string name="sending_file">αποστολή (ολοκλήρωση %1$d%%)</string>
+ <string name="preparing_file">Προετοιμασία του αρχείου για μετάδοση</string>
+ <string name="x_file_offered_for_download">το %s προσφέρθηκε για μεταφόρτωση</string>
+ <string name="cancel_transmission">Ακύρωση μετάδοσης</string>
+ <string name="file_transmission_failed">η μετάδοση του αρχείου απέτυχε</string>
+ <string name="file_deleted">Το αρχείο έχει διαγραφεί</string>
+ <string name="no_application_found_to_open_file">Δεν βρέθηκε εφαρμογή για να ανοίξει το αρχείο</string>
+ <string name="could_not_verify_fingerprint">Δεν ήταν δυνατή η επαλήθευση του αποτυπώματος</string>
+ <string name="manually_verify">Χειροκίνητη επαλήθευση</string>
+ <string name="are_you_sure_verify_fingerprint">Είστε βέβαιοι ότι θέλετε να επαληθεύσετε το αποτύπωμα OTR της επαφής σας;</string>
+ <string name="pref_show_dynamic_tags">Εμφάνιση δυναμικών ετικετών</string>
+ <string name="pref_show_dynamic_tags_summary">Εμφάνιση ετικετών μόνο για ανάγνωση κάτω από τις επαφές</string>
+ <string name="enable_notifications">Ενεργοποίηση ειδοποιήσεων</string>
+ <string name="conference_with">Δημιουργία συνδιάσκεψης με...</string>
+ <string name="no_conference_server_found">Δεν βρέθηκε διακομιστής συνδιάσκεψης</string>
+ <string name="conference_creation_failed">Η δημιουργία συνδιάσκεψης απέτυχε!</string>
+ <string name="conference_created">Η συνδιάσκεψη δημιουργήθηκε!</string>
+ <string name="secret_accepted">Το μυστικό έγινε δεκτό!</string>
+ <string name="reset">Επαναφορά</string>
+ <string name="account_image_description">Εικόνα προφίλ λογαριασμού</string>
+ <string name="copy_otr_clipboard_description">Αντιγραφή αποτυπώματος OTR στο πρόχειρο</string>
+ <string name="fetching_history_from_server">Ανάκτηση ιστορικού από τον διακομιστή</string>
+ <string name="no_more_history_on_server">Δεν υπάρχει άλλο ιστορικό στον διακομιστή</string>
+ <string name="updating">Ενημέρωση...</string>
+ <string name="password_changed">Επιτυχής αλλαγή συνθηματικου!</string>
+ <string name="could_not_change_password">Δεν ήταν δυνατή η αλλαγή του συνθηματικού</string>
+ <string name="otr_session_not_started">Αποστολή μηνύματος για την έναρξη κρυπτογραφημένης συνομιλίας</string>
+ <string name="ask_question">Ερώτηση</string>
+ <string name="smp_explain_question">Αν εσείς και η επαφή σας έχετε ένα κοινό μυστικό που κανείς άλλος δεν γνωρίζει (κάτι σαν δικό σας αστείο ή απλώς τι φάγατε την τελευταία φορά που συναντηθήκατε) μπορείτε να χρησιμοποιήσετε αυτό το μυστικό για νε επαληθεύσετε τα αποτυπώματά σας.\n\nΠροσφέρετε έναν υπαινιγμό ή μια ερώτηση για την επαφή σας, που θα απαντήσει με μια φράση στην οποία έχουν διαφοροποίηση τα πεζά από τα κεφαλαία.</string>
+ <string name="smp_explain_answer">Η επαφή σας θα ήθελε να επαληθεύσει το αποτύπωμά σας χρησιμοποιώντας ένα κοινό μυστικό. Η επαφή σας προμήθευση τον παρακάτω υπαινιγμό ή ερώτηση για το μυστικό αυτό.</string>
+ <string name="shared_secret_hint_should_not_be_empty">Ο υπαινιγμός σας δεν μπορεί να είναι κενός</string>
+ <string name="shared_secret_can_not_be_empty">Το κοινό μυστικό σας δεν μπορεί να είναι κενό</string>
+ <string name="manual_verification_explanation">Συγκρίνετε προσεκτικά το αποτύπωμα που φαίνεται παρακάτω με το αποτύπωμα της επαφής σας.\nΜπορείτε να χρησιμοποιήσετε οποιαδήποτε μορφή έμπιστης επικοινωνίας, όπως ένα κρυπτογραφημένο e-mail ή μια τηλεφωνική κλήση για να τα ανταλλάξετε.</string>
+ <string name="change_password">Αλλαγή συνθηματικού</string>
+ <string name="current_password">Τρέχον συνθηματικό</string>
+ <string name="new_password">Νέο συνθηματικό</string>
+ <string name="password_should_not_be_empty">Το συνθηματικό δε μπορεί να είναι κενό</string>
+ <string name="enable_all_accounts">Ενεργοποίηση όλων των λογαριασμών</string>
+ <string name="disable_all_accounts">Απενεργοποίηση όλων των λογαριασμών</string>
+ <string name="perform_action_with">Εκτέλεση ενέργειας με</string>
+ <string name="no_affiliation">Χωρίς δεσμό</string>
+ <string name="no_role">Χωρίς ρόλο</string>
+ <string name="outcast">Απόκληρος</string>
+ <string name="member">Μέλος</string>
+ <string name="advanced_mode">Κατάσταση για προχωρημένους</string>
+ <string name="grant_membership">Απόδοση ιδιότητας μέλους</string>
+ <string name="remove_membership">Ανάκληση ιδιότητας μέλους</string>
+ <string name="grant_admin_privileges">Απόδοση δικαιωμάτων διαχειριστή</string>
+ <string name="remove_admin_privileges">Ανάκληση δικαιωμάτων διαχειριστή</string>
+ <string name="remove_from_room">Αφαίρεση από την συνδιάσκεψη</string>
+ <string name="could_not_change_affiliation">Δεν ήταν δυνατή η αλλαγή του δεσμού της επαφής %s</string>
+ <string name="ban_from_conference">Αποκλεισμός από συνδιάσκεψη</string>
+ <string name="removing_from_public_conference">Προσπαθείτε να αφαιρέσετε την επαφή %s από μια δημόσια συνδιασκεψη. Ο μόνος τρόπος να γίνει αυτό είναι να αποκλείσετε αυτόν τον χρήστη για πάντα.</string>
+ <string name="ban_now">Αποκλεισμός τώρα</string>
+ <string name="could_not_change_role">Δεν ήταν δυνατή η αλλαγή ρόλου της επαφής %s</string>
+ <string name="public_conference">Συνδιάσκεψη δημόσιας πρόσβασης</string>
+ <string name="private_conference">Ιδιωτική συνδιάσκεψη, μόνο για μέλη</string>
+ <string name="conference_options">Επιλογές συνδιάσκεψης</string>
+ <string name="members_only">Ιδιωτική (μόνο για μέλη)</string>
+ <string name="non_anonymous">Μη-ανώνυμα</string>
+ <string name="modified_conference_options">Μεταβολή των επιλογών συνδιάσκεψης!</string>
+ <string name="could_not_modify_conference_options">Δεν ήταν δυνατή η μεταβολή των επιλογών συνδιάσκεψης</string>
+ <string name="never">Ποτέ</string>
+ <string name="thirty_minutes">30 λεπτά</string>
+ <string name="one_hour">1 ώρα</string>
+ <string name="two_hours">2 ώρες</string>
+ <string name="eight_hours">8 ώρες</string>
+ <string name="until_further_notice">Μέχρι νεωτέρας</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>
+ <string name="video">βίντεο</string>
+ <string name="image">εικόνα</string>
+ <string name="pdf_document">έγγραφο PDF</string>
+ <string name="apk">Εφαρμογή Android</string>
+ <string name="vcard">Επαφή</string>
+ <string name="received_x_file">Λήψη του %s</string>
+ <string name="disable_foreground_service">Απενεργοποίηση της υπηρεσίας στο προσκήνιο</string>
+ <string name="touch_to_open_conversations">Επιλέξτε για να ανοίξετε το Conversations</string>
+ <string name="avatar_has_been_published">Η εικόνα προφίλ έχει δημοσιευτεί!</string>
+ <string name="sending_x_file">Αποστολή του %s</string>
+ <string name="offering_x_file">Προσφορά του %s</string>
+ <string name="hide_offline">Απόκρυψη των εκτός σύνδεσης</string>
+ <string name="disable_account">Απενεργοποίηση λογαριασμού</string>
+ <string name="contact_is_typing">Ο χρήστης %s γράφει...</string>
+ <string name="contact_has_stopped_typing">Ο χρήστης %s σταμάτησε να γράφει</string>
+ <string name="pref_chat_states">Ειδοποιήσεις πληκτρολόγησης</string>
+ <string name="pref_chat_states_summary">Επιτρέψτε στην επαφή σας να γνωρίζει πότε γράφετε ένα νέο μήνυμα</string>
+ <string name="send_location">Αποστολή τοποθεσίας</string>
+ <string name="show_location">Εμφάνιση τοποθεσίας</string>
+ <string name="no_application_found_to_display_location">Δεν βρέθηκε εφαρμογή για την απεικόνιση τοποθεσίας</string>
+ <string name="location">Τοποθεσία</string>
+ <string name="received_location">Ελήφθη τοποθεσία</string>
+</resources>
diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml
index 579a226d..639eddfa 100644
--- a/src/main/res/values-es/strings.xml
+++ b/src/main/res/values-es/strings.xml
@@ -417,4 +417,14 @@
<string name="sending_x_file">Enviando %s</string>
<string name="offering_x_file">Ofreciendo %s</string>
<string name="hide_offline">Ocultar desconectados</string>
+ <string name="disable_account">Deshabilitar Cuenta</string>
+ <string name="contact_is_typing">%s está escribiendo...</string>
+ <string name="contact_has_stopped_typing">%s ha dejado de escribir</string>
+ <string name="pref_chat_states">Notificación de escritura</string>
+ <string name="pref_chat_states_summary">Permite a tus contactos saber cuando estás escribiendo un nuevo mensaje</string>
+ <string name="send_location">Enviar ubicación</string>
+ <string name="show_location">Mostrar ubicación</string>
+ <string name="no_application_found_to_display_location">No se ha encontrado ninguna aplicación para mostrar la ubicación</string>
+ <string name="location">Ubicación</string>
+ <string name="received_location">Ubicación recibida</string>
</resources>
diff --git a/src/main/res/values-eu/strings.xml b/src/main/res/values-eu/strings.xml
index a55e3f87..4d81c1bf 100644
--- a/src/main/res/values-eu/strings.xml
+++ b/src/main/res/values-eu/strings.xml
@@ -416,4 +416,15 @@
<string name="avatar_has_been_published">Profileko argazkia argitaratu da</string>
<string name="sending_x_file">%s bidaltzen</string>
<string name="offering_x_file">%s eskeintzen...</string>
+ <string name="hide_offline">Lineaz kanpokoak ezkutatu</string>
+ <string name="disable_account">Kontua ezgaitu</string>
+ <string name="contact_is_typing">%s idazten ari da...</string>
+ <string name="contact_has_stopped_typing">%s(e)k idazteari utzi dio</string>
+ <string name="pref_chat_states">Idazketa jakinarazpenak</string>
+ <string name="pref_chat_states_summary">Zure kontaktuak mezu berri bat noiz idazten ari zaren jakin dezan baimendu</string>
+ <string name="send_location">Kokapena partekatu</string>
+ <string name="show_location">Kokapena erakutsi</string>
+ <string name="no_application_found_to_display_location">Kokapena erakutsi dezakeen aplikaziorik ez da aurkitu</string>
+ <string name="location">Kokapena</string>
+ <string name="received_location">Kokapena jaso da</string>
</resources>
diff --git a/src/main/res/values-fa/strings.xml b/src/main/res/values-fa/strings.xml
new file mode 100644
index 00000000..c757504a
--- /dev/null
+++ b/src/main/res/values-fa/strings.xml
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources/>
diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml
index ba2fbcf7..69440362 100644
--- a/src/main/res/values-fr/strings.xml
+++ b/src/main/res/values-fr/strings.xml
@@ -325,7 +325,7 @@
<string name="no_otr_session_found">Aucune session valide d\'OTR n\'a été trouvée!</string>
<string name="conversations_foreground_service">Conversations</string>
<string name="pref_keep_foreground_service">Garder le service au premier-plan</string>
- <string name="pref_keep_foreground_service_summary">Evite que le système ferme votre connexion</string>
+ <string name="pref_keep_foreground_service_summary">Évite que le système ferme votre connexion</string>
<string name="choose_file">Choix d\'un fichier</string>
<string name="receiving_x_file">Réception %1$s (%2$d%% complété)</string>
<string name="download_x_file">Télecharger %s</string>
@@ -400,10 +400,31 @@
<string name="eight_hours">8 heures</string>
<string name="until_further_notice">Jusqu\'à nouvel ordre</string>
<string name="pref_input_options">Options de saisie</string>
+ <string name="pref_enter_is_send">Entrée permet d\'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 le bouton Émoticônes par un bouton Entrée</string>
<string name="audio">audio</string>
<string name="video">video</string>
<string name="image">image</string>
<string name="pdf_document">document PDF</string>
+ <string name="apk">Application Android</string>
<string name="vcard">Contact</string>
<string name="received_x_file">%s reçu</string>
+ <string name="disable_foreground_service">Cesser de garder le service au premier plan</string>
+ <string name="touch_to_open_conversations">Cliquez pour ouvrir Conversations</string>
+ <string name="avatar_has_been_published">L\'avatar a été envoyé !</string>
+ <string name="sending_x_file">Envoi de %s</string>
+ <string name="offering_x_file">Proposition pour %s</string>
+ <string name="hide_offline">Cacher hors-ligne</string>
+ <string name="disable_account">Désactiver le compte</string>
+ <string name="contact_is_typing">%s écrit un message...</string>
+ <string name="contact_has_stopped_typing">%s a arrêté d\'écrire</string>
+ <string name="pref_chat_states">Notifications d\'écriture</string>
+ <string name="pref_chat_states_summary">Permettre à votre contact de savoir que vous écrivez un message</string>
+ <string name="send_location">Envoyer la position</string>
+ <string name="show_location">Afficher la position</string>
+ <string name="no_application_found_to_display_location">Aucune application trouvée pour afficher la position</string>
+ <string name="location">Position</string>
+ <string name="received_location">Position reçue</string>
</resources>
diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml
index 011b018f..2b8c5501 100644
--- a/src/main/res/values-nl/strings.xml
+++ b/src/main/res/values-nl/strings.xml
@@ -417,4 +417,18 @@
<string name="sending_x_file">Bezig met versturen van %s</string>
<string name="offering_x_file">Bezig met aanbieden van %s</string>
<string name="hide_offline">Offline verbergen</string>
+ <string name="disable_account">Account uitzetten</string>
+ <string name="contact_is_typing">%s is aan het typen...</string>
+ <string name="contact_has_stopped_typing">%s is gestopt met typen</string>
+ <string name="pref_chat_states">Type-meldingen</string>
+ <string name="pref_chat_states_summary">Laat je contacten weten wanneer je een nieuw bericht aan het schrijven bent</string>
+ <string name="send_location">Locatie versturen</string>
+ <string name="show_location">Locatie weergeven</string>
+ <string name="no_application_found_to_display_location">Geen applicatie gevonden om locatie weer te geven</string>
+ <string name="location">Locatie</string>
+ <string name="received_location">Locatie ontvangen</string>
+ <plurals name="select_contact">
+ <item quantity="one">Selecteer %d contact</item>
+ <item quantity="other">Selecteer %d contacten</item>
+ </plurals>
</resources>
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
new file mode 100644
index 00000000..58dea13b
--- /dev/null
+++ b/src/main/res/values-pl/strings.xml
@@ -0,0 +1,89 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <string name="action_settings">Ustawienia</string>
+ <string name="action_add">Nowa konwersacja</string>
+ <string name="action_accounts">Zarządzaj kontami</string>
+ <string name="action_end_conversation">Zakończ konwersację</string>
+ <string name="action_contact_details">Szczegóły kontaktu</string>
+ <string name="action_muc_details">Szczególy konferencji</string>
+ <string name="action_add_account">Dodaj konto</string>
+ <string name="action_delete_contact">Usuń z rostera</string>
+ <string name="action_block_contact">Zablokuj kontakt</string>
+ <string name="action_unblock_contact">Odblokuj kontakt</string>
+ <string name="action_block_domain">Zablokuj domenę</string>
+ <string name="action_unblock_domain">Odblokuj domenę</string>
+ <string name="title_activity_manage_accounts">Zarządzaj kontami</string>
+ <string name="title_activity_settings">Ustawienia</string>
+ <string name="title_activity_conference_details">Szczegóły konferencji</string>
+ <string name="title_activity_contact_details">Szczegóły kontaktu</string>
+ <string name="title_activity_start_conversation">Rozpocznij konwersację</string>
+ <string name="title_activity_choose_contact">Wybierz kontakt</string>
+ <string name="title_activity_block_list">Czarna lista</string>
+ <string name="just_now">przed chwilą</string>
+ <string name="minute_ago">minutę temu</string>
+ <string name="minutes_ago">%d minut temu</string>
+ <string name="unread_conversations">nieprzeczytanych konwersacji</string>
+ <string name="sending">wysyłanie...</string>
+ <string name="encrypted_message">Deszyfrowanie wiadomości. Proszę czekać...</string>
+ <string name="admin">Admin</string>
+ <string name="owner">Właściciel</string>
+ <string name="moderator">Moderator</string>
+ <string name="participant">Uczestnik</string>
+ <string name="visitor">Gość</string>
+ <string name="remove_contact_text">Czy na pewno chcesz usunąć %s ze swojego rostera? Konwersacja powiązana z tym kontaktem nie zostanie usunięta.</string>
+ <string name="block_contact_text">Czy na pewno chcesz zablokować wiadomości od użytkownika %s?</string>
+ <string name="unblock_contact_text">Czy na pewno chcesz odblokować wiadomości przychodzące od użytkownika %s?</string>
+ <string name="block_domain_text">Zablokować wszystkie kontakty z %s?</string>
+ <string name="unblock_domain_text">Odblokować wszystkie kontakty z %s?</string>
+ <string name="contact_blocked">Kontakt zablokowany</string>
+ <string name="register_account">Zarejestruj nowe konto na serwerze</string>
+ <string name="change_password_on_server">Zmień hasło na serwerze</string>
+ <string name="start_conversation">Rozpocznij konwersację</string>
+ <string name="invite_contact">Zaproś kontakt</string>
+ <string name="contacts">Kontakty</string>
+ <string name="cancel">Anuluj</string>
+ <string name="add">Dodaj</string>
+ <string name="edit">Edytuj</string>
+ <string name="delete">Usuń</string>
+ <string name="block">Zablokuj</string>
+ <string name="unblock">Odblokuj</string>
+ <string name="save">Zapisz</string>
+ <string name="ok">Ok</string>
+ <string name="crash_report_title">Conversations uległo awarii</string>
+ <string name="send_now">Wyślij teraz</string>
+ <string name="send_never">Nie pytaj ponownie</string>
+ <string name="problem_connecting_to_account">Nie można połączyć się z kontem</string>
+ <string name="problem_connecting_to_accounts">Nie można połączyć się z wieloma kontami</string>
+ <string name="touch_to_fix">Dotknij tutaj aby zarządzać swoimi kontami</string>
+ <string name="attach_file">Dołącz plik</string>
+ <string name="not_in_roster">Kontakt nie jest na twoim rosterze. Czy chcesz go dodać?</string>
+ <string name="add_contact">Dodaj kontakt</string>
+ <string name="send_failed">wysyłanie nie powiodło się</string>
+ <string name="send_rejected">odrzucono</string>
+ <string name="action_clear_history">Wyczyść historię</string>
+ <string name="clear_conversation_history">Wyczyść historię konwersacji</string>
+ <string name="delete_messages">Usuń wiadomości</string>
+ <string name="change_password">Zmień hasło</string>
+ <string name="current_password">Obecne hasło</string>
+ <string name="new_password">Nowe hasło</string>
+ <string name="password_should_not_be_empty">Hasło nie może być puste</string>
+ <string name="enable_all_accounts">Aktywuj wszystkie konta</string>
+ <string name="disable_all_accounts">Wyłącz wszystkie konta</string>
+ <string name="never">Nigdy</string>
+ <string name="thirty_minutes">30 minut</string>
+ <string name="one_hour">1 godzina</string>
+ <string name="two_hours">2 godziny</string>
+ <string name="eight_hours">8 godzin</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>
+ <string name="video">plik wideo</string>
+ <string name="image">obraz</string>
+ <string name="vcard">Kontakt</string>
+ <string name="received_x_file">Odebrano %s</string>
+ <string name="avatar_has_been_published">Avatar został pomyślnie opublikowany!</string>
+ <string name="sending_x_file">Wysyłanie %s</string>
+ <string name="offering_x_file">Oferowanie %s</string>
+ <string name="hide_offline">Ukryj niedostępnych</string>
+</resources>
diff --git a/src/main/res/values-sk/strings.xml b/src/main/res/values-sk/strings.xml
index 78622fa5..bca8d3d2 100644
--- a/src/main/res/values-sk/strings.xml
+++ b/src/main/res/values-sk/strings.xml
@@ -417,4 +417,9 @@
<string name="sending_x_file">Posielam %s</string>
<string name="offering_x_file">Ponúkam %s</string>
<string name="hide_offline">Skryť neprihlásených</string>
+ <string name="disable_account">Vypnúť účet</string>
+ <string name="contact_is_typing">%s píše...</string>
+ <string name="contact_has_stopped_typing">%s prestal písať</string>
+ <string name="pref_chat_states">Upozornenia pri písaní</string>
+ <string name="pref_chat_states_summary">Upozorniť kontakt, keď píšete novú správu</string>
</resources>
diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml
index 874c206d..479889b7 100644
--- a/src/main/res/values-sr/strings.xml
+++ b/src/main/res/values-sr/strings.xml
@@ -329,6 +329,7 @@
<string name="choose_file">Изабери фајл</string>
<string name="receiving_x_file">Примам %1$s (%2$d%% завршено)</string>
<string name="download_x_file">Преузми %s</string>
+ <string name="file">фајл</string>
<string name="open_x_file">Отвори %s</string>
<string name="sending_file">шаљем (%1$d%% завршено)</string>
<string name="preparing_file">Припремам фајл за пренос</string>
@@ -401,6 +402,29 @@
<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>
+ <string name="video">видео</string>
+ <string name="image">слика</string>
<string name="pdf_document">ПДФ документ</string>
+ <string name="apk">Апликација за Андроид</string>
+ <string name="vcard">Контакт</string>
<string name="received_x_file">Примљено %s</string>
+ <string name="disable_foreground_service">Онемогући сервис у првом плану</string>
+ <string name="touch_to_open_conversations">Додирните да отворите Конверзацију</string>
+ <string name="avatar_has_been_published">Аватар је објављен!</string>
+ <string name="sending_x_file">Шаљем %s</string>
+ <string name="offering_x_file">Нудим %s</string>
+ <string name="hide_offline">Сакриј неповезане</string>
+ <string name="disable_account">Онемогући налог</string>
+ <string name="contact_is_typing">%s куца...</string>
+ <string name="contact_has_stopped_typing">%s престаде да куца</string>
+ <string name="pref_chat_states">Обавештења о куцању</string>
+ <string name="pref_chat_states_summary">Обзнаните контакту кад куцате нову поруку</string>
+ <string name="send_location">Пошаљи локацију</string>
+ <string name="show_location">Прикажи локацију</string>
+ <string name="no_application_found_to_display_location">Нема апликације за приказ локације</string>
+ <string name="location">Локација</string>
+ <string name="received_location">Примљена локација</string>
</resources>
diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml
index f1eb6acf..69a66808 100644
--- a/src/main/res/values-sv/strings.xml
+++ b/src/main/res/values-sv/strings.xml
@@ -98,7 +98,7 @@
<string name="pref_xmpp_resource">XMPP resurs</string>
<string name="pref_xmpp_resource_summary">Namnet som klienten identifierar sig med</string>
<string name="pref_accept_files">Acceptera filer</string>
- <string name="pref_accept_files_summary">Acceptera automatistk filer som är mindre än…</string>
+ <string name="pref_accept_files_summary">Acceptera automatiskt filer som är mindre än…</string>
<string name="pref_notification_settings">Notifieringsinställningar</string>
<string name="pref_notifications">Notifieringar</string>
<string name="pref_notifications_summary">Notifiera när meddelande tagits emot</string>
@@ -107,7 +107,7 @@
<string name="pref_sound">Ljud</string>
<string name="pref_sound_summary">Spela ljud med notifiering</string>
<string name="pref_conference_notifications">Konferensnotifieringar</string>
- <string name="pref_conference_notifications_summary">Notifiera alltid när nytt konferensmeddelande tagits emot istället för endast vid highlight</string>
+ <string name="pref_conference_notifications_summary">Notifiera alltid när nytt konferensmeddelande tagits emot i stället för endast vid highlight</string>
<string name="pref_notification_grace_period">Notifieringsfrist</string>
<string name="pref_notification_grace_period_summary">Inaktivera notifieringar en kort stund efter att en carbon copy tagits emot</string>
<string name="pref_advanced_options">Avancerade inställningar</string>
@@ -156,8 +156,8 @@
<string name="mgmt_account_edit">Ändra konto</string>
<string name="mgmt_account_delete">Ta bort</string>
<string name="mgmt_account_disable">Inaktivera tillfälligt</string>
- <string name="mgmt_account_publish_avatar">Publisera avatarbild</string>
- <string name="mgmt_account_publish_pgp">Publisera OpenPGP publik nyckel</string>
+ <string name="mgmt_account_publish_avatar">Publicera avatarbild</string>
+ <string name="mgmt_account_publish_pgp">Publicera OpenPGP publik nyckel</string>
<string name="mgmt_account_enable">Aktivera</string>
<string name="mgmt_account_are_you_sure">Är du säker?</string>
<string name="mgmt_account_delete_confirm_text">Om du tar bort kontot kommer all konversationshistorik att försvinna</string>
@@ -228,17 +228,17 @@
<string name="conference_not_found">Konferens hittades inte</string>
<string name="leave">Lämna</string>
<string name="contact_added_you">Kontakten lade till dig i sin kontaktlista</string>
- <string name="add_back">Addera tillbaks</string>
+ <string name="add_back">Addera tillbaka</string>
<string name="contact_has_read_up_to_this_point">%s har läst fram hit</string>
<string name="publish">Publicera</string>
<string name="touch_to_choose_picture">Tryck på avatarbild för att välja en bild från bildgalleriet</string>
<string name="publish_avatar_explanation">Notera: Alla som kan se dina tillgänglighetsuppdateringar kommer se denna bild.</string>
- <string name="publishing">Publiserar…</string>
- <string name="error_publish_avatar_server_reject">Servern kunde inte publisera</string>
+ <string name="publishing">Publicerar…</string>
+ <string name="error_publish_avatar_server_reject">Servern kunde inte publicera</string>
<string name="error_publish_avatar_converting">Något gick fel vid konvertering av din bild</string>
<string name="error_saving_avatar">Kunde inte spara avatarbild till disk</string>
<string name="or_long_press_for_default">(Eller tryck länge för att få tillbaks förvald)</string>
- <string name="error_publish_avatar_no_server_support">Din server stödjer inte publisering av avatarbilder</string>
+ <string name="error_publish_avatar_no_server_support">Din server stödjer inte publicering av avatarbilder</string>
<string name="private_message">privat meddelande</string>
<string name="private_message_to">till %s</string>
<string name="send_private_message_to">Skicka privat meddelande till %s</string>
@@ -247,7 +247,7 @@
<string name="next">Nästa</string>
<string name="server_info_session_established">Nuvarande session upprättad</string>
<string name="additional_information">Ytterligare information</string>
- <string name="skip">skippa</string>
+ <string name="skip">Hoppa över</string>
<string name="disable_notifications">Inaktivera notifieringar</string>
<string name="disable_notifications_for_this_conversation">Inaktivera notifieringar för denna konversation</string>
<string name="notifications_disabled">Notifieringar är inaktiverade</string>
@@ -264,7 +264,7 @@
<string name="pref_encryption_settings">Krypteringsinställningar</string>
<string name="pref_force_encryption">Tvinga kryptering</string>
<string name="pref_force_encryption_summary">Sänd alltid krypterade meddelanden (utom för konferenser)</string>
- <string name="pref_dont_save_encrypted">Spara in krypterade meddelanden</string>
+ <string name="pref_dont_save_encrypted">Spara inte krypterade meddelanden</string>
<string name="pref_dont_save_encrypted_summary">Varning: Detta kan leda till att meddelanden förloras</string>
<string name="pref_expert_options">Expertinställningar</string>
<string name="pref_expert_options_summary">Var försiktig med dem</string>
@@ -300,7 +300,7 @@
<string name="image_url">Bild-URL</string>
<string name="message_text">Meddelandetext</string>
<string name="url_copied_to_clipboard">URL kopierad till urklipp</string>
- <string name="message_copied_to_clipboard">Meddelande kopierad till urklipp</string>
+ <string name="message_copied_to_clipboard">Meddelande kopierat till urklipp</string>
<string name="image_transmission_failed">Bildöverföring lyckades inte</string>
<string name="scan_qr_code">Skanna QR-kod</string>
<string name="show_qr_code">Visa QR-kod</string>
@@ -363,7 +363,7 @@
<string name="smp_explain_answer">Din kontakt vill verifiera ditt fingeravtryck genom att utmana dig med en delad hemlighet. Din kontakt gav följande ledtråd eller fråga för hemligheten.</string>
<string name="shared_secret_hint_should_not_be_empty">Din ledtråd ska inte vara tom</string>
<string name="shared_secret_can_not_be_empty">Din delade hemlighet kan inte vara tom</string>
- <string name="manual_verification_explanation">Jämför noggrant fingeravtrycker nedan med din kontakts fingeravtryck.\nDu kan använda valfri typ av betrodd kommunikation som ett krypterat e-mail eller ett telefonsamtal för att utbyta dessa.</string>
+ <string name="manual_verification_explanation">Jämför noggrant fingeravtrycket nedan med din kontakts fingeravtryck.\nDu kan använda valfri typ av betrodd kommunikation som ett krypterat e-mail eller ett telefonsamtal för att utbyta dessa.</string>
<string name="change_password">Byt lösenord</string>
<string name="current_password">Nuvarande lösenord</string>
<string name="new_password">Nytt lösenord</string>
@@ -395,14 +395,14 @@
<string name="could_not_modify_conference_options">Kunde inte ändra konferensalternativ</string>
<string name="never">Aldrig</string>
<string name="thirty_minutes">30 minuter</string>
- <string name="one_hour">1 timma</string>
+ <string name="one_hour">1 timme</string>
<string name="two_hours">2 timmar</string>
<string name="eight_hours">8 timmar</string>
<string name="until_further_notice">Tills vidare</string>
<string name="pref_input_options">Inmatningsalternativ</string>
- <string name="pref_enter_is_send">Skicka på enter</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-tangent</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>
<string name="video">video</string>
@@ -411,10 +411,24 @@
<string name="apk">Android App</string>
<string name="vcard">Kontakt</string>
<string name="received_x_file">Tagit emot %s</string>
- <string name="disable_foreground_service">Deaktivera fögrundstjänst</string>
+ <string name="disable_foreground_service">Deaktivera förgrundstjänst</string>
<string name="touch_to_open_conversations">Tryck för att öppna Conversations</string>
- <string name="avatar_has_been_published">Avatarbild har publiserats!</string>
+ <string name="avatar_has_been_published">Avatarbild har publicerats!</string>
<string name="sending_x_file">Skickar %s</string>
<string name="offering_x_file">Erbjuder %s</string>
<string name="hide_offline">Dölj ej anslutna</string>
+ <string name="disable_account">Deaktivera konton</string>
+ <string name="contact_is_typing">%s skriver...</string>
+ <string name="contact_has_stopped_typing">%s har slutat skriva</string>
+ <string name="pref_chat_states">Skriv-notifieringar</string>
+ <string name="pref_chat_states_summary">Låter dina kontakter veta när du skriver ett nytt meddelande</string>
+ <string name="send_location">Skicka position</string>
+ <string name="show_location">Visa position</string>
+ <string name="no_application_found_to_display_location">Kunde inte hitta applikation för att visa position</string>
+ <string name="location">Position</string>
+ <string name="received_location">Mottog position</string>
+ <plurals name="select_contact">
+ <item quantity="one">Välj %d kontakt</item>
+ <item quantity="other">Välj %d kontakter</item>
+ </plurals>
</resources>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index ccb4b67a..b6e897cf 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -446,4 +446,18 @@
<string name="sending_x_file">Sending %s</string>
<string name="offering_x_file">Offering %s</string>
<string name="hide_offline">Hide offline</string>
+ <string name="disable_account">Disable Account</string>
+ <string name="contact_is_typing">%s is typing...</string>
+ <string name="contact_has_stopped_typing">%s has stopped typing</string>
+ <string name="pref_chat_states">Typing notifications</string>
+ <string name="pref_chat_states_summary">Let your contact know when you are writing a new message</string>
+ <string name="send_location">Send location</string>
+ <string name="show_location">Show location</string>
+ <string name="no_application_found_to_display_location">No application found to display location</string>
+ <string name="location">Location</string>
+ <string name="received_location">Received location</string>
+ <plurals name="select_contact">
+ <item quantity="one">Select %d contact</item>
+ <item quantity="other">Select %d contacts</item>
+ </plurals>
</resources>
diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml
index 2c666baa..d055f573 100644
--- a/src/main/res/xml/preferences.xml
+++ b/src/main/res/xml/preferences.xml
@@ -28,6 +28,12 @@
android:key="confirm_messages"
android:summary="@string/pref_confirm_messages_summary"
android:title="@string/pref_confirm_messages" />
+
+ <CheckBoxPreference
+ android:defaultValue="false"
+ android:key="chat_states"
+ android:summary="@string/pref_chat_states_summary"
+ android:title="@string/pref_chat_states" />
<CheckBoxPreference
android:defaultValue="true"
android:key="parse_emoticons"
diff --git a/todo.md b/todo.md
new file mode 100644
index 00000000..bace5987
--- /dev/null
+++ b/todo.md
@@ -0,0 +1,23 @@
+##GSOC teaser tasks
+
+####update Contacts last seen for muc messages as well
+The contact class (entities/Contact) has the ability to save the last time that Conversations
+ received a message from that contact. Currently this time only gets updated for one-on-one
+ messages. In non-anonymous mucs messages from a contact should also update the last seen
+ time.
+
+####Select multiple Contact in Choose Contact Activity
+Currently the choose Contact activity allows only for one contact to be selected. A long
+press on one contact should bring the activity in a mode where the user can select multiple
+contacts.
+The Activity should then return an array of contacts instead of just one
+
+####Request and respond to message receipts in MUC PNs
+Private MUC messages either dont request message receipts or dont respond to them. The source
+of error should be determined and eliminated. A rather small tasks that just teaches you a bit
+about the stanza parser and generator in Conversations
+
+####Edit dynamic tags / groups
+The context menu for the contact list (StartConversationActivity) should offer the ability to
+edit groups. (Any UI decissions are left to you) Quick suggestion though: Dialog with
+checkboxes for existing groups and some way to enter new tags.