diff --git a/build.gradle b/build.gradle
index 21b73b1bfa..a866675c2f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ buildscript {
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.3.1'
+ classpath 'com.android.tools.build:gradle:3.3.2'
classpath "io.realm:realm-gradle-plugin:3.1.1"
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'com.frogermcs.androiddevmetrics:androiddevmetrics-plugin:0.4'
diff --git a/xabber/build.gradle b/xabber/build.gradle
index 9d08ac2613..55ad17e937 100644
--- a/xabber/build.gradle
+++ b/xabber/build.gradle
@@ -10,8 +10,8 @@ android {
defaultConfig {
minSdkVersion 15
targetSdkVersion 28
- versionCode 591
- versionName '2.6.2(591)'
+ versionCode 602
+ versionName '2.6.3(602)'
}
lintOptions {
diff --git a/xabber/src/main/AndroidManifest.xml b/xabber/src/main/AndroidManifest.xml
index cd5135d42e..68d6000156 100644
--- a/xabber/src/main/AndroidManifest.xml
+++ b/xabber/src/main/AndroidManifest.xml
@@ -68,13 +68,10 @@
+
-
-
-
-
-
+
diff --git a/xabber/src/main/java/com/xabber/android/data/ActivityManager.java b/xabber/src/main/java/com/xabber/android/data/ActivityManager.java
index 57a99b395b..9c1947ed60 100644
--- a/xabber/src/main/java/com/xabber/android/data/ActivityManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/ActivityManager.java
@@ -21,6 +21,7 @@
import android.widget.Toast;
import com.xabber.android.R;
+import com.xabber.android.data.account.AccountManager;
import com.xabber.android.data.connection.CertificateManager;
import com.xabber.android.data.log.LogManager;
import com.xabber.android.ui.activity.AboutActivity;
@@ -219,6 +220,7 @@ public void onError(final int resourceId) {
application.addUIListener(OnErrorListener.class, onErrorListener);
CertificateManager.getInstance().registerActivity(activity);
+ AccountManager.getInstance().stopGracePeriod();
}
/**
diff --git a/xabber/src/main/java/com/xabber/android/data/Application.java b/xabber/src/main/java/com/xabber/android/data/Application.java
index c2bfdc0be5..6cd0bf4f21 100644
--- a/xabber/src/main/java/com/xabber/android/data/Application.java
+++ b/xabber/src/main/java/com/xabber/android/data/Application.java
@@ -41,6 +41,7 @@
import com.xabber.android.data.extension.blocking.BlockingManager;
import com.xabber.android.data.extension.capability.CapabilitiesManager;
import com.xabber.android.data.extension.carbons.CarbonManager;
+import com.xabber.android.data.extension.chat_markers.ChatMarkerManager;
import com.xabber.android.data.extension.cs.ChatStateManager;
import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager;
import com.xabber.android.data.extension.mam.MamManager;
@@ -376,6 +377,7 @@ private void addManagers() {
addManager(NetworkManager.getInstance());
addManager(ReconnectionManager.getInstance());
addManager(ReceiptManager.getInstance());
+ addManager(ChatMarkerManager.getInstance());
addManager(SSNManager.getInstance());
addManager(AttentionManager.getInstance());
addManager(CarbonManager.getInstance());
diff --git a/xabber/src/main/java/com/xabber/android/data/account/AccountItem.java b/xabber/src/main/java/com/xabber/android/data/account/AccountItem.java
index 9d4013f864..ee932d25b3 100644
--- a/xabber/src/main/java/com/xabber/android/data/account/AccountItem.java
+++ b/xabber/src/main/java/com/xabber/android/data/account/AccountItem.java
@@ -44,6 +44,7 @@
public class AccountItem extends ConnectionItem implements Comparable {
public static final String UNDEFINED_PASSWORD = "";
+ private static final long GRACE_PERIOD = 150000;
/**
* Id in database.
@@ -113,6 +114,8 @@ public class AccountItem extends ConnectionItem implements Comparable System.currentTimeMillis();
+ }
}
diff --git a/xabber/src/main/java/com/xabber/android/data/account/AccountManager.java b/xabber/src/main/java/com/xabber/android/data/account/AccountManager.java
index f9b5d44de2..9f3e559ea0 100644
--- a/xabber/src/main/java/com/xabber/android/data/account/AccountManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/account/AccountManager.java
@@ -1192,4 +1192,20 @@ public void setAllAccountAutoLoginToXabber(boolean autoLogin) {
}
}
+ public void startGracePeriod(AccountJid accountJid) {
+ AccountItem accountItem = getAccount(accountJid);
+ if (accountItem != null) accountItem.startGracePeriod();
+ }
+
+ public void stopGracePeriod(AccountJid accountJid) {
+ AccountItem accountItem = getAccount(accountJid);
+ if (accountItem != null) accountItem.stopGracePeriod();
+ }
+
+ public void stopGracePeriod() {
+ for (AccountJid accountJid : AccountManager.getInstance().getEnabledAccounts()) {
+ stopGracePeriod(accountJid);
+ }
+ }
+
}
diff --git a/xabber/src/main/java/com/xabber/android/data/account/ScreenManager.java b/xabber/src/main/java/com/xabber/android/data/account/ScreenManager.java
index 14850fc629..378000c390 100644
--- a/xabber/src/main/java/com/xabber/android/data/account/ScreenManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/account/ScreenManager.java
@@ -21,11 +21,11 @@
import android.content.IntentFilter;
import com.xabber.android.data.Application;
-import com.xabber.android.data.log.LogManager;
import com.xabber.android.data.OnCloseListener;
import com.xabber.android.data.OnInitializedListener;
import com.xabber.android.data.SettingsManager;
import com.xabber.android.data.extension.csi.ClientStateManager;
+import com.xabber.android.data.log.LogManager;
import com.xabber.android.receiver.GoAwayReceiver;
import com.xabber.android.receiver.GoXaReceiver;
import com.xabber.android.receiver.ScreenReceiver;
@@ -106,6 +106,7 @@ public void onScreen(Intent intent) {
alarmManager.cancel(goAwayPendingIntent);
alarmManager.cancel(goXaPendingIntent);
AccountManager.getInstance().wakeUp();
+ AccountManager.getInstance().stopGracePeriod();
// notify server(s) that client is now active
ClientStateManager.setActive();
diff --git a/xabber/src/main/java/com/xabber/android/data/database/MessageDatabaseManager.java b/xabber/src/main/java/com/xabber/android/data/database/MessageDatabaseManager.java
index f43f64a1ba..3575aad815 100644
--- a/xabber/src/main/java/com/xabber/android/data/database/MessageDatabaseManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/database/MessageDatabaseManager.java
@@ -19,10 +19,12 @@
import java.util.Date;
import io.realm.DynamicRealm;
+import io.realm.DynamicRealmObject;
import io.realm.FieldAttribute;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmMigration;
+import io.realm.RealmObjectSchema;
import io.realm.RealmQuery;
import io.realm.RealmResults;
import io.realm.RealmSchema;
@@ -31,7 +33,7 @@
public class MessageDatabaseManager {
private static final String REALM_MESSAGE_DATABASE_NAME = "xabber.realm";
- static final int REALM_MESSAGE_DATABASE_VERSION = 17;
+ static final int REALM_MESSAGE_DATABASE_VERSION = 19;
private final RealmConfiguration realmConfiguration;
private static MessageDatabaseManager instance;
@@ -300,6 +302,24 @@ public void migrate(DynamicRealm realm1, long oldVersion, long newVersion) {
oldVersion++;
}
+ if (oldVersion == 17) {
+ schema.get(MessageItem.class.getSimpleName())
+ .addField(MessageItem.Fields.DISPLAYED, boolean.class);
+
+ oldVersion++;
+ }
+
+ if (oldVersion == 18) {
+ schema.get(MessageItem.class.getSimpleName())
+ .transform(new RealmObjectSchema.Function() {
+ @Override
+ public void apply(DynamicRealmObject obj) {
+ obj.setBoolean(MessageItem.Fields.READ, true);
+ }
+ });
+ oldVersion++;
+ }
+
}
})
.build();
diff --git a/xabber/src/main/java/com/xabber/android/data/database/RealmManager.java b/xabber/src/main/java/com/xabber/android/data/database/RealmManager.java
index 4eefc326c9..50de97fb6e 100644
--- a/xabber/src/main/java/com/xabber/android/data/database/RealmManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/database/RealmManager.java
@@ -34,7 +34,7 @@
public class RealmManager {
private static final String REALM_DATABASE_NAME = "realm_database.realm";
- private static final int REALM_DATABASE_VERSION = 20;
+ private static final int REALM_DATABASE_VERSION = 21;
private static final String LOG_TAG = RealmManager.class.getSimpleName();
private final RealmConfiguration realmConfiguration;
@@ -302,10 +302,12 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
}
if (oldVersion == 20) {
- schema.create(UploadServer.class.getSimpleName())
- .addField(UploadServer.Fields.ID, String.class, FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED)
- .addField(UploadServer.Fields.ACCOUNT, String.class)
- .addField(UploadServer.Fields.SERVER, String.class);
+ if (!schema.contains(UploadServer.class.getSimpleName())) {
+ schema.create(UploadServer.class.getSimpleName())
+ .addField(UploadServer.Fields.ID, String.class, FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED)
+ .addField(UploadServer.Fields.ACCOUNT, String.class)
+ .addField(UploadServer.Fields.SERVER, String.class);
+ }
oldVersion++;
}
diff --git a/xabber/src/main/java/com/xabber/android/data/database/messagerealm/MessageItem.java b/xabber/src/main/java/com/xabber/android/data/database/messagerealm/MessageItem.java
index 10f43c8cfb..b9fddff349 100644
--- a/xabber/src/main/java/com/xabber/android/data/database/messagerealm/MessageItem.java
+++ b/xabber/src/main/java/com/xabber/android/data/database/messagerealm/MessageItem.java
@@ -54,6 +54,7 @@ public static class Fields {
public static final String ERROR = "error";
public static final String ERROR_DESCR = "errorDescription";
public static final String DELIVERED = "delivered";
+ public static final String DISPLAYED = "displayed";
public static final String SENT = "sent";
public static final String READ = "read";
public static final String STANZA_ID = "stanzaId";
@@ -131,6 +132,10 @@ public static class Fields {
* Receipt was received for sent message.
*/
private boolean delivered;
+ /**
+ * Chat marker was received for sent message.
+ */
+ private boolean displayed;
/**
* Message was sent.
*/
@@ -336,6 +341,14 @@ public void setDelivered(boolean delivered) {
this.delivered = delivered;
}
+ public boolean isDisplayed() {
+ return displayed;
+ }
+
+ public void setDisplayed(boolean displayed) {
+ this.displayed = displayed;
+ }
+
public boolean isSent() {
return sent;
}
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/carbons/CarbonCopyListener.java b/xabber/src/main/java/com/xabber/android/data/extension/carbons/CarbonCopyListener.java
index 5e9394fa38..8230fe4d1a 100644
--- a/xabber/src/main/java/com/xabber/android/data/extension/carbons/CarbonCopyListener.java
+++ b/xabber/src/main/java/com/xabber/android/data/extension/carbons/CarbonCopyListener.java
@@ -3,7 +3,8 @@
import com.xabber.android.data.Application;
import com.xabber.android.data.entity.AccountJid;
-import com.xabber.android.data.log.LogManager;
+import com.xabber.android.data.extension.chat_markers.ChatMarkerManager;
+import com.xabber.android.data.extension.cs.ChatStateManager;
import com.xabber.android.data.message.MessageManager;
import org.jivesoftware.smack.packet.Message;
@@ -24,11 +25,12 @@ class CarbonCopyListener implements CarbonCopyReceivedListener {
@Override
public void onCarbonCopyReceived(final CarbonExtension.Direction direction,
final Message carbonCopy, Message wrappingMessage) {
- LogManager.i(LOG_TAG, "onCarbonCopyReceived " + direction + " " + carbonCopy.getBody());
Application.getInstance().runOnUiThread(new Runnable() {
@Override
public void run() {
MessageManager.getInstance().processCarbonsMessage(account, carbonCopy, direction);
+ ChatMarkerManager.getInstance().processCarbonsMessage(account, carbonCopy, direction);
+ ChatStateManager.getInstance().processCarbonsMessage(account, carbonCopy, direction);
}
});
}
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/BackpressureMessageReader.java b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/BackpressureMessageReader.java
new file mode 100644
index 0000000000..5d672dd7db
--- /dev/null
+++ b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/BackpressureMessageReader.java
@@ -0,0 +1,101 @@
+package com.xabber.android.data.extension.chat_markers;
+
+import com.xabber.android.data.database.MessageDatabaseManager;
+import com.xabber.android.data.database.messagerealm.MessageItem;
+import com.xabber.android.data.log.LogManager;
+import com.xabber.android.data.message.AbstractChat;
+import com.xabber.android.data.message.MessageManager;
+import com.xabber.android.data.roster.AbstractContact;
+import com.xabber.android.data.roster.RosterManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import io.realm.Realm;
+import io.realm.RealmResults;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.functions.Action1;
+import rx.subjects.PublishSubject;
+
+
+/** Groups messages to send displayed. To avoid
+ * too often sending displayed chat markers.
+ * */
+public class BackpressureMessageReader {
+
+ private static BackpressureMessageReader instance;
+ private Map> queries = new HashMap<>();
+
+ public static BackpressureMessageReader getInstance() {
+ if (instance == null) {
+ instance = new BackpressureMessageReader();
+ }
+ return instance;
+ }
+
+ public void markAsRead(MessageItem messageItem, boolean trySendDisplayed) {
+ AbstractContact contact = RosterManager.getInstance().getAbstractContact(messageItem.getAccount(), messageItem.getUser());
+ PublishSubject subject = queries.get(contact);
+ if (subject == null) subject = createSubject(contact);
+ subject.onNext(new MessageHolder(messageItem, trySendDisplayed));
+ }
+
+ private PublishSubject createSubject(final AbstractContact contact) {
+ PublishSubject subject = PublishSubject.create();
+ subject.debounce(2000, TimeUnit.MILLISECONDS)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Action1() {
+ @Override
+ public void call(MessageHolder holder) {
+ MessageItem message = holder.messageItem;
+ if (holder.trySendDisplayed)
+ ChatMarkerManager.getInstance().sendDisplayed(message);
+
+ Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread();
+ RealmResults messages = getPreviousUnreadMessages(realm, message);
+ realm.beginTransaction();
+ List ids = new ArrayList<>();
+ for (MessageItem mes : messages) {
+ mes.setRead(true);
+ ids.add(mes.getUniqueId());
+ }
+ realm.commitTransaction();
+
+ AbstractChat chat = MessageManager.getInstance().getOrCreateChat(message.getAccount(), message.getUser());
+ if (chat != null) chat.approveRead(ids);
+ }
+ }, new Action1() {
+ @Override
+ public void call(Throwable throwable) {
+ LogManager.exception(this, throwable);
+ LogManager.d(this, "Exception is thrown. Subject was deleted.");
+ queries.remove(contact);
+ }
+ });
+ queries.put(contact, subject);
+ return subject;
+ }
+
+ private RealmResults getPreviousUnreadMessages(Realm realm, MessageItem messageItem) {
+ return realm.where(MessageItem.class)
+ .equalTo(MessageItem.Fields.ACCOUNT, messageItem.getAccount().toString())
+ .equalTo(MessageItem.Fields.USER, messageItem.getUser().toString())
+ .equalTo(MessageItem.Fields.READ, false)
+ .lessThanOrEqualTo(MessageItem.Fields.TIMESTAMP, messageItem.getTimestamp())
+ .findAll();
+ }
+
+ private class MessageHolder {
+ final MessageItem messageItem;
+ final boolean trySendDisplayed;
+
+ public MessageHolder(MessageItem messageItem, boolean trySendDisplayed) {
+ this.messageItem = messageItem;
+ this.trySendDisplayed = trySendDisplayed;
+ }
+ }
+
+}
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkerManager.java b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkerManager.java
new file mode 100644
index 0000000000..3f91ce0874
--- /dev/null
+++ b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkerManager.java
@@ -0,0 +1,212 @@
+package com.xabber.android.data.extension.chat_markers;
+
+import com.xabber.android.data.NetworkException;
+import com.xabber.android.data.account.AccountItem;
+import com.xabber.android.data.account.AccountManager;
+import com.xabber.android.data.connection.ConnectionItem;
+import com.xabber.android.data.connection.StanzaSender;
+import com.xabber.android.data.connection.listeners.OnPacketListener;
+import com.xabber.android.data.database.MessageDatabaseManager;
+import com.xabber.android.data.database.messagerealm.MessageItem;
+import com.xabber.android.data.entity.AccountJid;
+import com.xabber.android.data.entity.UserJid;
+import com.xabber.android.data.extension.chat_markers.filter.ChatMarkersFilter;
+import com.xabber.android.data.log.LogManager;
+import com.xabber.android.data.message.AbstractChat;
+import com.xabber.android.data.message.MessageManager;
+import com.xabber.android.data.message.MessageUpdateEvent;
+import com.xabber.android.data.notification.MessageNotificationManager;
+import com.xabber.android.data.roster.RosterManager;
+
+import org.greenrobot.eventbus.EventBus;
+import org.jivesoftware.smack.ConnectionCreationListener;
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.StanzaListener;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPConnectionRegistry;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.AndFilter;
+import org.jivesoftware.smack.filter.MessageTypeFilter;
+import org.jivesoftware.smack.filter.MessageWithBodiesFilter;
+import org.jivesoftware.smack.filter.NotFilter;
+import org.jivesoftware.smack.filter.StanzaFilter;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.packet.Stanza;
+import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
+import org.jivesoftware.smackx.chat_markers.element.ChatMarkersElements;
+import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
+import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
+import org.jxmpp.jid.Jid;
+
+import java.util.List;
+
+import io.realm.Realm;
+import io.realm.RealmResults;
+
+public class ChatMarkerManager implements OnPacketListener {
+
+ private static final StanzaFilter OUTGOING_MESSAGE_FILTER = new AndFilter(
+ MessageTypeFilter.NORMAL_OR_CHAT,
+ MessageWithBodiesFilter.INSTANCE,
+ new NotFilter(ChatMarkersFilter.INSTANCE),
+ EligibleForChatMarkerFilter.INSTANCE
+ );
+
+ private static ChatMarkerManager instance;
+
+ public static ChatMarkerManager getInstance() {
+ if (instance == null) instance = new ChatMarkerManager();
+ return instance;
+ }
+
+ public ChatMarkerManager() {
+ XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
+ @Override
+ public void connectionCreated(final XMPPConnection connection) {
+ ServiceDiscoveryManager.getInstanceFor(connection).addFeature(ChatMarkersElements.NAMESPACE);
+ connection.addPacketInterceptor(new StanzaListener() {
+ @Override
+ public void processStanza(Stanza packet) {
+ Message message = (Message) packet;
+ message.addExtension(new ChatMarkersElements.MarkableExtension());
+ }
+ }, OUTGOING_MESSAGE_FILTER);
+ }
+ });
+ }
+
+ @Override
+ public void onStanza(ConnectionItem connection, Stanza packet) {
+ if (packet instanceof Message) {
+ final Message message = (Message) packet;
+
+ if (ChatMarkersElements.MarkableExtension.from(message) != null) {
+ // markable
+ sendReceived(message, connection.getAccount());
+ } else if (ChatMarkersElements.ReceivedExtension.from(message) != null) {
+ // received
+ markAsDelivered(ChatMarkersElements.ReceivedExtension.from(message).getId());
+ } else if (ChatMarkersElements.DisplayedExtension.from(message) != null) {
+ // displayed
+ markAsDisplayed(ChatMarkersElements.DisplayedExtension.from(message).getId());
+ } else if (ChatMarkersElements.AcknowledgedExtension.from(message) != null) {
+ // acknowledged
+ }
+ }
+ }
+
+ public void sendDisplayed(MessageItem messageItem) {
+ if (messageItem.getStanzaId() == null || messageItem.getStanzaId().isEmpty()) return;
+
+ Message displayed = new Message(messageItem.getUser().getJid());
+ displayed.addExtension(new ChatMarkersElements.DisplayedExtension(messageItem.getStanzaId()));
+ displayed.setType(Message.Type.chat);
+ try {
+ StanzaSender.sendStanza(messageItem.getAccount(), displayed);
+ } catch (NetworkException e) {
+ LogManager.exception(this, e);
+ }
+ }
+
+ public void processCarbonsMessage(AccountJid account, final Message message, CarbonExtension.Direction direction) {
+ if (direction == CarbonExtension.Direction.sent) {
+ ChatMarkersElements.DisplayedExtension extension =
+ ChatMarkersElements.DisplayedExtension.from(message);
+ if (extension != null) {
+ UserJid companion;
+ try {
+ companion = UserJid.from(message.getTo()).getBareUserJid();
+ } catch (UserJid.UserJidCreateException e) {
+ return;
+ }
+ AbstractChat chat = MessageManager.getInstance().getOrCreateChat(account, companion);
+ if (chat != null) {
+ chat.markAsRead(extension.getId(), false);
+ MessageNotificationManager.getInstance().removeChatWithTimer(account, companion);
+
+ // start grace period
+ AccountManager.getInstance().startGracePeriod(account);
+ }
+ }
+ }
+ }
+
+ private boolean isClientSupportChatMarkers(AccountJid account, UserJid user) {
+ AccountItem accountItem = AccountManager.getInstance().getAccount(account);
+ if (accountItem == null) return false;
+
+ XMPPConnection connection = accountItem.getConnection();
+ final List allPresences = RosterManager.getInstance().getPresences(account, user.getJid());
+ boolean isChatMarkersSupport = false;
+
+ for (Presence presence : allPresences) {
+ Jid fromJid = presence.getFrom();
+ DiscoverInfo discoverInfo = null;
+ try {
+ discoverInfo = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(fromJid);
+ } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException |
+ SmackException.NotConnectedException | InterruptedException | ClassCastException e) {
+ e.printStackTrace();
+ }
+
+ isChatMarkersSupport = discoverInfo != null && discoverInfo.containsFeature(ChatMarkersElements.NAMESPACE);
+ if (isChatMarkersSupport) break;
+ }
+ return isChatMarkersSupport;
+ }
+
+ private void sendReceived(Message message, AccountJid account) {
+ if (message.getStanzaId() == null || message.getStanzaId().isEmpty()) return;
+
+ Message received = new Message(message.getFrom());
+ received.addExtension(new ChatMarkersElements.ReceivedExtension(message.getStanzaId()));
+ received.setThread(message.getThread());
+ received.setType(Message.Type.chat);
+
+ try {
+ StanzaSender.sendStanza(account, received);
+ } catch (NetworkException e) {
+ LogManager.exception(this, e);
+ }
+ }
+
+ private void markAsDisplayed(final String messageID) {
+ Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread();
+ MessageItem first = realm.where(MessageItem.class)
+ .equalTo(MessageItem.Fields.STANZA_ID, messageID).findFirst();
+
+ if (first != null) {
+ RealmResults results = realm.where(MessageItem.class)
+ .equalTo(MessageItem.Fields.ACCOUNT, first.getAccount().toString())
+ .equalTo(MessageItem.Fields.USER, first.getUser().toString())
+ .equalTo(MessageItem.Fields.INCOMING, false)
+ .equalTo(MessageItem.Fields.DISPLAYED, false)
+ .lessThanOrEqualTo(MessageItem.Fields.TIMESTAMP, first.getTimestamp())
+ .findAll();
+
+ if (results != null) {
+ realm.beginTransaction();
+ for (MessageItem item : results) {
+ item.setDisplayed(true);
+ }
+ realm.commitTransaction();
+ EventBus.getDefault().post(new MessageUpdateEvent());
+ }
+ }
+ }
+
+ private void markAsDelivered(final String stanzaID) {
+ Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread();
+
+ MessageItem first = realm.where(MessageItem.class)
+ .equalTo(MessageItem.Fields.STANZA_ID, stanzaID).findFirst();
+
+ if (first != null) {
+ realm.beginTransaction();
+ first.setDelivered(true);
+ realm.commitTransaction();
+ }
+ EventBus.getDefault().post(new MessageUpdateEvent());
+ }
+}
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkersElements.java b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkersElements.java
new file mode 100644
index 0000000000..cceef1a677
--- /dev/null
+++ b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkersElements.java
@@ -0,0 +1,182 @@
+package com.xabber.android.data.extension.chat_markers;
+
+import org.jivesoftware.smack.packet.ExtensionElement;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smack.util.XmlStringBuilder;
+
+
+public class ChatMarkersElements {
+
+ public static final String NAMESPACE = "urn:xmpp:chat-markers:0";
+
+ /**
+ * Markable extension class.
+ *
+ * @see XEP-0333: Chat
+ * Markers
+ * @author Fernando Ramirez
+ *
+ */
+ public static final class MarkableExtension implements ExtensionElement {
+
+ public static final MarkableExtension INSTANCE = new MarkableExtension();
+
+ /**
+ * markable element.
+ */
+ public static final String ELEMENT = ChatMarkersState.markable.toString();
+
+ private MarkableExtension() {
+ }
+
+ @Override
+ public String getElementName() {
+ return ELEMENT;
+ }
+
+ @Override
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ @Override
+ public CharSequence toXML() {
+ XmlStringBuilder xml = new XmlStringBuilder(this);
+ xml.closeEmptyElement();
+ return xml;
+ }
+
+ public static MarkableExtension from(Message message) {
+ return (MarkableExtension) message.getExtension(ELEMENT, NAMESPACE);
+ }
+ }
+
+ protected abstract static class ChatMarkerExtensionWithId implements ExtensionElement {
+ protected final String id;
+
+ protected ChatMarkerExtensionWithId(String id) {
+ this.id = StringUtils.requireNotNullOrEmpty(id, "Message ID must not be null");
+ }
+
+ /**
+ * Get the id.
+ *
+ * @return the id
+ */
+ public final String getId() {
+ return id;
+ }
+
+ @Override
+ public final XmlStringBuilder toXML() {
+ XmlStringBuilder xml = new XmlStringBuilder(this);
+ xml.attribute("id", id);
+ xml.closeEmptyElement();
+ return xml;
+ }
+ }
+
+ /**
+ * Received extension class.
+ *
+ * @see XEP-0333: Chat
+ * Markers
+ * @author Fernando Ramirez
+ *
+ */
+ public static class ReceivedExtension extends ChatMarkerExtensionWithId {
+
+ /**
+ * received element.
+ */
+ public static final String ELEMENT = ChatMarkersState.received.toString();
+
+ public ReceivedExtension(String id) {
+ super(id);
+ }
+
+ @Override
+ public String getElementName() {
+ return ELEMENT;
+ }
+
+ @Override
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public static ReceivedExtension from(Message message) {
+ return (ReceivedExtension) message.getExtension(ELEMENT, NAMESPACE);
+ }
+ }
+
+ /**
+ * Displayed extension class.
+ *
+ * @see XEP-0333: Chat
+ * Markers
+ * @author Fernando Ramirez
+ *
+ */
+ public static class DisplayedExtension extends ChatMarkerExtensionWithId {
+
+ /**
+ * displayed element.
+ */
+ public static final String ELEMENT = ChatMarkersState.displayed.toString();
+
+ public DisplayedExtension(String id) {
+ super(id);
+ }
+
+ @Override
+ public String getElementName() {
+ return ELEMENT;
+ }
+
+ @Override
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public static DisplayedExtension from(Message message) {
+ return (DisplayedExtension) message.getExtension(ELEMENT, NAMESPACE);
+ }
+ }
+
+ /**
+ * Acknowledged extension class.
+ *
+ * @see XEP-0333: Chat
+ * Markers
+ * @author Fernando Ramirez
+ *
+ */
+ public static class AcknowledgedExtension extends ChatMarkerExtensionWithId {
+
+ /**
+ * acknowledged element.
+ */
+ public static final String ELEMENT = ChatMarkersState.acknowledged.toString();
+
+ public AcknowledgedExtension(String id) {
+ super(id);
+ }
+
+ @Override
+ public String getElementName() {
+ return ELEMENT;
+ }
+
+ @Override
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public static AcknowledgedExtension from(Message message) {
+ return (AcknowledgedExtension) message.getExtension(ELEMENT, NAMESPACE);
+ }
+ }
+
+}
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkersState.java b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkersState.java
new file mode 100644
index 0000000000..3fe061e3e0
--- /dev/null
+++ b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkersState.java
@@ -0,0 +1,22 @@
+package com.xabber.android.data.extension.chat_markers;
+
+public enum ChatMarkersState {
+ /**
+ * Indicates that a message can be marked with a Chat Marker and is therefore
+ * a "markable message".
+ */
+ markable,
+ /**
+ * The message has been received by a client.
+ */
+ received,
+ /**
+ * The message has been displayed to a user in a active chat and not in a system notification.
+ */
+ displayed,
+ /**
+ * The message has been acknowledged by some user interaction e.g. pressing an
+ * acknowledgement button.
+ */
+ acknowledged
+}
\ No newline at end of file
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/EligibleForChatMarkerFilter.java b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/EligibleForChatMarkerFilter.java
new file mode 100644
index 0000000000..cc18070328
--- /dev/null
+++ b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/EligibleForChatMarkerFilter.java
@@ -0,0 +1,50 @@
+package com.xabber.android.data.extension.chat_markers;
+
+import org.jivesoftware.smack.filter.StanzaExtensionFilter;
+import org.jivesoftware.smack.packet.ExtensionElement;
+import org.jivesoftware.smack.packet.Stanza;
+import org.jivesoftware.smackx.chatstates.ChatState;
+import org.jivesoftware.smackx.chatstates.ChatStateManager;
+
+public final class EligibleForChatMarkerFilter extends StanzaExtensionFilter {
+
+ public static final EligibleForChatMarkerFilter INSTANCE = new EligibleForChatMarkerFilter(ChatStateManager.NAMESPACE);
+
+ private EligibleForChatMarkerFilter(String namespace) {
+ super(namespace);
+ }
+
+ /**
+ * From XEP-0333, Protocol Format: The Chat Marker MUST have an 'id' which is the 'id' of the
+ * message being marked.
+ * In order to make Chat Markers works together with XEP-0085 as it said in
+ * 8.5 Interaction with Chat States, only messages with active chat
+ * state are accepted.
+ *
+ * @param message to be analyzed.
+ * @return true if the message contains a stanza Id.
+ * @see XEP-0333: Chat Markers
+ */
+ @Override
+ public boolean accept(Stanza message) {
+ if (!message.hasStanzaIdSet()) {
+ return false;
+ }
+
+ if (super.accept(message)) {
+ ExtensionElement extension = message.getExtension(ChatStateManager.NAMESPACE);
+ String chatStateElementName = extension.getElementName();
+
+ ChatState state;
+ try {
+ state = ChatState.valueOf(chatStateElementName);
+ return (state == ChatState.active);
+ }
+ catch (Exception ex) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/filter/ChatMarkersFilter.java b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/filter/ChatMarkersFilter.java
new file mode 100644
index 0000000000..880bc6d28d
--- /dev/null
+++ b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/filter/ChatMarkersFilter.java
@@ -0,0 +1,15 @@
+package com.xabber.android.data.extension.chat_markers.filter;
+
+import com.xabber.android.data.extension.chat_markers.ChatMarkersElements;
+
+import org.jivesoftware.smack.filter.StanzaExtensionFilter;
+import org.jivesoftware.smack.filter.StanzaFilter;
+
+public final class ChatMarkersFilter extends StanzaExtensionFilter {
+
+ public static final StanzaFilter INSTANCE = new ChatMarkersFilter(ChatMarkersElements.NAMESPACE);
+
+ private ChatMarkersFilter(String namespace) {
+ super(namespace);
+ }
+}
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/cs/ChatStateManager.java b/xabber/src/main/java/com/xabber/android/data/extension/cs/ChatStateManager.java
index 3f9c00ccdd..54bad9602d 100644
--- a/xabber/src/main/java/com/xabber/android/data/extension/cs/ChatStateManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/extension/cs/ChatStateManager.java
@@ -19,12 +19,14 @@
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
+import android.util.Log;
import com.xabber.android.data.Application;
import com.xabber.android.data.NetworkException;
import com.xabber.android.data.OnCloseListener;
import com.xabber.android.data.SettingsManager;
import com.xabber.android.data.account.AccountItem;
+import com.xabber.android.data.account.AccountManager;
import com.xabber.android.data.connection.ConnectionItem;
import com.xabber.android.data.connection.ConnectionManager;
import com.xabber.android.data.connection.StanzaSender;
@@ -48,12 +50,15 @@
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Type;
import org.jivesoftware.smack.packet.Stanza;
+import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
import org.jivesoftware.smackx.chatstates.ChatState;
import org.jivesoftware.smackx.chatstates.packet.ChatStateExtension;
+import org.jivesoftware.smackx.chatstates.provider.ChatStateExtensionProvider;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.parts.Resourcepart;
+import org.jxmpp.stringprep.XmppStringprepException;
import java.util.Calendar;
import java.util.Map;
@@ -365,4 +370,17 @@ public void onClose() {
pauseIntents.clear();
}
+ public void processCarbonsMessage(AccountJid account, final Message message, CarbonExtension.Direction direction) {
+ if (direction == CarbonExtension.Direction.sent) {
+ for (ExtensionElement extension : message.getExtensions())
+ if (extension instanceof ChatStateExtension) {
+ ChatState chatState = ((ChatStateExtension) extension).getChatState();
+ if (chatState == ChatState.active || chatState == ChatState.composing) {
+ AccountManager.getInstance().startGracePeriod(account);
+ }
+ break;
+ }
+ }
+ }
+
}
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/file/FileManager.java b/xabber/src/main/java/com/xabber/android/data/extension/file/FileManager.java
index 72a4908fc1..fd18cf7afe 100644
--- a/xabber/src/main/java/com/xabber/android/data/extension/file/FileManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/extension/file/FileManager.java
@@ -66,7 +66,7 @@ public static boolean fileIsImage(File file) {
return extensionIsImage(extractRelevantExtension(file.getPath()));
}
- private static boolean extensionIsImage(String extension) {
+ public static boolean extensionIsImage(String extension) {
return Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(extension);
}
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/file/UriUtils.java b/xabber/src/main/java/com/xabber/android/data/extension/file/UriUtils.java
new file mode 100644
index 0000000000..1f2a5a1168
--- /dev/null
+++ b/xabber/src/main/java/com/xabber/android/data/extension/file/UriUtils.java
@@ -0,0 +1,63 @@
+package com.xabber.android.data.extension.file;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.OpenableColumns;
+import android.webkit.MimeTypeMap;
+
+import com.xabber.android.data.Application;
+
+import org.apache.commons.io.FilenameUtils;
+
+import java.util.UUID;
+
+public class UriUtils {
+
+ public static boolean uriIsImage(Uri uri) {
+ return FileManager.extensionIsImage(getExtensionFromUri(uri));
+ }
+
+ public static String getFullFileName(Uri uri) {
+ String extension = getExtensionFromUri(uri);
+ String name = getFileName(uri);
+ if (name == null) name = UUID.randomUUID().toString();
+ else name = name.replace(".", "");
+ String fileName = name + "." + extension;
+ return fileName;
+ }
+
+ public static String getMimeType(Uri uri) {
+ String type = Application.getInstance().getContentResolver().getType(uri);
+ if (type == null || type.isEmpty()) type = "*/*";
+ return type;
+ }
+
+ private static String getExtensionFromUri(Uri uri) {
+ String mimeType = Application.getInstance().getContentResolver().getType(uri);
+ return MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+ }
+
+ private static String getFileName(Uri uri) {
+ String result = null;
+ if (uri.getScheme().equals("content")) {
+ Cursor cursor = Application.getInstance().getContentResolver()
+ .query(uri, null, null, null, null);
+ try {
+ if (cursor != null && cursor.moveToFirst()) {
+ result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ if (result == null) {
+ result = uri.getPath();
+ int cut = result.lastIndexOf('/');
+ if (cut != -1) {
+ result = result.substring(cut + 1);
+ }
+ }
+ return FilenameUtils.getBaseName(result);
+ }
+
+}
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/httpfileupload/HttpFileUploadManager.java b/xabber/src/main/java/com/xabber/android/data/extension/httpfileupload/HttpFileUploadManager.java
index 9caa8f6f27..4f0a054951 100644
--- a/xabber/src/main/java/com/xabber/android/data/extension/httpfileupload/HttpFileUploadManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/extension/httpfileupload/HttpFileUploadManager.java
@@ -341,10 +341,9 @@ public void run() {
Realm realm = RealmManager.getInstance().getNewRealm();
UploadServer item = realm.where(UploadServer.class)
.equalTo(UploadServer.Fields.ACCOUNT, account.toString()).findFirst();
+ realm.beginTransaction();
if (item == null) item = new UploadServer(account, server);
else item.setServer(server);
-
- realm.beginTransaction();
realm.copyToRealmOrUpdate(item);
realm.commitTransaction();
}
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/mam/MamManager.java b/xabber/src/main/java/com/xabber/android/data/extension/mam/MamManager.java
index f2360ba1e5..f3dd41fe05 100644
--- a/xabber/src/main/java/com/xabber/android/data/extension/mam/MamManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/extension/mam/MamManager.java
@@ -135,7 +135,8 @@ private boolean updateIsSupported(AccountItem accountItem) {
}
} catch (SmackException.NoResponseException | XMPPException.XMPPErrorException
- | InterruptedException | SmackException.NotConnectedException | SmackException.NotLoggedInException e) {
+ | InterruptedException | SmackException.NotConnectedException | SmackException.NotLoggedInException
+ | ClassCastException e) {
LogManager.exception(this, e);
return false;
}
diff --git a/xabber/src/main/java/com/xabber/android/data/extension/muc/MUCManager.java b/xabber/src/main/java/com/xabber/android/data/extension/muc/MUCManager.java
index a2c103846c..9b563a793b 100644
--- a/xabber/src/main/java/com/xabber/android/data/extension/muc/MUCManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/extension/muc/MUCManager.java
@@ -116,7 +116,6 @@ public void run() {
ChatData chatData = ChatManager.getInstance().loadChatDataFromRealm(roomChat);
if (chatData != null) {
roomChat.setLastPosition(chatData.getLastPosition());
- roomChat.setUnreadMessageCount(chatData.getUnreadCount());
roomChat.setArchived(chatData.isArchived(), false);
roomChat.setNotificationState(chatData.getNotificationState(), false);
}
diff --git a/xabber/src/main/java/com/xabber/android/data/message/AbstractChat.java b/xabber/src/main/java/com/xabber/android/data/message/AbstractChat.java
index 1c5db16099..57d1974d96 100644
--- a/xabber/src/main/java/com/xabber/android/data/message/AbstractChat.java
+++ b/xabber/src/main/java/com/xabber/android/data/message/AbstractChat.java
@@ -14,6 +14,7 @@
*/
package com.xabber.android.data.message;
+import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
@@ -32,8 +33,10 @@
import com.xabber.android.data.entity.BaseEntity;
import com.xabber.android.data.entity.UserJid;
import com.xabber.android.data.extension.carbons.CarbonManager;
+import com.xabber.android.data.extension.chat_markers.BackpressureMessageReader;
import com.xabber.android.data.extension.cs.ChatStateManager;
import com.xabber.android.data.extension.file.FileManager;
+import com.xabber.android.data.extension.file.UriUtils;
import com.xabber.android.data.extension.forward.ForwardComment;
import com.xabber.android.data.extension.httpfileupload.ExtendedFormField;
import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager;
@@ -61,13 +64,16 @@
import java.io.File;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import io.realm.Realm;
import io.realm.RealmChangeListener;
import io.realm.RealmList;
+import io.realm.RealmQuery;
import io.realm.RealmResults;
import io.realm.Sort;
@@ -102,10 +108,11 @@ public abstract class AbstractChat extends BaseEntity implements RealmChangeList
private String threadId;
private int lastPosition;
- private int unreadMessageCount;
private boolean archived;
protected NotificationState notificationState;
+ private Set waitToMarkAsRead = new HashSet<>();
+
private boolean isPrivateMucChat;
private boolean isPrivateMucChatAccepted;
@@ -296,7 +303,7 @@ protected void createAndSaveNewMessage(boolean ui, String uid, Resourcepart reso
originalStanza, parentMessageId, originalFrom, forwardIds, fromMUC, fromMAM);
saveMessageItem(ui, messageItem);
- EventBus.getDefault().post(new NewMessageEvent());
+ //EventBus.getDefault().post(new NewMessageEvent());
}
protected void createAndSaveFileMessage(boolean ui, String uid, Resourcepart resource, String text, final ChatAction action,
@@ -310,23 +317,24 @@ protected void createAndSaveFileMessage(boolean ui, String uid, Resourcepart res
originalStanza, parentMessageId, originalFrom, null, fromMUC, fromMAM);
saveMessageItem(ui, messageItem);
- EventBus.getDefault().post(new NewMessageEvent());
+ //EventBus.getDefault().post(new NewMessageEvent());
}
public void saveMessageItem(boolean ui, final MessageItem messageItem) {
- final long startTime = System.currentTimeMillis();
- Realm realm;
- if (ui) realm = MessageDatabaseManager.getInstance().getRealmUiThread();
- else realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm();
-
- realm.executeTransactionAsync(new Realm.Transaction() {
- @Override
- public void execute(Realm realm) {
- realm.copyToRealm(messageItem);
- LogManager.d("REALM", Thread.currentThread().getName()
- + " save message item: " + (System.currentTimeMillis() - startTime));
- }
- });
+ if (ui) BackpressureMessageSaver.getInstance().saveMessageItem(messageItem);
+ else {
+ final long startTime = System.currentTimeMillis();
+ Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm();
+ realm.executeTransactionAsync(new Realm.Transaction() {
+ @Override
+ public void execute(Realm realm) {
+ realm.copyToRealm(messageItem);
+ LogManager.d("REALM", Thread.currentThread().getName()
+ + " save message item: " + (System.currentTimeMillis() - startTime));
+ EventBus.getDefault().post(new NewMessageEvent());
+ }
+ });
+ }
}
protected MessageItem createMessageItem(Resourcepart resource, String text, ChatAction action,
@@ -347,7 +355,7 @@ protected MessageItem createMessageItem(String uid, Resourcepart resource, Strin
RealmList forwardIds, boolean fromMUC, boolean fromMAM) {
final boolean visible = MessageManager.getInstance().isVisibleChat(this);
- boolean read = incoming ? visible : true;
+ boolean read = !incoming;
boolean send = incoming;
if (action == null && text == null) {
throw new IllegalArgumentException();
@@ -419,16 +427,8 @@ protected MessageItem createMessageItem(String uid, Resourcepart resource, Strin
if (notify && notifyAboutMessage() && !visible)
NotificationManager.getInstance().onMessageNotification(messageItem);
- // unread message count
- if (!visible && action == null) {
- if (incoming && !fromMAM) increaseUnreadMessageCount();
- else resetUnreadMessageCount();
- }
-
// remove notifications if get outgoing message with 2 sec delay
- if (!incoming) {
- MessageNotificationManager.getInstance().removeChatWithTimer(account, user);
- }
+ if (!incoming) MessageNotificationManager.getInstance().removeChatWithTimer(account, user);
// when getting new message, unarchive chat if chat not muted
if (this.notifyAboutMessage())
@@ -437,33 +437,16 @@ protected MessageItem createMessageItem(String uid, Resourcepart resource, Strin
return messageItem;
}
- public String newFileMessage(final List files) {
+ public String newFileMessage(final List files, final List uris) {
Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm();
-
final String messageId = UUID.randomUUID().toString();
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
-
- RealmList attachments = new RealmList<>();
- for (File file : files) {
- Attachment attachment = new Attachment();
- attachment.setFilePath(file.getPath());
- attachment.setFileSize(file.length());
- attachment.setTitle(file.getName());
- attachment.setIsImage(FileManager.fileIsImage(file));
- attachment.setMimeType(HttpFileUploadManager.getMimeType(file.getPath()));
- attachment.setDuration((long) 0);
-
- if (attachment.isImage()) {
- HttpFileUploadManager.ImageSize imageSize =
- HttpFileUploadManager.getImageSizes(file.getPath());
- attachment.setImageHeight(imageSize.getHeight());
- attachment.setImageWidth(imageSize.getWidth());
- }
- attachments.add(attachment);
- }
+ RealmList attachments;
+ if (files != null) attachments = attachmentsFromFiles(files);
+ else attachments = attachmentsFromUris(uris);
MessageItem messageItem = new MessageItem(messageId);
messageItem.setAccount(account);
@@ -484,6 +467,41 @@ public void execute(Realm realm) {
return messageId;
}
+ public RealmList attachmentsFromFiles(List files) {
+ RealmList attachments = new RealmList<>();
+ for (File file : files) {
+ Attachment attachment = new Attachment();
+ attachment.setFilePath(file.getPath());
+ attachment.setFileSize(file.length());
+ attachment.setTitle(file.getName());
+ attachment.setIsImage(FileManager.fileIsImage(file));
+ attachment.setMimeType(HttpFileUploadManager.getMimeType(file.getPath()));
+ attachment.setDuration((long) 0);
+
+ if (attachment.isImage()) {
+ HttpFileUploadManager.ImageSize imageSize =
+ HttpFileUploadManager.getImageSizes(file.getPath());
+ attachment.setImageHeight(imageSize.getHeight());
+ attachment.setImageWidth(imageSize.getWidth());
+ }
+ attachments.add(attachment);
+ }
+ return attachments;
+ }
+
+ public RealmList attachmentsFromUris(List uris) {
+ RealmList attachments = new RealmList<>();
+ for (Uri uri : uris) {
+ Attachment attachment = new Attachment();
+ attachment.setTitle(UriUtils.getFullFileName(uri));
+ attachment.setIsImage(UriUtils.uriIsImage(uri));
+ attachment.setMimeType(UriUtils.getMimeType(uri));
+ attachment.setDuration((long) 0);
+ attachments.add(attachment);
+ }
+ return attachments;
+ }
+
/**
* @return Whether chat accepts packets from specified user.
*/
@@ -787,24 +805,75 @@ public void onChange(RealmResults messageItems) {
updateLastMessage();
}
+ /** UNREAD MESSAGES */
+
+ public String getFirstUnreadMessageId() {
+ String id = null;
+ RealmResults results = getAllUnreadAscending();
+ if (results != null && !results.isEmpty()) {
+ MessageItem firstUnreadMessage = results.first();
+ if (firstUnreadMessage != null)
+ id = firstUnreadMessage.getUniqueId();
+ }
+ return id;
+ }
+
public int getUnreadMessageCount() {
- return unreadMessageCount;
+ int unread = ((int) getAllUnreadQuery().count()) - waitToMarkAsRead.size();
+ if (unread < 0) unread = 0;
+ return unread;
}
- public void increaseUnreadMessageCount() {
- this.unreadMessageCount++;
- ChatManager.getInstance().saveOrUpdateChatDataToRealm(this);
+ public void approveRead(List ids) {
+ for (String id : ids) {
+ waitToMarkAsRead.remove(id);
+ }
+ EventBus.getDefault().post(new MessageUpdateEvent(account, user));
}
- public void resetUnreadMessageCount() {
- this.unreadMessageCount = 0;
- ChatManager.getInstance().saveOrUpdateChatDataToRealm(this);
+ public void markAsRead(String messageId, boolean trySendDisplay) {
+ MessageItem message = MessageDatabaseManager.getInstance().getRealmUiThread()
+ .where(MessageItem.class).equalTo(MessageItem.Fields.STANZA_ID, messageId).findFirst();
+ if (message != null) executeRead(message, trySendDisplay);
+ }
+
+ public void markAsRead(MessageItem messageItem, boolean trySendDisplay) {
+ waitToMarkAsRead.add(messageItem.getUniqueId());
+ executeRead(messageItem, trySendDisplay);
+ }
+
+ public void markAsReadAll(boolean trySendDisplay) {
+ RealmResults results = getAllUnreadAscending();
+ if (results != null && !results.isEmpty()) {
+ for (MessageItem message : results) {
+ waitToMarkAsRead.add(message.getUniqueId());
+ }
+ MessageItem lastMessage = results.last();
+ if (lastMessage != null) executeRead(lastMessage, trySendDisplay);
+ }
}
- public void setUnreadMessageCount(int unreadMessageCount) {
- this.unreadMessageCount = unreadMessageCount;
+ private void executeRead(MessageItem messageItem, boolean trySendDisplay) {
+ EventBus.getDefault().post(new MessageUpdateEvent(account, user));
+ BackpressureMessageReader.getInstance().markAsRead(messageItem, trySendDisplay);
}
+ private RealmQuery getAllUnreadQuery() {
+ return MessageDatabaseManager.getInstance().getRealmUiThread().where(MessageItem.class)
+ .equalTo(MessageItem.Fields.ACCOUNT, account.toString())
+ .equalTo(MessageItem.Fields.USER, user.toString())
+ .isNull(MessageItem.Fields.PARENT_MESSAGE_ID)
+ .isNotNull(MessageItem.Fields.TEXT)
+ .equalTo(MessageItem.Fields.INCOMING, true)
+ .equalTo(MessageItem.Fields.READ, false);
+ }
+
+ private RealmResults getAllUnreadAscending() {
+ return getAllUnreadQuery().findAllSorted(MessageItem.Fields.TIMESTAMP, Sort.ASCENDING);
+ }
+
+ /** ^ UNREAD MESSAGES ^ */
+
public boolean isArchived() {
return archived;
}
diff --git a/xabber/src/main/java/com/xabber/android/data/message/BackpressureMessageSaver.java b/xabber/src/main/java/com/xabber/android/data/message/BackpressureMessageSaver.java
new file mode 100644
index 0000000000..8e07d4e1d3
--- /dev/null
+++ b/xabber/src/main/java/com/xabber/android/data/message/BackpressureMessageSaver.java
@@ -0,0 +1,77 @@
+package com.xabber.android.data.message;
+
+import com.xabber.android.data.database.MessageDatabaseManager;
+import com.xabber.android.data.database.messagerealm.MessageItem;
+import com.xabber.android.data.log.LogManager;
+
+import org.greenrobot.eventbus.EventBus;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import io.realm.Realm;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.functions.Action1;
+import rx.subjects.PublishSubject;
+
+
+/** Groups messages to save. It is necessary to avoid
+ * java.util.concurrent.RejectedExecutionException error,
+ * which can occur if you frequently save messages one at a time in Realm.
+ *
+ * Issue in crashlytics: https://www.fabric.io/redsolution/android/apps/com.xabber.android/issues/5c55e9edf8b88c29636f3fd3
+ * */
+public class BackpressureMessageSaver {
+
+ private static BackpressureMessageSaver instance;
+ private PublishSubject subject;
+
+ public static BackpressureMessageSaver getInstance() {
+ if (instance == null) instance = new BackpressureMessageSaver();
+ return instance;
+ }
+
+ public void saveMessageItem(MessageItem messageItem) {
+ subject.onNext(messageItem);
+ }
+
+ private BackpressureMessageSaver() {
+ createSubject();
+ }
+
+ private void createSubject() {
+ subject = PublishSubject.create();
+ subject.buffer(500, TimeUnit.MILLISECONDS)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Action1>() {
+ @Override
+ public void call(final List messageItems) {
+ if (messageItems == null || messageItems.isEmpty()) return;
+ try {
+ Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread();
+ realm.executeTransactionAsync(new Realm.Transaction() {
+ @Override
+ public void execute(Realm realm) {
+ realm.copyToRealm(messageItems);
+ }
+ }, new Realm.Transaction.OnSuccess() {
+ @Override
+ public void onSuccess() {
+ EventBus.getDefault().post(new NewMessageEvent());
+ }
+ });
+ } catch (Exception e) {
+ LogManager.exception(this, e);
+ }
+ }
+ }, new Action1() {
+ @Override
+ public void call(Throwable throwable) {
+ LogManager.exception(this, throwable);
+ LogManager.d(this, "Exception is thrown. Created new publish subject.");
+ createSubject();
+ }
+ });
+ }
+
+}
diff --git a/xabber/src/main/java/com/xabber/android/data/message/ChatData.java b/xabber/src/main/java/com/xabber/android/data/message/ChatData.java
index 60e72efce0..de5884abad 100644
--- a/xabber/src/main/java/com/xabber/android/data/message/ChatData.java
+++ b/xabber/src/main/java/com/xabber/android/data/message/ChatData.java
@@ -9,17 +9,15 @@ public class ChatData {
private String subject;
private String accountJid;
private String userJid;
- private int unreadCount;
private boolean archived;
private NotificationState notificationState;
private int lastPosition;
- public ChatData(String subject, String accountJid, String userJid, int unreadCount,
+ public ChatData(String subject, String accountJid, String userJid,
boolean archived, NotificationState notificationState, int lastPosition) {
this.subject = subject;
this.accountJid = accountJid;
this.userJid = userJid;
- this.unreadCount = unreadCount;
this.archived = archived;
this.notificationState = notificationState;
this.lastPosition = lastPosition;
@@ -49,14 +47,6 @@ public void setUserJid(String userJid) {
this.userJid = userJid;
}
- public int getUnreadCount() {
- return unreadCount;
- }
-
- public void setUnreadCount(int unreadCount) {
- this.unreadCount = unreadCount;
- }
-
public boolean isArchived() {
return archived;
}
diff --git a/xabber/src/main/java/com/xabber/android/data/message/ForwardManager.java b/xabber/src/main/java/com/xabber/android/data/message/ForwardManager.java
index 6cedcb923c..3b259bd9f9 100644
--- a/xabber/src/main/java/com/xabber/android/data/message/ForwardManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/message/ForwardManager.java
@@ -35,10 +35,10 @@ public static void forwardMessage(List messages, AccountJid account, Use
@Override
public void execute(Realm realm) {
realm.copyToRealm(messageItem);
+ EventBus.getDefault().post(new NewMessageEvent());
chat.sendMessages();
}
});
- EventBus.getDefault().post(new NewMessageEvent());
}
public static String parseForwardComment(Stanza packet) {
diff --git a/xabber/src/main/java/com/xabber/android/data/message/MessageManager.java b/xabber/src/main/java/com/xabber/android/data/message/MessageManager.java
index 4e1fbba776..4c5774ef8d 100644
--- a/xabber/src/main/java/com/xabber/android/data/message/MessageManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/message/MessageManager.java
@@ -14,6 +14,7 @@
*/
package com.xabber.android.data.message;
+import android.net.Uri;
import android.os.Environment;
import android.os.Looper;
import android.support.annotation.Nullable;
@@ -59,7 +60,6 @@
import com.xabber.android.data.roster.RosterManager;
import com.xabber.android.utils.StringUtils;
-import org.greenrobot.eventbus.EventBus;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
@@ -202,7 +202,6 @@ private RegularChat createChat(AccountJid account, UserJid user) {
ChatData chatData = ChatManager.getInstance().loadChatDataFromRealm(chat);
if (chatData != null) {
chat.setLastPosition(chatData.getLastPosition());
- chat.setUnreadMessageCount(chatData.getUnreadCount());
chat.setArchived(chatData.isArchived(), false);
chat.setNotificationState(chatData.getNotificationState(), false);
}
@@ -215,7 +214,6 @@ private RegularChat createPrivateMucChat(AccountJid account, FullJid fullJid) th
ChatData chatData = ChatManager.getInstance().loadChatDataFromRealm(chat);
if (chatData != null) {
chat.setLastPosition(chatData.getLastPosition());
- chat.setUnreadMessageCount(chatData.getUnreadCount());
chat.setArchived(chatData.isArchived(), false);
chat.setNotificationState(chatData.getNotificationState(), false);
}
@@ -256,30 +254,37 @@ public void removeChat(AbstractChat chat) {
public void sendMessage(AccountJid account, UserJid user, String text) {
AbstractChat chat = getOrCreateChat(account, user);
sendMessage(text, chat);
+
+ // stop grace period
+ AccountManager.getInstance().stopGracePeriod(account);
}
private void sendMessage(final String text, final AbstractChat chat) {
- final long startTime = System.currentTimeMillis();
-
MessageDatabaseManager.getInstance().getRealmUiThread()
.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
MessageItem newMessageItem = chat.createNewMessageItem(text);
realm.copyToRealm(newMessageItem);
- LogManager.d("REALM", Thread.currentThread().getName()
- + " save message before sending: " + (System.currentTimeMillis() - startTime));
if (chat.canSendMessage())
chat.sendMessages();
}
});
+
+ // mark incoming messages as read
+ chat.markAsReadAll(true);
}
public String createFileMessage(AccountJid account, UserJid user, List files) {
AbstractChat chat = getOrCreateChat(account, user);
+ chat.openChat();
+ return chat.newFileMessage(files, null);
+ }
+ public String createFileMessageFromUris(AccountJid account, UserJid user, List uris) {
+ AbstractChat chat = getOrCreateChat(account, user);
chat.openChat();
- return chat.newFileMessage(files);
+ return chat.newFileMessage(null, uris);
}
public void updateFileMessage(AccountJid account, UserJid user, final String messageId,
@@ -346,6 +351,10 @@ public void execute(Realm realm) {
if (messageItem != null) {
RealmList attachments = messageItem.getAttachments();
+ // remove temporary attachments created from uri
+ // to replace it with attachments created from files
+ attachments.deleteAllFromRealm();
+
for (File file : files) {
Attachment attachment = new Attachment();
attachment.setFilePath(file.getPath());
@@ -511,30 +520,8 @@ public void closeChat(AccountJid account, UserJid user) {
*/
public void setVisibleChat(BaseEntity visibleChat) {
AbstractChat chat = getChat(visibleChat.getAccount(), visibleChat.getUser());
- if (chat == null) {
+ if (chat == null)
chat = createChat(visibleChat.getAccount(), visibleChat.getUser());
- } else {
- final AccountJid account = chat.getAccount();
- final UserJid user = chat.getUser();
-
- MessageDatabaseManager.getInstance()
- .getRealmUiThread().executeTransactionAsync(new Realm.Transaction() {
- @Override
- public void execute(Realm realm) {
- RealmResults unreadMessages = realm.where(MessageItem.class)
- .equalTo(MessageItem.Fields.ACCOUNT, account.toString())
- .equalTo(MessageItem.Fields.USER, user.toString())
- .equalTo(MessageItem.Fields.READ, false)
- .findAll();
-
- List unreadMessagesList = new ArrayList<>(unreadMessages);
-
- for (MessageItem messageItem : unreadMessagesList) {
- messageItem.setRead(true);
- }
- }
- });
- }
this.visibleChat = chat;
}
@@ -787,37 +774,36 @@ public void processCarbonsMessage(AccountJid account, final Message message, Car
final AbstractChat finalChat = chat;
- MessageDatabaseManager.getInstance().getRealmUiThread()
- .executeTransactionAsync(new Realm.Transaction() {
- @Override
- public void execute(Realm realm) {
- String text = body;
- String uid = UUID.randomUUID().toString();
- RealmList forwardIds = finalChat.parseForwardedMessage(false, message, uid);
- String originalStanza = message.toXML().toString();
- String originalFrom = message.getFrom().toString();
- String forwardComment = ForwardManager.parseForwardComment(message);
- if (forwardComment != null) text = forwardComment;
-
- MessageItem newMessageItem = finalChat.createNewMessageItem(text);
- newMessageItem.setStanzaId(message.getStanzaId());
- newMessageItem.setSent(true);
- newMessageItem.setForwarded(true);
-
- // forwarding
- if (forwardIds != null) newMessageItem.setForwardedIds(forwardIds);
- newMessageItem.setOriginalStanza(originalStanza);
- newMessageItem.setOriginalFrom(originalFrom);
-
- // attachments
- RealmList attachments = HttpFileUploadManager.parseFileMessage(message);
- if (attachments.size() > 0)
- newMessageItem.setAttachments(attachments);
-
- realm.copyToRealm(newMessageItem);
- EventBus.getDefault().post(new NewMessageEvent());
- }
- });
+ String text = body;
+ String uid = UUID.randomUUID().toString();
+ RealmList forwardIds = finalChat.parseForwardedMessage(true, message, uid);
+ String originalStanza = message.toXML().toString();
+ String originalFrom = message.getFrom().toString();
+ String forwardComment = ForwardManager.parseForwardComment(message);
+ if (forwardComment != null) text = forwardComment;
+
+ MessageItem newMessageItem = finalChat.createNewMessageItem(text);
+ newMessageItem.setStanzaId(message.getStanzaId());
+ newMessageItem.setSent(true);
+ newMessageItem.setForwarded(true);
+
+ // forwarding
+ if (forwardIds != null) newMessageItem.setForwardedIds(forwardIds);
+ newMessageItem.setOriginalStanza(originalStanza);
+ newMessageItem.setOriginalFrom(originalFrom);
+
+ // attachments
+ RealmList attachments = HttpFileUploadManager.parseFileMessage(message);
+ if (attachments.size() > 0)
+ newMessageItem.setAttachments(attachments);
+
+ BackpressureMessageSaver.getInstance().saveMessageItem(newMessageItem);
+
+ // mark incoming messages as read
+ finalChat.markAsReadAll(false);
+
+ // start grace period
+ AccountManager.getInstance().startGracePeriod(account);
return;
}
diff --git a/xabber/src/main/java/com/xabber/android/data/message/ReceiptManager.java b/xabber/src/main/java/com/xabber/android/data/message/ReceiptManager.java
index 803c95f1fb..fe2601f2a9 100644
--- a/xabber/src/main/java/com/xabber/android/data/message/ReceiptManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/message/ReceiptManager.java
@@ -69,7 +69,7 @@ private ReceiptManager() {
@Override
public void connectionCreated(final XMPPConnection connection) {
DeliveryReceiptManager.getInstanceFor(connection).addReceiptReceivedListener(ReceiptManager.this);
- DeliveryReceiptManager.getInstanceFor(connection).autoAddDeliveryReceiptRequests();
+ //DeliveryReceiptManager.getInstanceFor(connection).autoAddDeliveryReceiptRequests();
}
});
@@ -108,6 +108,7 @@ public void run() {
receipt.addExtension(new DeliveryReceipt(id));
// the key problem is Thread - smack does not keep it in auto reply
receipt.setThread(message.getThread());
+ receipt.setType(Message.Type.chat);
try {
StanzaSender.sendStanza(account, receipt);
} catch (NetworkException e) {
diff --git a/xabber/src/main/java/com/xabber/android/data/message/RegularChat.java b/xabber/src/main/java/com/xabber/android/data/message/RegularChat.java
index c9f608588f..05b6d9bcb4 100644
--- a/xabber/src/main/java/com/xabber/android/data/message/RegularChat.java
+++ b/xabber/src/main/java/com/xabber/android/data/message/RegularChat.java
@@ -25,6 +25,7 @@
import com.xabber.android.data.database.messagerealm.MessageItem;
import com.xabber.android.data.entity.AccountJid;
import com.xabber.android.data.entity.UserJid;
+import com.xabber.android.data.extension.chat_markers.ChatMarkerManager;
import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager;
import com.xabber.android.data.extension.muc.MUCManager;
import com.xabber.android.data.extension.otr.OTRManager;
@@ -306,5 +307,4 @@ protected void onComplete() {
super.onComplete();
sendMessages();
}
-
}
diff --git a/xabber/src/main/java/com/xabber/android/data/message/chat/ChatManager.java b/xabber/src/main/java/com/xabber/android/data/message/chat/ChatManager.java
index 2ac2704037..c7111125ca 100644
--- a/xabber/src/main/java/com/xabber/android/data/message/chat/ChatManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/message/chat/ChatManager.java
@@ -510,7 +510,6 @@ public void execute(Realm realm) {
chatRealm = new ChatDataRealm(accountJid, userJid);
chatRealm.setLastPosition(chat.getLastPosition());
- chatRealm.setUnreadCount(chat.getUnreadMessageCount());
chatRealm.setArchived(chat.isArchived());
NotificationStateRealm notificationStateRealm = chatRealm.getNotificationState();
@@ -556,7 +555,6 @@ public ChatData loadChatDataFromRealm(AbstractChat chat) {
realmChat.getSubject(),
realmChat.getAccountJid(),
realmChat.getUserJid(),
- realmChat.getUnreadCount(),
realmChat.isArchived(),
notificationState,
realmChat.getLastPosition());
diff --git a/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationCreator.java b/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationCreator.java
index 3061e80706..5a2ebd9c17 100644
--- a/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationCreator.java
+++ b/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationCreator.java
@@ -19,6 +19,8 @@
import com.xabber.android.R;
import com.xabber.android.data.Application;
import com.xabber.android.data.SettingsManager;
+import com.xabber.android.data.account.AccountItem;
+import com.xabber.android.data.account.AccountManager;
import com.xabber.android.data.entity.AccountJid;
import com.xabber.android.data.entity.UserJid;
import com.xabber.android.data.extension.avatar.AvatarManager;
@@ -71,7 +73,7 @@ public void createNotification(MessageNotificationManager.Chat chat, boolean ale
.setContentIntent(createContentIntent(chat))
.setDeleteIntent(NotificationReceiver.createDeleteIntent(context, chat.getNotificationId()))
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
- .setPriority(inForeground ? NotificationCompat.PRIORITY_DEFAULT
+ .setPriority((inForeground || inGracePeriod(chat)) ? NotificationCompat.PRIORITY_DEFAULT
: NotificationCompat.PRIORITY_HIGH);
boolean showText = isNeedShowTextInNotification(chat);
@@ -85,7 +87,7 @@ public void createNotification(MessageNotificationManager.Chat chat, boolean ale
.setAutoCancel(true);
}
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O && alert)
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O && alert && !inGracePeriod(chat))
addEffects(builder, chat.getLastMessage().getMessageText().toString(), chat, context);
builder.addAction(createMarkAsReadAction(chat.getNotificationId()))
@@ -109,7 +111,7 @@ public void createBundleNotification(List chats
.setContentIntent(createBundleContentIntent())
.setDeleteIntent(NotificationReceiver.createDeleteIntent(context, MESSAGE_BUNDLE_NOTIFICATION_ID))
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
- .setPriority(inForeground ? NotificationCompat.PRIORITY_DEFAULT
+ .setPriority((inForeground || inGracePeriod(lastChat)) ? NotificationCompat.PRIORITY_DEFAULT
: NotificationCompat.PRIORITY_HIGH);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@@ -127,7 +129,7 @@ public void createBundleNotification(List chats
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O && alert) {
MessageNotificationManager.Message lastMessage = lastChat != null ? lastChat.getLastMessage() : null;
- if (lastMessage != null)
+ if (lastMessage != null && alert && !inGracePeriod(lastChat))
addEffects(builder, lastMessage.getMessageText().toString(), lastChat, context);
}
@@ -135,6 +137,9 @@ public void createBundleNotification(List chats
}
private String getChannelID(MessageNotificationManager.Chat chat) {
+ if (inGracePeriod(chat))
+ return NotificationChannelUtils.SILENT_CHANNEL_ID;
+
NotifyPrefs customPrefs = null;
boolean isGroup = false;
if (chat != null) {
@@ -158,6 +163,13 @@ private void sendNotification(NotificationCompat.Builder builder, int notificati
}
/** UTILS */
+ private static boolean inGracePeriod(MessageNotificationManager.Chat chat) {
+ if (chat == null) return false;
+ AccountItem accountItem = AccountManager.getInstance().getAccount(chat.getAccountJid());
+ if (accountItem != null) return accountItem.inGracePeriod();
+ else return false;
+ }
+
private CharSequence createNewMessagesTitle(int messageCount) {
return context.getString(R.string.new_chat_messages, messageCount,
StringUtils.getQuantityString(context.getResources(), R.array.chat_message_quantity, messageCount));
diff --git a/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationManager.java b/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationManager.java
index 1c02d8cd46..ed6dedc77c 100644
--- a/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationManager.java
@@ -3,10 +3,12 @@
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
+import android.os.Handler;
import com.xabber.android.R;
import com.xabber.android.data.Application;
import com.xabber.android.data.OnLoadListener;
+import com.xabber.android.data.account.AccountManager;
import com.xabber.android.data.database.RealmManager;
import com.xabber.android.data.database.messagerealm.Attachment;
import com.xabber.android.data.database.messagerealm.MessageItem;
@@ -25,8 +27,6 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
import java.util.UUID;
import io.realm.Realm;
@@ -114,7 +114,8 @@ public void onNotificationMarkedAsRead(int notificationId) {
AbstractChat chat = MessageManager.getInstance().getChat(
chatNotif.getAccountJid(), chatNotif.getUserJid());
if (chat != null) {
- chat.resetUnreadMessageCount();
+ AccountManager.getInstance().stopGracePeriod(chat.getAccount());
+ chat.markAsReadAll(true);
callUiUpdate();
}
}
@@ -420,7 +421,7 @@ public class Chat {
private CharSequence chatTitle;
private boolean isGroupChat;
private List messages = new ArrayList<>();
- private Timer removeTimer;
+ private Handler removeTimer;
public Chat(AccountJid accountJid, UserJid userJid, int notificationId,
CharSequence chatTitle, boolean isGroupChat) {
@@ -487,23 +488,28 @@ public boolean equals(AccountJid account, UserJid user) {
}
public void startRemoveTimer() {
- stopRemoveTimer();
- removeTimer = new Timer();
- removeTimer.schedule(new TimerTask() {
+ Application.getInstance().runOnUiThread(new Runnable() {
@Override
public void run() {
- Application.getInstance().runOnUiThread(new Runnable() {
+ stopRemoveTimer();
+ removeTimer = new Handler();
+ removeTimer.postDelayed(new Runnable() {
@Override
public void run() {
- removeChat(notificationId);
+ Application.getInstance().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ removeChat(notificationId);
+ }
+ });
}
- });
+ }, 500);
}
- }, 500);
+ });
}
public void stopRemoveTimer() {
- if (removeTimer != null) removeTimer.cancel();
+ if (removeTimer != null) removeTimer.removeCallbacksAndMessages(null);
}
}
diff --git a/xabber/src/main/java/com/xabber/android/data/notification/NotificationChannelUtils.java b/xabber/src/main/java/com/xabber/android/data/notification/NotificationChannelUtils.java
index 5638eea8ce..1ef2be71b5 100644
--- a/xabber/src/main/java/com/xabber/android/data/notification/NotificationChannelUtils.java
+++ b/xabber/src/main/java/com/xabber/android/data/notification/NotificationChannelUtils.java
@@ -27,6 +27,7 @@ public class NotificationChannelUtils {
public static final String DEFAULT_ATTENTION_CHANNEL_ID = "DEFAULT_ATTENTION_CHANNEL_ID";
public static final String PERSISTENT_CONNECTION_CHANNEL_ID = "PERSISTENT_CONNECTION_CHANNEL_ID";
public static final String EVENTS_CHANNEL_ID = "EVENTS_CHANNEL_ID";
+ public static final String SILENT_CHANNEL_ID = "SILENT_CHANNEL_ID";
public enum ChannelType {
privateChat,
@@ -133,6 +134,18 @@ public static String createEventsChannel(NotificationManager notifManager) {
return channel.getId();
}
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ public static String createSilentChannel(NotificationManager notifManager) {
+ @SuppressLint("WrongConstant") NotificationChannel channel =
+ new NotificationChannel(SILENT_CHANNEL_ID,
+ getString(R.string.channel_silent_title),
+ NotificationManager.IMPORTANCE_LOW);
+ channel.setDescription(getString(R.string.channel_silent_description));
+ channel.setShowBadge(true);
+ notifManager.createNotificationChannel(channel);
+ return channel.getId();
+ }
+
private static String getString(@StringRes int resid) {
return Application.getInstance().getString(resid);
}
diff --git a/xabber/src/main/java/com/xabber/android/data/notification/NotificationManager.java b/xabber/src/main/java/com/xabber/android/data/notification/NotificationManager.java
index 4e4dc1d509..da804d1cb5 100644
--- a/xabber/src/main/java/com/xabber/android/data/notification/NotificationManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/notification/NotificationManager.java
@@ -108,6 +108,7 @@ private NotificationManager() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && notificationManager != null) {
NotificationChannelUtils.createPresistentConnectionChannel(notificationManager);
NotificationChannelUtils.createEventsChannel(notificationManager);
+ NotificationChannelUtils.createSilentChannel(notificationManager);
}
handler = new Handler();
diff --git a/xabber/src/main/java/com/xabber/android/data/roster/PresenceManager.java b/xabber/src/main/java/com/xabber/android/data/roster/PresenceManager.java
index 66b7493e82..bec231daff 100644
--- a/xabber/src/main/java/com/xabber/android/data/roster/PresenceManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/roster/PresenceManager.java
@@ -116,6 +116,15 @@ public SubscriptionRequest getSubscriptionRequest(AccountJid account, UserJid us
* @throws NetworkException
*/
public void requestSubscription(AccountJid account, UserJid user) throws NetworkException {
+ requestSubscription(account, user, true);
+ }
+
+ /**
+ * Requests subscription to the contact.
+ * Create chat with new contact if need.
+ * @throws NetworkException
+ */
+ public void requestSubscription(AccountJid account, UserJid user, boolean createChat) throws NetworkException {
Presence packet = new Presence(Presence.Type.subscribe);
packet.setTo(user.getJid());
StanzaSender.sendStanza(account, packet);
@@ -125,7 +134,7 @@ public void requestSubscription(AccountJid account, UserJid user) throws Network
requestedSubscriptions.put(account, set);
}
set.add(user);
- createChatForNewContact(account, user);
+ if (createChat) createChatForNewContact(account, user);
}
private void removeRequestedSubscription(AccountJid account, UserJid user) {
diff --git a/xabber/src/main/java/com/xabber/android/data/xaccount/XMPPAuthManager.java b/xabber/src/main/java/com/xabber/android/data/xaccount/XMPPAuthManager.java
index cccaa4f962..3be199ae28 100644
--- a/xabber/src/main/java/com/xabber/android/data/xaccount/XMPPAuthManager.java
+++ b/xabber/src/main/java/com/xabber/android/data/xaccount/XMPPAuthManager.java
@@ -179,7 +179,7 @@ private void addContactToRoster(String apiJid, String clientJid) {
RosterManager.getInstance().createContact(account, user,
"xabber", Collections.EMPTY_LIST);
- PresenceManager.getInstance().requestSubscription(account, user);
+ PresenceManager.getInstance().requestSubscription(account, user, false);
} catch (UserJid.UserJidCreateException | XmppStringprepException | InterruptedException |
SmackException | NetworkException | XMPPException.XMPPErrorException e) {
diff --git a/xabber/src/main/java/com/xabber/android/presentation/mvp/contactlist/ContactListPresenter.java b/xabber/src/main/java/com/xabber/android/presentation/mvp/contactlist/ContactListPresenter.java
index d831703b19..c2dfe3e619 100644
--- a/xabber/src/main/java/com/xabber/android/presentation/mvp/contactlist/ContactListPresenter.java
+++ b/xabber/src/main/java/com/xabber/android/presentation/mvp/contactlist/ContactListPresenter.java
@@ -23,6 +23,7 @@
import com.xabber.android.data.message.ChatContact;
import com.xabber.android.data.message.CrowdfundingChat;
import com.xabber.android.data.message.MessageManager;
+import com.xabber.android.data.message.MessageUpdateEvent;
import com.xabber.android.data.message.NewMessageEvent;
import com.xabber.android.data.roster.AbstractContact;
import com.xabber.android.data.roster.CrowdfundingContact;
@@ -51,7 +52,6 @@
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
-import org.jxmpp.stringprep.XmppStringprepException;
import java.util.ArrayList;
import java.util.Collection;
@@ -209,6 +209,11 @@ public void onNewMessageEvent(NewMessageEvent event) {
updateBackpressure.refreshRequest();
}
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onEvent(MessageUpdateEvent event) {
+ updateBackpressure.refreshRequest();
+ }
+
@Override
public void update() {
// listener.hidePlaceholder();
@@ -452,6 +457,7 @@ public void update() {
} else view.hidePlaceholder();
view.updateItems(items);
}
+ updateUnreadCount();
}
/**
@@ -465,19 +471,17 @@ private GroupConfiguration getChatsGroup(Collection chats, ChatLis
List newChats = new ArrayList<>();
- int unreadMessageCount = 0;
for (AbstractChat abstractChat : chats) {
MessageItem lastMessage = abstractChat.getLastMessage();
if (lastMessage != null) {
AccountItem accountItem = AccountManager.getInstance().getAccount(abstractChat.getAccount());
if (accountItem != null && accountItem.isEnabled()) {
- int unread = abstractChat.getUnreadMessageCount();
- if (abstractChat.notifyAboutMessage()) unreadMessageCount = unreadMessageCount + unread;
switch (state) {
case unread:
- if (!abstractChat.isArchived() && unread > 0) newChats.add(abstractChat);
+ if (!abstractChat.isArchived() && abstractChat.getUnreadMessageCount() > 0)
+ newChats.add(abstractChat);
break;
case archived:
if (abstractChat.isArchived()) newChats.add(abstractChat);
@@ -508,9 +512,7 @@ private GroupConfiguration getChatsGroup(Collection chats, ChatLis
break;
}
}
- unreadMessageCount += unreadCount;
- EventBus.getDefault().post(new UpdateUnreadCountEvent(unreadMessageCount));
Collections.sort(newChats, ChatComparator.CHAT_COMPARATOR);
chatsGroup.setNotEmpty();
@@ -652,6 +654,18 @@ public ArrayList getTwoNextRecentChat() {
return items;
}
+ public void updateUnreadCount() {
+ int unreadMessageCount = 0;
+
+ for (AbstractChat abstractChat : MessageManager.getInstance().getChatsOfEnabledAccount()) {
+ if (abstractChat.notifyAboutMessage() && !abstractChat.isArchived())
+ unreadMessageCount += abstractChat.getUnreadMessageCount();
+ }
+
+ unreadMessageCount += CrowdfundingManager.getInstance().getUnreadMessageCount();
+ EventBus.getDefault().post(new UpdateUnreadCountEvent(unreadMessageCount));
+ }
+
public enum ChatListState {
recent,
unread,
diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/ContactListFragment.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/ContactListFragment.java
index d9603df4fa..5381f7883e 100644
--- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/ContactListFragment.java
+++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/ContactListFragment.java
@@ -268,6 +268,9 @@ public void onItemSwipe(int position, int direction) {
// showing snackbar with Undo option
showSnackbar(deletedItem, position);
+
+ // update unread count
+ presenter.updateUnreadCount();
}
}
@@ -381,6 +384,9 @@ public void onClick(View view) {
// undo is selected, restore the deleted item
adapter.addItem(deletedIndex, deletedItem);
+
+ // update unread count
+ presenter.updateUnreadCount();
}
});
snackbar.setActionTextColor(Color.YELLOW);
diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ContactVO.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ContactVO.java
index 4304b79ad8..65ffddbf6d 100644
--- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ContactVO.java
+++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ContactVO.java
@@ -32,6 +32,7 @@
import com.xabber.android.data.extension.muc.RoomChat;
import com.xabber.android.data.filedownload.FileCategory;
import com.xabber.android.data.message.AbstractChat;
+import com.xabber.android.data.message.ChatAction;
import com.xabber.android.data.message.MessageManager;
import com.xabber.android.data.message.NotificationState;
import com.xabber.android.data.notification.custom_notification.CustomNotifyPrefsManager;
@@ -184,6 +185,8 @@ public static ContactVO convert(AbstractContact contact, ContactClickListener li
messageText = FileCategory.getCategoryName(category, true) + attachment.getTitle();
} else if (lastMessage.getFilePath() != null) {
messageText = new File(lastMessage.getFilePath()).getName();
+ } else if (ChatAction.available.toString().equals(lastMessage.getAction())) {
+ messageText = "" + lastMessage.getText().trim() + "";
} else {
messageText = lastMessage.getText().trim();
}
@@ -197,18 +200,17 @@ public static ContactVO convert(AbstractContact contact, ContactClickListener li
messageOwner = lastMessage.getResource().toString();
// message status
- if (lastMessage.isForwarded()) {
+ if (lastMessage.isError()) {
+ messageStatus = 4;
+ } else if (!MessageItem.isUploadFileMessage(lastMessage) && !lastMessage.isSent()
+ && System.currentTimeMillis() - lastMessage.getTimestamp() > 1000) {
+ messageStatus = 5;
+ } else if (lastMessage.isDisplayed() || lastMessage.isReceivedFromMessageArchive()) {
messageStatus = 1;
- } else if (lastMessage.isReceivedFromMessageArchive()) {
+ } else if (lastMessage.isDelivered() || lastMessage.isForwarded()) {
messageStatus = 2;
- } else if (lastMessage.isError()) {
+ } else if (lastMessage.isAcknowledged()) {
messageStatus = 3;
- } else if (!lastMessage.isDelivered()) {
- if (lastMessage.isAcknowledged()) {
- messageStatus = 4;
- } else {
- messageStatus = 5;
- }
}
// forwarded
diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ExtContactVO.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ExtContactVO.java
index 47ddc972ac..f7a79371ca 100644
--- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ExtContactVO.java
+++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ExtContactVO.java
@@ -110,14 +110,18 @@ public void bindViewHolder(FlexibleAdapter adapter, ViewHolder viewHolder, int p
viewHolder.ivMessageStatus.setVisibility(text.isEmpty() ? View.INVISIBLE : View.VISIBLE);
switch (getMessageStatus()) {
- case 0:
+
+ case 1:
+ viewHolder.ivMessageStatus.setImageResource(R.drawable.ic_message_displayed);
+ break;
+ case 2:
viewHolder.ivMessageStatus.setImageResource(R.drawable.ic_message_delivered_14dp);
break;
case 3:
- viewHolder.ivMessageStatus.setImageResource(R.drawable.ic_message_has_error_14dp);
+ viewHolder.ivMessageStatus.setImageResource(R.drawable.ic_message_acknowledged_14dp);
break;
case 4:
- viewHolder.ivMessageStatus.setImageResource(R.drawable.ic_message_acknowledged_14dp);
+ viewHolder.ivMessageStatus.setImageResource(R.drawable.ic_message_has_error_14dp);
break;
default:
viewHolder.ivMessageStatus.setVisibility(View.GONE);
diff --git a/xabber/src/main/java/com/xabber/android/service/UploadService.java b/xabber/src/main/java/com/xabber/android/service/UploadService.java
index 289a0c5ab2..ebee560278 100644
--- a/xabber/src/main/java/com/xabber/android/service/UploadService.java
+++ b/xabber/src/main/java/com/xabber/android/service/UploadService.java
@@ -17,6 +17,7 @@
import com.xabber.android.data.entity.UserJid;
import com.xabber.android.data.extension.file.FileManager;
import com.xabber.android.data.extension.file.FileUtils;
+import com.xabber.android.data.extension.file.UriUtils;
import com.xabber.android.data.extension.httpfileupload.ImageCompressor;
import com.xabber.android.data.log.LogManager;
import com.xabber.android.data.message.MessageManager;
@@ -115,7 +116,10 @@ private void startWorkWithUris(AccountJid account, UserJid user, List fileU
}
// create message with progress
- String messageId = MessageManager.getInstance().createFileMessage(account, user, files);
+ String messageId;
+ if (remoteFiles.isEmpty())
+ messageId = MessageManager.getInstance().createFileMessage(account, user, files);
+ else messageId = MessageManager.getInstance().createFileMessageFromUris(account, user, remoteFiles);
// create dir
File directory = new File(getDownloadDirPath());
@@ -127,30 +131,31 @@ private void startWorkWithUris(AccountJid account, UserJid user, List fileU
// get files from uri's
List copiedFiles = new ArrayList<>();
- for (Uri uri : remoteFiles) {
- if (needStop) {
- stopWork(messageId);
- return;
- }
+ if (!remoteFiles.isEmpty()) {
+ for (Uri uri : remoteFiles) {
+ if (needStop) {
+ stopWork(messageId);
+ return;
+ }
- // copy file to local storage if need
- try {
- copiedFiles.add(new File(copyFileToLocalStorage(uri)));
- } catch (IOException e) {
- publishError(messageId, "Cannot get file: " + e.toString());
+ // copy file to local storage if need
+ try {
+ copiedFiles.add(new File(copyFileToLocalStorage(uri)));
+ } catch (IOException e) {
+ publishError(messageId, "Cannot get file: " + e.toString());
+ }
+ publishProgress(messageId, copiedFiles.size(), remoteFiles.size());
}
- publishProgress(messageId, copiedFiles.size(), remoteFiles.size());
}
// add attachments to message
- MessageManager.getInstance().updateMessageWithNewAttachments(messageId, copiedFiles);
+ files.addAll(copiedFiles);
+ MessageManager.getInstance().updateMessageWithNewAttachments(messageId, files);
// startWork for upload files
List filePaths = new ArrayList<>();
for (File file : files)
filePaths.add(file.getPath());
- for (File file : copiedFiles)
- filePaths.add(file.getPath());
startWork(account, user, filePaths, uploadServerUrl, messageId);
}
@@ -341,11 +346,7 @@ private String generateErrorDescriptionForFiles(List files, List
}
private String copyFileToLocalStorage(Uri uri) throws IOException {
- String extension = getExtensionFromUri(uri);
- String name = getFileName(uri);
- if (name == null) name = UUID.randomUUID().toString();
- else name = name.replace(".", "");
- String fileName = name + "." + extension;
+ String fileName = UriUtils.getFullFileName(uri);
File file = new File(getDownloadDirPath(), fileName);
OutputStream os = null;
@@ -371,31 +372,4 @@ private String copyFileToLocalStorage(Uri uri) throws IOException {
}
return file.getPath();
}
-
- private String getExtensionFromUri(Uri uri) {
- String mimeType = getContentResolver().getType(uri);
- return MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
- }
-
- private String getFileName(Uri uri) {
- String result = null;
- if (uri.getScheme().equals("content")) {
- Cursor cursor = getContentResolver().query(uri, null, null, null, null);
- try {
- if (cursor != null && cursor.moveToFirst()) {
- result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
- }
- } finally {
- cursor.close();
- }
- }
- if (result == null) {
- result = uri.getPath();
- int cut = result.lastIndexOf('/');
- if (cut != -1) {
- result = result.substring(cut + 1);
- }
- }
- return FilenameUtils.getBaseName(result);
- }
}
diff --git a/xabber/src/main/java/com/xabber/android/ui/activity/ChatActivity.java b/xabber/src/main/java/com/xabber/android/ui/activity/ChatActivity.java
index 888ec7656b..7c8e5c9906 100644
--- a/xabber/src/main/java/com/xabber/android/ui/activity/ChatActivity.java
+++ b/xabber/src/main/java/com/xabber/android/ui/activity/ChatActivity.java
@@ -52,7 +52,6 @@
import com.xabber.android.data.extension.attention.AttentionManager;
import com.xabber.android.data.extension.blocking.BlockingManager;
import com.xabber.android.data.extension.blocking.OnBlockedListChangedListener;
-import com.xabber.android.data.extension.file.FileUtils;
import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager;
import com.xabber.android.data.extension.muc.MUCManager;
import com.xabber.android.data.extension.muc.RoomChat;
@@ -62,6 +61,7 @@
import com.xabber.android.data.message.AbstractChat;
import com.xabber.android.data.message.CrowdfundingChat;
import com.xabber.android.data.message.MessageManager;
+import com.xabber.android.data.message.MessageUpdateEvent;
import com.xabber.android.data.message.NewMessageEvent;
import com.xabber.android.data.message.NotificationState;
import com.xabber.android.data.message.RegularChat;
@@ -280,6 +280,14 @@ public static Intent createSendUriIntent(Context context, AccountJid account,
return intent;
}
+ public static Intent createSendUrisIntent(Context context, AccountJid account,
+ UserJid user, ArrayList uris) {
+ Intent intent = ChatActivity.createSpecificChatIntent(context, account, user);
+ intent.setAction(Intent.ACTION_SEND_MULTIPLE);
+ intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
+ return intent;
+ }
+
public static Intent createAttentionRequestIntent(Context context, AccountJid account, UserJid user) {
Intent intent = ChatActivity.createClearTopIntent(context, account, user);
intent.setAction(ACTION_ATTENTION);
@@ -372,15 +380,23 @@ protected void onResume() {
&& intent.getParcelableExtra(Intent.EXTRA_STREAM) != null) {
Uri receivedUri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
+ intent.removeExtra(Intent.EXTRA_STREAM);
handleShareFileUri(receivedUri);
} else if (Intent.ACTION_SEND.equals(intent.getAction())) {
- extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
+ extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
if (extraText != null) {
intent.removeExtra(Intent.EXTRA_TEXT);
exitOnSend = true;
}
+
+ } else if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
+ ArrayList uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ if (uris != null) {
+ intent.removeExtra(Intent.EXTRA_STREAM);
+ handleShareFileUris(uris);
+ }
}
needScrollToUnread = intent.getBooleanExtra(EXTRA_NEED_SCROLL_TO_UNREAD, false);
@@ -404,19 +420,26 @@ protected void onResume() {
}
public void handleShareFileUri(Uri fileUri) {
- final String path = FileUtils.getPath(this, fileUri);
-
- LogManager.i(this, String.format("File uri: %s, path: %s", fileUri, path));
+ if (PermissionsRequester.requestFileReadPermissionIfNeeded(this, PERMISSIONS_REQUEST_ATTACH_FILE)) {
+ List uris = new ArrayList<>();
+ uris.add(fileUri);
+ HttpFileUploadManager.getInstance().uploadFileViaUri(account, user, uris, this);
+ }
+ }
- if (path == null) {
+ public void handleShareFileUris(ArrayList uris) {
+ if (uris.size() == 0) {
Toast.makeText(this, R.string.could_not_get_path_to_file, Toast.LENGTH_SHORT).show();
return;
}
+ if (uris.size() > 10) {
+ Toast.makeText(this, R.string.too_many_files_at_once, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
if (PermissionsRequester.requestFileReadPermissionIfNeeded(this, PERMISSIONS_REQUEST_ATTACH_FILE)) {
- List paths = new ArrayList<>();
- paths.add(path);
- HttpFileUploadManager.getInstance().uploadFile(account, user, paths, this);
+ HttpFileUploadManager.getInstance().uploadFileViaUri(account, user, uris, this);
}
}
@@ -499,6 +522,9 @@ private void getSelectedPageDataFromIntent() {
case Intent.ACTION_SEND:
selectedPagePosition = ChatViewerAdapter.PAGE_POSITION_CHAT;
break;
+ case Intent.ACTION_SEND_MULTIPLE:
+ selectedPagePosition = ChatViewerAdapter.PAGE_POSITION_CHAT;
+ break;
case ACTION_FORWARD:
selectedPagePosition = ChatViewerAdapter.PAGE_POSITION_CHAT;
break;
@@ -579,6 +605,11 @@ public void onNewMessageEvent(NewMessageEvent event) {
updateBackpressure.refreshRequest();
}
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onEvent(MessageUpdateEvent event) {
+ updateBackpressure.refreshRequest();
+ }
+
@Override
public void onContactsChanged(Collection entities) {
updateBackpressure.refreshRequest();
diff --git a/xabber/src/main/java/com/xabber/android/ui/activity/ContactAddActivity.java b/xabber/src/main/java/com/xabber/android/ui/activity/ContactAddActivity.java
index 4f20a70580..3d8a4b425f 100644
--- a/xabber/src/main/java/com/xabber/android/ui/activity/ContactAddActivity.java
+++ b/xabber/src/main/java/com/xabber/android/ui/activity/ContactAddActivity.java
@@ -21,6 +21,8 @@
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.View;
+import android.widget.ProgressBar;
import com.xabber.android.R;
import com.xabber.android.data.entity.AccountJid;
@@ -33,7 +35,9 @@
public class ContactAddActivity extends ManagedActivity implements ContactAddFragment.Listener {
- BarPainter barPainter;
+ private BarPainter barPainter;
+ private ProgressBar progressBar;
+ private Toolbar toolbar;
public static Intent createIntent(Context context) {
return createIntent(context, null);
@@ -59,9 +63,9 @@ private static UserJid getUser(Intent intent) {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_with_toolbar_and_container);
+ setContentView(R.layout.activity_with_toolbar_progress_and_container);
- Toolbar toolbar = ToolbarHelper.setUpDefaultToolbar(this, null, R.drawable.ic_clear_white_24dp);
+ toolbar = ToolbarHelper.setUpDefaultToolbar(this, null, R.drawable.ic_clear_white_24dp);
toolbar.inflateMenu(R.menu.toolbar_add_contact);
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
@@ -70,6 +74,7 @@ public boolean onMenuItemClick(MenuItem item) {
}
});
+ progressBar = findViewById(R.id.toolbarProgress);
barPainter = new BarPainter(this, toolbar);
barPainter.setDefaultColor();
@@ -112,4 +117,12 @@ public boolean onOptionsItemSelected(MenuItem item) {
public void onAccountSelected(AccountJid account) {
barPainter.updateWithAccountName(account);
}
+
+ @Override
+ public void showProgress(boolean show) {
+ if (progressBar != null) {
+ progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
+ toolbar.getMenu().findItem(R.id.action_add_contact).setVisible(!show);
+ }
+ }
}
diff --git a/xabber/src/main/java/com/xabber/android/ui/activity/ContactListActivity.java b/xabber/src/main/java/com/xabber/android/ui/activity/ContactListActivity.java
index 1e54e709cf..f3e5ed6700 100644
--- a/xabber/src/main/java/com/xabber/android/ui/activity/ContactListActivity.java
+++ b/xabber/src/main/java/com/xabber/android/ui/activity/ContactListActivity.java
@@ -192,6 +192,7 @@ private static UserJid getRoomInviteUser(Intent intent) {
public void onCreate(Bundle savedInstanceState) {
if (Intent.ACTION_VIEW.equals(getIntent().getAction())
|| Intent.ACTION_SEND.equals(getIntent().getAction())
+ || Intent.ACTION_SEND_MULTIPLE.equals(getIntent().getAction())
|| Intent.ACTION_SENDTO.equals(getIntent().getAction())
|| Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) {
ActivityManager.getInstance().startNewTask(this);
@@ -342,6 +343,8 @@ protected void onResume() {
switch (action) {
case ContactListActivity.ACTION_ROOM_INVITE:
case Intent.ACTION_SEND:
+ case Intent.ACTION_SEND_MULTIPLE:
+ case ChatActivity.ACTION_FORWARD:
case Intent.ACTION_CREATE_SHORTCUT:
if (Intent.ACTION_SEND.equals(action)) {
sendText = getIntent().getStringExtra(Intent.EXTRA_TEXT);
@@ -629,6 +632,15 @@ public void onContactClick(AbstractContact abstractContact) {
finish();
}
break;
+ case Intent.ACTION_SEND_MULTIPLE:
+ if (getIntent().getExtras() != null) {
+ action = null;
+ startActivity(ChatActivity.createSendUrisIntent(this,
+ abstractContact.getAccount(), abstractContact.getUser(),
+ getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM)));
+ finish();
+ }
+ break;
case Intent.ACTION_CREATE_SHORTCUT: {
createShortcut(abstractContact);
finish();
diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/CustomMessageMenuAdapter.java b/xabber/src/main/java/com/xabber/android/ui/adapter/CustomMessageMenuAdapter.java
index 8160b0b6fc..a3045a75d3 100644
--- a/xabber/src/main/java/com/xabber/android/ui/adapter/CustomMessageMenuAdapter.java
+++ b/xabber/src/main/java/com/xabber/android/ui/adapter/CustomMessageMenuAdapter.java
@@ -22,6 +22,7 @@ public class CustomMessageMenuAdapter extends BaseAdapter {
public final static String STATUS_ACK = "status_acknowledged";
public final static String STATUS_DELIVERED = "status_delivered";
+ public final static String STATUS_DISPLAYED = "status_displayed";
public final static String STATUS_ERROR = "status_error";
public final static String STATUS_FORWARDED = "status_forwarded";
public final static String STATUS_SYNCED = "status_synced";
@@ -75,6 +76,10 @@ public View getView(int position, View convertView, ViewGroup parent) {
textView.setText(R.string.message_status_delivered);
ivStatus.setImageResource(R.drawable.ic_message_delivered_14dp);
break;
+ case STATUS_DISPLAYED:
+ textView.setText(R.string.message_status_displayed);
+ ivStatus.setImageResource(R.drawable.ic_message_displayed);
+ break;
case STATUS_SYNCED:
textView.setText(R.string.message_status_synced);
ivStatus.setImageResource(R.drawable.ic_message_synced_14dp);
diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/IncomingMessageVH.java b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/IncomingMessageVH.java
index 94f31b43c0..cf9724bfce 100644
--- a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/IncomingMessageVH.java
+++ b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/IncomingMessageVH.java
@@ -25,23 +25,30 @@ public class IncomingMessageVH extends FileMessageVH {
public ImageView avatar;
public ImageView avatarBackground;
+ private BindListener listener;
+
+ public interface BindListener {
+ void onBind(MessageItem message);
+ }
IncomingMessageVH(View itemView, MessageClickListener messageListener,
MessageLongClickListener longClickListener,
- FileListener fileListener, @StyleRes int appearance) {
+ FileListener fileListener, BindListener listener, @StyleRes int appearance) {
super(itemView, messageListener, longClickListener, fileListener, appearance);
avatar = itemView.findViewById(R.id.avatar);
avatarBackground = itemView.findViewById(R.id.avatarBackground);
+ this.listener = listener;
}
- public void bind(MessageItem messageItem, MessagesAdapter.MessageExtraData extraData) {
+ public void bind(final MessageItem messageItem, MessagesAdapter.MessageExtraData extraData) {
super.bind(messageItem, extraData);
Context context = extraData.getContext();
boolean needTail = extraData.isNeedTail();
// setup ARCHIVED icon
- statusIcon.setVisibility(messageItem.isReceivedFromMessageArchive() ? View.VISIBLE : View.GONE);
+ //statusIcon.setVisibility(messageItem.isReceivedFromMessageArchive() ? View.VISIBLE : View.GONE);
+ statusIcon.setVisibility(View.GONE);
// setup FORWARDED
boolean haveForwarded = messageItem.haveForwardedMessages();
@@ -105,6 +112,18 @@ public void bind(MessageItem messageItem, MessagesAdapter.MessageExtraData extra
messageBalloon.setVisibility(View.VISIBLE);
messageTime.setVisibility(View.VISIBLE);
}
+
+ itemView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ if (listener != null) listener.onBind(messageItem);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ unsubscribeAll();
+ }
+ });
}
private void setUpAvatar(MessageItem messageItem, boolean isMUC, String userName, boolean needTail) {
diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/MessagesAdapter.java b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/MessagesAdapter.java
index 842fb01ac2..19c875d5ce 100644
--- a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/MessagesAdapter.java
+++ b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/MessagesAdapter.java
@@ -47,6 +47,7 @@ public class MessagesAdapter extends RealmRecyclerViewAdapter itemsNeedOriginalText = new ArrayList<>();
@@ -78,7 +79,7 @@ public MessagesAdapter(
Context context, RealmResults messageItems,
AbstractChat chat, MessageVH.MessageClickListener messageListener,
FileMessageVH.FileListener fileListener, ForwardedAdapter.ForwardListener fwdListener,
- Listener listener, AnchorHolder anchorHolder) {
+ Listener listener, IncomingMessageVH.BindListener bindListener, AnchorHolder anchorHolder) {
super(context, messageItems, true);
this.context = context;
@@ -87,6 +88,7 @@ public MessagesAdapter(
this.fwdListener = fwdListener;
this.listener = listener;
this.anchorHolder = anchorHolder;
+ this.bindListener = bindListener;
account = chat.getAccount();
user = chat.getUser();
@@ -135,12 +137,12 @@ public BasicMessageVH onCreateViewHolder(ViewGroup parent, int viewType) {
case VIEW_TYPE_INCOMING_MESSAGE:
return new IncomingMessageVH(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_message_incoming, parent, false),
- this, this, this, appearanceStyle);
+ this, this, this, bindListener, appearanceStyle);
case VIEW_TYPE_INCOMING_MESSAGE_NOFLEX:
return new NoFlexIncomingMsgVH(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_message_incoming_noflex, parent, false),
- this, this, this, appearanceStyle);
+ this, this, this, bindListener, appearanceStyle);
case VIEW_TYPE_OUTGOING_MESSAGE:
return new OutgoingMessageVH(LayoutInflater.from(parent.getContext())
@@ -173,7 +175,7 @@ public void onBindViewHolder(final BasicMessageVH holder, int position) {
((MessageVH)holder).messageId = messageItem.getUniqueId();
// setup message as unread
- boolean unread = position == getItemCount() - unreadCount;
+ boolean unread = messageItem.getUniqueId().equals(firstUnreadMessageID);
// setup message as checked
boolean checked = checkedItemIds.contains(messageItem.getUniqueId());
@@ -265,15 +267,8 @@ public int findMessagePosition(String uniqueId) {
return RecyclerView.NO_POSITION;
}
- public boolean setUnreadCount(int unreadCount) {
- if (this.unreadCount != unreadCount) {
- this.unreadCount = unreadCount;
- return true;
- } else return false;
- }
-
- public int getUnreadCount() {
- return unreadCount;
+ public void setFirstUnreadMessageId(String id) {
+ firstUnreadMessageID = id;
}
public void addOrRemoveItemNeedOriginalText(String messageId) {
diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/NoFlexIncomingMsgVH.java b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/NoFlexIncomingMsgVH.java
index 8723e45230..e14c120aa6 100644
--- a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/NoFlexIncomingMsgVH.java
+++ b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/NoFlexIncomingMsgVH.java
@@ -8,8 +8,8 @@ public class NoFlexIncomingMsgVH extends IncomingMessageVH {
public NoFlexIncomingMsgVH(View itemView, MessageClickListener messageListener,
MessageLongClickListener longClickListener,
- FileListener fileListener, int appearance) {
- super(itemView, messageListener, longClickListener, fileListener, appearance);
+ FileListener fileListener, BindListener listener, int appearance) {
+ super(itemView, messageListener, longClickListener, fileListener, listener, appearance);
}
@Override
diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/OutgoingMessageVH.java b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/OutgoingMessageVH.java
index b5aa1678cb..12dcdde180 100644
--- a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/OutgoingMessageVH.java
+++ b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/OutgoingMessageVH.java
@@ -105,21 +105,22 @@ private void setStatusIcon(MessageItem messageItem) {
if (isFileUploadInProgress)
progressBar.setVisibility(View.VISIBLE);
- int messageIcon = R.drawable.ic_message_delivered_14dp;
- if (messageItem.isForwarded()) {
- messageIcon = R.drawable.ic_message_forwarded_14dp;
- } else if (messageItem.isReceivedFromMessageArchive()) {
- messageIcon = R.drawable.ic_message_synced_14dp;
- } else if (messageItem.isError()) {
+ int messageIcon = 0;
+
+ if (messageItem.isError()) {
messageIcon = R.drawable.ic_message_has_error_14dp;
} else if (!isFileUploadInProgress && !messageItem.isSent()
&& System.currentTimeMillis() - messageItem.getTimestamp() > 1000) {
messageIcon = R.drawable.ic_message_not_sent_14dp;
- } else if (!messageItem.isDelivered()) {
- if (messageItem.isAcknowledged())
- messageIcon = R.drawable.ic_message_acknowledged_14dp;
- else statusIcon.setVisibility(View.GONE);
+ } else if (messageItem.isDisplayed() || messageItem.isReceivedFromMessageArchive()) {
+ messageIcon = R.drawable.ic_message_displayed;
+ } else if (messageItem.isDelivered() || messageItem.isForwarded()) {
+ messageIcon = R.drawable.ic_message_delivered_14dp;
+ } else if (messageItem.isAcknowledged()) {
+ messageIcon = R.drawable.ic_message_acknowledged_14dp;
}
- statusIcon.setImageResource(messageIcon);
+
+ if (messageIcon != 0) statusIcon.setImageResource(messageIcon);
+ else statusIcon.setVisibility(View.INVISIBLE);
}
}
diff --git a/xabber/src/main/java/com/xabber/android/ui/fragment/AccountAddFragment.java b/xabber/src/main/java/com/xabber/android/ui/fragment/AccountAddFragment.java
index 3322b697b4..796ec08880 100644
--- a/xabber/src/main/java/com/xabber/android/ui/fragment/AccountAddFragment.java
+++ b/xabber/src/main/java/com/xabber/android/ui/fragment/AccountAddFragment.java
@@ -97,7 +97,7 @@ public void addAccount() {
AccountJid account;
try {
account = AccountManager.getInstance().addAccount(
- userView.getText().toString().trim(),
+ userView.getText().toString().replace(" ", ""),
passwordView.getText().toString(),
"",
false,
diff --git a/xabber/src/main/java/com/xabber/android/ui/fragment/ChatFragment.java b/xabber/src/main/java/com/xabber/android/ui/fragment/ChatFragment.java
index 3237d7d02e..27f3ed01e8 100644
--- a/xabber/src/main/java/com/xabber/android/ui/fragment/ChatFragment.java
+++ b/xabber/src/main/java/com/xabber/android/ui/fragment/ChatFragment.java
@@ -76,6 +76,7 @@
import com.xabber.android.data.message.MessageManager;
import com.xabber.android.data.message.MessageUpdateEvent;
import com.xabber.android.data.message.NewIncomingMessageEvent;
+import com.xabber.android.data.message.NewMessageEvent;
import com.xabber.android.data.message.RegularChat;
import com.xabber.android.data.message.chat.ChatManager;
import com.xabber.android.data.notification.NotificationManager;
@@ -86,6 +87,7 @@
import com.xabber.android.ui.activity.QuestionActivity;
import com.xabber.android.ui.adapter.CustomMessageMenuAdapter;
import com.xabber.android.ui.adapter.ResourceAdapter;
+import com.xabber.android.ui.adapter.chat.IncomingMessageVH;
import com.xabber.android.ui.adapter.chat.MessageVH;
import com.xabber.android.ui.adapter.chat.MessagesAdapter;
import com.xabber.android.ui.color.ColorManager;
@@ -122,7 +124,8 @@
public class ChatFragment extends FileInteractionFragment implements PopupMenu.OnMenuItemClickListener,
View.OnClickListener, Toolbar.OnMenuItemClickListener, MessageVH.MessageClickListener,
MessagesAdapter.Listener, AdapterView.OnItemClickListener, PopupWindow.OnDismissListener,
- OnAccountChangedListener, ForwardPanel.OnCloseListener, MessagesAdapter.AnchorHolder {
+ OnAccountChangedListener, ForwardPanel.OnCloseListener, MessagesAdapter.AnchorHolder,
+ IncomingMessageVH.BindListener {
public static final String ARGUMENT_ACCOUNT = "ARGUMENT_ACCOUNT";
public static final String ARGUMENT_USER = "ARGUMENT_USER";
@@ -339,7 +342,6 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
}
showScrollDownButtonIfNeed();
- hideUnreadMessageCountIfNeed();
/** Necessary for
* @see MessageVH#bind ()
@@ -399,7 +401,8 @@ public void setChat(AccountJid accountJid, UserJid userJid) {
}
chatMessageAdapter = new MessagesAdapter(getActivity(), messageItems, abstractChat,
- this, this, this, this, this);
+ this, this, this, this, this,
+ this);
realmRecyclerView.setAdapter(chatMessageAdapter);
restoreInputState();
@@ -731,7 +734,10 @@ public void onEvent(PreviousHistoryLoadFinishedEvent event) {
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(MessageUpdateEvent event) {
- chatMessageAdapter.onChange();
+ if (account.equals(event.getAccount()) && user.equals(event.getUser())) {
+ updateUnread();
+ chatMessageAdapter.onChange();
+ }
}
@Subscribe(threadMode = ThreadMode.MAIN)
@@ -739,11 +745,14 @@ public void onEvent(NewIncomingMessageEvent event) {
if (event.getAccount().equals(account) && event.getUser().equals(user)) {
listener.playIncomingAnimation();
//playIncomingSound();
- increaseUnreadMessageCountIfNeed();
- chatMessageAdapter.setUnreadCount(chatMessageAdapter.getUnreadCount() + 1);
}
}
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onEvent(NewMessageEvent event) {
+ updateUnread();
+ }
+
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(AuthAskEvent event) {
if (event.getAccount() == getAccount() && event.getUser() == getUser()) {
@@ -858,7 +867,7 @@ private void sendMessage() {
private void sendMessage(String text) {
MessageManager.getInstance().sendMessage(account, user, text);
- hideUnreadMessageBackground();
+ setFirstUnreadMessageId(null);
scrollDown();
}
@@ -868,15 +877,27 @@ public void updateContact() {
updateSendButtonSecurityLevel();
}
+ private void onScrollDownClick() {
+ AbstractChat chat = getChat();
+ if (chat != null) {
+ int unread = chat.getUnreadMessageCount();
+ int lastVisiblePosition = layoutManager.findLastVisibleItemPosition();
+ if (unread == 0 || lastVisiblePosition + 2 >= chatMessageAdapter.getItemCount() - unread) {
+ // scroll down
+ scrollDown();
+
+ // scroll to unread
+ } else scrollToFirstUnread(unread);
+ }
+ }
+
private void scrollDown() {
- LogManager.i(this, "scrollDown");
realmRecyclerView.scrollToPosition(chatMessageAdapter.getItemCount() - 1);
}
private void scrollToFirstUnread(int unreadCount) {
layoutManager.scrollToPositionWithOffset(
chatMessageAdapter.getItemCount() - unreadCount, 200);
- showUnreadMessage(unreadCount);
}
private void updateSecurityButton() {
@@ -1072,10 +1093,7 @@ public void onClick(View v) {
showJoinButtonIfNeed();
}
if (v.getId() == R.id.btnScrollDown) {
- AbstractChat chat = getChat();
- if (chat != null && chat.getUnreadMessageCount() > 0)
- scrollToFirstUnread(chat.getUnreadMessageCount());
- else scrollDown();
+ onScrollDownClick();
}
}
@@ -1165,14 +1183,12 @@ public void showCustomMenu(View anchor) {
CustomMessageMenu.addMenuItem(menuItems, "action_message_show_original_otr", getString(R.string.message_otr_show_original));
}
- if (clickedMessageItem.isForwarded()) {
- CustomMessageMenu.addMenuItem(menuItems, "action_message_status", CustomMessageMenuAdapter.STATUS_FORWARDED);
- } else if (clickedMessageItem.isReceivedFromMessageArchive()) {
- CustomMessageMenu.addMenuItem(menuItems, "action_message_status", CustomMessageMenuAdapter.STATUS_SYNCED);
- } else if (clickedMessageItem.isError()) {
+ if (clickedMessageItem.isError()) {
CustomMessageMenu.addMenuItem(menuItems, "action_message_status", CustomMessageMenuAdapter.STATUS_ERROR);
} else if (!clickedMessageItem.isSent()) {
CustomMessageMenu.addMenuItem(menuItems, "action_message_status", CustomMessageMenuAdapter.STATUS_NOT_SEND);
+ } else if (clickedMessageItem.isDisplayed()) {
+ CustomMessageMenu.addMenuItem(menuItems, "action_message_status", CustomMessageMenuAdapter.STATUS_DISPLAYED);
} else if (clickedMessageItem.isDelivered()) {
CustomMessageMenu.addMenuItem(menuItems, "action_message_status", CustomMessageMenuAdapter.STATUS_DELIVERED);
} else if (clickedMessageItem.isAcknowledged()) {
@@ -1304,18 +1320,15 @@ public void saveScrollState() {
public void restoreScrollState(boolean fromNotification) {
AbstractChat chat = getChat();
- int position;
- int unread;
if (chat != null) {
- position = chat.getLastPosition();
- unread = chat.getUnreadMessageCount();
+ int position = chat.getLastPosition();
+ int unread = chat.getUnreadMessageCount();
if ((position == 0 || fromNotification) && unread > 0)
scrollToFirstUnread(unread);
- else if (position > 0) {
+ else if (position > 0)
layoutManager.scrollToPosition(position);
- showUnreadMessage(unread);
- updateNewReceivedMessageCounter(unread);
- }
+ setFirstUnreadMessageId(chat.getFirstUnreadMessageId());
+ updateNewReceivedMessageCounter(unread);
}
}
@@ -1435,42 +1448,28 @@ private void updateTopDateIfNeed() {
tvTopDate.setText(StringUtils.getDateStringForMessage(message.getTimestamp()));
}
+ @Override
+ public void onBind(MessageItem message) {
+ if (message != null && message.isValid() && !message.isRead()) {
+ AbstractChat chat = getChat();
+ if (chat != null) chat.markAsRead(message, true);
+ }
+ }
+
+ private void updateUnread() {
+ AbstractChat chat = getChat();
+ if (chat != null) updateNewReceivedMessageCounter(chat.getUnreadMessageCount());
+ }
+
private void showScrollDownButtonIfNeed() {
int pastVisibleItems = layoutManager.findLastVisibleItemPosition();
boolean isBottom = pastVisibleItems >= chatMessageAdapter.getItemCount() - 1;
if (isBottom) {
btnScrollDown.setVisibility(View.GONE);
- hideUnreadMessageBackground();
} else btnScrollDown.setVisibility(View.VISIBLE);
}
- private void hideUnreadMessageCountIfNeed() {
- AbstractChat chat = getChat();
- if (chat == null) return;
- int pastVisibleItems = layoutManager.findLastVisibleItemPosition();
- if (pastVisibleItems >= chatMessageAdapter.getItemCount() - chat.getUnreadMessageCount()) {
- resetUnreadMessageCount();
- }
- }
-
- private void increaseUnreadMessageCountIfNeed() {
- AbstractChat chat = getChat();
- if (btnScrollDown.getVisibility() == View.VISIBLE && chat != null) {
- chat.increaseUnreadMessageCount();
- updateNewReceivedMessageCounter(chat.getUnreadMessageCount());
- }
- }
-
- private void resetUnreadMessageCount() {
- AbstractChat chat = getChat();
- if (chat != null) {
- chat.resetUnreadMessageCount();
- updateNewReceivedMessageCounter(0);
- ((ChatActivity)getActivity()).updateRecentChats();
- }
- }
-
private void updateNewReceivedMessageCounter(int count) {
tvNewReceivedCount.setText(String.valueOf(count));
if (count > 0)
@@ -1478,22 +1477,11 @@ private void updateNewReceivedMessageCounter(int count) {
else tvNewReceivedCount.setVisibility(View.GONE);
}
- private void showUnreadMessage(int count) {
- chatMessageAdapter.setUnreadCount(count);
+ private void setFirstUnreadMessageId(String id) {
+ chatMessageAdapter.setFirstUnreadMessageId(id);
chatMessageAdapter.notifyDataSetChanged();
}
- private void hideUnreadMessageBackground() {
- if (chatMessageAdapter.setUnreadCount(0)) {
- realmRecyclerView.post(new Runnable() {
- @Override
- public void run() {
- chatMessageAdapter.notifyDataSetChanged();
- }
- });
- }
- }
-
private void closeInteractionPanel() {
chatMessageAdapter.resetCheckedItems();
setUpInputViewButtons();
@@ -1540,7 +1528,7 @@ private void showForwardPanel(List forwardIds) {
private void sendForwardMessage(List messages, String text) {
ForwardManager.forwardMessage(messages, account, user, text);
hideForwardPanel();
- hideUnreadMessageBackground();
+ setFirstUnreadMessageId(null);
scrollDown();
}
diff --git a/xabber/src/main/java/com/xabber/android/ui/fragment/ContactAddFragment.java b/xabber/src/main/java/com/xabber/android/ui/fragment/ContactAddFragment.java
index b8b3157269..2f8a92bb52 100644
--- a/xabber/src/main/java/com/xabber/android/ui/fragment/ContactAddFragment.java
+++ b/xabber/src/main/java/com/xabber/android/ui/fragment/ContactAddFragment.java
@@ -1,7 +1,6 @@
package com.xabber.android.ui.fragment;
import android.app.Activity;
-import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
@@ -14,12 +13,11 @@
import com.xabber.android.R;
import com.xabber.android.data.Application;
-import com.xabber.android.data.log.LogManager;
import com.xabber.android.data.NetworkException;
import com.xabber.android.data.account.AccountManager;
import com.xabber.android.data.entity.AccountJid;
import com.xabber.android.data.entity.UserJid;
-import com.xabber.android.data.message.MessageManager;
+import com.xabber.android.data.log.LogManager;
import com.xabber.android.data.roster.PresenceManager;
import com.xabber.android.data.roster.RosterManager;
import com.xabber.android.ui.adapter.AccountChooseAdapter;
@@ -27,7 +25,11 @@
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
+import org.jxmpp.jid.EntityBareJid;
+import org.jxmpp.jid.impl.JidCreate;
+import org.jxmpp.stringprep.XmppStringprepException;
+import java.util.ArrayList;
import java.util.Collection;
public class ContactAddFragment extends GroupEditorFragment
@@ -172,6 +174,7 @@ public void addContact() {
}
String contactString = userView.getText().toString();
+ contactString = contactString.replace(" ", "");
if (TextUtils.isEmpty(contactString)) {
Toast.makeText(getActivity(), getString(R.string.EMPTY_USER_NAME),
@@ -179,43 +182,69 @@ public void addContact() {
return ;
}
- UserJid user;
+ final UserJid user;
try {
- user = UserJid.from(contactString);
- } catch (UserJid.UserJidCreateException e) {
- LogManager.exception(this, e);
+ EntityBareJid entityFullJid = JidCreate.entityBareFrom(contactString);
+ user = UserJid.from(entityFullJid);
+ } catch (XmppStringprepException | UserJid.UserJidCreateException e) {
+ e.printStackTrace();
+ Toast.makeText(getActivity(), getString(R.string.INCORRECT_USER_NAME), Toast.LENGTH_LONG).show();
return;
}
LogManager.i(this, "user: " + user);
- AccountJid account = (AccountJid) accountView.getSelectedItem();
+ final AccountJid account = (AccountJid) accountView.getSelectedItem();
if (account == null) {
Toast.makeText(getActivity(), getString(R.string.EMPTY_ACCOUNT),
Toast.LENGTH_LONG).show();
return;
}
- try {
- RosterManager.getInstance().createContact(account, user,
- nameView.getText().toString(), getSelected());
- PresenceManager.getInstance().requestSubscription(account, user);
- MessageManager.getInstance().openChat(account, user);
- } catch (SmackException.NotLoggedInException | SmackException.NotConnectedException e) {
- Application.getInstance().onError(R.string.NOT_CONNECTED);
- } catch (XMPPException.XMPPErrorException e) {
- Application.getInstance().onError(R.string.XMPP_EXCEPTION);
- } catch (SmackException.NoResponseException e) {
- Application.getInstance().onError(R.string.CONNECTION_FAILED);
- } catch (NetworkException e) {
- Application.getInstance().onError(e);
- } catch (InterruptedException e) {
- LogManager.exception(this, e);
- }
- getActivity().finish();
+ listenerActivity.showProgress(true);
+ final String name = nameView.getText().toString();
+ final ArrayList groups = getSelected();
+
+ Application.getInstance().runInBackgroundUserRequest(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ RosterManager.getInstance().createContact(account, user, name, groups);
+ PresenceManager.getInstance().requestSubscription(account, user);
+ } catch (SmackException.NotLoggedInException | SmackException.NotConnectedException e) {
+ Application.getInstance().onError(R.string.NOT_CONNECTED);
+ stopAddContactProcess(false);
+ } catch (XMPPException.XMPPErrorException e) {
+ Application.getInstance().onError(R.string.XMPP_EXCEPTION);
+ stopAddContactProcess(false);
+ } catch (SmackException.NoResponseException e) {
+ Application.getInstance().onError(R.string.CONNECTION_FAILED);
+ stopAddContactProcess(false);
+ } catch (NetworkException e) {
+ Application.getInstance().onError(e);
+ stopAddContactProcess(false);
+ } catch (InterruptedException e) {
+ LogManager.exception(this, e);
+ stopAddContactProcess(false);
+ }
+
+ stopAddContactProcess(true);
+ }
+ });
+ }
+
+ private void stopAddContactProcess(final boolean success) {
+ Application.getInstance().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ listenerActivity.showProgress(false);
+ if (success) getActivity().finish();
+ }
+ });
}
public interface Listener {
void onAccountSelected(AccountJid account);
+ void showProgress(boolean show);
}
}
diff --git a/xabber/src/main/java/com/xabber/android/ui/helper/NewContactTitleInflater.java b/xabber/src/main/java/com/xabber/android/ui/helper/NewContactTitleInflater.java
index 401556eaaa..1ad79c0f82 100644
--- a/xabber/src/main/java/com/xabber/android/ui/helper/NewContactTitleInflater.java
+++ b/xabber/src/main/java/com/xabber/android/ui/helper/NewContactTitleInflater.java
@@ -7,6 +7,7 @@
import android.widget.TextView;
import com.xabber.android.R;
+import com.xabber.android.data.account.StatusMode;
import com.xabber.android.data.extension.avatar.AvatarManager;
import com.xabber.android.data.extension.cs.ChatStateManager;
import com.xabber.android.data.message.NotificationState;
@@ -84,12 +85,12 @@ private static void setStatus(Context context, View titleView, AbstractContact a
} else if (chatState == ChatState.paused) {
statusText = context.getString(R.string.chat_state_paused);
} else {
- statusText = abstractContact.getStatusText().trim();
- if (statusText.toString().isEmpty()) {
+ if (StatusMode.unavailable == abstractContact.getStatusMode())
statusText = getLastActivity(abstractContact);
- if (statusText.length() <= 0)
- statusText = context.getString(abstractContact.getStatusMode().getStringID());
- }
+ else statusText = abstractContact.getStatusText().trim();
+
+ if (statusText.toString().isEmpty())
+ statusText = context.getString(abstractContact.getStatusMode().getStringID());
}
statusTextView.setText(statusText);
}
diff --git a/xabber/src/main/java/com/xabber/android/ui/preferences/ChannelSettingsFragment.java b/xabber/src/main/java/com/xabber/android/ui/preferences/ChannelSettingsFragment.java
index 010acebf87..ee718c1a79 100644
--- a/xabber/src/main/java/com/xabber/android/ui/preferences/ChannelSettingsFragment.java
+++ b/xabber/src/main/java/com/xabber/android/ui/preferences/ChannelSettingsFragment.java
@@ -136,7 +136,8 @@ private String getSoundTitle(NotificationChannel channel) {
if (channel == null) return null;
Uri uri = channel.getSound();
Ringtone ringtone = RingtoneManager.getRingtone(getActivity(), uri);
- return ringtone.getTitle(getActivity());
+ if (ringtone != null) return ringtone.getTitle(getActivity());
+ else return "Unknown ringtone";
}
@Override
@@ -144,7 +145,4 @@ protected void setNewRingtone(ChannelRingtoneHolder ringtoneHolder) {
NotificationChannelUtils.updateMessageChannel(notificationManager, ringtoneHolder.type,
Uri.parse(ringtoneHolder.uri), null, null);
}
-}
-
-
-
+}
\ No newline at end of file
diff --git a/xabber/src/main/java/com/xabber/android/ui/preferences/CustomNotifSettingsFragment.java b/xabber/src/main/java/com/xabber/android/ui/preferences/CustomNotifSettingsFragment.java
index 9e4b0feb18..3837376423 100644
--- a/xabber/src/main/java/com/xabber/android/ui/preferences/CustomNotifSettingsFragment.java
+++ b/xabber/src/main/java/com/xabber/android/ui/preferences/CustomNotifSettingsFragment.java
@@ -123,13 +123,15 @@ private String getSoundTitle(NotificationChannel channel) {
if (channel == null) return null;
Uri uri = channel.getSound();
Ringtone ringtone = RingtoneManager.getRingtone(getActivity(), uri);
- return ringtone.getTitle(getActivity());
+ if (ringtone != null) return ringtone.getTitle(getActivity());
+ else return "Unknown ringtone";
}
private String getSoundTitle(NotifyPrefs notifyPrefs) {
Uri uri = Uri.parse(notifyPrefs.getSound());
Ringtone ringtone = RingtoneManager.getRingtone(getActivity(), uri);
- return ringtone.getTitle(getActivity());
+ if (ringtone != null) return ringtone.getTitle(getActivity());
+ else return "Unknown ringtone";
}
private String getVibroSummary(Context context, NotifyPrefs notifyPrefs) {
diff --git a/xabber/src/main/java/com/xabber/android/ui/preferences/NotificationsSettingsFragment.java b/xabber/src/main/java/com/xabber/android/ui/preferences/NotificationsSettingsFragment.java
index fbc7f9e47d..e73f4c036d 100644
--- a/xabber/src/main/java/com/xabber/android/ui/preferences/NotificationsSettingsFragment.java
+++ b/xabber/src/main/java/com/xabber/android/ui/preferences/NotificationsSettingsFragment.java
@@ -5,29 +5,41 @@
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
+import android.os.Build;
import android.os.Bundle;
import android.preference.Preference;
import android.widget.Toast;
import com.xabber.android.R;
import com.xabber.android.data.SettingsManager;
+import com.xabber.android.data.notification.NotificationChannelUtils;
import com.xabber.android.data.notification.custom_notification.CustomNotifyPrefsManager;
import com.xabber.android.ui.activity.PreferenceSummaryHelperActivity;
import static com.xabber.android.data.SettingsManager.NOTIFICATION_PREFERENCES;
public class NotificationsSettingsFragment extends android.preference.PreferenceFragment {
+
+ protected NotificationManager notificationManager;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPreferenceManager().setSharedPreferencesName(NOTIFICATION_PREFERENCES);
-
addPreferencesFromResource(R.xml.preference_notifications);
+ notificationManager = (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
PreferenceSummaryHelperActivity.updateSummary(getPreferenceScreen());
- Preference resetPreference = (Preference) getPreferenceScreen().findPreference(getString(R.string.events_reset_key));
- resetPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ Preference resetPreference = getPreferenceScreen().findPreference(getString(R.string.events_reset_key));
+ if (resetPreference != null) setupResetPreferences(resetPreference, notificationManager);
+
+ Preference removeCustomNotifPreference = getPreferenceScreen().findPreference(getString(R.string.events_remove_all_custom_key));
+ if (removeCustomNotifPreference != null) setupRemoveCustomNotifPreferences(removeCustomNotifPreference, notificationManager);
+ }
+
+ protected void setupResetPreferences(Preference preference, final NotificationManager notificationManager) {
+ preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
@@ -37,6 +49,8 @@ public boolean onPreferenceClick(Preference preference) {
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getActivity(), R.string.events_reset_toast, Toast.LENGTH_SHORT).show();
SettingsManager.resetPreferences(getActivity(), NOTIFICATION_PREFERENCES);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ NotificationChannelUtils.resetMessageChannels(notificationManager);
((NotificationsSettings) getActivity()).restartFragment();
}
})
@@ -50,35 +64,31 @@ public void onClick(DialogInterface dialog, int which) {
return true;
}
});
+ }
- Preference removeCustomNotifPreference = getPreferenceScreen()
- .findPreference(getString(R.string.events_remove_all_custom_key));
- if (removeCustomNotifPreference != null) {
- removeCustomNotifPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setMessage(R.string.events_remove_all_custom_summary)
- .setPositiveButton(R.string.remove, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- NotificationManager notificationManager = (NotificationManager)
- getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
- Toast.makeText(getActivity(), R.string.events_reset_toast, Toast.LENGTH_SHORT).show();
- CustomNotifyPrefsManager.getInstance().deleteAllNotifyPrefs(notificationManager);
- ((NotificationsSettings) getActivity()).restartFragment();
- }
- })
- .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- });
- builder.create().show();
- return true;
- }
- });
- }
+ protected void setupRemoveCustomNotifPreferences(Preference preference, final NotificationManager notificationManager) {
+ preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setMessage(R.string.events_remove_all_custom_summary)
+ .setPositiveButton(R.string.remove, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Toast.makeText(getActivity(), R.string.events_reset_toast, Toast.LENGTH_SHORT).show();
+ CustomNotifyPrefsManager.getInstance().deleteAllNotifyPrefs(notificationManager);
+ ((NotificationsSettings) getActivity()).restartFragment();
+ }
+ })
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+ builder.create().show();
+ return true;
+ }
+ });
}
}
diff --git a/xabber/src/main/res/drawable/ic_message_acknowledged_14dp.xml b/xabber/src/main/res/drawable/ic_message_acknowledged_14dp.xml
index edffcca84d..65fe1fba3e 100644
--- a/xabber/src/main/res/drawable/ic_message_acknowledged_14dp.xml
+++ b/xabber/src/main/res/drawable/ic_message_acknowledged_14dp.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/xabber/src/main/res/drawable/ic_message_delivered_14dp.xml b/xabber/src/main/res/drawable/ic_message_delivered_14dp.xml
index 020ff956be..edffcca84d 100644
--- a/xabber/src/main/res/drawable/ic_message_delivered_14dp.xml
+++ b/xabber/src/main/res/drawable/ic_message_delivered_14dp.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/xabber/src/main/res/drawable/ic_message_displayed.xml b/xabber/src/main/res/drawable/ic_message_displayed.xml
new file mode 100644
index 0000000000..020ff956be
--- /dev/null
+++ b/xabber/src/main/res/drawable/ic_message_displayed.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/xabber/src/main/res/drawable/ic_message_forwarded_14dp.xml b/xabber/src/main/res/drawable/ic_message_forwarded_14dp.xml
index 97a5c0b1a9..5dfd1664e1 100644
--- a/xabber/src/main/res/drawable/ic_message_forwarded_14dp.xml
+++ b/xabber/src/main/res/drawable/ic_message_forwarded_14dp.xml
@@ -1,8 +1,7 @@
-
-
+
\ No newline at end of file
diff --git a/xabber/src/main/res/layout/activity_with_toolbar_progress_and_container.xml b/xabber/src/main/res/layout/activity_with_toolbar_progress_and_container.xml
new file mode 100644
index 0000000000..07f2f6b4a0
--- /dev/null
+++ b/xabber/src/main/res/layout/activity_with_toolbar_progress_and_container.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xabber/src/main/res/layout/item_message.xml b/xabber/src/main/res/layout/item_message.xml
index 2a87311b40..ef28129cce 100644
--- a/xabber/src/main/res/layout/item_message.xml
+++ b/xabber/src/main/res/layout/item_message.xml
@@ -131,8 +131,7 @@
android:id="@+id/message_status_icon"
android:layout_width="14dp"
android:layout_height="14dp"
- android:src="@drawable/ic_message_synced_14dp"
- android:alpha="0.5"/>
+ android:src="@drawable/ic_message_synced_14dp" />
+ android:src="@drawable/ic_message_synced_14dp" />
+
+
+
+
+
+
diff --git a/xabber/src/main/res/values-ru-rRU/preference_editor.xml b/xabber/src/main/res/values-ru-rRU/preference_editor.xml
index f29daa8094..e69a605d81 100644
--- a/xabber/src/main/res/values-ru-rRU/preference_editor.xml
+++ b/xabber/src/main/res/values-ru-rRU/preference_editor.xml
@@ -46,6 +46,7 @@
Отключить графические смайлы
Настройки подключения\nНастройки вариантов подключения
Список контактов\nНастройки отображения списка контактов
+ Настройки отображения чата
Настройки отладки\nВыберите этот пункт, если хотите помочь разработчикам сделать Xabber ещё лучше
Настройки
Оповещения\nНастройки вариантов оповещения
@@ -142,7 +143,7 @@
Все настройки уведомлений будут установлены в значения по умолчанию
Сбросить настройки уведомлений?
Настройки были сброшены
- Удалить все спецтальные настройки уведомлений
+ Удалить все специальные настройки уведомлений
Все специальные настройки уведомлений (для контактов, групп и аккаунтов) будут удалены
Мелодия не доступна
diff --git a/xabber/src/main/res/values/chat_viewer.xml b/xabber/src/main/res/values/chat_viewer.xml
index a0a4167d3b..dc6a529d46 100644
--- a/xabber/src/main/res/values/chat_viewer.xml
+++ b/xabber/src/main/res/values/chat_viewer.xml
@@ -100,6 +100,7 @@
Error description:
Sent
Delivered
+ Displayed
Received from history
Error
Sent from another device
diff --git a/xabber/src/main/res/values/notification_bar.xml b/xabber/src/main/res/values/notification_bar.xml
index eb1c686452..5e33f5b0ed 100644
--- a/xabber/src/main/res/values/notification_bar.xml
+++ b/xabber/src/main/res/values/notification_bar.xml
@@ -59,5 +59,7 @@
Persistent connection
Persistent notification for establish connection to receive messages
Events
+ Silent message notifications
Notifications for events like: group chat invites, subscription requests, OTR requests
+ Silent notifications about messages
\ No newline at end of file
diff --git a/xabber/src/main/res/values/preference_editor.xml b/xabber/src/main/res/values/preference_editor.xml
index 285311e8f3..61c4304065 100644
--- a/xabber/src/main/res/values/preference_editor.xml
+++ b/xabber/src/main/res/values/preference_editor.xml
@@ -54,6 +54,7 @@
Connection settings\nConnection settings
Contact list\nCustomize contact list appearance
+ Customize chat appearance
Debug settings\nSettings to help developers improve Xabber
Settings
Notifications\nNotification settings
diff --git a/xabber/src/main/res/xml/preference_editor.xml b/xabber/src/main/res/xml/preference_editor.xml
index c8e5c61282..3efcbf78ca 100644
--- a/xabber/src/main/res/xml/preference_editor.xml
+++ b/xabber/src/main/res/xml/preference_editor.xml
@@ -25,6 +25,7 @@
/>