diff options
88 files changed, 3322 insertions, 635 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/ManualBuildForFdroid.md b/ManualBuildForFdroid.md new file mode 100644 index 00000000..26e4f3e5 --- /dev/null +++ b/ManualBuildForFdroid.md @@ -0,0 +1,10 @@ +### Build with Android Studio +1. Adjust build.gradle + a. increment versionCode + b. enter new versionName +2. Generate APK + a. (menu) Build -> Generate Signed APK + b. choose Key store path (AndroidKeystore.jks) -> choose key alias (android-key) -> Next -> Finish +3. Upload APK to VM with installed FDroid into folder /var/www/fdroid/repo (Repo Folder) +4. Run fdroid update --create-metadata in directory /var/www/fdroid +5. Rsync Repo Folder to public F-Droid Repo
\ No newline at end of file diff --git a/build.gradle b/build.gradle index 4b695b0d..79e147f8 100644 --- a/build.gradle +++ b/build.gradle @@ -20,23 +20,20 @@ allprojects { apply plugin: 'com.android.application' repositories { - maven { - url "http://jitsi.github.com/otr4j/repository/" - } jcenter() mavenCentral() } 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.gnu.inet:libidn:1.15' - compile 'com.google.zxing:core:3.1.0' - compile 'com.google.zxing:android-integration:3.1.0' - compile 'de.measite.minidns:minidns:0.1.3' + 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.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' + compile 'de.measite.minidns:minidns:0.1.3' } android { @@ -46,8 +43,8 @@ android { defaultConfig { minSdkVersion 14 targetSdkVersion 21 - versionCode 46 - versionName "1.0.1" + versionCode 63 + versionName "1.2.6" } 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/manifest-merger-release-report.txt b/manifest-merger-release-report.txt new file mode 100644 index 00000000..b02dcf65 --- /dev/null +++ b/manifest-merger-release-report.txt @@ -0,0 +1,270 @@ +-- Merging decision tree log --- +manifest +ADDED from AndroidManifest.xml:2:1 + package + ADDED from AndroidManifest.xml:3:5 + android:versionName + INJECTED from AndroidManifest.xml:0:0 + INJECTED from AndroidManifest.xml:0:0 + xmlns:tools + ADDED from AndroidManifest.xml:5:5 + xmlns:android + ADDED from AndroidManifest.xml:4:5 + android:versionCode + INJECTED from AndroidManifest.xml:0:0 + INJECTED from AndroidManifest.xml:0:0 +uses-permission#android.permission.WRITE_EXTERNAL_STORAGE +ADDED from AndroidManifest.xml:7:5 + android:name + ADDED from AndroidManifest.xml:7:22 +uses-permission#android.permission.READ_EXTERNAL_STORAGE +ADDED from AndroidManifest.xml:8:5 + android:name + ADDED from AndroidManifest.xml:8:22 +uses-permission#android.permission.READ_CONTACTS +ADDED from AndroidManifest.xml:9:5 + android:name + ADDED from AndroidManifest.xml:9:22 +uses-permission#android.permission.READ_PROFILE +ADDED from AndroidManifest.xml:10:5 + android:name + ADDED from AndroidManifest.xml:10:22 +uses-permission#android.permission.INTERNET +ADDED from AndroidManifest.xml:11:5 + android:name + ADDED from AndroidManifest.xml:11:22 +uses-permission#android.permission.ACCESS_NETWORK_STATE +ADDED from AndroidManifest.xml:12:5 + android:name + ADDED from AndroidManifest.xml:12:22 +uses-permission#android.permission.WAKE_LOCK +ADDED from AndroidManifest.xml:13:5 + android:name + ADDED from AndroidManifest.xml:13:22 +uses-permission#android.permission.RECEIVE_BOOT_COMPLETED +ADDED from AndroidManifest.xml:14:5 + android:name + ADDED from AndroidManifest.xml:14:22 +uses-permission#android.permission.VIBRATE +ADDED from AndroidManifest.xml:15:5 + android:name + ADDED from AndroidManifest.xml:15:22 +uses-permission#android.permission.NFC +ADDED from AndroidManifest.xml:16:5 + android:name + ADDED from AndroidManifest.xml:16:22 +application +ADDED from AndroidManifest.xml:18:5 +MERGED from Conversations.libs:openpgp-api-lib:unspecified:11:5 +MERGED from Conversations.libs:MemorizingTrustManager:unspecified:11:5 +MERGED from com.android.support:support-v13:21.0.3:16:5 +MERGED from com.android.support:support-v4:21.0.3:16:5 + android:label + ADDED from AndroidManifest.xml:21:9 + REJECTED from Conversations.libs:MemorizingTrustManager:unspecified:11:18 + android:allowBackup + ADDED from AndroidManifest.xml:19:9 + android:icon + ADDED from AndroidManifest.xml:20:9 + android:theme + ADDED from AndroidManifest.xml:22:9 + tools:replace + ADDED from AndroidManifest.xml:23:9 +service#eu.siacs.conversations.services.XmppConnectionService +ADDED from AndroidManifest.xml:24:9 + android:name + ADDED from AndroidManifest.xml:24:18 +receiver#eu.siacs.conversations.services.EventReceiver +ADDED from AndroidManifest.xml:26:9 + android:name + ADDED from AndroidManifest.xml:26:19 +intent-filter#android.intent.action.ACTION_SHUTDOWN+android.intent.action.BOOT_COMPLETED+android.net.conn.CONNECTIVITY_CHANGE +ADDED from AndroidManifest.xml:27:13 +action#android.intent.action.BOOT_COMPLETED +ADDED from AndroidManifest.xml:28:17 + android:name + ADDED from AndroidManifest.xml:28:25 +action#android.net.conn.CONNECTIVITY_CHANGE +ADDED from AndroidManifest.xml:29:17 + android:name + ADDED from AndroidManifest.xml:29:25 +action#android.intent.action.ACTION_SHUTDOWN +ADDED from AndroidManifest.xml:30:17 + android:name + ADDED from AndroidManifest.xml:30:25 +activity#eu.siacs.conversations.ui.ConversationActivity +ADDED from AndroidManifest.xml:34:9 + android:label + ADDED from AndroidManifest.xml:36:13 + android:launchMode + ADDED from AndroidManifest.xml:37:13 + android:windowSoftInputMode + ADDED from AndroidManifest.xml:38:13 + android:name + ADDED from AndroidManifest.xml:35:13 +intent-filter#android.intent.action.MAIN+android.intent.category.LAUNCHER +ADDED from AndroidManifest.xml:39:13 +action#android.intent.action.MAIN +ADDED from AndroidManifest.xml:40:17 + android:name + ADDED from AndroidManifest.xml:40:25 +category#android.intent.category.LAUNCHER +ADDED from AndroidManifest.xml:42:17 + android:name + ADDED from AndroidManifest.xml:42:27 +activity#eu.siacs.conversations.ui.StartConversationActivity +ADDED from AndroidManifest.xml:45:9 + android:label + ADDED from AndroidManifest.xml:48:13 + android:configChanges + ADDED from AndroidManifest.xml:47:13 + android:name + ADDED from AndroidManifest.xml:46:13 +intent-filter#android.intent.action.SENDTO+android.intent.category.DEFAULT +ADDED from AndroidManifest.xml:49:13 +action#android.intent.action.SENDTO +ADDED from AndroidManifest.xml:50:17 + android:name + ADDED from AndroidManifest.xml:50:25 +category#android.intent.category.DEFAULT +ADDED from AndroidManifest.xml:52:17 + android:name + ADDED from AndroidManifest.xml:52:27 +data +ADDED from AndroidManifest.xml:54:17 + android:scheme + ADDED from AndroidManifest.xml:54:23 +intent-filter#android.intent.action.VIEW+android.intent.category.BROWSABLE+android.intent.category.DEFAULT +ADDED from AndroidManifest.xml:57:13 +action#android.intent.action.VIEW +ADDED from AndroidManifest.xml:58:17 + android:name + ADDED from AndroidManifest.xml:58:25 +category#android.intent.category.BROWSABLE +ADDED from AndroidManifest.xml:61:17 + android:name + ADDED from AndroidManifest.xml:61:27 +intent-filter#android.intent.category.DEFAULT+android.nfc.action.NDEF_DISCOVERED +ADDED from AndroidManifest.xml:65:13 +action#android.nfc.action.NDEF_DISCOVERED +ADDED from AndroidManifest.xml:66:17 + android:name + ADDED from AndroidManifest.xml:66:25 +activity#eu.siacs.conversations.ui.SettingsActivity +ADDED from AndroidManifest.xml:73:9 + android:label + ADDED from AndroidManifest.xml:75:13 + android:name + ADDED from AndroidManifest.xml:74:13 +activity#eu.siacs.conversations.ui.ChooseContactActivity +ADDED from AndroidManifest.xml:76:9 + android:label + ADDED from AndroidManifest.xml:78:13 + android:name + ADDED from AndroidManifest.xml:77:13 +activity#eu.siacs.conversations.ui.BlocklistActivity +ADDED from AndroidManifest.xml:79:9 + android:label + ADDED from AndroidManifest.xml:81:13 + android:name + ADDED from AndroidManifest.xml:80:13 +activity#eu.siacs.conversations.ui.ChangePasswordActivity +ADDED from AndroidManifest.xml:82:6 + android:label + ADDED from AndroidManifest.xml:84:7 + android:name + ADDED from AndroidManifest.xml:83:7 +activity#eu.siacs.conversations.ui.ManageAccountActivity +ADDED from AndroidManifest.xml:85:9 + android:label + ADDED from AndroidManifest.xml:88:13 + android:configChanges + ADDED from AndroidManifest.xml:87:13 + android:name + ADDED from AndroidManifest.xml:86:13 +activity#eu.siacs.conversations.ui.EditAccountActivity +ADDED from AndroidManifest.xml:89:9 + android:windowSoftInputMode + ADDED from AndroidManifest.xml:91:13 + android:name + ADDED from AndroidManifest.xml:90:13 +activity#eu.siacs.conversations.ui.ConferenceDetailsActivity +ADDED from AndroidManifest.xml:92:9 + android:label + ADDED from AndroidManifest.xml:94:13 + android:windowSoftInputMode + ADDED from AndroidManifest.xml:95:13 + android:name + ADDED from AndroidManifest.xml:93:13 +activity#eu.siacs.conversations.ui.ContactDetailsActivity +ADDED from AndroidManifest.xml:96:9 + android:label + ADDED from AndroidManifest.xml:98:13 + android:windowSoftInputMode + ADDED from AndroidManifest.xml:99:13 + android:name + ADDED from AndroidManifest.xml:97:13 +activity#eu.siacs.conversations.ui.PublishProfilePictureActivity +ADDED from AndroidManifest.xml:100:9 + android:label + ADDED from AndroidManifest.xml:102:13 + android:windowSoftInputMode + ADDED from AndroidManifest.xml:103:13 + android:name + ADDED from AndroidManifest.xml:101:13 +activity#eu.siacs.conversations.ui.VerifyOTRActivity +ADDED from AndroidManifest.xml:104:9 + android:label + ADDED from AndroidManifest.xml:106:13 + android:windowSoftInputMode + ADDED from AndroidManifest.xml:107:13 + android:name + ADDED from AndroidManifest.xml:105:13 +activity#eu.siacs.conversations.ui.ShareWithActivity +ADDED from AndroidManifest.xml:108:9 + android:label + ADDED from AndroidManifest.xml:110:13 + android:name + ADDED from AndroidManifest.xml:109:13 +intent-filter#android.intent.action.SEND+android.intent.category.DEFAULT +ADDED from AndroidManifest.xml:111:13 +action#android.intent.action.SEND +ADDED from AndroidManifest.xml:112:17 + android:name + ADDED from AndroidManifest.xml:112:25 +activity#de.duenndns.ssl.MemorizingActivity +ADDED from AndroidManifest.xml:126:9 +MERGED from Conversations.libs:MemorizingTrustManager:unspecified:12:9 + android:theme + ADDED from AndroidManifest.xml:128:13 + REJECTED from Conversations.libs:MemorizingTrustManager:unspecified:14:13 + tools:replace + ADDED from AndroidManifest.xml:129:13 + android:name + ADDED from AndroidManifest.xml:127:13 +activity#eu.siacs.conversations.ui.AboutActivity +ADDED from AndroidManifest.xml:130:9 + android:label + ADDED from AndroidManifest.xml:132:13 + android:parentActivityName + ADDED from AndroidManifest.xml:133:13 + android:name + ADDED from AndroidManifest.xml:131:13 +meta-data#android.support.PARENT_ACTIVITY +ADDED from AndroidManifest.xml:134:13 + android:value + ADDED from AndroidManifest.xml:136:17 + android:name + ADDED from AndroidManifest.xml:135:17 +uses-sdk +INJECTED from AndroidManifest.xml:0:0 reason: use-sdk injection requested +MERGED from Conversations.libs:openpgp-api-lib:unspecified:7:5 +MERGED from Conversations.libs:MemorizingTrustManager:unspecified:7:5 +MERGED from com.android.support:support-v13:21.0.3:15:5 +MERGED from com.android.support:support-v4:21.0.3:15:5 + android:targetSdkVersion + INJECTED from AndroidManifest.xml:0:0 + INJECTED from AndroidManifest.xml:0:0 + android:minSdkVersion + INJECTED from AndroidManifest.xml:0:0 + INJECTED from AndroidManifest.xml:0:0 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..473195bd 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.parser; +import android.util.Log; + import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -41,8 +43,14 @@ public abstract class AbstractParser { if (stamp == null) { return now; } - long time = parseTimestamp(stamp).getTime(); - return now < time ? now : time; + /*long time = parseTimestamp(stamp).getTime(); + return now < time ? now : time;*/ + try { + long time = parseTimestamp(stamp).getTime(); + return now < time ? now : time; + } catch (ParseException e) { + return now; + } } /** @@ -53,17 +61,29 @@ public abstract class AbstractParser { * @return Date * @throws ParseException */ - public static Date parseTimestamp(String timestamp) { - try { + public static Date parseTimestamp(String timestamp) throws ParseException { + /*try { + Log.d("TIMESTAMP", timestamp); return DatatypeFactory.newInstance().newXMLGregorianCalendar(timestamp).toGregorianCalendar().getTime(); } catch (DatatypeConfigurationException e) { + Log.d("TIMESTAMP", e.getMessage()); return new Date(); - } + }*/ + timestamp = timestamp.replace("Z", "+0000"); + SimpleDateFormat dateFormat; + timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5,timestamp.length()); + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US); + return dateFormat.parse(timestamp); } 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..fa079829 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,14 +212,24 @@ 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(this.getLedNotificationColor(preferences), 2000, 4000); final Notification notification = mBuilder.build(); notificationManager.notify(NOTIFICATION_ID, notification); } } + private int getLedNotificationColor(SharedPreferences preferences) { + String ledColorString = preferences.getString("led_notification_color", "0xffffffff"); + try { + return Integer.valueOf(ledColorString); + } catch (NumberFormatException e) { + return 0xffffffff; + } + } + private Builder buildMultipleConversation() { final Builder mBuilder = new NotificationCompat.Builder( mXmppConnectionService); @@ -277,6 +289,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 +357,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 +377,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 +431,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 +472,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 +538,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..fac4cc91 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; @@ -114,7 +120,9 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { Pair<String,Boolean> preview = UIHelper.getMessagePreview(activity,message); mLastMessage.setVisibility(View.VISIBLE); imagePreview.setVisibility(View.GONE); - mLastMessage.setText(preview.first); + boolean parseEmoticons = Settings.PARSE_EMOTICONS; + CharSequence msgText = parseEmoticons ? UIHelper.transformAsciiEmoticons(getContext(), preview.first) : preview.first; + mLastMessage.setText(msgText); if (preview.second) { if (conversation.isRead()) { mLastMessage.setTypeface(null, Typeface.ITALIC); @@ -132,8 +140,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..c98dbdd0 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> { @@ -225,17 +226,17 @@ public class MessageAdapter extends ArrayAdapter<Message> { final String formattedBody = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND, nick + " "); if (message.getType() != Message.TYPE_PRIVATE) { - boolean parseEmoticons = Settings.PARSE_EMOTICONS; - viewHolder.messageBody.setText(parseEmoticons ? UIHelper - .transformAsciiEmoticons(getContext(), message.getMergedBody()) - : message.getMergedBody()); + if (message.hasMeCommand()) { final Spannable span = new SpannableString(formattedBody); span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); viewHolder.messageBody.setText(span); } else { - viewHolder.messageBody.setText(message.getMergedBody()); + boolean parseEmoticons = Settings.PARSE_EMOTICONS; + viewHolder.messageBody.setText(parseEmoticons ? UIHelper + .transformAsciiEmoticons(getContext(), message.getMergedBody()) + : message.getMergedBody()); } } else { String privateMarker; @@ -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 Binary files differnew file mode 100644 index 00000000..b1ee2974 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_autorenew_white_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_block_white_24dp.png b/src/main/res/drawable-hdpi/ic_block_white_24dp.png Binary files differnew file mode 100644 index 00000000..13f716d7 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_block_white_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_room_white_24dp.png b/src/main/res/drawable-hdpi/ic_room_white_24dp.png Binary files differnew file mode 100644 index 00000000..c2ccd510 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_room_white_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_autorenew_white_24dp.png b/src/main/res/drawable-mdpi/ic_autorenew_white_24dp.png Binary files differnew file mode 100644 index 00000000..86b71938 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_autorenew_white_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_block_white_24dp.png b/src/main/res/drawable-mdpi/ic_block_white_24dp.png Binary files differnew file mode 100644 index 00000000..4e5093f7 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_block_white_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_room_white_24dp.png b/src/main/res/drawable-mdpi/ic_room_white_24dp.png Binary files differnew file mode 100644 index 00000000..a4fcd770 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_room_white_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_autorenew_white_24dp.png b/src/main/res/drawable-xhdpi/ic_autorenew_white_24dp.png Binary files differnew file mode 100644 index 00000000..e8462223 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_autorenew_white_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_block_white_24dp.png b/src/main/res/drawable-xhdpi/ic_block_white_24dp.png Binary files differnew file mode 100644 index 00000000..b86e6c47 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_block_white_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_room_white_24dp.png b/src/main/res/drawable-xhdpi/ic_room_white_24dp.png Binary files differnew file mode 100644 index 00000000..e1e60a5c --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_room_white_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_autorenew_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_autorenew_white_24dp.png Binary files differnew file mode 100644 index 00000000..b871381a --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_autorenew_white_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_block_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_block_white_24dp.png Binary files differnew file mode 100644 index 00000000..d2be2d61 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_block_white_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_room_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_room_white_24dp.png Binary files differnew file mode 100644 index 00000000..00b15508 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_room_white_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_autorenew_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_autorenew_white_24dp.png Binary files differnew file mode 100644 index 00000000..569bafe2 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_autorenew_white_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_block_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_block_white_24dp.png Binary files differnew file mode 100644 index 00000000..b538af61 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_block_white_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_room_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_room_white_24dp.png Binary files differnew file mode 100644 index 00000000..a5dde3b9 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_room_white_24dp.png 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..14e5b091 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,20 @@ <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> + <string name="pref_led_notification_color">LED-Benachrichtigung Farbe</string> + <string name="pref_led_notification_color_summary">Setze die Farbe der LED-Benachrichtigung</string> </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..adc15c6f 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" @@ -145,6 +151,11 @@ android:key="keep_foreground_service" android:title="@string/pref_keep_foreground_service" android:summary="@string/pref_keep_foreground_service_summary" /> + <EditTextPreference + android:defaultValue="0xffffffff" + android:key="led_notification_color" + android:title="@string/pref_led_notification_color" + android:summary="@string/pref_led_notification_color_summary"/> </PreferenceCategory> </PreferenceScreen> 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. |