send and show read markers in private, non-anonymous groups
This commit is contained in:
parent
1c65a17ff1
commit
28e005f926
|
@ -82,7 +82,7 @@ public final class Config {
|
|||
public static final long OMEMO_AUTO_EXPIRY = 7 * MILLISECONDS_IN_DAY;
|
||||
public static final boolean REMOVE_BROKEN_DEVICES = false;
|
||||
public static final boolean OMEMO_PADDING = false;
|
||||
public static boolean PUT_AUTH_TAG_INTO_KEY = true;
|
||||
public static final boolean PUT_AUTH_TAG_INTO_KEY = true;
|
||||
|
||||
|
||||
public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb
|
||||
|
|
|
@ -289,6 +289,17 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
return null;
|
||||
}
|
||||
|
||||
public Message findMessageWithRemoteId(String id) {
|
||||
synchronized (this.messages) {
|
||||
for(Message message : this.messages) {
|
||||
if (id.equals(message.getRemoteMsgId()) || id.equals(message.getUuid())) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasMessageWithCounterpart(Jid counterpart) {
|
||||
synchronized (this.messages) {
|
||||
for(Message message : this.messages) {
|
||||
|
|
|
@ -3,9 +3,17 @@ package eu.siacs.conversations.entities;
|
|||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
|
||||
|
@ -62,6 +70,7 @@ public class Message extends AbstractEntity {
|
|||
public static final String FINGERPRINT = "axolotl_fingerprint";
|
||||
public static final String READ = "read";
|
||||
public static final String ERROR_MESSAGE = "errorMsg";
|
||||
public static final String READ_BY_MARKERS = "readByMarkers";
|
||||
public static final String ME_COMMAND = "/me ";
|
||||
|
||||
|
||||
|
@ -88,11 +97,13 @@ public class Message extends AbstractEntity {
|
|||
private Message mPreviousMessage = null;
|
||||
private String axolotlFingerprint = null;
|
||||
private String errorMessage = null;
|
||||
protected Set<ReadByMarker> readByMarkers = new HashSet<>();
|
||||
|
||||
private Boolean isGeoUri = null;
|
||||
private Boolean isEmojisOnly = null;
|
||||
private Boolean treatAsDownloadable = null;
|
||||
private FileParams fileParams = null;
|
||||
private List<MucOptions.User> counterparts;
|
||||
|
||||
private Message(Conversation conversation) {
|
||||
this.conversation = conversation;
|
||||
|
@ -120,6 +131,7 @@ public class Message extends AbstractEntity {
|
|||
true,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
|
@ -128,7 +140,7 @@ public class Message extends AbstractEntity {
|
|||
final int encryption, final int status, final int type, final boolean carbon,
|
||||
final String remoteMsgId, final String relativeFilePath,
|
||||
final String serverMsgId, final String fingerprint, final boolean read,
|
||||
final String edited, final boolean oob, final String errorMessage) {
|
||||
final String edited, final boolean oob, final String errorMessage, final Set<ReadByMarker> readByMarkers) {
|
||||
this.conversation = conversation;
|
||||
this.uuid = uuid;
|
||||
this.conversationUuid = conversationUUid;
|
||||
|
@ -148,6 +160,7 @@ public class Message extends AbstractEntity {
|
|||
this.edited = edited;
|
||||
this.oob = oob;
|
||||
this.errorMessage = errorMessage;
|
||||
this.readByMarkers = new HashSet<>();
|
||||
}
|
||||
|
||||
public static Message fromCursor(Cursor cursor, Conversation conversation) {
|
||||
|
@ -193,7 +206,8 @@ public class Message extends AbstractEntity {
|
|||
cursor.getInt(cursor.getColumnIndex(READ)) > 0,
|
||||
cursor.getString(cursor.getColumnIndex(EDITED)),
|
||||
cursor.getInt(cursor.getColumnIndex(OOB)) > 0,
|
||||
cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)));
|
||||
cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)),
|
||||
ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))));
|
||||
}
|
||||
|
||||
public static Message createStatusMessage(Conversation conversation, String body) {
|
||||
|
@ -248,6 +262,7 @@ public class Message extends AbstractEntity {
|
|||
values.put(EDITED, edited);
|
||||
values.put(OOB, oob ? 1 : 0);
|
||||
values.put(ERROR_MESSAGE,errorMessage);
|
||||
values.put(READ_BY_MARKERS,ReadByMarker.toJson(readByMarkers).toString());
|
||||
return values;
|
||||
}
|
||||
|
||||
|
@ -415,6 +430,25 @@ public class Message extends AbstractEntity {
|
|||
this.transferable = transferable;
|
||||
}
|
||||
|
||||
public boolean addReadByMarker(ReadByMarker readByMarker) {
|
||||
if (readByMarker.getRealJid() != null) {
|
||||
if (readByMarker.getRealJid().toBareJid().equals(trueCounterpart)) {
|
||||
Log.d(Config.LOGTAG,"trying to add read marker by "+readByMarker.getRealJid()+" to "+body);
|
||||
return false;
|
||||
}
|
||||
} else if (readByMarker.getFullJid() != null) {
|
||||
if (readByMarker.getFullJid().equals(counterpart)) {
|
||||
Log.d(Config.LOGTAG,"trying to add read marker by "+readByMarker.getFullJid()+" to "+body);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this.readByMarkers.add(readByMarker);
|
||||
}
|
||||
|
||||
public Set<ReadByMarker> getReadByMarkers() {
|
||||
return Collections.unmodifiableSet(this.readByMarkers);
|
||||
}
|
||||
|
||||
public boolean similar(Message message) {
|
||||
if (type != TYPE_PRIVATE && this.serverMsgId != null && message.getServerMsgId() != null) {
|
||||
return this.serverMsgId.equals(message.getServerMsgId());
|
||||
|
@ -515,7 +549,8 @@ public class Message extends AbstractEntity {
|
|||
!this.bodyIsOnlyEmojis() &&
|
||||
!message.bodyIsOnlyEmojis() &&
|
||||
((this.axolotlFingerprint == null && message.axolotlFingerprint == null) || this.axolotlFingerprint.equals(message.getFingerprint())) &&
|
||||
UIHelper.sameDay(message.getTimeSent(),this.getTimeSent())
|
||||
UIHelper.sameDay(message.getTimeSent(),this.getTimeSent()) &&
|
||||
this.getReadByMarkers().equals(message.getReadByMarkers())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -529,6 +564,14 @@ public class Message extends AbstractEntity {
|
|||
);
|
||||
}
|
||||
|
||||
public void setCounterparts(List<MucOptions.User> counterparts) {
|
||||
this.counterparts = counterparts;
|
||||
}
|
||||
|
||||
public List<MucOptions.User> getCounterparts() {
|
||||
return this.counterparts;
|
||||
}
|
||||
|
||||
public static class MergeSeparator {}
|
||||
|
||||
public SpannableStringBuilder getMergedBody() {
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.Set;
|
|||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.utils.JidHelper;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
import eu.siacs.conversations.xml.Namespace;
|
||||
import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
||||
import eu.siacs.conversations.xmpp.forms.Data;
|
||||
|
@ -280,6 +281,10 @@ public class MucOptions {
|
|||
return options.getAccount();
|
||||
}
|
||||
|
||||
public Conversation getConversation() {
|
||||
return options.getConversation();
|
||||
}
|
||||
|
||||
public Jid getFullJid() {
|
||||
return fullJid;
|
||||
}
|
||||
|
@ -521,6 +526,21 @@ public class MucOptions {
|
|||
return null;
|
||||
}
|
||||
|
||||
public User findUser(ReadByMarker readByMarker) {
|
||||
if (readByMarker.getRealJid() != null) {
|
||||
User user = findUserByRealJid(readByMarker.getRealJid().toBareJid());
|
||||
if (user == null) {
|
||||
user = new User(this,readByMarker.getFullJid());
|
||||
user.setRealJid(readByMarker.getRealJid());
|
||||
}
|
||||
return user;
|
||||
} else if (readByMarker.getFullJid() != null) {
|
||||
return findUserByFullJid(readByMarker.getFullJid());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isContactInRoom(Contact contact) {
|
||||
return findUserByRealJid(contact.getJid().toBareJid()) != null;
|
||||
}
|
||||
|
@ -655,17 +675,9 @@ public class MucOptions {
|
|||
if (builder.length() != 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
Contact contact = user.getContact();
|
||||
if (contact != null && !contact.getDisplayName().isEmpty()) {
|
||||
builder.append(contact.getDisplayName().split("\\s+")[0]);
|
||||
} else {
|
||||
final String name = user.getName();
|
||||
final Jid jid = user.getRealJid();
|
||||
String name = UIHelper.getDisplayName(user);
|
||||
if (name != null) {
|
||||
builder.append(name.split("\\s+")[0]);
|
||||
} else if (jid != null) {
|
||||
builder.append(jid.getLocalpart());
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
package eu.siacs.conversations.entities;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
|
||||
public class ReadByMarker {
|
||||
|
||||
private ReadByMarker() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ReadByMarker marker = (ReadByMarker) o;
|
||||
|
||||
if (fullJid != null ? !fullJid.equals(marker.fullJid) : marker.fullJid != null)
|
||||
return false;
|
||||
return realJid != null ? realJid.equals(marker.realJid) : marker.realJid == null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = fullJid != null ? fullJid.hashCode() : 0;
|
||||
result = 31 * result + (realJid != null ? realJid.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Jid fullJid;
|
||||
private Jid realJid;
|
||||
|
||||
public Jid getFullJid() {
|
||||
return fullJid;
|
||||
}
|
||||
|
||||
public Jid getRealJid() {
|
||||
return realJid;
|
||||
}
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
if (fullJid != null) {
|
||||
try {
|
||||
jsonObject.put("fullJid", fullJid.toPreppedString());
|
||||
} catch (JSONException e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
if (realJid != null) {
|
||||
try {
|
||||
jsonObject.put("realJid", realJid.toPreppedString());
|
||||
} catch (JSONException e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public static Set<ReadByMarker> fromJson(JSONArray jsonArray) {
|
||||
HashSet<ReadByMarker> readByMarkers = new HashSet<>();
|
||||
for(int i = 0; i < jsonArray.length(); ++i) {
|
||||
try {
|
||||
readByMarkers.add(fromJson(jsonArray.getJSONObject(i)));
|
||||
} catch (JSONException e) {
|
||||
//ignored
|
||||
}
|
||||
}
|
||||
return readByMarkers;
|
||||
}
|
||||
|
||||
public static ReadByMarker from(Jid fullJid, Jid realJid) {
|
||||
final ReadByMarker marker = new ReadByMarker();
|
||||
marker.fullJid = fullJid;
|
||||
marker.realJid = realJid;
|
||||
return marker;
|
||||
}
|
||||
|
||||
public static ReadByMarker from(Message message) {
|
||||
final ReadByMarker marker = new ReadByMarker();
|
||||
marker.fullJid = message.getCounterpart();
|
||||
marker.realJid = message.getTrueCounterpart();
|
||||
return marker;
|
||||
}
|
||||
|
||||
public static ReadByMarker from(MucOptions.User user) {
|
||||
final ReadByMarker marker = new ReadByMarker();
|
||||
marker.fullJid = user.getFullJid();
|
||||
marker.realJid = user.getRealJid();
|
||||
return marker;
|
||||
}
|
||||
|
||||
public static Set<ReadByMarker> from(Collection<MucOptions.User> users) {
|
||||
final HashSet<ReadByMarker> markers = new HashSet<>();
|
||||
for(MucOptions.User user : users) {
|
||||
markers.add(from(user));
|
||||
}
|
||||
return markers;
|
||||
}
|
||||
|
||||
public static ReadByMarker fromJson(JSONObject jsonObject) {
|
||||
ReadByMarker marker = new ReadByMarker();
|
||||
try {
|
||||
marker.fullJid = Jid.fromString(jsonObject.getString("fullJid"),true);
|
||||
} catch (JSONException | InvalidJidException e) {
|
||||
marker.fullJid = null;
|
||||
}
|
||||
try {
|
||||
marker.realJid = Jid.fromString(jsonObject.getString("realJid"),true);
|
||||
} catch (JSONException | InvalidJidException e) {
|
||||
marker.realJid = null;
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
public static Set<ReadByMarker> fromJsonString(String json) {
|
||||
try {
|
||||
return fromJson(new JSONArray(json));
|
||||
} catch (JSONException | NullPointerException e) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static JSONArray toJson(Set<ReadByMarker> readByMarkers) {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
for(ReadByMarker marker : readByMarkers) {
|
||||
jsonArray.put(marker.toJson());
|
||||
}
|
||||
return jsonArray;
|
||||
}
|
||||
|
||||
public static boolean contains(ReadByMarker needle, Set<ReadByMarker> readByMarkers) {
|
||||
for(ReadByMarker marker : readByMarkers) {
|
||||
if (marker.realJid != null && needle.realJid != null) {
|
||||
if (marker.realJid.toBareJid().equals(needle.realJid.toBareJid())) {
|
||||
return true;
|
||||
}
|
||||
} else if (marker.fullJid != null && needle.fullJid != null) {
|
||||
if (marker.fullJid.equals(needle.fullJid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean allUsersRepresented(Collection<MucOptions.User> users, Set<ReadByMarker> markers) {
|
||||
for(MucOptions.User user : users) {
|
||||
if (!contains(from(user),markers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -39,7 +39,6 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||
packet.setTo(message.getCounterpart());
|
||||
packet.setType(MessagePacket.TYPE_CHAT);
|
||||
packet.addChild("markable", "urn:xmpp:chat-markers:0");
|
||||
if (this.mXmppConnectionService.indicateReceived()) {
|
||||
packet.addChild("request", "urn:xmpp:receipts");
|
||||
}
|
||||
|
@ -54,6 +53,10 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
packet.setTo(message.getCounterpart().toBareJid());
|
||||
packet.setType(MessagePacket.TYPE_GROUPCHAT);
|
||||
}
|
||||
if (conversation.getMode() == Conversation.MODE_SINGLE ||
|
||||
(conversation.getMucOptions().nonanonymous() && conversation.getMucOptions().membersOnly() && message.getType() != Message.TYPE_PRIVATE)) {
|
||||
packet.addChild("markable", "urn:xmpp:chat-markers:0");
|
||||
}
|
||||
packet.setFrom(account.getJid());
|
||||
packet.setId(message.getUuid());
|
||||
packet.addChild("origin-id", Namespace.STANZA_IDS).setAttribute("id",message.getUuid());
|
||||
|
@ -170,10 +173,10 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket confirm(final Account account, final Jid to, final String id) {
|
||||
public MessagePacket confirm(final Account account, final Jid to, final String id, final boolean groupChat) {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_CHAT);
|
||||
packet.setTo(to);
|
||||
packet.setType(groupChat ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT);
|
||||
packet.setTo(groupChat ? to.toBareJid() : to);
|
||||
packet.setFrom(account.getJid());
|
||||
Element received = packet.addChild("displayed","urn:xmpp:chat-markers:0");
|
||||
received.setAttribute("id", id);
|
||||
|
|
|
@ -29,6 +29,7 @@ import eu.siacs.conversations.entities.Conversation;
|
|||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.MucOptions;
|
||||
import eu.siacs.conversations.entities.Presence;
|
||||
import eu.siacs.conversations.entities.ReadByMarker;
|
||||
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
|
||||
import eu.siacs.conversations.http.HttpConnectionManager;
|
||||
import eu.siacs.conversations.services.MessageArchiveService;
|
||||
|
@ -700,13 +701,29 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
}
|
||||
Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0");
|
||||
if (displayed != null) {
|
||||
final String id = displayed.getAttribute("id");
|
||||
if (packet.fromAccount(account)) {
|
||||
Conversation conversation = mXmppConnectionService.find(account, counterpart.toBareJid());
|
||||
if (conversation != null && (query == null || query.isCatchup())) {
|
||||
mXmppConnectionService.markRead(conversation);
|
||||
}
|
||||
} else if (isTypeGroupChat) {
|
||||
Conversation conversation = mXmppConnectionService.find(account, counterpart.toBareJid());
|
||||
if (conversation != null && id != null) {
|
||||
Message message = conversation.findMessageWithRemoteId(id);
|
||||
if (message != null) {
|
||||
final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
|
||||
Jid trueJid = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
|
||||
ReadByMarker readByMarker = ReadByMarker.from(counterpart,trueJid);
|
||||
if (!conversation.getMucOptions().isSelf(counterpart) && message.addReadByMarker(readByMarker)) {
|
||||
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": added read by ("+readByMarker.getRealJid()+") to message '"+message.getBody()+"'");
|
||||
mXmppConnectionService.updateMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
final Message displayedMessage = mXmppConnectionService.markMessage(account, from.toBareJid(), displayed.getAttribute("id"), Message.STATUS_SEND_DISPLAYED);
|
||||
final Message displayedMessage = mXmppConnectionService.markMessage(account, from.toBareJid(), id, Message.STATUS_SEND_DISPLAYED);
|
||||
Message message = displayedMessage == null ? null : displayedMessage.prev();
|
||||
while (message != null
|
||||
&& message.getStatus() == Message.STATUS_SEND_RECEIVED
|
||||
|
|
|
@ -60,7 +60,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
|||
private static DatabaseBackend instance = null;
|
||||
|
||||
private static final String DATABASE_NAME = "history";
|
||||
private static final int DATABASE_VERSION = 36;
|
||||
private static final int DATABASE_VERSION = 37;
|
||||
|
||||
private static String CREATE_CONTATCS_STATEMENT = "create table "
|
||||
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
|
||||
|
@ -197,6 +197,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
|||
+ Message.READ + " NUMBER DEFAULT 1, "
|
||||
+ Message.OOB + " INTEGER, "
|
||||
+ Message.ERROR_MESSAGE + " TEXT,"
|
||||
+ Message.READ_BY_MARKERS + " TEXT,"
|
||||
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
|
||||
+ Message.CONVERSATION + ") REFERENCES "
|
||||
+ Conversation.TABLENAME + "(" + Conversation.UUID
|
||||
|
@ -454,6 +455,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
|||
+ "=?", new String[]{account.getUuid()});
|
||||
}
|
||||
}
|
||||
|
||||
if (oldVersion < 37 && newVersion >= 37) {
|
||||
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ_BY_MARKERS + " TEXTs");
|
||||
}
|
||||
}
|
||||
|
||||
private static ContentValues createFingerprintStatusContentValues(FingerprintStatus.Trust trust, boolean active) {
|
||||
|
|
|
@ -10,10 +10,14 @@ import android.graphics.Typeface;
|
|||
import android.net.Uri;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.LruCache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
|
@ -39,6 +43,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
|
|||
private static final String PREFIX_GENERIC = "generic";
|
||||
|
||||
final private ArrayList<Integer> sizes = new ArrayList<>();
|
||||
final private HashMap<String,Set<String>> conversationDependentKeys = new HashMap<>();
|
||||
|
||||
protected XmppConnectionService mXmppConnectionService = null;
|
||||
|
||||
|
@ -184,6 +189,17 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
|
|||
clear(conversation.getContact());
|
||||
} else {
|
||||
clear(conversation.getMucOptions());
|
||||
synchronized (this.conversationDependentKeys) {
|
||||
Set<String> keys = this.conversationDependentKeys.get(conversation.getUuid());
|
||||
if (keys == null) {
|
||||
return;
|
||||
}
|
||||
LruCache<String, Bitmap> cache = this.mXmppConnectionService.getBitmapCache();
|
||||
for(String key : keys) {
|
||||
cache.remove(key);
|
||||
}
|
||||
keys.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,17 +210,36 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
|
|||
return bitmap;
|
||||
}
|
||||
final List<MucOptions.User> users = mucOptions.getUsers(5);
|
||||
if (users.size() == 0) {
|
||||
bitmap = getImpl(mucOptions.getConversation().getName(),size);
|
||||
} else {
|
||||
bitmap = getImpl(users,size);
|
||||
}
|
||||
this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private Bitmap get(List<MucOptions.User> users, int size, boolean cachedOnly) {
|
||||
final String KEY = key(users, size);
|
||||
Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY);
|
||||
if (bitmap != null || cachedOnly) {
|
||||
return bitmap;
|
||||
}
|
||||
bitmap = getImpl(users, size);
|
||||
this.mXmppConnectionService.getBitmapCache().put(KEY,bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private Bitmap getImpl(List<MucOptions.User> users, int size) {
|
||||
int count = users.size();
|
||||
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
bitmap.eraseColor(TRANSPARENT);
|
||||
|
||||
if (count == 0) {
|
||||
String name = mucOptions.getConversation().getName();
|
||||
drawTile(canvas, name, 0, 0, size, size);
|
||||
throw new AssertionError("Unable to draw tiles for 0 users");
|
||||
} else if (count == 1) {
|
||||
drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size);
|
||||
drawTile(canvas, mucOptions.getConversation().getAccount(), size / 2 + 1, 0, size, size);
|
||||
drawTile(canvas, users.get(0).getAccount(), size / 2 + 1, 0, size, size);
|
||||
} else if (count == 2) {
|
||||
drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size);
|
||||
drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size);
|
||||
|
@ -226,7 +261,6 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
|
|||
drawTile(canvas, "\u2026", PLACEHOLDER_COLOR, size / 2 + 1, size / 2 + 1,
|
||||
size, size);
|
||||
}
|
||||
this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
|
@ -248,6 +282,31 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
|
|||
+ "_" + String.valueOf(size);
|
||||
}
|
||||
|
||||
private String key(List<MucOptions.User> users, int size) {
|
||||
final Conversation conversation = users.get(0).getConversation();
|
||||
StringBuilder builder = new StringBuilder("TILE_");
|
||||
builder.append(conversation.getUuid());
|
||||
|
||||
for(MucOptions.User user : users) {
|
||||
builder.append("\0");
|
||||
builder.append(user.getRealJid() == null ? "" : user.getRealJid().toBareJid().toPreppedString());
|
||||
builder.append("\0");
|
||||
builder.append(user.getFullJid() == null ? "" : user.getFullJid().toPreppedString());
|
||||
}
|
||||
final String key = builder.toString();
|
||||
synchronized (this.conversationDependentKeys) {
|
||||
Set<String> keys;
|
||||
if (this.conversationDependentKeys.containsKey(conversation.getUuid())) {
|
||||
keys = this.conversationDependentKeys.get(conversation.getUuid());
|
||||
} else {
|
||||
keys = new HashSet<>();
|
||||
this.conversationDependentKeys.put(conversation.getUuid(),keys);
|
||||
}
|
||||
keys.add(key);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
public Bitmap get(Account account, int size) {
|
||||
return get(account, size, false);
|
||||
}
|
||||
|
@ -268,7 +327,9 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
|
|||
|
||||
public Bitmap get(Message message, int size, boolean cachedOnly) {
|
||||
final Conversation conversation = message.getConversation();
|
||||
if (message.getStatus() == Message.STATUS_RECEIVED) {
|
||||
if (message.getType() == Message.TYPE_STATUS && message.getCounterparts() != null && message.getCounterparts().size() > 1) {
|
||||
return get(message.getCounterparts(),size,cachedOnly);
|
||||
} else if (message.getStatus() == Message.STATUS_RECEIVED) {
|
||||
Contact c = message.getContact();
|
||||
if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) {
|
||||
return get(c, size, cachedOnly);
|
||||
|
@ -320,11 +381,16 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
|
|||
if (bitmap != null || cachedOnly) {
|
||||
return bitmap;
|
||||
}
|
||||
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||
bitmap = getImpl(name, size);
|
||||
mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private Bitmap getImpl(final String name, final int size) {
|
||||
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
final String trimmedName = name == null ? "" : name.trim();
|
||||
drawTile(canvas, trimmedName, 0, 0, size, size);
|
||||
mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
|
|
|
@ -3394,11 +3394,13 @@ public class XmppConnectionService extends Service {
|
|||
if (confirmMessages()
|
||||
&& markable != null
|
||||
&& markable.trusted()
|
||||
&& markable.getRemoteMsgId() != null) {
|
||||
&& markable.getRemoteMsgId() != null
|
||||
&& markable.getType() != Message.TYPE_PRIVATE) {
|
||||
Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending read marker to " + markable.getCounterpart().toString());
|
||||
Account account = conversation.getAccount();
|
||||
final Jid to = markable.getCounterpart();
|
||||
MessagePacket packet = mMessageGenerator.confirm(account, to, markable.getRemoteMsgId());
|
||||
final boolean groupChat = conversation.getMode() == Conversation.MODE_MULTI;
|
||||
MessagePacket packet = mMessageGenerator.confirm(account, to, markable.getRemoteMsgId(), groupChat);
|
||||
this.sendMessagePacket(conversation.getAccount(), packet);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@ import android.support.v13.view.inputmethod.InputConnectionCompat;
|
|||
import android.support.v13.view.inputmethod.InputContentInfoCompat;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.ContextMenu;
|
||||
|
@ -49,7 +47,9 @@ import net.java.otr4j.session.SessionStatus;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
|
@ -63,6 +63,7 @@ import eu.siacs.conversations.entities.DownloadableFile;
|
|||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.MucOptions;
|
||||
import eu.siacs.conversations.entities.Presence;
|
||||
import eu.siacs.conversations.entities.ReadByMarker;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.TransferablePlaceholder;
|
||||
import eu.siacs.conversations.http.HttpDownloadConnection;
|
||||
|
@ -75,7 +76,6 @@ import eu.siacs.conversations.ui.adapter.MessageAdapter;
|
|||
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked;
|
||||
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked;
|
||||
import eu.siacs.conversations.ui.widget.EditMessage;
|
||||
import eu.siacs.conversations.ui.widget.ListSelectionManager;
|
||||
import eu.siacs.conversations.utils.MessageUtils;
|
||||
import eu.siacs.conversations.utils.NickValidityChecker;
|
||||
import eu.siacs.conversations.utils.StylingHelper;
|
||||
|
@ -1394,12 +1394,51 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
|||
}
|
||||
}
|
||||
} else {
|
||||
final MucOptions mucOptions = conversation.getMucOptions();
|
||||
final List<MucOptions.User> allUsers = mucOptions.getUsers();
|
||||
final Set<ReadByMarker> addedMarkers = new HashSet<>();
|
||||
ChatState state = ChatState.COMPOSING;
|
||||
List<MucOptions.User> users = conversation.getMucOptions().getUsersWithChatState(state,5);
|
||||
if (users.size() == 0) {
|
||||
state = ChatState.PAUSED;
|
||||
users = conversation.getMucOptions().getUsersWithChatState(state, 5);
|
||||
|
||||
}
|
||||
int markersAdded = 0;
|
||||
if (mucOptions.membersOnly() && mucOptions.nonanonymous()) {
|
||||
//addedMarkers.addAll(ReadByMarker.from(users));
|
||||
for (int i = this.messageList.size() - 1; i >= 0; --i) {
|
||||
final Set<ReadByMarker> markersForMessage = messageList.get(i).getReadByMarkers();
|
||||
final List<MucOptions.User> shownMarkers = new ArrayList<>();
|
||||
for (ReadByMarker marker : markersForMessage) {
|
||||
if (!ReadByMarker.contains(marker, addedMarkers)) {
|
||||
addedMarkers.add(marker); //may be put outside this condition. set should do dedup anyway
|
||||
MucOptions.User user = mucOptions.findUser(marker);
|
||||
if (user != null && !users.contains(user)) {
|
||||
shownMarkers.add(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
final ReadByMarker markerForSender = ReadByMarker.from(messageList.get(i));
|
||||
final Message statusMessage;
|
||||
if (shownMarkers.size() > 1) {
|
||||
statusMessage = Message.createStatusMessage(conversation, getString(R.string.contacts_have_read_up_to_this_point, UIHelper.concatNames(shownMarkers)));
|
||||
statusMessage.setCounterparts(shownMarkers);
|
||||
} else if (shownMarkers.size() == 1) {
|
||||
statusMessage = Message.createStatusMessage(conversation, getString(R.string.contact_has_read_up_to_this_point, UIHelper.getDisplayName(shownMarkers.get(0))));
|
||||
statusMessage.setCounterpart(shownMarkers.get(0).getFullJid());
|
||||
statusMessage.setTrueCounterpart(shownMarkers.get(0).getRealJid());
|
||||
} else {
|
||||
statusMessage = null;
|
||||
}
|
||||
if (statusMessage != null) {
|
||||
++markersAdded;
|
||||
this.messageList.add(i + 1, statusMessage);
|
||||
}
|
||||
addedMarkers.add(markerForSender);
|
||||
if (ReadByMarker.allUsersRepresented(allUsers, addedMarkers)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (users.size() > 0) {
|
||||
Message statusMessage;
|
||||
|
@ -1410,15 +1449,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
|||
statusMessage.setTrueCounterpart(user.getRealJid());
|
||||
statusMessage.setCounterpart(user.getFullJid());
|
||||
} else {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for(MucOptions.User user : users) {
|
||||
if (builder.length() != 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append(UIHelper.getDisplayName(user));
|
||||
}
|
||||
int id = state == ChatState.COMPOSING ? R.string.contacts_are_typing : R.string.contacts_have_stopped_typing;
|
||||
statusMessage = Message.createStatusMessage(conversation, getString(id, builder.toString()));
|
||||
statusMessage = Message.createStatusMessage(conversation, getString(id, UIHelper.concatNames(users)));
|
||||
statusMessage.setCounterparts(users);
|
||||
}
|
||||
this.messageList.add(statusMessage);
|
||||
}
|
||||
|
|
|
@ -6,11 +6,13 @@ import android.content.pm.PackageManager;
|
|||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
|
@ -709,7 +711,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||
showAvatar = true;
|
||||
loadAvatar(message,viewHolder.contact_picture,activity.getPixel(32));
|
||||
} else if (message.getCounterpart() != null ){
|
||||
} else if (message.getCounterpart() != null || message.getTrueCounterpart() != null || (message.getCounterparts() != null && message.getCounterparts().size() > 0)) {
|
||||
showAvatar = true;
|
||||
loadAvatar(message,viewHolder.contact_picture,activity.getPixel(32));
|
||||
} else {
|
||||
|
@ -1052,9 +1054,15 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
if (bm != null) {
|
||||
cancelPotentialWork(message, imageView);
|
||||
imageView.setImageBitmap(bm);
|
||||
imageView.setBackgroundColor(0x00000000);
|
||||
imageView.setBackgroundColor(Color.TRANSPARENT);
|
||||
} else {
|
||||
imageView.setBackgroundColor(UIHelper.getColorForName(UIHelper.getMessageDisplayName(message)));
|
||||
@ColorInt int bg;
|
||||
if (message.getType() == Message.TYPE_STATUS && message.getCounterparts() != null && message.getCounterparts().size() > 1) {
|
||||
bg = Color.TRANSPARENT;
|
||||
} else {
|
||||
bg = UIHelper.getColorForName(UIHelper.getMessageDisplayName(message));
|
||||
}
|
||||
imageView.setBackgroundColor(bg);
|
||||
imageView.setImageDrawable(null);
|
||||
final BitmapWorkerTask task = new BitmapWorkerTask(imageView, size);
|
||||
final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
|
||||
|
|
|
@ -8,6 +8,9 @@ import android.widget.PopupMenu;
|
|||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
@ -28,6 +31,35 @@ import eu.siacs.conversations.xmpp.jid.Jid;
|
|||
|
||||
public class UIHelper {
|
||||
|
||||
|
||||
private static int COLORS[] = {
|
||||
0xFFE91E63, //pink 500
|
||||
0xFFAD1457, //pink 800
|
||||
0xFF9C27B0, //purple 500
|
||||
0xFF6A1B9A, //purple 800
|
||||
0xFF673AB7, //deep purple 500,
|
||||
0xFF4527A0, //deep purple 800,
|
||||
0xFF3F51B5, //indigo 500,
|
||||
0xFF283593, //indigo 800
|
||||
0xFF2196F3, //blue 500
|
||||
0xFF1565C0, //blue 800
|
||||
0xFF03A9F4, //light blue 500
|
||||
0xFF0277BD, //light blue 800
|
||||
0xFF00BCD4, //cyan 500
|
||||
0xFF00838F, //cyan 800
|
||||
0xFF009688, //teal 500,
|
||||
0xFF00695C, //teal 800,
|
||||
//0xFF558B2F, //light green 800
|
||||
0xFFC0CA33, //lime 600
|
||||
0xFF9E9D24, //lime 800
|
||||
0xFFEF6C00, //orange 800
|
||||
0xFFD84315, //deep orange 800,
|
||||
0xFF795548, //brown 500,
|
||||
//0xFF4E342E, //brown 800
|
||||
0xFF607D8B, //blue grey 500,
|
||||
0xFF37474F //blue grey 800
|
||||
};
|
||||
|
||||
private static final List<String> LOCATION_QUESTIONS = Arrays.asList(
|
||||
"where are you", //en
|
||||
"where are you now", //en
|
||||
|
@ -150,10 +182,18 @@ public class UIHelper {
|
|||
if (name == null || name.isEmpty()) {
|
||||
return 0xFF202020;
|
||||
}
|
||||
int colors[] = {0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5,
|
||||
0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722,
|
||||
0xFF795548, 0xFF607d8b};
|
||||
return colors[(int) ((name.hashCode() & 0xffffffffl) % colors.length)];
|
||||
return COLORS[getIntForName(name) % COLORS.length];
|
||||
}
|
||||
|
||||
private static int getIntForName(String name) {
|
||||
try {
|
||||
final MessageDigest messageDigest = MessageDigest.getInstance("MD5");
|
||||
messageDigest.update(name.getBytes());
|
||||
byte[] bytes = messageDigest.digest();
|
||||
return Math.abs(new BigInteger(bytes).intValue());
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static Pair<String,Boolean> getMessagePreview(final Context context, final Message message) {
|
||||
|
@ -312,8 +352,29 @@ public class UIHelper {
|
|||
if (contact != null) {
|
||||
return contact.getDisplayName();
|
||||
} else {
|
||||
return user.getName();
|
||||
final String name = user.getName();
|
||||
if (name != null) {
|
||||
return name;
|
||||
}
|
||||
final Jid realJid = user.getRealJid();
|
||||
if (realJid != null) {
|
||||
return JidHelper.localPartOrFallback(realJid);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String concatNames(List<MucOptions.User> users) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
final boolean shortNames = users.size() >= 3;
|
||||
for(MucOptions.User user : users) {
|
||||
if (builder.length() != 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
final String name = UIHelper.getDisplayName(user);
|
||||
builder.append(shortNames ? name.split("\\s+")[0] : name);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String getFileDescriptionString(final Context context, final Message message) {
|
||||
|
|
|
@ -247,6 +247,7 @@
|
|||
<string name="contact_added_you">Contact added you to contact list</string>
|
||||
<string name="add_back">Add back</string>
|
||||
<string name="contact_has_read_up_to_this_point">%s has read up to this point</string>
|
||||
<string name="contacts_have_read_up_to_this_point">%s have read up to this point</string>
|
||||
<string name="publish">Publish</string>
|
||||
<string name="touch_to_choose_picture">Touch avatar to select picture from gallery</string>
|
||||
<string name="publish_avatar_explanation">Please note: Everyone subscribed to your presence updates will be allowed to see this picture.</string>
|
||||
|
|
Loading…
Reference in New Issue