Merge tag '2.5.11' into develop

This commit is contained in:
Martin/Geno 2019-09-28 17:19:06 +02:00
commit 82e244b96b
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
55 changed files with 1430 additions and 421 deletions

View File

@ -1,6 +1,16 @@
# Changelog
## Version 2.5.8
### Version 2.5.11
* Fixed crash on Android <5.0
### Version 2.5.10
* Fixed crash on Xiaomi devices running Android 8.0 + 8.1
### Version 2.5.9
* fixed minor security issues
* Share XMPP uri from channel search by long pressing a result
### Version 2.5.8
* fixed connection issues over Tor
* P2P file transfer (Jingle) now offers direct candidates
* Support XEP-0396: Jingle Encrypted Transports - OMEMO

View File

@ -249,7 +249,11 @@ Conversations is trying to get rid of old behaviours and set an example for
other clients.
#### Translations
Translations are managed on [Transifex](https://www.transifex.com/projects/p/conversations/)
Translations are managed on [Transifex](https://www.transifex.com/projects/p/conversations/).
If you want to become a translator Please register on transifex, apply to join
the translation team and then step by our group chat on
[conversations@conference.siacs.eu](https://conversations.im/j/conversations@conference.siacs.eu)
and introduce yourself to `iNPUTmice` so he can approve your join request.
#### How do I backup / move Conversations to a new device?
On the one hand Conversations supports Message Archive Management to keep a server side history of your messages so when migrating to a new device that device can display your entire history. However that does not work if you enable OMEMO due to its forward secrecy. (Read [The State of Mobile XMPP in 2016](https://gultsch.de/xmpp_2016.html) especially the section on encryption.)

View File

@ -6,7 +6,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath 'com.android.tools.build:gradle:3.5.0'
}
}
@ -63,13 +63,14 @@ dependencies {
implementation project(':libs:xmpp-addr')
implementation 'org.osmdroid:osmdroid-android:6.1.0'
implementation 'org.hsluv:hsluv:0.2'
implementation 'org.conscrypt:conscrypt-android:2.1.0'
implementation 'org.conscrypt:conscrypt-android:2.2.1'
implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation "com.leinardi.android:speed-dial:2.0.1"
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
implementation 'com.squareup.okhttp3:okhttp:3.12.5'
implementation 'com.google.guava:guava:27.1-android'
quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.10.1'
quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.10.16'
}
ext {
@ -83,8 +84,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 25
versionCode 338
versionName "2.5.8"
versionCode 341
versionName "2.5.11"
archivesBaseName += "-$versionName"
applicationId "eu.sum7.conversations"
resValue "string", "applicationId", applicationId

340
doap.rdf Normal file
View File

@ -0,0 +1,340 @@
<?xml version="1.0"?>
<?xml-stylesheet href="../style.xsl" type="text/xsl"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<Project xmlns="http://usefulinc.com/ns/doap#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:xmpp="https://linkmauve.fr/ns/xmpp-doap#">
<name>Conversations</name>
<created>2014-01-14</created>
<shortdesc xml:lang="en">Android XMPP Client</shortdesc>
<description xml:lang="en">Conversations is an open source XMPP/Jabber client for the Android platform</description>
<homepage rdf:resource="https://conversations.im/"/>
<download-page rdf:resource="https://play.google.com/store/apps/details?id=eu.siacs.conversations"/>
<bug-database rdf:resource="https://github.com/siacs/Conversations/issues"/>
<!-- See https://github.com/ewilderj/doap/issues/53 -->
<developer-forum rdf:resource="xmpp:conversations@siacs.conference.eu?join"/>
<support-forum rdf:resource="xmpp:conversations@siacs.conference.eu?join"/>
<license rdf:resource="https://github.com/siacs/Conversations/blob/master/LICENSE"/>
<!-- See https://github.com/ewilderj/doap/issues/49 -->
<language>en</language>
<logo rdf:resource="https://raw.githubusercontent.com/siacs/Conversations/master/doap.rdf"/>
<programming-language>Java</programming-language>
<os>Android</os>
<!-- TODO: Categories are URIs, find a better location for them. -->
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-xmpp"/>
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-jabber"/>
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-client"/>
<maintainer>
<foaf:Person>
<foaf:name>Daniel Gultsch</foaf:name>
<foaf:homepage rdf:resource="https://gultsch.de/"/>
</foaf:Person>
</maintainer>
<repository>
<GitRepository>
<browse rdf:resource="https://github.com/siacs/Conversations"/>
<location rdf:resource="https://github.com/siacs/Conversations.git"/>
</GitRepository>
</repository>
<implements rdf:resource="https://xmpp.org/rfcs/rfc6120.html"/>
<implements rdf:resource="https://xmpp.org/rfcs/rfc6121.html"/>
<implements rdf:resource="https://xmpp.org/rfcs/rfc6122.html"/>
<implements rdf:resource="https://xmpp.org/rfcs/rfc7590.html"/>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0027.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.4</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0030.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>2.5rc3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0045.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.32.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0048.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1.3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0085.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>2.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0092.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0115.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.5.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0163.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.2.1</xmpp:version>
<xmpp:note>Avatar, Nick, OMEMO</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0166.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1.2</xmpp:version>
<xmpp:note>File transfer only</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0172.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1</xmpp:version>
<xmpp:note>read only</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0184.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.4.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0191.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0198.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.6</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0199.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>2.0.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0234.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.19.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0237.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0245.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0249.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.2</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0260.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0261.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0280.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.13.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0308.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0313.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.6.3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0319.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.2</xmpp:version>
<xmpp:note>opt-in</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0352.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.3.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0357.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.4.0</xmpp:version>
<xmpp:note>Only available in the version distributed over Google Play</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0368.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0377.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.2</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0384.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.3.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0391.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.1.2</xmpp:version>
<xmpp:since>2.5.8</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0392.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.6.0</xmpp:version>
<xmpp:since>2.3.1</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0393.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.1.4</xmpp:version>
<xmpp:since>1.22.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0396.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.1</xmpp:version>
<xmpp:since>2.5.8</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0398.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.2.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0410.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.1</xmpp:version>
<xmpp:since>2.5.4</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0411.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.2.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<release>
<Version>
<revision>2.5.8</revision>
<created>2019-09-12</created>
<file-release rdf:resource="https://github.com/siacs/Conversations/archive/2.5.8.tar.gz"/>
</Version>
</release>
</Project>
</rdf:RDF>

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

3
proguard-rules.pro vendored
View File

@ -21,6 +21,9 @@
-dontwarn java.lang.**
-dontwarn javax.lang.**
-keepclassmembers class eu.siacs.conversations.http.services.** {
!transient <fields>;
}
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_a_server">اختر مزود خدمة XMPP الخاص بك</string>
<string name="use_conversations.im">استخدِم conversations.im</string>
<string name="create_new_account">أنشئ حسابًا جديدًا</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="use_conversations.im">Använd conversations.im</string>
<string name="create_new_account">Skapa nytt konto</string>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_a_server">选择您的XMPP提供者</string>
<string name="use_conversations.im">使用 conversations.im</string>
<string name="create_new_account">创建新账户</string>
<string name="do_you_have_an_account">您已经拥有一个XMPP账户了吗如果您之前使用过其他的XMPP客户端的话那么您已经拥有这种账户了。如果没有账户的话您可以现在创建一个。\n提示有些电子邮件服务也提供XMPP账户。</string>
</resources>

View File

@ -116,6 +116,8 @@ public final class Config {
public static final boolean IGNORE_ID_REWRITE_IN_MUC = true;
public static final boolean MUC_LEAVE_BEFORE_JOIN = true;
public static final boolean USE_LMC_VERSION_1_1 = false;
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 5;
public static final int MAM_MAX_MESSAGES = 750;

View File

@ -48,7 +48,6 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.pep.PublishOptions;
@ -67,8 +66,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public static final String LOGPREFIX = "AxolotlService";
public static final int NUM_KEYS_TO_PUBLISH = 100;
public static final int publishTriesThreshold = 3;
private static final int NUM_KEYS_TO_PUBLISH = 100;
private static final int publishTriesThreshold = 3;
private final Account account;
private final XmppConnectionService mXmppConnectionService;
@ -1469,9 +1468,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} else {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": nothing to flush. Not republishing key");
}
if (trustedOrPreviouslyResponded(session)) {
completeSession(session);
}
}
}
public void processPostponed() {
if (postponedSessions.size() > 0) {
@ -1479,23 +1480,44 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
publishBundlesIfNeeded(false, false);
}
}
Iterator<XmppAxolotlSession> iterator = postponedSessions.iterator();
final Iterator<XmppAxolotlSession> iterator = postponedSessions.iterator();
while (iterator.hasNext()) {
completeSession(iterator.next());
final XmppAxolotlSession session = iterator.next();
if (trustedOrPreviouslyResponded(session)) {
completeSession(session);
}
iterator.remove();
}
Iterator<SignalProtocolAddress> postponedHealingAttemptsIterator = postponedHealing.iterator();
final Iterator<SignalProtocolAddress> postponedHealingAttemptsIterator = postponedHealing.iterator();
while (postponedHealingAttemptsIterator.hasNext()) {
notifyRequiresHealing(postponedHealingAttemptsIterator.next());
postponedHealingAttemptsIterator.remove();
}
}
private boolean trustedOrPreviouslyResponded(XmppAxolotlSession session) {
try {
return trustedOrPreviouslyResponded(Jid.of(session.getRemoteAddress().getName()));
} catch (IllegalArgumentException e) {
return false;
}
}
public boolean trustedOrPreviouslyResponded(Jid jid) {
final Contact contact = account.getRoster().getContact(jid);
if (contact.showInRoster() || contact.isSelf()) {
return true;
}
final Conversation conversation = mXmppConnectionService.find(account, jid);
return conversation != null && conversation.sentMessagesCount() > 0;
}
private void completeSession(XmppAxolotlSession session) {
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
axolotlMessage.addDevice(session, true);
try {
Jid jid = Jid.of(session.getRemoteAddress().getName());
final Jid jid = Jid.of(session.getRemoteAddress().getName());
MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
mXmppConnectionService.sendMessagePacket(account, packet);
} catch (IllegalArgumentException e) {
@ -1505,9 +1527,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) {
XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
XmppAxolotlSession session = getReceivingSession(message);
final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
final XmppAxolotlSession session = getReceivingSession(message);
try {
keyTransportMessage = message.getParameters(session, getOwnDeviceId());
Integer preKeyId = session.getPreKeyIdAndReset();
@ -1516,7 +1537,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
} catch (CryptoFailedException e) {
Log.d(Config.LOGTAG, "could not decrypt keyTransport message " + e.getMessage());
keyTransportMessage = null;
return null;
}
if (session.isFresh() && keyTransportMessage != null) {
@ -1527,7 +1548,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
private void putFreshSession(XmppAxolotlSession session) {
Log.d(Config.LOGTAG, "put fresh session");
sessions.put(session);
if (Config.X509_VERIFICATION) {
if (session.getIdentityKey() != null) {

View File

@ -135,7 +135,7 @@ public class XmppAxolotlMessage {
break;
}
}
Element payloadElement = axolotlMessage.findChild(PAYLOAD);
final Element payloadElement = axolotlMessage.findChildEnsureSingle(PAYLOAD, AxolotlService.PEP_PREFIX);
if (payloadElement != null) {
ciphertext = Base64.decode(payloadElement.getContent().trim(), Base64.DEFAULT);
}

View File

@ -2,7 +2,6 @@ package eu.siacs.conversations.entities;
import android.content.ContentValues;
import android.database.Cursor;
import android.graphics.Color;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
@ -13,23 +12,19 @@ import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.OmemoSetting;
import eu.siacs.conversations.crypto.PgpDecryptionService;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.utils.JidHelper;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.mam.MamReference;
import rocks.xmpp.addr.Jid;
@ -311,11 +306,12 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart, boolean received, boolean carbon) {
synchronized (this.messages) {
for (int i = this.messages.size() - 1; i >= 0; --i) {
Message message = messages.get(i);
final Message message = messages.get(i);
if (counterpart.equals(message.getCounterpart())
&& ((message.getStatus() == Message.STATUS_RECEIVED) == received)
&& (carbon == message.isCarbon() || received)) {
if (id.equals(message.getRemoteMsgId()) && !message.isFileOrImage() && !message.treatAsDownloadable()) {
final boolean idMatch = id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id);
if (idMatch && !message.isFileOrImage() && !message.treatAsDownloadable()) {
return message;
} else {
return null;

View File

@ -43,8 +43,8 @@ public class IndividualMessage extends Message {
super(conversation);
}
private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, String edited, boolean oob, String errorMessage, Set<ReadByMarker> readByMarkers, boolean markable, boolean deleted) {
super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, edited, oob, errorMessage, readByMarkers, markable, deleted);
private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, String edited, boolean oob, String errorMessage, Set<ReadByMarker> readByMarkers, boolean markable, boolean deleted, String bodyLanguage) {
super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, edited, oob, errorMessage, readByMarkers, markable, deleted, bodyLanguage);
}
@Override
@ -116,6 +116,8 @@ public class IndividualMessage extends Message {
cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)),
ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))),
cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0,
cursor.getInt(cursor.getColumnIndex(DELETED)) > 0);
cursor.getInt(cursor.getColumnIndex(DELETED)) > 0,
cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE))
);
}
}

View File

@ -62,6 +62,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
public static final String COUNTERPART = "counterpart";
public static final String TRUE_COUNTERPART = "trueCounterpart";
public static final String BODY = "body";
public static final String BODY_LANGUAGE = "bodyLanguage";
public static final String TIME_SENT = "timeSent";
public static final String ENCRYPTION = "encryption";
public static final String STATUS = "status";
@ -100,6 +101,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
protected String relativeFilePath;
protected boolean read = true;
protected String remoteMsgId = null;
private String bodyLanguage = null;
protected String serverMsgId = null;
private final Conversational conversation;
protected Transferable transferable = null;
@ -145,7 +147,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
null,
null,
false,
false);
false,
null);
}
protected Message(final Conversational conversation, final String uuid, final String conversationUUid, final Jid counterpart,
@ -154,7 +157,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
final String remoteMsgId, final String relativeFilePath,
final String serverMsgId, final String fingerprint, final boolean read,
final String edited, final boolean oob, final String errorMessage, final Set<ReadByMarker> readByMarkers,
final boolean markable, final boolean deleted) {
final boolean markable, final boolean deleted, final String bodyLanguage) {
this.conversation = conversation;
this.uuid = uuid;
this.conversationUuid = conversationUUid;
@ -177,6 +180,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
this.readByMarkers = readByMarkers == null ? new HashSet<>() : readByMarkers;
this.markable = markable;
this.deleted = deleted;
this.bodyLanguage = bodyLanguage;
}
public static Message fromCursor(Cursor cursor, Conversation conversation) {
@ -201,7 +205,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)),
ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))),
cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0,
cursor.getInt(cursor.getColumnIndex(DELETED)) > 0);
cursor.getInt(cursor.getColumnIndex(DELETED)) > 0,
cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE))
);
}
private static Jid fromString(String value) {
@ -266,6 +272,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
values.put(READ_BY_MARKERS, ReadByMarker.toJson(readByMarkers).toString());
values.put(MARKABLE, markable ? 1 : 0);
values.put(DELETED, deleted ? 1 : 0);
values.put(BODY_LANGUAGE, bodyLanguage);
return values;
}
@ -430,6 +437,23 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
this.edits.add(new Edited(edited, serverMsgId));
}
public boolean remoteMsgIdMatchInEdit(String id) {
for(Edited edit : this.edits) {
if (id.equals(edit.getEditedId())) {
return true;
}
}
return false;
}
public String getBodyLanguage() {
return this.bodyLanguage;
}
public void setBodyLanguage(String language) {
this.bodyLanguage = language;
}
public boolean edited() {
return this.edits.size() > 0;
}
@ -717,6 +741,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
}
public String getEditedIdWireFormat() {
if (edits.size() > 0) {
return edits.get(Config.USE_LMC_VERSION_1_1 ? 0 : edits.size() - 1).getEditedId();
} else {
throw new IllegalStateException("Attempting to store unedited message");
}
}
public void setOob(boolean isOob) {
this.oob = isOob;
}

View File

@ -58,7 +58,7 @@ public class MessageGenerator extends AbstractGenerator {
packet.setId(message.getUuid());
packet.addChild("origin-id", Namespace.STANZA_IDS).setAttribute("id", message.getUuid());
if (message.edited()) {
packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedId());
packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedIdWireFormat());
}
return packet;
}
@ -193,6 +193,10 @@ public class MessageGenerator extends AbstractGenerator {
if (password != null) {
x.setAttribute("password", password);
}
if (contact.isFullJid()) {
packet.addChild("no-store", "urn:xmpp:hints");
packet.addChild("no-copy", "urn:xmpp:hints");
}
return packet;
}

View File

@ -4,7 +4,6 @@ import com.google.common.base.Objects;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import eu.siacs.conversations.services.AvatarService;
@ -83,7 +82,7 @@ public interface MuclumbusService {
class SearchRequest {
public Set<String> keywords;
public final Set<String> keywords;
public SearchRequest(String keyword) {
this.keywords = Collections.singleton(keyword);

View File

@ -33,6 +33,7 @@ import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.LocalizedContent;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.InvalidJid;
@ -124,8 +125,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
} catch (BrokenSessionException e) {
if (checkedForDuplicates) {
if (service.trustedOrPreviouslyResponded(from.asBareJid())) {
service.reportBrokenSessionException(e, postpone);
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
} else {
Log.d(Config.LOGTAG, "ignoring broken session exception because contact was not trusted");
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
}
} else {
Log.d(Config.LOGTAG,"ignoring broken session exception because checkForDuplicates failed");
//TODO should be still emit a failed message?
@ -147,31 +153,28 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
return null;
}
private Invite extractInvite(Account account, Element message) {
Element x = message.findChild("x", "http://jabber.org/protocol/muc#user");
if (x != null) {
Element invite = x.findChild("invite");
private Invite extractInvite(Element message) {
final Element mucUser = message.findChild("x", Namespace.MUC_USER);
if (mucUser != null) {
Element invite = mucUser.findChild("invite");
if (invite != null) {
String password = x.findChildContent("password");
String password = mucUser.findChildContent("password");
Jid from = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("from"));
Contact contact = from == null ? null : account.getRoster().getContact(from);
Jid room = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
if (room == null) {
return null;
}
return new Invite(room, password, contact);
return new Invite(room, password, false, from);
}
} else {
x = message.findChild("x", "jabber:x:conference");
if (x != null) {
}
final Element conference = message.findChild("x", "jabber:x:conference");
if (conference != null) {
Jid from = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
Contact contact = from == null ? null : account.getRoster().getContact(from);
Jid room = InvalidJid.getNullForInvalid(x.getAttributeAsJid("jid"));
Jid room = InvalidJid.getNullForInvalid(conference.getAttributeAsJid("jid"));
if (room == null) {
return null;
}
return new Invite(room, x.getAttribute("password"), contact);
}
return new Invite(room, conference.getAttribute("password"), true, from);
}
return null;
}
@ -328,8 +331,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
if (timestamp == null) {
timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
}
final String body = packet.getBody();
final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
final LocalizedContent body = packet.getBody();
final Element mucUserElement = packet.findChild("x", Namespace.MUC_USER);
final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
final Element oob = packet.findChild("x", Namespace.OOB);
@ -337,7 +340,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
final URL xP1S3url = xP1S3 == null ? null : P1S3UrlStreamHandler.of(xP1S3);
final String oobUrl = oob != null ? oob.findChildContent("url") : null;
final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
int status;
final Jid counterpart;
final Jid to = packet.getTo();
@ -377,10 +380,17 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
selfAddressed = false;
}
Invite invite = extractInvite(account, packet);
if (invite != null && invite.execute(account)) {
final Invite invite = extractInvite(packet);
if (invite != null) {
if (isTypeGroupChat) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignoring invite to "+invite.jid+" because type=groupchat");
} else if (invite.direct && (mucUserElement != null || invite.inviter == null || mXmppConnectionService.isMuc(account, invite.inviter))) {
Log.d(Config.LOGTAG, account.getJid().asBareJid()+": ignoring direct invite to "+invite.jid+" because it was received in MUC");
} else {
invite.execute(account);
return;
}
}
if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null || xP1S3 != null) && !isMucStatusMessage) {
final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain());
@ -409,12 +419,15 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId)) {
return;
} else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
Message message = conversation.findSentMessageWithBody(packet.getBody());
LocalizedContent localizedBody = packet.getBody();
if (localizedBody != null) {
Message message = conversation.findSentMessageWithBody(localizedBody.content);
if (message != null) {
mXmppConnectionService.markMessage(message, status);
return;
}
}
}
} else {
status = Message.STATUS_RECEIVED;
}
@ -491,7 +504,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
}
} else {
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
message = new Message(conversation, body.content, Message.ENCRYPTION_NONE, status);
if (body.count > 1) {
message.setBodyLanguage(body.language);
}
}
message.setCounterpart(counterpart);
@ -499,7 +515,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
message.setServerMsgId(serverMsgId);
message.setCarbon(isCarbon);
message.setTime(timestamp);
if (body != null && body.equals(oobUrl)) {
if (body != null && body.content != null && body.content.equals(oobUrl)) {
message.setOob(true);
if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
@ -702,11 +718,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
if (isTypeGroupChat) {
if (packet.hasChild("subject")) {
if (packet.hasChild("subject")) { //TODO usually we would want to check for lack of body; however some servers do set a body :(
if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
String subject = packet.findInternationalizedChildContent("subject");
if (conversation.getMucOptions().setSubject(subject)) {
final LocalizedContent subject = packet.findInternationalizedChildContentInDefaultNamespace("subject");
if (subject != null && conversation.getMucOptions().setSubject(subject.content)) {
mXmppConnectionService.updateConversation(conversation);
}
mXmppConnectionService.updateConversationUi();
@ -791,9 +807,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
mXmppConnectionService.markRead(conversation);
}
} else if (!counterpart.isBareJid() && trueJid != null) {
ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid);
final ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid);
if (message.addReadByMarker(readByMarker)) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": added read by (" + readByMarker.getRealJid() + ") to message '" + message.getBody() + "'");
mXmppConnectionService.updateMessage(message, false);
}
}
@ -873,11 +888,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
private class Invite {
final Jid jid;
final String password;
final Contact inviter;
final boolean direct;
final Jid inviter;
Invite(Jid jid, String password, Contact inviter) {
Invite(Jid jid, String password, boolean direct, Jid inviter) {
this.jid = jid;
this.password = password;
this.direct = direct;
this.inviter = inviter;
}
@ -890,7 +907,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} else {
conversation.getMucOptions().setPassword(password);
mXmppConnectionService.databaseBackend.updateConversation(conversation);
mXmppConnectionService.joinMuc(conversation, inviter != null && inviter.mutualPresenceSubscription());
final Contact contact = inviter != null ? account.getRoster().getContactFromContactList(inviter) : null;
mXmppConnectionService.joinMuc(conversation, contact != null && contact.mutualPresenceSubscription());
mXmppConnectionService.updateConversationUi();
}
return true;

View File

@ -60,7 +60,7 @@ public class PresenceParser extends AbstractParser implements
final Jid from = packet.getFrom();
if (!from.isBareJid()) {
final String type = packet.getAttribute("type");
final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user");
final Element x = packet.findChild("x", Namespace.MUC_USER);
Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update"));
final List<String> codes = getStatusCodes(x);
if (type == null) {
@ -364,7 +364,7 @@ public class PresenceParser extends AbstractParser implements
@Override
public void onPresencePacketReceived(Account account, PresencePacket packet) {
if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
if (packet.hasChild("x", Namespace.MUC_USER)) {
this.parseConferencePresence(packet, account);
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
this.parseConferencePresence(packet, account);

View File

@ -61,7 +61,7 @@ import rocks.xmpp.addr.Jid;
public class DatabaseBackend extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "history";
private static final int DATABASE_VERSION = 44;
private static final int DATABASE_VERSION = 45;
private static DatabaseBackend instance = null;
private static String CREATE_CONTATCS_STATEMENT = "create table "
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@ -225,6 +225,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ Message.READ_BY_MARKERS + " TEXT,"
+ Message.MARKABLE + " NUMBER DEFAULT 0,"
+ Message.DELETED + " NUMBER DEFAULT 0,"
+ Message.BODY_LANGUAGE + " TEXT,"
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
+ Message.CONVERSATION + ") REFERENCES "
+ Conversation.TABLENAME + "(" + Conversation.UUID
@ -522,6 +523,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.execSQL(CREATE_MESSAGE_TYPE_INDEX);
}
if (oldVersion < 45 && newVersion >= 45) {
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.BODY_LANGUAGE);
}
db.execSQL("DROP TABLE resolver_results");
}

View File

@ -1,11 +1,10 @@
package eu.siacs.conversations.services;
import android.support.annotation.NonNull;
import android.util.Log;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.io.IOException;
import java.util.Collections;
@ -17,6 +16,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.http.HttpConnectionManager;
import eu.siacs.conversations.http.services.MuclumbusService;
import okhttp3.OkHttpClient;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
@ -32,13 +32,14 @@ public class ChannelDiscoveryService {
private final Cache<String, List<MuclumbusService.Room>> cache;
public ChannelDiscoveryService(XmppConnectionService service) {
ChannelDiscoveryService(XmppConnectionService service) {
this.service = service;
this.cache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build();
}
public void initializeMuclumbusService() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
void initializeMuclumbusService() {
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (service.useTorToConnect()) {
try {
builder.proxy(HttpConnectionManager.getProxy());
@ -55,9 +56,8 @@ public class ChannelDiscoveryService {
this.muclumbusService = retrofit.create(MuclumbusService.class);
}
public void discover(String query, OnChannelSearchResultsFound onChannelSearchResultsFound) {
void discover(String query, OnChannelSearchResultsFound onChannelSearchResultsFound) {
final boolean all = query == null || query.trim().isEmpty();
Log.d(Config.LOGTAG, "discover channels. query=" + query);
List<MuclumbusService.Room> result = cache.getIfPresent(all ? "" : query);
if (result != null) {
onChannelSearchResultsFound.onChannelSearchResultsFound(result);
@ -75,9 +75,11 @@ public class ChannelDiscoveryService {
try {
call.enqueue(new Callback<MuclumbusService.Rooms>() {
@Override
public void onResponse(Call<MuclumbusService.Rooms> call, Response<MuclumbusService.Rooms> response) {
public void onResponse(@NonNull Call<MuclumbusService.Rooms> call, @NonNull Response<MuclumbusService.Rooms> response) {
final MuclumbusService.Rooms body = response.body();
if (body == null) {
listener.onChannelSearchResultsFound(Collections.emptyList());
logError(response);
return;
}
cache.put("", body.items);
@ -85,7 +87,7 @@ public class ChannelDiscoveryService {
}
@Override
public void onFailure(Call<MuclumbusService.Rooms> call, Throwable throwable) {
public void onFailure(@NonNull Call<MuclumbusService.Rooms> call, @NonNull Throwable throwable) {
Log.d(Config.LOGTAG, "Unable to query muclumbus on " + Config.CHANNEL_DISCOVERY, throwable);
listener.onChannelSearchResultsFound(Collections.emptyList());
}
@ -96,14 +98,16 @@ public class ChannelDiscoveryService {
}
private void discoverChannels(final String query, OnChannelSearchResultsFound listener) {
Call<MuclumbusService.SearchResult> searchResultCall = muclumbusService.search(new MuclumbusService.SearchRequest(query));
MuclumbusService.SearchRequest searchRequest = new MuclumbusService.SearchRequest(query);
Call<MuclumbusService.SearchResult> searchResultCall = muclumbusService.search(searchRequest);
searchResultCall.enqueue(new Callback<MuclumbusService.SearchResult>() {
@Override
public void onResponse(Call<MuclumbusService.SearchResult> call, Response<MuclumbusService.SearchResult> response) {
System.out.println(response.message());
MuclumbusService.SearchResult body = response.body();
public void onResponse(@NonNull Call<MuclumbusService.SearchResult> call, @NonNull Response<MuclumbusService.SearchResult> response) {
final MuclumbusService.SearchResult body = response.body();
if (body == null) {
listener.onChannelSearchResultsFound(Collections.emptyList());
logError(response);
return;
}
cache.put(query, body.result.items);
@ -111,13 +115,26 @@ public class ChannelDiscoveryService {
}
@Override
public void onFailure(Call<MuclumbusService.SearchResult> call, Throwable throwable) {
public void onFailure(@NonNull Call<MuclumbusService.SearchResult> call, @NonNull Throwable throwable) {
Log.d(Config.LOGTAG, "Unable to query muclumbus on " + Config.CHANNEL_DISCOVERY, throwable);
listener.onChannelSearchResultsFound(Collections.emptyList());
}
});
}
private static void logError(final Response response) {
final ResponseBody errorBody = response.errorBody();
Log.d(Config.LOGTAG, "code from muclumbus=" + response.code());
if (errorBody == null) {
return;
}
try {
Log.d(Config.LOGTAG,"error body="+errorBody.string());
} catch (IOException e) {
//ignored
}
}
public interface OnChannelSearchResultsFound {
void onChannelSearchResultsFound(List<MuclumbusService.Room> results);
}

View File

@ -954,7 +954,7 @@ public class NotificationService {
createTryAgainIntent());
mBuilder.setDeleteIntent(createDismissErrorIntent());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mBuilder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
mBuilder.setVisibility(Notification.VISIBILITY_PRIVATE);
mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
} else {
mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning);

View File

@ -1383,10 +1383,8 @@ public class XmppConnectionService extends Service {
}
}
final boolean inProgressJoin;
synchronized (account.inProgressConferenceJoins) {
inProgressJoin = conversation.getMode() == Conversational.MODE_MULTI && (account.inProgressConferenceJoins.contains(conversation) || account.pendingConferenceJoins.contains(conversation));
}
final boolean inProgressJoin = isJoinInProgress(conversation);
if (account.isOnlineAndConnected() && !inProgressJoin) {
switch (message.getEncryption()) {
@ -1516,6 +1514,23 @@ public class XmppConnectionService extends Service {
}
}
private boolean isJoinInProgress(final Conversation conversation) {
final Account account = conversation.getAccount();
synchronized (account.inProgressConferenceJoins) {
if (conversation.getMode() == Conversational.MODE_MULTI) {
final boolean inProgress = account.inProgressConferenceJoins.contains(conversation);
final boolean pending = account.pendingConferenceJoins.contains(conversation);
final boolean inProgressJoin = inProgress || pending;
if (inProgressJoin) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": holding back message to group. inProgress="+inProgress+", pending="+pending);
}
return inProgressJoin;
} else {
return false;
}
}
}
private void sendUnsentMessages(final Conversation conversation) {
conversation.findWaitingMessages(message -> resendMessage(message, true));
}
@ -2190,6 +2205,7 @@ public class XmppConnectionService extends Service {
leaveMuc(conversation);
}
conversations.remove(conversation);
mNotificationService.clear(conversation);
}
}
if (account.getXmppConnection() != null) {
@ -2204,7 +2220,7 @@ public class XmppConnectionService extends Service {
this.accounts.remove(account);
this.mRosterSyncTaskManager.clear(account);
updateAccountUi();
getNotificationService().updateErrorNotification();
mNotificationService.updateErrorNotification();
syncEnabledAccountSetting();
toggleForegroundService();
}
@ -2550,6 +2566,9 @@ public class XmppConnectionService extends Service {
final MucOptions mucOptions = conversation.getMucOptions();
if (mucOptions.nonanonymous() && !mucOptions.membersOnly() && !conversation.getBooleanAttribute("accept_non_anonymous", false)) {
synchronized (account.inProgressConferenceJoins) {
account.inProgressConferenceJoins.remove(conversation);
}
mucOptions.setError(MucOptions.Error.NON_ANONYMOUS);
updateConversationUi();
if (onConferenceJoined != null) {
@ -2943,8 +2962,10 @@ public class XmppConnectionService extends Service {
for (Jid invite : jids) {
invite(conversation, invite);
}
if (account.countPresences() > 1) {
directInvite(conversation, account.getJid().asBareJid());
for(String resource : account.getSelfContact().getPresences().toResourceArray()) {
Jid other = account.getJid().withResource(resource);
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": sending direct invite to "+other);
directInvite(conversation, other);
}
saveConversationAsBookmark(conversation, name);
if (callback != null) {
@ -2989,7 +3010,6 @@ public class XmppConnectionService extends Service {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.RESULT) {
final MucOptions mucOptions = conversation.getMucOptions();
final Bookmark bookmark = conversation.getBookmark();
final boolean sameBefore = StringUtils.equals(bookmark == null ? null : bookmark.getBookmarkName(), mucOptions.getName());
@ -3011,9 +3031,10 @@ public class XmppConnectionService extends Service {
}
updateConversationUi();
} else if (packet.getType() == IqPacket.TYPE.ERROR) {
} else if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": received timeout waiting for conference configuration fetch");
} else {
if (callback != null) {
callback.onFetchFailed(conversation, packet.getError());
}
@ -3073,7 +3094,6 @@ public class XmppConnectionService extends Service {
if (packet.getType() == IqPacket.TYPE.RESULT) {
Data data = Data.parse(packet.query().findChild("x", Namespace.DATA));
data.submit(options);
Log.d(Config.LOGTAG,data.toString());
IqPacket set = new IqPacket(IqPacket.TYPE.SET);
set.setTo(conversation.getJid().asBareJid());
set.query("http://jabber.org/protocol/muc#owner").addChild(data);

View File

@ -2,8 +2,10 @@ package eu.siacs.conversations.ui;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.databinding.DataBindingUtil;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.text.Html;
@ -37,11 +39,8 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
private static final String CHANNEL_DISCOVERY_OPT_IN = "channel_discovery_opt_in";
private final ChannelSearchResultAdapter adapter = new ChannelSearchResultAdapter();
private ActivityChannelDiscoveryBinding binding;
private final PendingItem<String> mInitialSearchValue = new PendingItem<>();
private ActivityChannelDiscoveryBinding binding;
private MenuItem mMenuSearchView;
private EditText mSearchEditText;
@ -198,6 +197,26 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
}
@Override
public boolean onContextItemSelected(MenuItem item) {
final MuclumbusService.Room room = adapter.getCurrent();
if (room != null) {
switch (item.getItemId()) {
case R.id.share_with:
StartConversationActivity.shareAsChannel(this, room.address);
return true;
case R.id.open_join_dialog:
final Intent intent = new Intent(this, StartConversationActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra("force_dialog", true);
intent.setData(Uri.parse(String.format("xmpp:%s?join", room.address)));
startActivity(intent);
return true;
}
}
return false;
}
public void joinChannelSearchResult(String accountJid, MuclumbusService.Room result) {
final boolean syncAutojoin = getBooleanPreference("autojoin", R.bool.autojoin);
Account account = xmppConnectionService.findAccountByJid(Jid.of(accountJid));

View File

@ -13,6 +13,9 @@ import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.RelativeSizeSpan;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@ -49,6 +52,7 @@ import eu.siacs.conversations.ui.util.JidDialog;
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
import eu.siacs.conversations.utils.AccountUtils;
import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.Emoticons;
import eu.siacs.conversations.utils.IrregularUnicodeDetector;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.utils.XmppUri;
@ -328,14 +332,19 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
List<String> statusMessages = contact.getPresences().getStatusMessages();
if (statusMessages.size() == 0) {
binding.statusMessage.setVisibility(View.GONE);
} else if (statusMessages.size() == 1) {
final String message = statusMessages.get(0);
binding.statusMessage.setVisibility(View.VISIBLE);
final Spannable span = new SpannableString(message);
if (Emoticons.isOnlyEmoji(message)) {
span.setSpan(new RelativeSizeSpan(2.0f), 0, message.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
binding.statusMessage.setText(span);
} else {
StringBuilder builder = new StringBuilder();
binding.statusMessage.setVisibility(View.VISIBLE);
int s = statusMessages.size();
for (int i = 0; i < s; ++i) {
if (s > 1) {
builder.append("");
}
builder.append(statusMessages.get(i));
if (i < s - 1) {
builder.append("\n");

View File

@ -1552,7 +1552,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
intent = GeoHelper.getFetchIntent(activity);
break;
}
if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
final Context context = getActivity();
if (context != null && intent.resolveActivity(context.getPackageManager()) != null) {
if (chooser) {
startActivityForResult(
Intent.createChooser(intent, getString(R.string.perform_action_with)),

View File

@ -4,6 +4,7 @@ import android.app.Activity;
import android.app.Dialog;
import android.databinding.DataBindingUtil;
import android.support.annotation.NonNull;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
@ -65,9 +66,9 @@ public class JoinConferenceDialog extends DialogFragment implements OnBackendCon
builder.setNegativeButton(R.string.cancel, null);
AlertDialog dialog = builder.create();
dialog.show();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(view -> mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.jid, binding.bookmark.isChecked()));
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(view -> mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.accountJidLayout, binding.jid, binding.bookmark.isChecked()));
binding.jid.setOnEditorActionListener((v, actionId, event) -> {
mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.jid, binding.bookmark.isChecked());
mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.accountJidLayout, binding.jid, binding.bookmark.isChecked());
return true;
});
return dialog;
@ -116,6 +117,6 @@ public class JoinConferenceDialog extends DialogFragment implements OnBackendCon
}
public interface JoinConferenceDialogListener {
void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, AutoCompleteTextView jid, boolean isBookmarkChecked);
void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, TextInputLayout jidLayout, AutoCompleteTextView jid, boolean isBookmarkChecked);
}
}

View File

@ -15,6 +15,7 @@ import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
@ -405,14 +406,18 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
protected void shareBookmarkUri(int position) {
Bookmark bookmark = (Bookmark) conferences.get(position);
shareAsChannel(this, bookmark.getJid().asBareJid().toEscapedString());
}
public static void shareAsChannel(final Context context, final String address) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, "xmpp:" + bookmark.getJid().asBareJid().toEscapedString() + "?join");
shareIntent.putExtra(Intent.EXTRA_TEXT, "xmpp:" + address + "?join");
shareIntent.setType("text/plain");
try {
startActivity(Intent.createChooser(shareIntent, getText(R.string.share_uri_with)));
context.startActivity(Intent.createChooser(shareIntent, context.getText(R.string.share_uri_with)));
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
Toast.makeText(context, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
}
}
@ -833,6 +838,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (uri != null) {
Invite invite = new Invite(intent.getData(), intent.getBooleanExtra("scanned", false));
invite.account = intent.getStringExtra("account");
invite.forceDialog = intent.getBooleanExtra("force_dialog", false);
return invite.invite();
} else {
return false;
@ -845,7 +851,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid(), invite.account);
if (invite.isAction(XmppUri.ACTION_JOIN)) {
Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
if (muc != null) {
if (muc != null && !invite.forceDialog) {
switchToConversationDoNotAppend(muc, invite.getBody());
return true;
} else {
@ -1000,7 +1006,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
@Override
public void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, AutoCompleteTextView jid, boolean isBookmarkChecked) {
public void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, TextInputLayout layout, AutoCompleteTextView jid, boolean isBookmarkChecked) {
if (!xmppConnectionServiceBound) {
return;
}
@ -1008,17 +1014,26 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (account == null) {
return;
}
final Jid conferenceJid;
final String input = jid.getText().toString();
Jid conferenceJid;
try {
conferenceJid = Jid.of(jid.getText().toString());
conferenceJid = Jid.of(input);
} catch (final IllegalArgumentException e) {
jid.setError(getString(R.string.invalid_jid));
final XmppUri xmppUri = new XmppUri(input);
if (xmppUri.isJidValid() && xmppUri.isAction(XmppUri.ACTION_JOIN)) {
final Editable editable = jid.getEditableText();
editable.clear();
editable.append(xmppUri.getJid().toEscapedString());
conferenceJid = xmppUri.getJid();
} else {
layout.setError(getString(R.string.invalid_jid));
return;
}
}
if (isBookmarkChecked) {
if (account.hasBookmarkFor(conferenceJid)) {
jid.setError(getString(R.string.bookmark_already_exists));
layout.setError(getString(R.string.bookmark_already_exists));
} else {
final Bookmark bookmark = new Bookmark(account, conferenceJid.asBareJid());
bookmark.setAutojoin(getBooleanPreference("autojoin", R.bool.autojoin));
@ -1274,6 +1289,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
public String account;
public boolean forceDialog = false;
public Invite(final Uri uri) {
super(uri);
}

View File

@ -1,11 +1,13 @@
package eu.siacs.conversations.ui.adapter;
import android.app.Activity;
import android.databinding.DataBindingUtil;
import android.support.annotation.NonNull;
import android.support.v7.recyclerview.extensions.ListAdapter;
import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -15,11 +17,11 @@ import java.util.Locale;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.SearchResultItemBinding;
import eu.siacs.conversations.http.services.MuclumbusService;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
import rocks.xmpp.addr.Jid;
public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Room, ChannelSearchResultAdapter.ViewHolder> {
private OnChannelSearchResultSelected listener;
public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Room, ChannelSearchResultAdapter.ViewHolder> implements View.OnCreateContextMenuListener {
private static final DiffUtil.ItemCallback<MuclumbusService.Room> DIFF = new DiffUtil.ItemCallback<MuclumbusService.Room>() {
@Override
@ -32,6 +34,8 @@ public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Roo
return a.equals(b);
}
};
private OnChannelSearchResultSelected listener;
private MuclumbusService.Room current;
public ChannelSearchResultAdapter() {
super(DIFF);
@ -61,15 +65,37 @@ public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Roo
viewHolder.binding.language.setText(language.toUpperCase(Locale.ENGLISH));
viewHolder.binding.language.setVisibility(View.VISIBLE);
}
viewHolder.binding.room.setText(searchResult.getRoom().asBareJid().toString());
final Jid room = searchResult.getRoom();
viewHolder.binding.room.setText(room != null ? room.asBareJid().toString() : "");
AvatarWorkerTask.loadAvatar(searchResult, viewHolder.binding.avatar, R.dimen.avatar);
viewHolder.binding.getRoot().setOnClickListener(v -> listener.onChannelSearchResult(searchResult));
final View root = viewHolder.binding.getRoot();
root.setTag(searchResult);
root.setOnClickListener(v -> listener.onChannelSearchResult(searchResult));
root.setOnCreateContextMenuListener(this);
}
public void setOnChannelSearchResultSelectedListener(OnChannelSearchResultSelected listener) {
this.listener = listener;
}
public MuclumbusService.Room getCurrent() {
return this.current;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
final Activity activity = XmppActivity.find(v);
final Object tag = v.getTag();
if (activity != null && tag instanceof MuclumbusService.Room) {
activity.getMenuInflater().inflate(R.menu.channel_item_context, menu);
this.current = (MuclumbusService.Room) tag;
}
}
public interface OnChannelSearchResultSelected {
void onChannelSearchResult(MuclumbusService.Room result);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
@ -80,8 +106,4 @@ public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Roo
this.binding = binding;
}
}
public interface OnChannelSearchResultSelected {
void onChannelSearchResult(MuclumbusService.Room result);
}
}

View File

@ -36,6 +36,7 @@ import com.google.common.base.Strings;
import java.net.URL;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -282,30 +283,32 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
viewHolder.indicator.setVisibility(View.VISIBLE);
}
String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getMergedTimeSent());
if (message.getStatus() <= Message.STATUS_RECEIVED) {
final String formattedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getMergedTimeSent());
final String bodyLanguage = message.getBodyLanguage();
final String bodyLanguageInfo = bodyLanguage == null ? "" : String.format(" \u00B7 %s", bodyLanguage.toUpperCase(Locale.US));
if (message.getStatus() <= Message.STATUS_RECEIVED) { ;
if ((filesize != null) && (info != null)) {
viewHolder.time.setText(formatedTime + " \u00B7 " + filesize + " \u00B7 " + info);
viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + " \u00B7 " + info + bodyLanguageInfo);
} else if ((filesize == null) && (info != null)) {
viewHolder.time.setText(formatedTime + " \u00B7 " + info);
viewHolder.time.setText(formattedTime + " \u00B7 " + info + bodyLanguageInfo);
} else if ((filesize != null) && (info == null)) {
viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + bodyLanguageInfo);
} else {
viewHolder.time.setText(formatedTime);
viewHolder.time.setText(formattedTime+bodyLanguageInfo);
}
} else {
if ((filesize != null) && (info != null)) {
viewHolder.time.setText(filesize + " \u00B7 " + info);
viewHolder.time.setText(filesize + " \u00B7 " + info + bodyLanguageInfo);
} else if ((filesize == null) && (info != null)) {
if (error) {
viewHolder.time.setText(info + " \u00B7 " + formatedTime);
viewHolder.time.setText(info + " \u00B7 " + formattedTime + bodyLanguageInfo);
} else {
viewHolder.time.setText(info);
}
} else if ((filesize != null) && (info == null)) {
viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
viewHolder.time.setText(filesize + " \u00B7 " + formattedTime + bodyLanguageInfo);
} else {
viewHolder.time.setText(formatedTime);
viewHolder.time.setText(formattedTime+bodyLanguageInfo);
}
}
}

View File

@ -147,6 +147,8 @@ public final class MucDetailsContextMenuHelper {
activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.ADMIN, onAffiliationChanged);
return true;
case R.id.give_membership:
case R.id.remove_admin_privileges:
case R.id.revoke_owner_privileges:
activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.MEMBER, onAffiliationChanged);
return true;
case R.id.give_owner_privileges:
@ -155,10 +157,6 @@ public final class MucDetailsContextMenuHelper {
case R.id.remove_membership:
activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.NONE, onAffiliationChanged);
return true;
case R.id.remove_admin_privileges:
case R.id.revoke_owner_privileges:
activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.MEMBER, onAffiliationChanged);
return true;
case R.id.remove_from_room:
removeFromRoom(user, activity, onAffiliationChanged);
return true;
@ -180,7 +178,7 @@ public final class MucDetailsContextMenuHelper {
return true;
case R.id.invite:
if (user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
activity.xmppConnectionService.directInvite(conversation, jid);
activity.xmppConnectionService.directInvite(conversation, jid.asBareJid());
} else {
activity.xmppConnectionService.invite(conversation, jid);
}

View File

@ -1,14 +1,12 @@
package eu.siacs.conversations.ui.widget;
import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import android.widget.TextView;
public class CopyTextView extends TextView {
public class CopyTextView extends AppCompatTextView {
public CopyTextView(Context context) {
super(context);
@ -22,14 +20,8 @@ public class CopyTextView extends TextView {
super(context, attrs, defStyleAttr);
}
@SuppressWarnings("unused")
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CopyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public interface CopyHandler {
public String transformTextForCopy(CharSequence text, int start, int end);
String transformTextForCopy(CharSequence text, int start, int end);
}
private CopyHandler copyHandler;
@ -40,7 +32,7 @@ public class CopyTextView extends TextView {
@Override
public boolean onTextContextMenuItem(int id) {
CharSequence text = getText();
final CharSequence text = getText();
int min = 0;
int max = text.length();
if (isFocused()) {

View File

@ -1,8 +1,5 @@
package eu.siacs.conversations.ui.widget;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@ -13,12 +10,17 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ListSelectionManager {
private static final int MESSAGE_SEND_RESET = 1;
private static final int MESSAGE_RESET = 2;
private static final int MESSAGE_START_SELECTION = 3;
private static final Field FIELD_EDITOR;
private static final Method METHOD_START_SELECTION;
private static final boolean SUPPORTED;
private static final Handler HANDLER = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
@ -45,30 +47,58 @@ public class ListSelectionManager {
}
});
private static class StartSelectionHolder {
public final ListSelectionManager listSelectionManager;
public final TextView textView;
public final int start;
public final int end;
public StartSelectionHolder(ListSelectionManager listSelectionManager, TextView textView,
int start, int end) {
this.listSelectionManager = listSelectionManager;
this.textView = textView;
this.start = start;
this.end = end;
static {
Field editor;
try {
editor = TextView.class.getDeclaredField("mEditor");
editor.setAccessible(true);
} catch (Exception e) {
editor = null;
}
FIELD_EDITOR = editor;
Method startSelection = null;
if (editor != null) {
String[] startSelectionNames = {"startSelectionActionMode", "startSelectionActionModeWithSelection"};
for (String startSelectionName : startSelectionNames) {
try {
startSelection = editor.getType().getDeclaredMethod(startSelectionName);
startSelection.setAccessible(true);
break;
} catch (Exception e) {
startSelection = null;
}
}
}
METHOD_START_SELECTION = startSelection;
SUPPORTED = FIELD_EDITOR != null && METHOD_START_SELECTION != null;
}
private ActionMode selectionActionMode;
private Object selectionIdentifier;
private TextView selectionTextView;
private Object futureSelectionIdentifier;
private int futureSelectionStart;
private int futureSelectionEnd;
public static boolean isSupported() {
return SUPPORTED;
}
private static void startSelection(TextView textView, int start, int end) {
final CharSequence text = textView.getText();
if (SUPPORTED && start >= 0 && end > start && textView.isTextSelectable() && text instanceof Spannable) {
final Spannable spannable = (Spannable) text;
start = Math.min(start, spannable.length());
end = Math.min(end, spannable.length());
Selection.setSelection(spannable, start, end);
try {
final Object editor = FIELD_EDITOR != null ? FIELD_EDITOR.get(textView) : textView;
METHOD_START_SELECTION.invoke(editor);
} catch (Exception e) {
}
}
}
public void onCreate(TextView textView, ActionMode.Callback additionalCallback) {
final CustomCallback callback = new CustomCallback(textView, additionalCallback);
textView.setCustomSelectionActionModeCallback(callback);
@ -76,14 +106,17 @@ public class ListSelectionManager {
public void onUpdate(TextView textView, Object identifier) {
if (SUPPORTED) {
CustomCallback callback = (CustomCallback) textView.getCustomSelectionActionModeCallback();
callback.identifier = identifier;
final ActionMode.Callback callback = textView.getCustomSelectionActionModeCallback();
if (callback instanceof CustomCallback) {
final CustomCallback customCallback = (CustomCallback) textView.getCustomSelectionActionModeCallback();
customCallback.identifier = identifier;
if (futureSelectionIdentifier == identifier) {
HANDLER.obtainMessage(MESSAGE_START_SELECTION, new StartSelectionHolder(this,
textView, futureSelectionStart, futureSelectionEnd)).sendToTarget();
}
}
}
}
public void onBeforeNotifyDataSetChanged() {
if (SUPPORTED) {
@ -109,13 +142,29 @@ public class ListSelectionManager {
}
}
private static class StartSelectionHolder {
final ListSelectionManager listSelectionManager;
final TextView textView;
public final int start;
public final int end;
StartSelectionHolder(ListSelectionManager listSelectionManager, TextView textView,
int start, int end) {
this.listSelectionManager = listSelectionManager;
this.textView = textView;
this.start = start;
this.end = end;
}
}
private class CustomCallback implements ActionMode.Callback {
private final TextView textView;
private final ActionMode.Callback additionalCallback;
public Object identifier;
Object identifier;
public CustomCallback(TextView textView, ActionMode.Callback additionalCallback) {
CustomCallback(TextView textView, ActionMode.Callback additionalCallback) {
this.textView = textView;
this.additionalCallback = additionalCallback;
}
@ -159,53 +208,4 @@ public class ListSelectionManager {
}
}
}
private static final Field FIELD_EDITOR;
private static final Method METHOD_START_SELECTION;
private static final boolean SUPPORTED;
static {
Field editor;
try {
editor = TextView.class.getDeclaredField("mEditor");
editor.setAccessible(true);
} catch (Exception e) {
editor = null;
}
FIELD_EDITOR = editor;
Method startSelection = null;
if (editor != null) {
String[] startSelectionNames = {"startSelectionActionMode", "startSelectionActionModeWithSelection"};
for (String startSelectionName : startSelectionNames) {
try {
startSelection = editor.getType().getDeclaredMethod(startSelectionName);
startSelection.setAccessible(true);
break;
} catch (Exception e) {
startSelection = null;
}
}
}
METHOD_START_SELECTION = startSelection;
SUPPORTED = FIELD_EDITOR != null && METHOD_START_SELECTION != null;
}
public static boolean isSupported() {
return SUPPORTED;
}
public static void startSelection(TextView textView, int start, int end) {
final CharSequence text = textView.getText();
if (SUPPORTED && start >= 0 && end > start && textView.isTextSelectable() && text instanceof Spannable) {
final Spannable spannable = (Spannable) text;
start = Math.min(start, spannable.length());
end = Math.min(end, spannable.length());
Selection.setSelection(spannable, start, end);
try {
final Object editor = FIELD_EDITOR != null ? FIELD_EDITOR.get(textView) : textView;
METHOD_START_SELECTION.invoke(editor);
} catch (Exception e) {
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, Daniel Gultsch All rights reserved.
* Copyright (c) 2018-2019, Daniel Gultsch All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
@ -40,6 +40,8 @@ import android.text.style.ForegroundColorSpan;
import android.util.LruCache;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -57,6 +59,7 @@ public class IrregularUnicodeDetector {
private static final Map<Character.UnicodeBlock, Character.UnicodeBlock> NORMALIZATION_MAP;
private static final LruCache<Jid, PatternTuple> CACHE = new LruCache<>(4096);
private static final List<String> AMBIGUOUS_CYRILLIC = Arrays.asList("а","г","е","ѕ","і","q","о","р","с","у");
static {
Map<Character.UnicodeBlock, Character.UnicodeBlock> temp = new HashMap<>();
@ -185,13 +188,41 @@ public class IrregularUnicodeDetector {
private static Set<String> findIrregularCodePoints(String word) {
Set<String> codePoints;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
codePoints = eliminateFirstAndGetCodePointsCompat(mapCompat(word));
final Map<Character.UnicodeBlock, List<String>> map = mapCompat(word);
final Set<String> set = asSet(map);
if (containsOnlyAmbiguousCyrillic(set)) {
return set;
}
codePoints = eliminateFirstAndGetCodePointsCompat(map);
} else {
codePoints = eliminateFirstAndGetCodePoints(map(word));
final Map<Character.UnicodeScript, List<String>> map = map(word);
final Set<String> set = asSet(map);
if (containsOnlyAmbiguousCyrillic(set)) {
return set;
}
codePoints = eliminateFirstAndGetCodePoints(map);
}
return codePoints;
}
private static Set<String> asSet(Map<?, List<String>> map) {
final Set<String> flat = new HashSet<>();
for(List<String> value : map.values()) {
flat.addAll(value);
}
return flat;
}
private static boolean containsOnlyAmbiguousCyrillic(Collection<String> codePoints) {
for (String codePoint : codePoints) {
if (!AMBIGUOUS_CYRILLIC.contains(codePoint)) {
return false;
}
}
return true;
}
private static PatternTuple find(Jid jid) {
synchronized (CACHE) {
PatternTuple pattern = CACHE.get(jid);

View File

@ -1,15 +1,11 @@
package eu.siacs.conversations.xml;
import android.support.annotation.NonNull;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.XmlHelper;
import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
@ -71,31 +67,8 @@ public class Element {
return element == null ? null : element.getContent();
}
public String findInternationalizedChildContent(String name) {
return findInternationalizedChildContent(name, Locale.getDefault().getLanguage());
}
private String findInternationalizedChildContent(String name, @NonNull String language) {
final HashMap<String,String> contents = new HashMap<>();
for(Element child : this.children) {
if (name.equals(child.getName())) {
String lang = child.getAttribute("xml:lang");
String content = child.getContent();
if (content != null) {
if (language.equals(lang)) {
return content;
} else {
contents.put(lang, content);
}
}
}
}
final String value = contents.get(null);
if (value != null) {
return value;
}
final String[] values = contents.values().toArray(new String[0]);
return values.length == 0 ? null : values[0];
public LocalizedContent findInternationalizedChildContentInDefaultNamespace(String name) {
return LocalizedContent.get(this, name);
}
public Element findChild(String name, String xmlns) {
@ -107,6 +80,19 @@ public class Element {
return null;
}
public Element findChildEnsureSingle(String name, String xmlns) {
final List<Element> results = new ArrayList<>();
for (Element child : this.children) {
if (name.equals(child.getName()) && xmlns.equals(child.getAttribute("xmlns"))) {
results.add(child);
}
}
if (results.size() == 1) {
return results.get(0);
}
return null;
}
public String findChildContent(String name, String xmlns) {
Element element = findChild(name,xmlns);
return element == null ? null : element.getContent();

View File

@ -0,0 +1,59 @@
package eu.siacs.conversations.xml;
import com.google.common.collect.Iterables;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public class LocalizedContent {
public static final String STREAM_LANGUAGE = "en";
public final String content;
public final String language;
public final int count;
private LocalizedContent(String content, String language, int count) {
this.content = content;
this.language = language;
this.count = count;
}
public static LocalizedContent get(final Element element, String name) {
final HashMap<String, String> contents = new HashMap<>();
final String parentLanguage = element.getAttribute("xml:lang");
for(Element child : element.children) {
if (name.equals(child.getName())) {
final String namespace = child.getNamespace();
final String childLanguage = child.getAttribute("xml:lang");
final String lang = childLanguage == null ? parentLanguage : childLanguage;
final String content = child.getContent();
if (content != null && (namespace == null || "jabber:client".equals(namespace))) {
if (contents.put(lang, content) != null) {
//anything that has multiple contents for the same language is invalid
return null;
}
}
}
}
if (contents.size() == 0) {
return null;
}
final String userLanguage = Locale.getDefault().getLanguage();
final String localized = contents.get(userLanguage);
if (localized != null) {
return new LocalizedContent(localized, userLanguage, contents.size());
}
final String defaultLanguageContent = contents.get(null);
if (defaultLanguageContent != null) {
return new LocalizedContent(defaultLanguageContent, STREAM_LANGUAGE, contents.size());
}
final String streamLanguageContent = contents.get(STREAM_LANGUAGE);
if (streamLanguageContent != null) {
return new LocalizedContent(streamLanguageContent, STREAM_LANGUAGE, contents.size());
}
final Map.Entry<String, String> first = Iterables.get(contents.entrySet(), 0);
return new LocalizedContent(first.getValue(), first.getKey(), contents.size());
}
}

View File

@ -32,4 +32,5 @@ public final class Namespace {
public static final String COMMANDS = "http://jabber.org/protocol/commands";
public static final String JINGLE_ENCRYPTED_TRANSPORT = "urn:xmpp:jingle:jet:0";
public static final String JINGLE_ENCRYPTED_TRANSPORT_OMEMO = "urn:xmpp:jingle:jet-omemo:0";
public static final String MUC_USER = "http://jabber.org/protocol/muc#user";
}

View File

@ -80,6 +80,7 @@ import eu.siacs.conversations.utils.SSLSocketHelper;
import eu.siacs.conversations.utils.SocksSocketFactory;
import eu.siacs.conversations.utils.XmlHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.LocalizedContent;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xml.Tag;
import eu.siacs.conversations.xml.TagWriter;
@ -1313,7 +1314,7 @@ public class XmppConnection implements Runnable {
final Tag stream = Tag.start("stream:stream");
stream.setAttribute("to", account.getServer());
stream.setAttribute("version", "1.0");
stream.setAttribute("xml:lang", "en");
stream.setAttribute("xml:lang", LocalizedContent.STREAM_LANGUAGE);
stream.setAttribute("xmlns", "jabber:client");
stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
tagWriter.writeTag(stream);

View File

@ -142,7 +142,7 @@ public class JingleSocks5Transport extends JingleTransport {
this.isEstablished = true;
FileBackend.close(serverSocket);
} else {
this.socket.close();
FileBackend.close(socket);
}
} else {
socket.close();

View File

@ -4,6 +4,7 @@ import android.util.Pair;
import eu.siacs.conversations.parser.AbstractParser;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.LocalizedContent;
public class MessagePacket extends AbstractAcknowledgeableStanza {
public static final int TYPE_CHAT = 0;
@ -16,8 +17,8 @@ public class MessagePacket extends AbstractAcknowledgeableStanza {
super("message");
}
public String getBody() {
return findChildContent("body");
public LocalizedContent getBody() {
return findInternationalizedChildContentInDefaultNamespace("body");
}
public void setBody(String text) {

View File

@ -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/share_with"
android:title="@string/share_uri_with" />
<item
android:id="@+id/open_join_dialog"
android:title="@string/open_join_dialog"/>
</menu>

View File

@ -7,6 +7,7 @@
<string name="action_end_conversation">أغلق هذه المحادثة</string>
<string name="action_contact_details">بيانات جهة الإتصال</string>
<string name="action_muc_details">تفاصيل مجموعة المحادثة</string>
<string name="channel_details">تفاصيل القناة</string>
<string name="action_secure">تشفير المحادثة</string>
<string name="action_add_account">إضافة حساب</string>
<string name="action_edit_contact">تعديل الإسم</string>
@ -16,6 +17,8 @@
<string name="action_unblock_contact">إنهاء حجب جهة اتصال</string>
<string name="action_block_domain">حجب دومين</string>
<string name="action_unblock_domain">إنهاء حجب دومين</string>
<string name="action_block_participant">احجب المشارِك</string>
<string name="action_unblock_participant">إلغاء حجب المشارِك</string>
<string name="title_activity_manage_accounts">إدارة الحسابات</string>
<string name="title_activity_settings">إعدادات</string>
<string name="title_activity_sharewith">مشاركة مع محادثة</string>
@ -50,6 +53,7 @@
<string name="share_with">مشاركة مع</string>
<string name="start_conversation">إبداء المحادثة</string>
<string name="invite_contact">دعوة جهة إتصال</string>
<string name="invite">دعوة</string>
<string name="contacts">جهات الإتصال</string>
<string name="contact">جهة إتصال</string>
<string name="cancel">الغاء</string>
@ -143,6 +147,8 @@
<string name="account_status_tls_error">فشلت عملية التفاوض عبر TLS</string>
<string name="account_status_policy_violation">خرق للقواعد</string>
<string name="account_status_incompatible_server">لا يتوافق مع السيرفر</string>
<string name="account_status_stream_error">خطأ في التدفق</string>
<string name="account_status_stream_opening_error">خطأ عند فتح التدفق</string>
<string name="encryption_choice_unencrypted">غير مشفر</string>
<string name="encryption_choice_otr">رسالة مشفرة عبر OTR</string>
<string name="encryption_choice_pgp">رسالة مشفرة عبر OpenPGP</string>
@ -157,6 +163,8 @@
<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">عنوان XMPP</string>
<string name="block_jabber_id">احجب عنوان XMPP</string>
<string name="account_settings_example_jabber_id">username@example.com</string>
<string name="password">كلمة السر</string>
<string name="error_out_of_memory">خارج الذاكرة. الصورة كبيرة جدا</string>
@ -186,11 +194,13 @@
<string name="omemo_fingerprint">بصمة OMEMO</string>
<string name="omemo_fingerprint_x509">بصمة v\\OMEMO</string>
<string name="omemo_fingerprint_selected_message">بصمة OMEMO للرسالة</string>
<string name="omemo_fingerprint_x509_selected_message">بصمة v\\OMEMO للرسالة</string>
<string name="other_devices">أجهزة أخرى</string>
<string name="trust_omemo_fingerprints">الثقة في بصمات أوميمو OMEMO</string>
<string name="fetching_keys">جارإحضار المفاتيح ...</string>
<string name="done">تم</string>
<string name="decrypt">فك الشيفرة</string>
<string name="bookmarks">الفواصل المرجعية</string>
<string name="search">بحث</string>
<string name="enter_contact">قم بإدخال جهة إتصال</string>
<string name="delete_contact">حذف جهة الإتصال</string>
@ -200,11 +210,18 @@
<string name="create">أضف</string>
<string name="select">إختر</string>
<string name="contact_already_exists">جهة الاتصال موجودة لديك مسبقا</string>
<string name="join">دخول</string>
<string name="join">التحق</string>
<string name="channel_full_jid_example">channel@conference.example.com/nick</string>
<string name="channel_bare_jid_example">channel@conference.example.com</string>
<string name="save_as_bookmark">حفظ بالمفضلة</string>
<string name="delete_bookmark">إحذف من المفضلة</string>
<string name="destroy_room">دمر فريق المحادثة</string>
<string name="destroy_channel">دمر القناة</string>
<string name="could_not_destroy_room">لم نتمكن مِن تدمير فريق المحادثة</string>
<string name="could_not_destroy_channel">لم نتمكن مِن تدمير القناة</string>
<string name="bookmark_already_exists">موجوده بالمفضلة سابقا</string>
<string name="action_edit_subject">تعديل موضوع مجموعة المحادثة</string>
<string name="topic">الموضوع</string>
<string name="joining_conference">في صدد الإنظمام إلى مجموعة المحادثة ...</string>
<string name="leave">غادر</string>
<string name="contact_added_you">جهة اتصال أضافتك </string>
@ -237,6 +254,7 @@
<string name="pref_allow_message_correction_summary">السماح لمراسليك بتعديل رسائلهم</string>
<string name="pref_expert_options">إعدادات متقدمة</string>
<string name="pref_expert_options_summary">كن حذراً مع هذه من فضلك</string>
<string name="title_activity_about_x">عن %s</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>
@ -244,6 +262,7 @@
<string name="pref_quiet_hours_summary">سوف تكتم التنبيهات إبان ساعات السكون</string>
<string name="pref_use_indicate_received">طلب تقارير تسليم الرسائل</string>
<string name="pref_expert_options_other">أخرى</string>
<string name="pref_autojoin">زامِن مع الفواصل المرجعية</string>
<string name="toast_message_omemo_fingerprint">تم نسخ بصمة OMEMO إلى الحافظة !</string>
<string name="conference_banned">حسابك محظور للإلتحاق بمجموعة المحادثة هذه</string>
<string name="conference_members_only">هذه المجموعة متاحة للأعضاء المنتمين إليها فقط</string>
@ -253,11 +272,16 @@
<string name="using_account">أنت تستعمل حساب %s</string>
<string name="not_connected_try_again">انقطع الإتصال .. حاول مرة أخرى</string>
<string name="check_x_filesize">تحقق من حجم %s</string>
<string name="check_x_filesize_on_host">تحقق مِن حجم %1$s على %2$s</string>
<string name="message_options">خيارات الرسالة</string>
<string name="quote">إقتبس</string>
<string name="paste_as_quote">ألصقه كاقتباس</string>
<string name="copy_original_url">أنسخ الرابط الأصلي</string>
<string name="send_again">أعد الإرسال</string>
<string name="file_url">رابط الملف</string>
<string name="jabber_id_copied_to_clipboard">تم نسخ عنوان الـ XMPP إلى الحافظة</string>
<string name="error_message_copied_to_clipboard">تم نسخ رسالة الخطأ إلى الحافظة</string>
<string name="web_address">عنوان الويب</string>
<string name="scan_qr_code">إمسح شفرة التّعرّف 2D</string>
<string name="show_qr_code">أظهر شفرة التّعرّف 2D</string>
<string name="show_block_list">إعرض قائمة المحبوسين</string>
@ -266,6 +290,10 @@
<string name="try_again">حاول مرة أخرى</string>
<string name="pref_keep_foreground_service">احتفظ بالتطبيق يعمل في المقدمة</string>
<string name="pref_keep_foreground_service_summary">منع نظام التشغيل من انهاء اتصالك</string>
<string name="pref_create_backup">أنشئ نسخة احتياطية</string>
<string name="notification_backup_created_title">تم إنشاء نسختك الاحتياطية</string>
<string name="notification_restored_backup_title">تم استرجاع نسختك الاحتياطية</string>
<string name="notification_restored_backup_subtitle">لا تنسى تنشيط الحساب.</string>
<string name="choose_file">اختيار ملف</string>
<string name="receiving_x_file">اكتمل الإستلام %1$s (%2$d%% بنسبة)</string>
<string name="download_x_file">تنزيل %s</string>
@ -280,8 +308,10 @@
<string name="file_deleted">تم حذف الملف</string>
<string name="no_application_found_to_open_file">لا يوجد تطبيق متاح لعرض الملف</string>
<string name="no_application_found_to_open_link">تعذر العثور على تطبيق يمكنه فتح الرابط</string>
<string name="pref_show_dynamic_tags">وسوم ديناميكية</string>
<string name="pref_show_dynamic_tags_summary">عرض علامات للقراءة فقط أسفل بيانات جهات الإتصال </string>
<string name="enable_notifications">تفعيل الإشعارات</string>
<string name="no_conference_server_found">لم يُعثر على أي خادم للمحادثات الجماعية</string>
<string name="conference_creation_failed">فشلت عملية إنشاء مجموعة المحادثة !</string>
<string name="account_image_description">الصورة الرمزية للحساب</string>
<string name="copy_omemo_clipboard_description">انسخ بصمة OMEMO إلى الحافظة</string>
@ -308,11 +338,14 @@
<string name="grant_admin_privileges">منح امتيازات الإداره</string>
<string name="remove_admin_privileges">إلغاء امتيازات الإدارة</string>
<string name="remove_from_room">التنحية من مجموعة المحادثة</string>
<string name="remove_from_channel">أزله مِن القناة</string>
<string name="could_not_change_affiliation">لا يمكن تغيير انتساب %s</string>
<string name="ban_from_conference">الحظر من دخول مجموعة المحادثة</string>
<string name="ban_now">حظر الآن</string>
<string name="could_not_change_role">لا يمكن تغيير دول %s</string>
<string name="channel_options">إعدادات القناة العمومية</string>
<string name="members_only">سرِّي ، للأعضاء فقط</string>
<string name="moderated">اجعل القناة تحت الإشراف</string>
<string name="you_are_not_participating">لست مشتركا في المجموعة</string>
<string name="modified_conference_options">تم تعديل خيارات فريق المحادثة !</string>
<string name="could_not_modify_conference_options">تعذر تغيير خيارات فريق المحادثة</string>
@ -359,6 +392,7 @@
<string name="recently_used">التي تم استعمالها كثيرا مؤخرا</string>
<string name="choose_quick_action">إختر حركة سريعة</string>
<string name="search_contacts">البحث في جهات الإتصال</string>
<string name="search_bookmarks">البحث في الفواصل المرجعية</string>
<string name="send_private_message">إبعث برسالة على الخاص</string>
<string name="user_has_left_conference">لقد غادَر %1$s فريق المحادثة !</string>
<string name="username">إسم المستخدم</string>
@ -413,6 +447,7 @@
<string name="notify_never">تعطيل الإخطارات</string>
<string name="notify_paused">الإشعارات موقفة</string>
<string name="pref_picture_compression">ضغط الصورة</string>
<string name="pref_picture_compression_summary">تغيير حجم الصور وضغطها</string>
<string name="always">دائماً</string>
<string name="automatically">آليا</string>
<string name="battery_optimizations_enabled">وضع تحسين أداء البطارية مفعّل</string>
@ -426,6 +461,8 @@
<string name="security_error_invalid_file_access">خطأ في الأمان : نفاذ غير سليم إلى ملف</string>
<string name="no_application_to_share_uri">تعذر العثور على تطبيق يُمكنُ بواسطته مشاركة الرابط</string>
<string name="share_uri_with">شارك الرابط مع ...</string>
<string name="agree_and_continue">وافق ثم واصل</string>
<string name="your_full_jid_will_be">عنوان XMPP الخاص بك سيكون: %s</string>
<string name="create_account">إنشاء حساب</string>
<string name="use_own_provider">إستخدم مزودي الخاص</string>
<string name="pick_your_username">إختر إسم المستخدم</string>
@ -442,6 +479,7 @@
<string name="choose_participants">إختر المشاركين</string>
<string name="creating_conference">جارٍ إنشاء مجموعة المحادثة ...</string>
<string name="invite_again">أعد إرسال الدعوة</string>
<string name="gp_disable">تعطيل</string>
<string name="gp_short">قصير</string>
<string name="gp_medium">متوسط</string>
<string name="gp_long">طويل</string>
@ -556,9 +594,11 @@
<string name="account_status_regis_web">لا يمكن تسجيل حسابات على هذا الخادوم إلا عبر موقع الويب</string>
<string name="open_website">فتح موقع الإنترنت</string>
<string name="application_found_to_open_website">تعذر العثور على تطبيق يُمكنه فتح موقع الويب</string>
<string name="pref_headsup_notifications_summary">أظهر الإشعارات العلوية</string>
<string name="today">اليوم</string>
<string name="yesterday">البارحة</string>
<string name="pref_validate_hostname">التحقق من صحة إسم المضيف بواسطة DNSSEC</string>
<string name="certificate_does_not_contain_jid">الشهادة لا تحتوي على عنوان XMPP</string>
<string name="server_info_partial">جُزْئِيًّا</string>
<string name="attach_record_video">تسجيل فيديو</string>
<string name="copy_to_clipboard">النسخ إلى الحافظة</string>
@ -572,11 +612,18 @@
<string name="mtm_cert_details">تفاصيل الشهادة :</string>
<string name="once">مرة واحدة</string>
<string name="pref_scroll_to_bottom">السحب إلى أسفل</string>
<string name="pref_scroll_to_bottom_summary">التمرير إلى أسفل بعد إرسال رسالة</string>
<string name="edit_status_message_title">تعديل رسالة حالة الحضور</string>
<string name="edit_status_message">تعديل رسالة حالة الحضور</string>
<string name="disable_encryption">تعطيل التعمية</string>
<string name="error_trustkey_device_list">تعذر جلب قائمة الأجهزة</string>
<string name="disable_now">تعطيله حالًا</string>
<string name="draft">المسودة:</string>
<string name="pref_omemo_setting">التعمية بـ OMEMO</string>
<string name="pref_omemo_setting_summary_default_on">سوف يُستخدَم OMEMO افتراضيا في المحادثات الجديدة.</string>
<string name="pref_font_size">حجم الخط</string>
<string name="default_on">نشِط مبدئيًا</string>
<string name="default_off">معطل مبدئيًا</string>
<string name="small">صغير</string>
<string name="medium">متوسط</string>
<string name="large">كبير</string>
@ -588,16 +635,28 @@
<string name="title_activity_share_location">مشاركة الموقع</string>
<string name="title_activity_show_location">إظهار الموقع</string>
<string name="share">مشاركة</string>
<string name="please_wait">يرجى الانتظار…</string>
<string name="search_messages">البحث عن رسائل</string>
<string name="view_conversation">مشاهدة المحادثة</string>
<string name="copy_link">نسخ العنوان الإلكتروني</string>
<string name="copy_jabber_id">انسخ عنوان الـ XMPP</string>
<string name="pref_start_search">بحث مباشر</string>
<string name="contact_name">إسم جهة الإتصال</string>
<string name="nickname">إسم مستعار</string>
<string name="group_chat_name">إسم</string>
<string name="providing_a_name_is_optional">إدخال الاسم اختياري</string>
<string name="create_dialog_group_chat_name">اسم فريق المحادثة</string>
<string name="unable_to_save_recording">لا يمكن حفظ التسجيل</string>
<string name="foreground_service_channel_name">الخدمة الأمامية</string>
<string name="notification_group_status_information">معلومات عن الحالة</string>
<string name="error_channel_name">مشاكل إتّصال</string>
<string name="notification_group_messages">رسائل</string>
<string name="messages_channel_name">رسائل</string>
<string name="pref_more_notification_settings">إعدادات الإشعار</string>
<string name="video_compression_channel_name">ضغط الفيديو</string>
<string name="view_media">اعرض الوسائط</string>
<string name="view_users">اعرض المشارِكين</string>
<string name="group_chat_members">المشارِكون</string>
<string name="pref_video_compression">جودة الفيديو</string>
<string name="video_360p">متوسط (360ب)</string>
<string name="video_720p">عالي (720ب)</string>
@ -605,10 +664,22 @@
<string name="choose_a_country">إختار الدولة</string>
<string name="phone_number">رقم هاتف</string>
<string name="verify_your_phone_number">تحقق من رقم هاتفك</string>
<string name="not_a_valid_phone_number">ليس %s برقم هاتف صالح.</string>
<string name="please_enter_your_phone_number">يرجى إدخال رقم هاتفك.</string>
<string name="search_countries">البحث عن الدول</string>
<string name="verify_x">تحقق مِن %s</string>
<string name="resend_sms">إعادة إرسال الإرسالية القصيرة</string>
<string name="wait_x">يرجى الانتظار (%s)</string>
<string name="back">رجوع</string>
<string name="yes">نعم</string>
<string name="no">لا</string>
<string name="verifying">جارٍ التحقق…</string>
<string name="requesting_sms">جارٍ طلب الرسالة النصية القصيرة…</string>
<string name="unknown_api_error_network">خطأ شبكي مجهول.</string>
<string name="unable_to_connect_to_server">تعذر الربط بالخادم.</string>
<string name="no_network_connection">ليس هناك اتصال بالشبكة.</string>
<string name="try_again_in_x">يرجى إعادة المحاولة في غضون %s</string>
<string name="update">تحديث</string>
<string name="your_name">إسمك</string>
<string name="enter_your_name">أدخل إسمك</string>
<string name="no_name_set_instructions">إضغط على زرّ التعديل لضبط إسمك</string>
@ -620,4 +691,31 @@
<string name="open_with">إفتح بـ...</string>
<string name="set_profile_picture">صورة حساب كونفرسايشنز</string>
<string name="choose_account">إختيار الحساب</string>
<string name="restore_backup">استرجِع نسخة احتياطية</string>
<string name="restore">استرجِع</string>
<string name="enter_jabber_id">ادخِل عنوان XMPP</string>
<string name="create_group_chat">أنشئ فريق محادثة</string>
<string name="join_public_channel">إلتحِق بقناة عمومية</string>
<string name="create_private_group_chat">أنشئ فريق محادثة خاص</string>
<string name="create_public_channel">أنشئ قناة عمومية</string>
<string name="create_dialog_channel_name">اسم القناة</string>
<string name="xmpp_address">عنوان XMPP</string>
<string name="please_enter_name">يرجى إدخال اسمٍ للقناة</string>
<string name="creating_channel">جارٍ إنشاء القناة العمومية…</string>
<string name="joined_an_existing_channel">لقد التحقت بقناة موجودة سابقا</string>
<string name="allow_participants_to_invite_others">اسمح لأي كان دعوة الآخرين</string>
<string name="owners_can_edit_subject">يمكن للمالِكين تعديل الموضوع.</string>
<string name="manage_permission">إدارة الصلاحيات</string>
<string name="search_participants">البحث عن مشارِكين</string>
<string name="file_too_large">حجم الملف كبير جدًا</string>
<string name="attach">أرفِق</string>
<string name="discover_channels">استكشاف القنوات</string>
<string name="search_channels">البحث عن قنوات</string>
<string name="i_already_have_an_account">لدي حساب</string>
<string name="add_existing_account">إضافة حساب موجود</string>
<string name="register_new_account">تسجيل حساب جديد</string>
<string name="add_anway">أضفه على أي حال</string>
<string name="event">حَدَث</string>
<string name="open_backup">افتح النسخة الاحتياطية</string>
<string name="please_enter_password">يرجى إدخال الكلمة السرية للحساب</string>
</resources>

View File

@ -872,4 +872,5 @@
<string name="account_already_setup">Dieses Konto wurde bereits eingerichtet</string>
<string name="please_enter_password">Bitte gib das Passwort für dieses Konto ein</string>
<string name="unable_to_perform_this_action">Diese Aktion kann nicht ausgeführt werden</string>
<string name="open_join_dialog">Öffentlichen Channel beitreten...</string>
</resources>

View File

@ -30,6 +30,7 @@
<string name="just_now">ahora</string>
<string name="minute_ago">hace 1 min</string>
<string name="minutes_ago">hace %d min</string>
<string name="x_unread_conversations">%dconversaciones sin leer</string>
<string name="sending">enviando…</string>
<string name="message_decrypting">Descifrando mensaje. Por favor, espera...</string>
<string name="pgp_message">Mensaje cifrado con OpenPGP</string>
@ -870,4 +871,5 @@
<string name="not_a_backup_file">El fichero seleccionado no es un respaldo de Conversations</string>
<string name="account_already_setup">Esta cuenta ya fue configurada</string>
<string name="please_enter_password">Por favor ingrese la contraseña para esta cuenta</string>
<string name="unable_to_perform_this_action">No se ha podido realizar esta acción</string>
</resources>

View File

@ -872,4 +872,5 @@
<string name="account_already_setup">Esta conta xa foi configurada</string>
<string name="please_enter_password">Introduza o contrasinal de esta conta</string>
<string name="unable_to_perform_this_action">Non se puido completar a acción</string>
<string name="open_join_dialog">Unirse a canle pública...</string>
</resources>

View File

@ -30,6 +30,7 @@
<string name="just_now">adesso</string>
<string name="minute_ago">1 min fa</string>
<string name="minutes_ago">%d min fa</string>
<string name="x_unread_conversations">%d conversazioni non lette</string>
<string name="sending">invio…</string>
<string name="message_decrypting">Decifrazione messaggio. Attendere prego...</string>
<string name="pgp_message">Messaggio cifrato con OpenPGP</string>

View File

@ -30,6 +30,7 @@
<string name="just_now">zojuist</string>
<string name="minute_ago">1 min. geleden</string>
<string name="minutes_ago">%d min. geleden</string>
<string name="x_unread_conversations">%d ongelezen gesprekken</string>
<string name="sending">versturen…</string>
<string name="message_decrypting">Bericht aan het ontsleutelen. Even geduld…</string>
<string name="pgp_message">OpenPGP-versleuteld bericht</string>

View File

@ -30,6 +30,7 @@
<string name="just_now">przed chwilą</string>
<string name="minute_ago">minutę temu</string>
<string name="minutes_ago">%d minut temu</string>
<string name="x_unread_conversations">%d nieprzeczytanych konwersacji</string>
<string name="sending">wysyłanie...</string>
<string name="message_decrypting">Odszyfrowywanie wiadomości. To zajmie tylko chwilę...</string>
<string name="pgp_message">Wiadomość zaszyfrowana OpenPGP</string>
@ -884,8 +885,9 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż
<string name="conversations_backup">Kopia zapasowa Conversations</string>
<string name="event">Zdarzenie</string>
<string name="open_backup">Otwórz kopię zapasową</string>
<string name="not_a_backup_file">Plik który otworzyłeś nie jest plikiem kopii zapasowej Conversations</string>
<string name="not_a_backup_file">Plik, który otworzyłeś, nie jest plikiem kopii zapasowej Conversations</string>
<string name="account_already_setup">To konto zostało już ustawione</string>
<string name="please_enter_password">Proszę podać hasło dla tego konta</string>
<string name="unable_to_perform_this_action">Nie można wykonać tej akcji</string>
<string name="open_join_dialog">Dołącz do publicznego kanału...</string>
</resources>

View File

@ -30,6 +30,7 @@
<string name="just_now">în acest moment</string>
<string name="minute_ago">acum un minut</string>
<string name="minutes_ago">acum %d minute</string>
<string name="x_unread_conversations">%d conversații necitite</string>
<string name="sending">trimitere...</string>
<string name="message_decrypting">Decriptez mesaj. Te rog așteaptă...</string>
<string name="pgp_message">Mesaj criptat cu OpenPGP</string>
@ -412,7 +413,7 @@
<string name="sending_x_file">Trimit %s</string>
<string name="offering_x_file">Ofer %s</string>
<string name="hide_offline">Ascunde deconectat</string>
<string name="contact_is_typing">%s tasteaza...</string>
<string name="contact_is_typing">%s tastează...</string>
<string name="contact_has_stopped_typing">%s s-a oprit din scris</string>
<string name="contacts_are_typing">%s tastează...</string>
<string name="contacts_have_stopped_typing">%s s-au oprit din scris</string>
@ -880,4 +881,5 @@ sau chiar pierderea mesajelor.\nÎn continuare veți fi rugați să dezactivați
<string name="account_already_setup">Acest cont a fost deja configurat</string>
<string name="please_enter_password">Va rugăm să introduceți parola pentru acest cont</string>
<string name="unable_to_perform_this_action">Nu se poate realiza această acțiune</string>
<string name="open_join_dialog">Alătură-te unui canal public...</string>
</resources>

View File

@ -3,7 +3,10 @@
<string name="action_settings">Inställningar</string>
<string name="action_add">Ny konversation</string>
<string name="action_accounts">Kontoinställningar</string>
<string name="action_end_conversation">Stäng denna konversation</string>
<string name="action_contact_details">Kontaktdetaljer</string>
<string name="action_muc_details">Gruppchattdetaljer</string>
<string name="channel_details">Kanaldetaljer</string>
<string name="action_secure">Säker konversation</string>
<string name="action_add_account">Lägg till konto</string>
<string name="action_edit_contact">Ändra namn</string>
@ -13,18 +16,25 @@
<string name="action_unblock_contact">Avblockera kontakt</string>
<string name="action_block_domain">Blockera domän</string>
<string name="action_unblock_domain">Avblockera domän</string>
<string name="action_block_participant">Blockera deltagare</string>
<string name="action_unblock_participant">Avblockera deltagare</string>
<string name="title_activity_manage_accounts">Hantera konton</string>
<string name="title_activity_settings">Inställningar</string>
<string name="title_activity_sharewith">Dela med konversation</string>
<string name="title_activity_start_conversation">Starta konversation</string>
<string name="title_activity_choose_contact">Välj kontakt</string>
<string name="title_activity_choose_contacts">Välj kontakter</string>
<string name="title_activity_share_via_account">Dela via konto</string>
<string name="title_activity_block_list">Blockeringslista</string>
<string name="just_now">just nu</string>
<string name="minute_ago">1 min sedan</string>
<string name="minutes_ago">%d min sedan</string>
<string name="x_unread_conversations">%d olästa konversationer</string>
<string name="sending">skickar…</string>
<string name="message_decrypting">Avkrypterar meddelande. Vänta…</string>
<string name="pgp_message">OpenPGP-krypterat meddelande</string>
<string name="nick_in_use">Nick används redan</string>
<string name="invalid_muc_nick">Ogiltigt nick</string>
<string name="admin">Admin</string>
<string name="owner">Ägare</string>
<string name="moderator">Moderator</string>
@ -36,11 +46,16 @@
<string name="block_domain_text">Blockera alla kontakter från %s?</string>
<string name="unblock_domain_text">Avblockera alla kontakter från %s?</string>
<string name="contact_blocked">Kontakt blockerad</string>
<string name="blocked">Blockerad</string>
<string name="remove_bookmark_text">Vill du ta bort %s som bokmärke? Konversationer associerade med detta bokmärke kommer inte tas bort.</string>
<string name="register_account">Registrera nytt konto på servern</string>
<string name="change_password_on_server">Byt lösenord på server</string>
<string name="share_with">Dela med…</string>
<string name="start_conversation">Börja konversation</string>
<string name="invite_contact">Bjud in kontakt</string>
<string name="invite">Bjud in</string>
<string name="contacts">Kontakter</string>
<string name="contact">Kontakt</string>
<string name="cancel">Avbryt</string>
<string name="set">Sätt</string>
<string name="add">Lägg till</string>
@ -66,6 +81,9 @@
<string name="sharing_files_please_wait">Delar filer. Vänta...</string>
<string name="action_clear_history">Rensa historik</string>
<string name="clear_conversation_history">Rensa konversationshistorik</string>
<string name="clear_histor_msg">Är du säker på att du vill ta bort alla meddelanden i denna konversation?\n\n<b>Varning:</b> Detta kommer inte att ta bort kopior av dessa meddelanden på andra enheter eller servrar.</string>
<string name="delete_file_dialog">Ta bort fil</string>
<string name="also_end_conversation">Stäng denna konversation efteråt</string>
<string name="choose_presence">Välj enhet</string>
<string name="send_unencrypted_message">Skicka okrypterat meddelande</string>
<string name="send_message">Skicka meddelande</string>
@ -106,8 +124,10 @@
<string name="pref_confirm_messages">Bekräfta meddelanden</string>
<string name="pref_confirm_messages_summary">Låt dina kontakter veta när du har mottagit och läst deras meddelanden</string>
<string name="pref_ui_options">Gränssnitt</string>
<string name="bad_key_for_encryption">Dålig krypterings-nyckel.</string>
<string name="accept">Acceptera</string>
<string name="error">Ett fel har inträffat</string>
<string name="recording_error">Fel</string>
<string name="your_account">Ditt konto</string>
<string name="send_presence_updates">Skicka tillgänglighetsuppdatering</string>
<string name="receive_presence_updates">Ta emot tillgänglighetsuppdateringar</string>
@ -150,8 +170,11 @@
<string name="mgmt_account_are_you_sure">Är du säker?</string>
<string name="mgmt_account_delete_confirm_text">Om du tar bort ditt konto kommer hela konversationshistoriken att försvinna</string>
<string name="attach_record_voice">Spela in röst</string>
<string name="account_settings_jabber_id">XMPP-adress</string>
<string name="block_jabber_id">Blockera XMPP-adress</string>
<string name="account_settings_example_jabber_id">användarnamn@exempel.se</string>
<string name="password">Lösenord</string>
<string name="invalid_jid">Detta är inte en giltig XMPP-adress</string>
<string name="error_out_of_memory">Slut på minne. Bilden är för stor</string>
<string name="add_phone_book_text">Vill du lägga till %s i din enhets kontakter?</string>
<string name="server_info_show_more">Server-info</string>
@ -186,8 +209,10 @@
<string name="fetching_keys">Hämtar nycklar...</string>
<string name="done">Klar</string>
<string name="decrypt">Avkryptera</string>
<string name="bookmarks">Bokmärken</string>
<string name="search">Sök</string>
<string name="enter_contact">Fyll i kontakt</string>
<string name="delete_contact">Ta bort kontakt</string>
<string name="view_contact_details">Se kontaktdetaljer</string>
<string name="block_contact">Blockera kontakt</string>
<string name="unblock_contact">Avblockera kontakt</string>
@ -198,10 +223,13 @@
<string name="save_as_bookmark">Spara som bokmärke</string>
<string name="delete_bookmark">Ta bort bokmärke</string>
<string name="bookmark_already_exists">Detta bokmärke finns redan</string>
<string name="topic">Ämne</string>
<string name="joining_conference">Går med i gruppchatt...</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 tillbaka</string>
<string name="contact_has_read_up_to_this_point">%s har läst hit</string>
<string name="everyone_has_read_up_to_this_point">Alla har läst fram till 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="publishing">Publicerar…</string>
@ -220,6 +248,7 @@
<string name="skip">Hoppa över</string>
<string name="disable_notifications">Inaktivera notifieringar</string>
<string name="enable">Aktivera</string>
<string name="conference_requires_password">Gruppchatten kräver lösenord</string>
<string name="enter_password">Fyll i lösenord</string>
<string name="request_presence_updates">Begär tillgänglighetsuppdateringar från din kontakt först.\n\n<small>Detta används för att se vilken klient/klienter din kontakt använder.</small></string>
<string name="request_now">Begär nu</string>
@ -230,6 +259,7 @@
<string name="pref_allow_message_correction_summary">Tillåt att dina kontakter kan ändra sina meddelanden i efterhand</string>
<string name="pref_expert_options">Expertinställningar</string>
<string name="pref_expert_options_summary">Var försiktig med dem</string>
<string name="title_activity_about_x">Om %s</string>
<string name="title_pref_quiet_hours">Tysta timmar</string>
<string name="title_pref_quiet_hours_start_time">Starttid</string>
<string name="title_pref_quiet_hours_end_time">Sluttid</string>
@ -240,7 +270,10 @@
<string name="pref_use_indicate_received_summary">Mottagna meddelanden markeras med en grön bock om det stöds</string>
<string name="pref_use_send_button_to_indicate_status_summary">Färglägg skickaknappen för att indikera kontaktens status</string>
<string name="pref_expert_options_other">Annat</string>
<string name="pref_autojoin">Synkronisera med bokmärken</string>
<string name="toast_message_omemo_fingerprint">OMEMO-fingeravtryck har kopierats till urklipp!</string>
<string name="conference_resource_constraint">Resursbegränsning</string>
<string name="conference_shutdown">Gruppchatten stängdes ner</string>
<string name="using_account">använder konto %s</string>
<string name="checking_x">Kontrollerar %s på webbserver</string>
<string name="not_connected_try_again">Du är inte ansluten. Försök igen senare</string>
@ -248,9 +281,14 @@
<string name="check_x_filesize_on_host">Kontrollera filstorlek för %1$s på %2$s</string>
<string name="message_options">Meddelandealternativ</string>
<string name="quote">Citera</string>
<string name="paste_as_quote">Klistra in som citat</string>
<string name="copy_original_url">Kopiera orginal-URL</string>
<string name="send_again">Skicka igen</string>
<string name="file_url">Fil-URL</string>
<string name="url_copied_to_clipboard">Kopierade URL till urklipp</string>
<string name="jabber_id_copied_to_clipboard">Kopierade XMPP-adress till urklipp</string>
<string name="error_message_copied_to_clipboard">Kopierade felmeddelande till urklipp</string>
<string name="web_address">webbadress</string>
<string name="scan_qr_code">Scanna 2D-streckkod</string>
<string name="show_qr_code">Visa 2D-streckkod</string>
<string name="show_block_list">Visa blockeringslista</string>
@ -259,6 +297,14 @@
<string name="try_again">Försök igen</string>
<string name="pref_keep_foreground_service">Håll tjänst i förgrunden</string>
<string name="pref_keep_foreground_service_summary">Förehindrar operativsystemet att ta ner uppkopplingen</string>
<string name="pref_create_backup">Skapa säkerhetskopia</string>
<string name="pref_create_backup_summary">Säkerhetskopians filer lagras i %s</string>
<string name="notification_create_backup_title">Skapar filer för säkerhetskopia</string>
<string name="notification_backup_created_title">Din säkerhetskopia har skapats</string>
<string name="notification_backup_created_subtitle">Säkerhetskopians filer har lagrats i %s</string>
<string name="restoring_backup">Återställer säkerhetskopia</string>
<string name="notification_restored_backup_title">Din säkerhetskopia har återställts</string>
<string name="notification_restored_backup_subtitle">Glöm inte att aktivera kontot.</string>
<string name="choose_file">Välj fil</string>
<string name="receiving_x_file">Tar emot %1$s (%2$d%% klart)</string>
<string name="download_x_file">Ladda ner %s</string>
@ -272,8 +318,11 @@
<string name="file_transmission_failed">filöverföring lyckades inte</string>
<string name="file_deleted">Filen har blivit borttagen</string>
<string name="no_application_found_to_open_file">Ingen applikation kunde hittas för att öppna filen</string>
<string name="no_application_found_to_open_link">Ingen applikation kunde hittas för att öppna länken</string>
<string name="no_application_found_to_view_contact">Ingen applikation kunde hittas för att visa kontakten</string>
<string name="pref_show_dynamic_tags_summary">Visa skrivskyddade taggar under kontakter</string>
<string name="enable_notifications">Aktivera notifieringar</string>
<string name="conference_creation_failed">Misslyckades skapa gruppchatt!</string>
<string name="account_image_description">Kontots avatarbild</string>
<string name="copy_omemo_clipboard_description">Kopiera OMEMO-fingeravtryck till urklipp</string>
<string name="regenerate_omemo_key">Regenerera OMEMO-nyckel</string>
@ -298,6 +347,8 @@
<string name="advanced_mode">Avancerat läge</string>
<string name="grant_admin_privileges">Bevilja administratörsbehörighet</string>
<string name="remove_admin_privileges">Återkalla administratörsbehörighet</string>
<string name="remove_from_room">Ta bort från gruppchatt</string>
<string name="remove_from_channel">Ta bort från kanal</string>
<string name="could_not_change_affiliation">Kunde inte ändra tillhörigheten för %s</string>
<string name="ban_now">Bannlys nu</string>
<string name="could_not_change_role">Kunde inte ändra rollen för %s</string>
@ -349,6 +400,7 @@
<string name="recently_used">Senast använd</string>
<string name="choose_quick_action">Välj snabbfunktion</string>
<string name="send_private_message">Skicka privat meddelande</string>
<string name="user_has_left_conference">%1$s har lämnat gruppchatten!</string>
<string name="username">Användarnamn</string>
<string name="username_hint">Användarnamn</string>
<string name="invalid_username">Inte ett giltigt användanamn</string>
@ -360,8 +412,10 @@
<string name="account_status_bind_failure">Bind-fel</string>
<string name="account_status_host_unknown">Servern är inte ansvarig för domänen</string>
<string name="server_info_broken">Sönder</string>
<string name="pref_presence_settings">Tillgänglighet</string>
<string name="pref_away_when_screen_off">Status borta när skärmen är av</string>
<string name="pref_away_when_screen_off_summary">Sätter din tillgänglighet till borta när skrämen är av</string>
<string name="pref_dnd_on_silent_mode">\"Stör ej\" i tyst läge</string>
<string name="pref_treat_vibrate_as_silent">Hantera vibrationsläge som tyst läge</string>
<string name="pref_show_connection_options">Utökade anslutningsinställningar</string>
<string name="pref_show_connection_options_summary">Visa val av servernamn och port vid inställning av konto</string>
@ -398,10 +452,14 @@
<string name="shared_image_with_x">Delade bild med %s</string>
<string name="shared_images_with_x">Delade bilder med %s</string>
<string name="shared_text_with_x">Delade text med %s</string>
<string name="no_storage_permission">Conversations behöver tillgång till extern lagring</string>
<string name="no_camera_permission">Conversations behöver tillgång till kameran</string>
<string name="sync_with_contacts">Synkronisera med kontakter</string>
<string name="notify_on_all_messages">Notifiera för alla meddelanden</string>
<string name="notify_never">Notifieringar deaktiverade</string>
<string name="notify_paused">Notifieringar pausade</string>
<string name="pref_picture_compression">Bildkomprimering</string>
<string name="pref_picture_compression_summary">Ändra storlek på och komprimera bilder</string>
<string name="always">Alltid</string>
<string name="automatically">Automatiskt</string>
<string name="battery_optimizations_enabled">Batterioptimeringar aktiverade</string>
@ -417,6 +475,8 @@
<string name="security_error_invalid_file_access">Säkerhetsfel: Ogiltig filaccess</string>
<string name="no_application_to_share_uri">Ingen applikation kunde hittas för att dela URI</string>
<string name="share_uri_with">Dela URI med...</string>
<string name="agree_and_continue">Godkänn &amp; fortsätt</string>
<string name="your_full_jid_will_be">Din fullständiga XMPP-adress kommer att vara: %s</string>
<string name="create_account">Skapa konto</string>
<string name="use_own_provider">Använd min egen leverantör</string>
<string name="pick_your_username">Välj användarnamn</string>
@ -431,6 +491,7 @@
<string name="registration_please_wait">Registreringfel: Försök igen senare</string>
<string name="registration_password_too_weak">Registreringsfel: Lösenordet är för svagt</string>
<string name="choose_participants">Välj deltagare</string>
<string name="creating_conference">Skapar gruppchatt...</string>
<string name="invite_again">Bjud in igen</string>
<string name="gp_short">Kort</string>
<string name="gp_medium">Medium</string>
@ -492,10 +553,35 @@
<string name="verify_omemo_keys">Verifiera OMEMO-nycklar</string>
<string name="distrust_omemo_key">Lita ej på enhet</string>
<string name="distrust_omemo_key_text">Är du säker på att du vill ta bort verifieringen av denna enhet?\nDenna enhet och meddelanden som kommer från enheten kommer att markeras som ej pålitliga.</string>
<plurals name="seconds">
<item quantity="one">%d sekund</item>
<item quantity="other">%d sekunder</item>
</plurals>
<plurals name="minutes">
<item quantity="one">%d minut</item>
<item quantity="other">%d minuter</item>
</plurals>
<plurals name="hours">
<item quantity="one">%d timme</item>
<item quantity="other">%d timmar</item>
</plurals>
<plurals name="days">
<item quantity="one">%d dag</item>
<item quantity="other">%d dagar</item>
</plurals>
<plurals name="weeks">
<item quantity="one">%d vecka</item>
<item quantity="other">%d veckor</item>
</plurals>
<plurals name="months">
<item quantity="one">%d månad</item>
<item quantity="other">%d månader</item>
</plurals>
<string name="pref_automatically_delete_messages">Automatisk borttagning av meddelanden</string>
<string name="pref_automatically_delete_messages_description">Ta automatiskt bort meddelanden från denna enhet som är äldre än den konfigurerade tidsramen.</string>
<string name="encrypting_message">Krypterar meddelande</string>
<string name="not_fetching_history_retention_period">Hämtar inte meddelanden på grund av inställningen för borttagning av gamla meddelanden.</string>
<string name="transcoding_video">Komprimerar video</string>
<string name="corresponding_conversations_closed">Motsvarande konversationer är stängda.</string>
<string name="contact_blocked_past_tense">Kontakt blockerad.</string>
<string name="pref_notifications_from_strangers">Notifieringar från främlingar</string>
@ -506,4 +592,29 @@
<string name="online_right_now">online just nu</string>
<string name="retry_decryption">Försök dekryptera igen</string>
<string name="session_failure">Sessionsfel</string>
<string name="today">Idag</string>
<string name="yesterday">Igår</string>
<string name="certificate_does_not_contain_jid">Certifikatet innehåller ej en XMPP-adress</string>
<string name="attach_record_video">Spela in video</string>
<string name="message">Meddelande</string>
<string name="mtm_accept_cert">Godkänn okänt certifikat?</string>
<string name="draft">Utkast:</string>
<string name="title_activity_share_location">Dela plats</string>
<string name="title_activity_show_location">Visa plats</string>
<string name="share">Dela</string>
<string name="copy_jabber_id">Kopiera XMPP-adress</string>
<string name="create_dialog_group_chat_name">Gruppchattens namn</string>
<string name="group_chat_members">Deltagare</string>
<string name="choose_a_country">Välj ett land</string>
<string name="phone_number">telefonnummer</string>
<string name="verify_your_phone_number">Bekräfta ditt telefonnummer</string>
<string name="create_group_chat">Skapa gruppchatt</string>
<string name="create_private_group_chat">Skapa sluten gruppchatt</string>
<string name="create_dialog_channel_name">Kanalnamn</string>
<string name="please_enter_name">Vänligen ange ett namn på kanalen</string>
<string name="channel_already_exists">Denna kanal finns redan</string>
<string name="joined_an_existing_channel">Du har gått med i en befintlig kanal</string>
<string name="no_users_hint_group_chat">Denna slutna gruppchatt har inga deltagare.</string>
<string name="discover_channels">Upptäck kanaler</string>
<string name="this_looks_like_channel">Detta ser ut som en kanaladress</string>
</resources>

View File

@ -1,33 +1,36 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="action_settings">设置</string>
<string name="action_add">会话</string>
<string name="action_add">聊天</string>
<string name="action_accounts">管理账户</string>
<string name="action_account">管理账户</string>
<string name="action_end_conversation">关闭对话</string>
<string name="action_end_conversation">关闭聊天</string>
<string name="action_contact_details">联系人详情</string>
<string name="action_muc_details">群聊详情</string>
<string name="channel_details">频道详情</string>
<string name="action_secure">安全对话</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="action_add_phone_book">添加到联系人</string>
<string name="action_delete_contact">从XMPP联系人中删除</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="action_block_participant">封禁成员</string>
<string name="action_unblock_participant">解封成员</string>
<string name="title_activity_manage_accounts">管理账户</string>
<string name="title_activity_settings">设置</string>
<string name="title_activity_sharewith">分享会话</string>
<string name="title_activity_start_conversation">开始会话</string>
<string name="title_activity_sharewith">通过Conversations分享</string>
<string name="title_activity_start_conversation">开始聊天</string>
<string name="title_activity_choose_contact">选择联系人</string>
<string name="title_activity_choose_contacts">选择联系人</string>
<string name="title_activity_share_via_account">通过帐户分享</string>
<string name="title_activity_block_list">屏蔽列表</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="x_unread_conversations">%d条未读消息</string>
<string name="sending">正在发送…</string>
<string name="message_decrypting">解密信息中. 请稍候…</string>
<string name="pgp_message">OpenPGP 加密的信息</string>
@ -35,19 +38,19 @@
<string name="invalid_muc_nick">无效的用户名</string>
<string name="admin">管理员</string>
<string name="owner">所有者</string>
<string name="moderator"></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_contact_text">将 %s 从XMPP联系人中移除? 与该联系人的会话消息不会清除.</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="blocked">已屏蔽</string>
<string name="remove_bookmark_text">从书签中移除 %s ?相关会话消息不会被清除 .</string>
<string name="register_account">在服务器上注册新账户</string>
<string name="change_password_on_server">在服务器上改密码</string>
<string name="change_password_on_server">在服务器上改密码</string>
<string name="share_with">分享……</string>
<string name="start_conversation">开始会话</string>
<string name="invite_contact">邀请联系人</string>
@ -379,9 +382,14 @@
<string name="could_not_change_affiliation">不能修改 %s 的从属关系</string>
<string name="ban_from_conference">屏蔽群聊</string>
<string name="ban_from_channel">从频道中屏蔽</string>
<string name="removing_from_public_conference">%s将被从公共群聊中移除。只有将此用户封禁才能将他从群聊永远移除。</string>
<string name="ban_now">现在屏蔽</string>
<string name="could_not_change_role">不能修改 %s 的角色</string>
<string name="conference_options">私密群聊设置</string>
<string name="channel_options">公开群聊设置</string>
<string name="members_only">私密,只有成员可以加入</string>
<string name="non_anonymous">使XMPP地址对所有人可见</string>
<string name="moderated">使群聊受到管理</string>
<string name="you_are_not_participating">您尚未参与</string>
<string name="modified_conference_options">群组设置修改成功!</string>
<string name="could_not_modify_conference_options">无法更改群组设置</string>
@ -416,6 +424,8 @@
<string name="no_application_found_to_display_location">无法找到显示位置的应用</string>
<string name="location">位置</string>
<string name="title_undo_swipe_out_conversation">会话已关闭</string>
<string name="title_undo_swipe_out_group_chat">离开私密群聊</string>
<string name="title_undo_swipe_out_channel">离开公开群聊</string>
<string name="pref_dont_trust_system_cas_title">不相信系统 CA</string>
<string name="pref_dont_trust_system_cas_summary">所有证书必须人工通过</string>
<string name="pref_remove_trusted_certificates_title">移除证书</string>
@ -433,6 +443,7 @@
<string name="recently_used">最近常用</string>
<string name="choose_quick_action">选择快捷操作</string>
<string name="search_contacts">搜索联系人</string>
<string name="search_bookmarks">搜索书签</string>
<string name="send_private_message">发送私密消息</string>
<string name="user_has_left_conference">%1$s 离开了群聊!</string>
<string name="username">用户名</string>
@ -466,6 +477,7 @@
<string name="captcha_required">需要验证码</string>
<string name="captcha_hint">输入上图中的文字</string>
<string name="certificate_chain_is_not_trusted">证书链不受信任</string>
<string name="jid_does_not_match_certificate">XMPP地址与证书不匹配</string>
<string name="action_renew_certificate">更新证书</string>
<string name="error_fetching_omemo_key">获取 OMEMO 密钥错误!</string>
<string name="verified_omemo_key_with_certificate">请用证书验证 OMEMO 密钥!</string>
@ -490,6 +502,8 @@
<string name="no_storage_permission">Conversations 需要外部储存权限</string>
<string name="no_camera_permission">Conversations 需要摄像头权限</string>
<string name="sync_with_contacts">同步联系人</string>
<string name="sync_with_contacts_long">将服务器端联系人与本地联系人匹配可以显示联系人的全名与头像。\n\n此应用只在本地读取并匹配联系人。\n\n现在应用将请求联系人权限。</string>
<string name="sync_with_contacts_quicksy"><![CDATA[Quicksy可以匹配您的通讯录以确定哪些人已经在使用此应用。<br><br>我们并不储存这些号码。\n\n更多信息请阅读<a href="https://quicksy.im/#privacy">隐私政策</a>。接下来将请求通讯录权限。]]></string>
<string name="notify_on_all_messages">为所有信息显示通知</string>
<string name="notify_only_when_highlighted">只在被提到时通知</string>
<string name="notify_never">禁用通知</string>
@ -514,6 +528,9 @@
<string name="security_error_invalid_file_access">安全错误:文件访问权限无效</string>
<string name="no_application_to_share_uri">未找到可以分享此链接的应用</string>
<string name="share_uri_with">分享链接……</string>
<string name="agree_and_continue">同意 &amp; 继续</string>
<string name="magic_create_text">此向导将为您在conversations.im¹上创建一个账户。\n您的联系人可以通过您的XMPP完整地址与您聊天。</string>
<string name="your_full_jid_will_be">您的XMPP完整地址将是%s</string>
<string name="create_account">创建账户</string>
<string name="use_own_provider">使用我自己的服务端</string>
<string name="pick_your_username">输入您的用户名</string>
@ -639,6 +656,7 @@
<string name="yesterday">昨天</string>
<string name="pref_validate_hostname">使用 DNSSEC 来验证主机名</string>
<string name="pref_validate_hostname_summary">包含已验证的主机名的服务器证书被认为是已验证的</string>
<string name="certificate_does_not_contain_jid">证书不包含XMPP地址</string>
<string name="server_info_partial">部分的</string>
<string name="attach_record_video">录制视频</string>
<string name="copy_to_clipboard">复制</string>
@ -680,6 +698,7 @@
<string name="medium"></string>
<string name="large"></string>
<string name="not_encrypted_for_this_device">该设备的消息未加密。</string>
<string name="omemo_decryption_failed">解密OMEMO消息失败</string>
<string name="undo">撤销</string>
<string name="location_disabled">位置分享已停用</string>
<string name="action_fix_to_location">固定位置</string>
@ -697,7 +716,9 @@
<string name="gif">GIF</string>
<string name="view_conversation">查看对话</string>
<string name="pref_use_share_location_plugin">分享位置插件</string>
<string name="pref_use_share_location_plugin_summary">不使用内置地图,使用“分享位置”插件</string>
<string name="copy_link">复制web地址</string>
<string name="copy_jabber_id">复制XMPP地址</string>
<string name="p1_s3_filetransfer">用于S3的HTTP文件共享</string>
<string name="pref_start_search">直接搜索</string>
<string name="pref_start_search_summary">在“开始对话”屏幕上打开键盘并将光标放在搜索栏中</string>
@ -724,6 +745,8 @@
<string name="pref_more_notification_settings_summary">重要性,声音,振动</string>
<string name="video_compression_channel_name">视频压缩</string>
<string name="view_media">查看媒体文件</string>
<string name="view_users">查看成员</string>
<string name="group_chat_members">成员</string>
<string name="media_browser">媒体浏览器</string>
<string name="security_violation_not_attaching_file">文件由于违反安全策略而被删除。</string>
<string name="pref_video_compression">视频质量</string>
@ -732,4 +755,115 @@
<string name="video_720p">720p</string>
<string name="cancelled">已取消</string>
<string name="already_drafting_message">你已经在起草一条消息了。</string>
<string name="feature_not_implemented">功能不支持。</string>
<string name="invalid_country_code">无效国家代码</string>
<string name="choose_a_country">选择国家</string>
<string name="phone_number">手机号</string>
<string name="verify_your_phone_number">验证手机号</string>
<string name="enter_country_code_and_phone_number">Quicksy将发送验证码短信运营商可能收费。请输入国家代码和手机号</string>
<string name="we_will_be_verifying"><![CDATA[我们将验证<br/><br/><b>%s</b><br/><br/>。电话号码正确吗?]]></string>
<string name="not_a_valid_phone_number">%s不是有效的电话号码</string>
<string name="please_enter_your_phone_number">请输入手机号。</string>
<string name="search_countries">搜索国家</string>
<string name="verify_x">验证%s</string>
<string name="we_have_sent_you_an_sms_to_x"><![CDATA[短信已发至 <b>%s</b>。]]></string>
<string name="we_have_sent_you_another_sms">已重新发送6位数验证码短信</string>
<string name="please_enter_pin_below">输入6位数的PIN</string>
<string name="resend_sms">重新发送短信</string>
<string name="resend_sms_in">重发短信(%s</string>
<string name="wait_x">请稍候(%s</string>
<string name="back">返回</string>
<string name="possible_pin">已自动从剪贴板粘贴验证码</string>
<string name="please_enter_pin">请输入6位代码</string>
<string name="abort_registration_procedure">确定放弃注册?</string>
<string name="yes"></string>
<string name="no"></string>
<string name="verifying">正在验证......</string>
<string name="requesting_sms">请求短信...</string>
<string name="incorrect_pin">验证码错误。</string>
<string name="pin_expired">验证码已失效</string>
<string name="unknown_api_error_network">未知网络错误</string>
<string name="unknown_api_error_response">未知服务器应答</string>
<string name="unable_to_connect_to_server">无法连接服务器。</string>
<string name="unable_to_establish_secure_connection">无法建立安全连接。</string>
<string name="unable_to_find_server">找不到服务器</string>
<string name="something_went_wrong_processing_your_request">处理请求时出错</string>
<string name="invalid_user_input">用户输入无效</string>
<string name="temporarily_unavailable">暂时无法连接。请稍候再试。</string>
<string name="no_network_connection">无网络连接</string>
<string name="try_again_in_x">请在%s后重试</string>
<string name="rate_limited">频率过高</string>
<string name="too_many_attempts">尝试次数过多</string>
<string name="the_app_is_out_of_date">您正在使用旧版应用。</string>
<string name="update">更新</string>
<string name="logged_in_with_another_device">此号码已在其他设备上登录。</string>
<string name="enter_your_name_instructions">请输入您的姓名。这样,对方就能知道您是谁。</string>
<string name="your_name">您的姓名</string>
<string name="enter_your_name">输入姓名</string>
<string name="no_name_set_instructions">点击编辑按钮以编辑用户名。</string>
<string name="reject_request">拒绝请求</string>
<string name="install_orbot">安装Orbot</string>
<string name="start_orbot">启动Orbot</string>
<string name="no_market_app_installed">软件商店未安装</string>
<string name="group_chat_will_make_your_jabber_id_public">此群聊将公开你的XMPP地址</string>
<string name="ebook">电子书</string>
<string name="video_original">原始(未压缩)</string>
<string name="open_with">打开方式</string>
<string name="set_profile_picture">聊天头像</string>
<string name="choose_account">选择账户</string>
<string name="restore_backup">恢复备份</string>
<string name="restore">恢复</string>
<string name="enter_password_to_restore">输入%s的密码以恢复备份</string>
<string name="restore_warning">仅在迁移或丢失原设备时恢复备份。</string>
<string name="unable_to_restore_backup">无法恢复备份。</string>
<string name="unable_to_decrypt_backup">无法解密备份。密码是否正确?</string>
<string name="backup_channel_name">备份与恢复</string>
<string name="enter_jabber_id">输入XMPP地址</string>
<string name="create_group_chat">创建群聊</string>
<string name="join_public_channel">加入公开群聊</string>
<string name="create_private_group_chat">创建私密群聊</string>
<string name="create_public_channel">创建公开群聊</string>
<string name="create_dialog_channel_name">群聊名称</string>
<string name="xmpp_address">XMPP地址</string>
<string name="please_enter_name">请为群聊提供一个名称。</string>
<string name="please_enter_xmpp_address">请提供XMPP地址。</string>
<string name="this_is_an_xmpp_address">这是一个XMPP地址。请提供一个名称。</string>
<string name="creating_channel">创建公开群聊</string>
<string name="channel_already_exists">群聊已存在</string>
<string name="joined_an_existing_channel">您加入了一个已经存在的群聊</string>
<string name="unable_to_set_channel_configuration">无法配置群聊</string>
<string name="allow_participants_to_edit_subject">允许任何成员修改主题</string>
<string name="allow_participants_to_invite_others">允许任何成员邀请其他人</string>
<string name="anyone_can_edit_subject">允许任何成员修改主题</string>
<string name="owners_can_edit_subject">拥有者可修改话题</string>
<string name="admins_can_edit_subject">管理员可修改主题</string>
<string name="owners_can_invite_others">所有者可以邀请其他人</string>
<string name="anyone_can_invite_others">允许任何成员邀请其他人</string>
<string name="jabber_ids_are_visible_to_admins">XMPP地址对管理员可见。</string>
<string name="jabber_ids_are_visible_to_anyone">XMPP地址对所有人可见</string>
<string name="no_users_hint_channel">此公开群聊无成员。邀请成员或使用分享按钮分享地址。</string>
<string name="no_users_hint_group_chat">此私密群聊无成员</string>
<string name="manage_permission">管理权限</string>
<string name="search_participants">搜索成员</string>
<string name="file_too_large">文件过大</string>
<string name="attach">附加</string>
<string name="discover_channels">发现群聊</string>
<string name="search_channels">搜索群聊</string>
<string name="channel_discovery_opt_in_title">可能侵犯隐私!</string>
<string name="channel_discover_opt_in_message"><![CDATA[探索群聊功能使用一个叫<a href="https://search.jabbercat.org">search.jabbercat.org</a>的第三方服务。在探索群聊时您的IP地址和搜索内容将传送到他们的服务器上。有关更多信息请参阅他们的<a href="https://search.jabbercat.org/privacy">隐私政策</a>。]]></string>
<string name="i_already_have_an_account">我已有账户</string>
<string name="add_existing_account">添加已有账户</string>
<string name="register_new_account">注册新账户</string>
<string name="this_looks_like_a_domain">这好像是一个域名地址</string>
<string name="add_anway">仍然添加</string>
<string name="this_looks_like_channel">这好像是一个群聊地址</string>
<string name="share_backup_files">分享备份文件</string>
<string name="conversations_backup">备份文件</string>
<string name="event">事件</string>
<string name="open_backup">打开备份</string>
<string name="not_a_backup_file">选择的文件不是备份文件</string>
<string name="account_already_setup">账户已设置</string>
<string name="please_enter_password">请输入此账户的密码</string>
<string name="unable_to_perform_this_action">无法执行此操作</string>
<string name="open_join_dialog">加入公开群聊</string>
</resources>

View File

@ -874,4 +874,5 @@
<string name="account_already_setup">This account has already been setup</string>
<string name="please_enter_password">Please enter the password for this account</string>
<string name="unable_to_perform_this_action">Unable to perform this action</string>
<string name="open_join_dialog">Join public channel…</string>
</resources>

View File

@ -6,4 +6,5 @@
<string name="pref_broadcast_last_activity_summary">إجعل كلّ جهات إتصالك تعلم أنك تستعمل كويكسي</string>
<string name="no_microphone_permission">كويكسي يحتاج الإتصال بالمايكروفون</string>
<string name="set_profile_picture">صورة حساب كويكسي</string>
<string name="not_available_in_your_country">إن كويكسي Quicksy غير متوفر في بلدكم.</string>
</resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="crash_report_title">Quicksy har kraschat</string>
<string name="no_camera_permission">Quicksy behöver tillgång till kameran</string>
<string name="pref_broadcast_last_activity_summary">Berätta för alla dina kontakter när du använder Quicksy</string>
<string name="no_microphone_permission">Quicksy behöver tillgång till mikrofonen</string>
<string name="not_available_in_your_country">Quicksy är inte tillgängligt i ditt land.</string>
<string name="unknown_security_error">Okänt säkerhetsfel.</string>
</resources>

View File

@ -19,4 +19,8 @@
<string name="no_microphone_permission">Quicksy需要麦克风权限</string>
<string name="foreground_service_channel_description">此通知类别用于显示表明Quicksy正在运行的永久通知。</string>
<string name="set_profile_picture">Quicksy个人资料图片</string>
<string name="not_available_in_your_country">Quicksy在您的国家无服务。</string>
<string name="unable_to_verify_server_identity">无法确认服务器身份</string>
<string name="unknown_security_error">未知安全错误</string>
<string name="timeout_while_connecting_to_server">服务器已超时</string>
</resources>