show avatars in chat
This commit is contained in:
parent
805d0db486
commit
4fae8d4e11
|
@ -212,12 +212,12 @@ public abstract class ChatDao {
|
||||||
+ " accountId=c.accountId AND address=c.address AND vCardPhoto NOT NULL LIMIT 1)"
|
+ " accountId=c.accountId AND address=c.address AND vCardPhoto NOT NULL LIMIT 1)"
|
||||||
+ " ELSE NULL END) as vCardPhoto,(SELECT thumb_id FROM avatar WHERE"
|
+ " ELSE NULL END) as vCardPhoto,(SELECT thumb_id FROM avatar WHERE"
|
||||||
+ " avatar.accountId=c.accountId AND avatar.address=c.address) as avatar,(CASE WHEN"
|
+ " avatar.accountId=c.accountId AND avatar.address=c.address) as avatar,(CASE WHEN"
|
||||||
+ " c.type='MUC' THEN (SELECT count(distinct(df.feature)) == 2 FROM disco_item di"
|
+ " c.type IN ('MUC','MUC_PM') THEN (SELECT count(distinct(df.feature)) == 2 FROM"
|
||||||
+ " JOIN disco_feature df ON di.discoId = df.discoId WHERE di.address=c.address AND"
|
+ " disco_item di JOIN disco_feature df ON di.discoId = df.discoId WHERE"
|
||||||
+ " df.feature IN('muc_membersonly','muc_nonanonymous')) ELSE 0 END) as"
|
+ " di.address=c.address AND df.feature IN('muc_membersonly','muc_nonanonymous'))"
|
||||||
+ " membersOnlyNonAnonymous FROM CHAT c LEFT JOIN message m ON (m.id = (SELECT id"
|
+ " ELSE 0 END) as membersOnlyNonAnonymous FROM CHAT c LEFT JOIN message m ON (m.id"
|
||||||
+ " FROM message WHERE chatId=c.id ORDER by receivedAt DESC LIMIT 1)) WHERE"
|
+ " = (SELECT id FROM message WHERE chatId=c.id ORDER by receivedAt DESC LIMIT 1))"
|
||||||
+ " (:accountId IS NULL OR c.accountId=:accountId) AND (:groupId IS NULL OR"
|
+ " WHERE (:accountId IS NULL OR c.accountId=:accountId) AND (:groupId IS NULL OR"
|
||||||
+ " (c.address IN(SELECT roster.address FROM roster JOIN roster_group ON"
|
+ " (c.address IN(SELECT roster.address FROM roster JOIN roster_group ON"
|
||||||
+ " roster.id=roster_group.rosterItemId WHERE roster.accountId=c.accountId AND"
|
+ " roster.id=roster_group.rosterItemId WHERE roster.accountId=c.accountId AND"
|
||||||
+ " roster_group.groupId=:groupId) OR c.address IN(SELECT address FROM bookmark"
|
+ " roster_group.groupId=:groupId) OR c.address IN(SELECT address FROM bookmark"
|
||||||
|
@ -236,10 +236,11 @@ public abstract class ChatDao {
|
||||||
+ " disco_item.accountId=c.accountId AND disco_item.address=c.address LIMIT 1) as"
|
+ " disco_item.accountId=c.accountId AND disco_item.address=c.address LIMIT 1) as"
|
||||||
+ " discoIdentityName,(SELECT name FROM bookmark WHERE"
|
+ " discoIdentityName,(SELECT name FROM bookmark WHERE"
|
||||||
+ " bookmark.accountId=c.accountId AND bookmark.address=c.address) as"
|
+ " bookmark.accountId=c.accountId AND bookmark.address=c.address) as"
|
||||||
+ " bookmarkName,(CASE WHEN c.type='MUC' THEN (SELECT count(distinct(df.feature))"
|
+ " bookmarkName,(CASE WHEN c.type IN ('MUC','MUC_PM') THEN (SELECT"
|
||||||
+ " == 2 FROM disco_item di JOIN disco_feature df ON di.discoId = df.discoId WHERE"
|
+ " count(distinct(df.feature)) == 2 FROM disco_item di JOIN disco_feature df ON"
|
||||||
+ " di.address=c.address AND df.feature IN('muc_membersonly','muc_nonanonymous'))"
|
+ " di.discoId = df.discoId WHERE di.address=c.address AND df.feature"
|
||||||
+ " ELSE 0 END) as membersOnlyNonAnonymous FROM chat c WHERE c.id=:chatId")
|
+ " IN('muc_membersonly','muc_nonanonymous')) ELSE 0 END) as"
|
||||||
|
+ " membersOnlyNonAnonymous FROM chat c WHERE c.id=:chatId")
|
||||||
public abstract LiveData<ChatInfo> getChatInfo(final long chatId);
|
public abstract LiveData<ChatInfo> getChatInfo(final long chatId);
|
||||||
|
|
||||||
public PagingSource<Integer, ChatOverviewItem> getChatOverview(final ChatFilter chatFilter) {
|
public PagingSource<Integer, ChatOverviewItem> getChatOverview(final ChatFilter chatFilter) {
|
||||||
|
|
|
@ -425,29 +425,57 @@ public abstract class MessageDao {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT message.id as"
|
"SELECT c.accountId,m.id as id,type as"
|
||||||
+ " id,sentAt,outgoing,toBare,toResource,fromBare,fromResource,modification,latestVersion"
|
+ " chatType,sentAt,outgoing,toBare,toResource,fromBare,fromResource,senderIdentity"
|
||||||
+ " as version,inReplyToMessageEntityId,encryption,message_version.identityKey,trust"
|
+ " as sender,(SELECT name FROM roster WHERE roster.accountId=c.accountId AND"
|
||||||
+ " FROM chat JOIN message on message.chatId=chat.id JOIN message_version ON"
|
+ " roster.address=m.senderIdentity) as senderRosterName,(SELECT nick FROM nick"
|
||||||
+ " message.latestVersion=message_version.id LEFT JOIN axolotl_identity ON"
|
+ " WHERE nick.accountId=c.accountId AND nick.address=m.senderIdentity) as"
|
||||||
+ " chat.accountId=axolotl_identity.accountId AND"
|
+ " senderNick,(SELECT vCardPhoto FROM presence WHERE accountId=c.accountId AND"
|
||||||
+ " message.senderIdentity=axolotl_identity.address AND"
|
+ " address=m.senderIdentity AND vCardPhoto NOT NULL LIMIT 1) as"
|
||||||
+ " message_version.identityKey=axolotl_identity.identityKey WHERE chat.id=:chatId"
|
+ " senderVcardPhoto,(SELECT thumb_id FROM avatar WHERE"
|
||||||
+ " AND latestVersion IS NOT NULL ORDER BY message.receivedAt")
|
+ " avatar.accountId=c.accountId AND avatar.address=m.senderIdentity) as"
|
||||||
|
+ " senderAvatar,(CASE WHEN c.type IN ('MUC','MUC_PM') THEN (SELECT vCardPhoto FROM"
|
||||||
|
+ " presence WHERE accountId=c.accountId AND address=c.address AND"
|
||||||
|
+ " occupantId=m.occupantId AND vCardPhoto NOT NULL LIMIT 1) ELSE NULL END) as"
|
||||||
|
+ " occupantVcardPhoto,modification,latestVersion as"
|
||||||
|
+ " version,inReplyToMessageEntityId,encryption,message_version.identityKey,trust,(CASE"
|
||||||
|
+ " WHEN c.type IN ('MUC','MUC_PM') THEN (SELECT count(distinct(df.feature)) == 2"
|
||||||
|
+ " FROM disco_item di JOIN disco_feature df ON di.discoId = df.discoId WHERE"
|
||||||
|
+ " di.address=c.address AND df.feature IN('muc_membersonly','muc_nonanonymous'))"
|
||||||
|
+ " ELSE 0 END) as membersOnlyNonAnonymous FROM chat c JOIN message m on"
|
||||||
|
+ " m.chatId=c.id JOIN message_version ON m.latestVersion=message_version.id LEFT"
|
||||||
|
+ " JOIN axolotl_identity ON c.accountId=axolotl_identity.accountId AND"
|
||||||
|
+ " m.senderIdentity=axolotl_identity.address AND"
|
||||||
|
+ " message_version.identityKey=axolotl_identity.identityKey WHERE c.id=:chatId AND"
|
||||||
|
+ " latestVersion IS NOT NULL ORDER BY m.receivedAt DESC")
|
||||||
public abstract List<MessageWithContentReactions> getMessagesForTesting(long chatId);
|
public abstract List<MessageWithContentReactions> getMessagesForTesting(long chatId);
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT message.id as"
|
"SELECT c.accountId,m.id as id,type as"
|
||||||
+ " id,sentAt,outgoing,toBare,toResource,fromBare,fromResource,modification,latestVersion"
|
+ " chatType,sentAt,outgoing,toBare,toResource,fromBare,fromResource,senderIdentity"
|
||||||
+ " as version,inReplyToMessageEntityId,encryption,message_version.identityKey,trust"
|
+ " as sender,(SELECT name FROM roster WHERE roster.accountId=c.accountId AND"
|
||||||
+ " FROM chat JOIN message on message.chatId=chat.id JOIN message_version ON"
|
+ " roster.address=m.senderIdentity) as senderRosterName,(SELECT nick FROM nick"
|
||||||
+ " message.latestVersion=message_version.id LEFT JOIN axolotl_identity ON"
|
+ " WHERE nick.accountId=c.accountId AND nick.address=m.senderIdentity) as"
|
||||||
+ " chat.accountId=axolotl_identity.accountId AND"
|
+ " senderNick,(SELECT vCardPhoto FROM presence WHERE accountId=c.accountId AND"
|
||||||
+ " message.senderIdentity=axolotl_identity.address AND"
|
+ " address=m.senderIdentity AND vCardPhoto NOT NULL LIMIT 1) as"
|
||||||
+ " message_version.identityKey=axolotl_identity.identityKey WHERE chat.id=:chatId"
|
+ " senderVcardPhoto,(SELECT thumb_id FROM avatar WHERE"
|
||||||
+ " AND latestVersion IS NOT NULL ORDER BY message.receivedAt DESC")
|
+ " avatar.accountId=c.accountId AND avatar.address=m.senderIdentity) as"
|
||||||
public abstract PagingSource<Integer,MessageWithContentReactions> getMessages(long chatId);
|
+ " senderAvatar,(CASE WHEN c.type IN ('MUC','MUC_PM') THEN (SELECT vCardPhoto FROM"
|
||||||
|
+ " presence WHERE accountId=c.accountId AND address=c.address AND"
|
||||||
|
+ " occupantId=m.occupantId AND vCardPhoto NOT NULL LIMIT 1) ELSE NULL END) as"
|
||||||
|
+ " occupantVcardPhoto,modification,latestVersion as"
|
||||||
|
+ " version,inReplyToMessageEntityId,encryption,message_version.identityKey,trust,(CASE"
|
||||||
|
+ " WHEN c.type IN ('MUC','MUC_PM') THEN (SELECT count(distinct(df.feature)) == 2"
|
||||||
|
+ " FROM disco_item di JOIN disco_feature df ON di.discoId = df.discoId WHERE"
|
||||||
|
+ " di.address=c.address AND df.feature IN('muc_membersonly','muc_nonanonymous'))"
|
||||||
|
+ " ELSE 0 END) as membersOnlyNonAnonymous FROM chat c JOIN message m on"
|
||||||
|
+ " m.chatId=c.id JOIN message_version ON m.latestVersion=message_version.id LEFT"
|
||||||
|
+ " JOIN axolotl_identity ON c.accountId=axolotl_identity.accountId AND"
|
||||||
|
+ " m.senderIdentity=axolotl_identity.address AND"
|
||||||
|
+ " message_version.identityKey=axolotl_identity.identityKey WHERE c.id=:chatId AND"
|
||||||
|
+ " latestVersion IS NOT NULL ORDER BY m.receivedAt DESC")
|
||||||
|
public abstract PagingSource<Integer, MessageWithContentReactions> getMessages(long chatId);
|
||||||
|
|
||||||
public void setInReplyTo(
|
public void setInReplyTo(
|
||||||
ChatIdentifier chat,
|
ChatIdentifier chat,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package im.conversations.android.database.model;
|
package im.conversations.android.database.model;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.BareJid;
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
|
||||||
public class ChatInfo {
|
public class ChatInfo implements IndividualName {
|
||||||
|
|
||||||
public long accountId;
|
public long accountId;
|
||||||
public String address;
|
public String address;
|
||||||
|
@ -24,28 +25,6 @@ public class ChatInfo {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private String individualName() {
|
|
||||||
if (notNullNotEmpty(rosterName)) {
|
|
||||||
return rosterName.trim();
|
|
||||||
}
|
|
||||||
if (notNullNotEmpty(nick)) {
|
|
||||||
return nick.trim();
|
|
||||||
}
|
|
||||||
return fallbackName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String fallbackName() {
|
|
||||||
final Jid jid = getJidAddress();
|
|
||||||
if (jid == null) {
|
|
||||||
return this.address;
|
|
||||||
}
|
|
||||||
if (jid.hasLocalpart()) {
|
|
||||||
return jid.getLocalpartOrThrow().toString();
|
|
||||||
} else {
|
|
||||||
return jid.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String mucName() {
|
private String mucName() {
|
||||||
if (notNullNotEmpty(this.bookmarkName)) {
|
if (notNullNotEmpty(this.bookmarkName)) {
|
||||||
return this.bookmarkName.trim();
|
return this.bookmarkName.trim();
|
||||||
|
@ -53,7 +32,29 @@ public class ChatInfo {
|
||||||
if (notNullNotEmpty(this.discoIdentityName)) {
|
if (notNullNotEmpty(this.discoIdentityName)) {
|
||||||
return this.discoIdentityName.trim();
|
return this.discoIdentityName.trim();
|
||||||
}
|
}
|
||||||
return fallbackName();
|
final var jid = getJidAddress();
|
||||||
|
if (jid == null) {
|
||||||
|
return this.address;
|
||||||
|
} else if (jid.hasLocalpart()) {
|
||||||
|
return jid.getLocalpartOrThrow().toString();
|
||||||
|
} else {
|
||||||
|
return jid.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String individualRosterName() {
|
||||||
|
return this.rosterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String individualNick() {
|
||||||
|
return nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BareJid individualAddress() {
|
||||||
|
return address == null ? null : JidCreate.fromOrNull(address).asBareJid();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean notNullNotEmpty(final String value) {
|
private static boolean notNullNotEmpty(final String value) {
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package im.conversations.android.database.model;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.BareJid;
|
||||||
|
|
||||||
|
public interface IndividualName {
|
||||||
|
|
||||||
|
default String individualName() {
|
||||||
|
final var rosterName = individualRosterName();
|
||||||
|
if (notNullNotEmpty(rosterName)) {
|
||||||
|
return rosterName.trim();
|
||||||
|
}
|
||||||
|
final var nick = individualNick();
|
||||||
|
if (notNullNotEmpty(nick)) {
|
||||||
|
return nick.trim();
|
||||||
|
}
|
||||||
|
final var address = individualAddress();
|
||||||
|
if (address == null) {
|
||||||
|
return null;
|
||||||
|
} else if (address.hasLocalpart()) {
|
||||||
|
return address.getLocalpartOrThrow().toString();
|
||||||
|
} else {
|
||||||
|
return address.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String individualRosterName();
|
||||||
|
|
||||||
|
String individualNick();
|
||||||
|
|
||||||
|
BareJid individualAddress();
|
||||||
|
|
||||||
|
private static boolean notNullNotEmpty(final String value) {
|
||||||
|
return value != null && !value.trim().isEmpty();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package im.conversations.android.database.model;
|
package im.conversations.android.database.model;
|
||||||
|
|
||||||
import androidx.room.Relation;
|
import androidx.room.Relation;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableSortedSet;
|
import com.google.common.collect.ImmutableSortedSet;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
@ -12,25 +11,44 @@ import im.conversations.android.database.entity.MessageEntity;
|
||||||
import im.conversations.android.database.entity.MessageReactionEntity;
|
import im.conversations.android.database.entity.MessageReactionEntity;
|
||||||
import im.conversations.android.database.entity.MessageStateEntity;
|
import im.conversations.android.database.entity.MessageStateEntity;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import org.jxmpp.jid.BareJid;
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
import org.jxmpp.jid.parts.Resourcepart;
|
||||||
import org.whispersystems.libsignal.IdentityKey;
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
|
|
||||||
public class MessageWithContentReactions {
|
public class MessageWithContentReactions implements IndividualName {
|
||||||
|
|
||||||
|
public long accountId;
|
||||||
|
|
||||||
public long id;
|
public long id;
|
||||||
|
|
||||||
|
public ChatType chatType;
|
||||||
|
public boolean membersOnlyNonAnonymous;
|
||||||
|
|
||||||
public Instant sentAt;
|
public Instant sentAt;
|
||||||
|
|
||||||
public boolean outgoing;
|
public boolean outgoing;
|
||||||
|
|
||||||
public Jid toBare;
|
public BareJid toBare;
|
||||||
public String toResource;
|
public String toResource;
|
||||||
public Jid fromBare;
|
public BareJid fromBare;
|
||||||
public String fromResource;
|
public Resourcepart fromResource;
|
||||||
|
|
||||||
|
// TODO retrieve occupantResource (current resource inferred by occupant id)
|
||||||
|
|
||||||
|
public BareJid sender;
|
||||||
|
public String senderVcardPhoto;
|
||||||
|
public String senderAvatar;
|
||||||
|
public String senderRosterName;
|
||||||
|
public String senderNick;
|
||||||
|
|
||||||
|
public String occupantVcardPhoto;
|
||||||
|
|
||||||
public Modification modification;
|
public Modification modification;
|
||||||
public long version;
|
public long version;
|
||||||
|
@ -73,9 +91,62 @@ public class MessageWithContentReactions {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String textContent() {
|
public String textContent() {
|
||||||
final var content = Iterables.getFirst(this.contents,null);
|
final var content = Iterables.getFirst(this.contents, null);
|
||||||
final var text = Strings.nullToEmpty(content == null ? null : content.body);
|
final var text = Strings.nullToEmpty(content == null ? null : content.body);
|
||||||
return text;
|
return text;
|
||||||
//return text.substring(0,Math.min(text.length(),20));
|
// return text.substring(0,Math.min(text.length(),20));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressWithName getAddressWithName() {
|
||||||
|
if (isIndividual()) {
|
||||||
|
return new AddressWithName(individualAddress(), individualName());
|
||||||
|
} else {
|
||||||
|
final Jid address = JidCreate.fullFrom(fromBare, fromResource);
|
||||||
|
final String name = fromResource.toString();
|
||||||
|
return new AddressWithName(address, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvatarWithAccount getAvatar() {
|
||||||
|
final var address = getAddressWithName();
|
||||||
|
if (address == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (isIndividual()) {
|
||||||
|
if (this.senderAvatar != null) {
|
||||||
|
return new AvatarWithAccount(accountId, address, AvatarType.PEP, this.senderAvatar);
|
||||||
|
}
|
||||||
|
if (this.senderVcardPhoto != null) {
|
||||||
|
return new AvatarWithAccount(
|
||||||
|
accountId, address, AvatarType.VCARD, this.senderVcardPhoto);
|
||||||
|
}
|
||||||
|
} else if (occupantVcardPhoto != null) {
|
||||||
|
return new AvatarWithAccount(
|
||||||
|
accountId, address, AvatarType.VCARD, this.occupantVcardPhoto);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isIndividual() {
|
||||||
|
return chatType == ChatType.INDIVIDUAL
|
||||||
|
|| (Arrays.asList(ChatType.MUC, ChatType.MUC_PM).contains(chatType)
|
||||||
|
&& membersOnlyNonAnonymous
|
||||||
|
&& sender != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String individualRosterName() {
|
||||||
|
return senderRosterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String individualNick() {
|
||||||
|
return senderNick;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BareJid individualAddress() {
|
||||||
|
return sender;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import im.conversations.android.database.model.ChatInfo;
|
||||||
import im.conversations.android.database.model.ChatOverviewItem;
|
import im.conversations.android.database.model.ChatOverviewItem;
|
||||||
import im.conversations.android.database.model.GroupIdentifier;
|
import im.conversations.android.database.model.GroupIdentifier;
|
||||||
import im.conversations.android.database.model.MessageWithContentReactions;
|
import im.conversations.android.database.model.MessageWithContentReactions;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
|
@ -64,9 +64,7 @@ public class BindingAdapters {
|
||||||
if (sameDay(instant, now) || now.minus(SIX_HOURS).isBefore(instant)) {
|
if (sameDay(instant, now) || now.minus(SIX_HOURS).isBefore(instant)) {
|
||||||
textView.setText(
|
textView.setText(
|
||||||
DateUtils.formatDateTime(
|
DateUtils.formatDateTime(
|
||||||
context,
|
context, instant.toEpochMilli(), DateUtils.FORMAT_SHOW_TIME));
|
||||||
instant.toEpochMilli(),
|
|
||||||
DateUtils.FORMAT_SHOW_TIME));
|
|
||||||
} else if (sameYear(instant, now) || now.minus(THREE_MONTH).isBefore(instant)) {
|
} else if (sameYear(instant, now) || now.minus(THREE_MONTH).isBefore(instant)) {
|
||||||
textView.setText(
|
textView.setText(
|
||||||
DateUtils.formatDateTime(
|
DateUtils.formatDateTime(
|
||||||
|
@ -90,16 +88,14 @@ public class BindingAdapters {
|
||||||
@BindingAdapter("time")
|
@BindingAdapter("time")
|
||||||
public static void setTime(final TextView textView, final Instant instant) {
|
public static void setTime(final TextView textView, final Instant instant) {
|
||||||
if (instant == null || instant.getEpochSecond() <= 0) {
|
if (instant == null || instant.getEpochSecond() <= 0) {
|
||||||
textView.setVisibility(View.GONE);
|
textView.setVisibility(View.INVISIBLE);
|
||||||
} else {
|
} else {
|
||||||
final Context context = textView.getContext();
|
final Context context = textView.getContext();
|
||||||
final Instant now = Instant.now();
|
final Instant now = Instant.now();
|
||||||
textView.setVisibility(View.VISIBLE);
|
textView.setVisibility(View.VISIBLE);
|
||||||
textView.setText(
|
textView.setText(
|
||||||
DateUtils.formatDateTime(
|
DateUtils.formatDateTime(
|
||||||
context,
|
context, instant.toEpochMilli(), DateUtils.FORMAT_SHOW_TIME));
|
||||||
instant.toEpochMilli(),
|
|
||||||
DateUtils.FORMAT_SHOW_TIME));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,10 @@ package im.conversations.android.ui;
|
||||||
import androidx.paging.PagingDataAdapter;
|
import androidx.paging.PagingDataAdapter;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class RecyclerViewScroller {
|
public class RecyclerViewScroller {
|
||||||
|
|
||||||
|
@ -16,7 +14,6 @@ public class RecyclerViewScroller {
|
||||||
|
|
||||||
private final RecyclerView recyclerView;
|
private final RecyclerView recyclerView;
|
||||||
|
|
||||||
|
|
||||||
public RecyclerViewScroller(RecyclerView recyclerView) {
|
public RecyclerViewScroller(RecyclerView recyclerView) {
|
||||||
this.recyclerView = recyclerView;
|
this.recyclerView = recyclerView;
|
||||||
}
|
}
|
||||||
|
@ -35,7 +32,6 @@ public class RecyclerViewScroller {
|
||||||
|
|
||||||
private int accumulatedDelay = 0;
|
private int accumulatedDelay = 0;
|
||||||
|
|
||||||
|
|
||||||
private ReliableScroller(RecyclerView recyclerView) {
|
private ReliableScroller(RecyclerView recyclerView) {
|
||||||
this.recyclerViewReference = new WeakReference<>(recyclerView);
|
this.recyclerViewReference = new WeakReference<>(recyclerView);
|
||||||
}
|
}
|
||||||
|
@ -49,8 +45,15 @@ public class RecyclerViewScroller {
|
||||||
final var isSurroundingRendered = isSurroundingRendered(recyclerView, position);
|
final var isSurroundingRendered = isSurroundingRendered(recyclerView, position);
|
||||||
final var doneUpdating = !recyclerView.hasPendingAdapterUpdates();
|
final var doneUpdating = !recyclerView.hasPendingAdapterUpdates();
|
||||||
final var viewHolder = recyclerView.findViewHolderForAdapterPosition(position);
|
final var viewHolder = recyclerView.findViewHolderForAdapterPosition(position);
|
||||||
LOGGER.info("Item is loaded {}, isSurroundingRendered {}, doneUpdating {} accumulatedDelay {}", isItemLoaded, isSurroundingRendered, doneUpdating, accumulatedDelay);
|
LOGGER.info(
|
||||||
if ((isItemLoaded && isSurroundingRendered && doneUpdating && viewHolder != null) || accumulatedDelay >= MAX_DELAY) {
|
"Item is loaded {}, isSurroundingRendered {}, doneUpdating {} accumulatedDelay"
|
||||||
|
+ " {}",
|
||||||
|
isItemLoaded,
|
||||||
|
isSurroundingRendered,
|
||||||
|
doneUpdating,
|
||||||
|
accumulatedDelay);
|
||||||
|
if ((isItemLoaded && isSurroundingRendered && doneUpdating && viewHolder != null)
|
||||||
|
|| accumulatedDelay >= MAX_DELAY) {
|
||||||
final var layoutManager = recyclerView.getLayoutManager();
|
final var layoutManager = recyclerView.getLayoutManager();
|
||||||
if (viewHolder != null && layoutManager instanceof LinearLayoutManager llm) {
|
if (viewHolder != null && layoutManager instanceof LinearLayoutManager llm) {
|
||||||
final var child = viewHolder.itemView;
|
final var child = viewHolder.itemView;
|
||||||
|
@ -65,12 +68,15 @@ public class RecyclerViewScroller {
|
||||||
}
|
}
|
||||||
recyclerView.scrollToPosition(position);
|
recyclerView.scrollToPosition(position);
|
||||||
accumulatedDelay += INTERVAL;
|
accumulatedDelay += INTERVAL;
|
||||||
recyclerView.postDelayed(()->{
|
recyclerView.postDelayed(
|
||||||
|
() -> {
|
||||||
scrollToPosition(position);
|
scrollToPosition(position);
|
||||||
},INTERVAL);
|
},
|
||||||
|
INTERVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSurroundingRendered(final RecyclerView recyclerView, final int requestedPosition) {
|
private static boolean isSurroundingRendered(
|
||||||
|
final RecyclerView recyclerView, final int requestedPosition) {
|
||||||
final var layoutManager = recyclerView.getLayoutManager();
|
final var layoutManager = recyclerView.getLayoutManager();
|
||||||
if (layoutManager instanceof LinearLayoutManager llm) {
|
if (layoutManager instanceof LinearLayoutManager llm) {
|
||||||
final var first = llm.findFirstVisibleItemPosition();
|
final var first = llm.findFirstVisibleItemPosition();
|
||||||
|
@ -78,14 +84,23 @@ public class RecyclerViewScroller {
|
||||||
if (first == -1 || last == -1) {
|
if (first == -1 || last == -1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final var isItemLoaded = isItemLoaded(recyclerView, first) && isItemLoaded(recyclerView, last);
|
final var isItemLoaded =
|
||||||
|
isItemLoaded(recyclerView, first) && isItemLoaded(recyclerView, last);
|
||||||
if (isItemLoaded) {
|
if (isItemLoaded) {
|
||||||
final var requestedIsOnly = first == requestedPosition && last == requestedPosition;
|
final var requestedIsOnly =
|
||||||
|
first == requestedPosition && last == requestedPosition;
|
||||||
final var firstCompletelyVisible = llm.findFirstCompletelyVisibleItemPosition();
|
final var firstCompletelyVisible = llm.findFirstCompletelyVisibleItemPosition();
|
||||||
final var lastCompletelyVisible = llm.findLastCompletelyVisibleItemPosition();
|
final var lastCompletelyVisible = llm.findLastCompletelyVisibleItemPosition();
|
||||||
|
|
||||||
final var requestedInRange = firstCompletelyVisible <= requestedPosition && requestedPosition <= lastCompletelyVisible;
|
final var requestedInRange =
|
||||||
LOGGER.info("firstComp {} lastComp {} requested {} inRange {}", firstCompletelyVisible, lastCompletelyVisible, requestedPosition, requestedInRange);
|
firstCompletelyVisible <= requestedPosition
|
||||||
|
&& requestedPosition <= lastCompletelyVisible;
|
||||||
|
LOGGER.info(
|
||||||
|
"firstComp {} lastComp {} requested {} inRange {}",
|
||||||
|
firstCompletelyVisible,
|
||||||
|
lastCompletelyVisible,
|
||||||
|
requestedPosition,
|
||||||
|
requestedInRange);
|
||||||
return requestedIsOnly || requestedInRange;
|
return requestedIsOnly || requestedInRange;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -97,7 +112,7 @@ public class RecyclerViewScroller {
|
||||||
|
|
||||||
private static boolean isItemLoaded(final RecyclerView recyclerView, final int position) {
|
private static boolean isItemLoaded(final RecyclerView recyclerView, final int position) {
|
||||||
final var adapter = recyclerView.getAdapter();
|
final var adapter = recyclerView.getAdapter();
|
||||||
if (adapter instanceof PagingDataAdapter<?,?> pagingDataAdapter) {
|
if (adapter instanceof PagingDataAdapter<?, ?> pagingDataAdapter) {
|
||||||
return Objects.nonNull(pagingDataAdapter.peek(position));
|
return Objects.nonNull(pagingDataAdapter.peek(position));
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -3,33 +3,34 @@ package im.conversations.android.ui.adapter;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.databinding.DataBindingUtil;
|
import androidx.databinding.DataBindingUtil;
|
||||||
import androidx.paging.PagingDataAdapter;
|
import androidx.paging.PagingDataAdapter;
|
||||||
import androidx.recyclerview.widget.DiffUtil;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import im.conversations.android.R;
|
import im.conversations.android.R;
|
||||||
import im.conversations.android.database.model.MessageWithContentReactions;
|
import im.conversations.android.database.model.MessageWithContentReactions;
|
||||||
import im.conversations.android.databinding.ItemMessageReceivedBinding;
|
import im.conversations.android.databinding.ItemMessageReceivedBinding;
|
||||||
|
import im.conversations.android.ui.AvatarFetcher;
|
||||||
|
|
||||||
public class MessageAdapter extends PagingDataAdapter<MessageWithContentReactions, MessageAdapter.AbstractMessageViewHolder> {
|
public class MessageAdapter
|
||||||
|
extends PagingDataAdapter<
|
||||||
|
MessageWithContentReactions, MessageAdapter.AbstractMessageViewHolder> {
|
||||||
|
|
||||||
public MessageAdapter(@NonNull DiffUtil.ItemCallback<MessageWithContentReactions> diffCallback) {
|
public MessageAdapter(
|
||||||
|
@NonNull DiffUtil.ItemCallback<MessageWithContentReactions> diffCallback) {
|
||||||
super(diffCallback);
|
super(diffCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public AbstractMessageViewHolder onCreateViewHolder(final @NonNull ViewGroup parent, final int viewType) {
|
public AbstractMessageViewHolder onCreateViewHolder(
|
||||||
|
final @NonNull ViewGroup parent, final int viewType) {
|
||||||
final var layoutInflater = LayoutInflater.from(parent.getContext());
|
final var layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
if (viewType == 0) {
|
if (viewType == 0) {
|
||||||
return new MessageReceivedViewHolder(DataBindingUtil.inflate(
|
return new MessageReceivedViewHolder(
|
||||||
layoutInflater,
|
DataBindingUtil.inflate(
|
||||||
R.layout.item_message_received,
|
layoutInflater, R.layout.item_message_received, parent, false));
|
||||||
parent,
|
|
||||||
false));
|
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException(String.format("viewType %d not implemented", viewType));
|
throw new IllegalArgumentException(String.format("viewType %d not implemented", viewType));
|
||||||
}
|
}
|
||||||
|
@ -41,6 +42,17 @@ public class MessageAdapter extends PagingDataAdapter<MessageWithContentReaction
|
||||||
holder.setMessage(null);
|
holder.setMessage(null);
|
||||||
}
|
}
|
||||||
holder.setMessage(message);
|
holder.setMessage(message);
|
||||||
|
if (holder instanceof MessageReceivedViewHolder messageReceivedViewHolder) {
|
||||||
|
final var addressWithName = message == null ? null : message.getAddressWithName();
|
||||||
|
final var avatar = message == null ? null : message.getAvatar();
|
||||||
|
if (avatar != null) {
|
||||||
|
messageReceivedViewHolder.binding.avatar.setVisibility(View.VISIBLE);
|
||||||
|
AvatarFetcher.fetchInto(messageReceivedViewHolder.binding.avatar, avatar);
|
||||||
|
} else if (addressWithName != null) {
|
||||||
|
messageReceivedViewHolder.binding.avatar.setVisibility(View.VISIBLE);
|
||||||
|
AvatarFetcher.setDefault(messageReceivedViewHolder.binding.avatar, addressWithName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract static class AbstractMessageViewHolder extends RecyclerView.ViewHolder {
|
protected abstract static class AbstractMessageViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
@ -56,7 +68,6 @@ public class MessageAdapter extends PagingDataAdapter<MessageWithContentReaction
|
||||||
|
|
||||||
private final ItemMessageReceivedBinding binding;
|
private final ItemMessageReceivedBinding binding;
|
||||||
|
|
||||||
|
|
||||||
public MessageReceivedViewHolder(@NonNull ItemMessageReceivedBinding binding) {
|
public MessageReceivedViewHolder(@NonNull ItemMessageReceivedBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
|
|
|
@ -2,17 +2,20 @@ package im.conversations.android.ui.adapter;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.DiffUtil;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
|
|
||||||
import im.conversations.android.database.model.MessageWithContentReactions;
|
import im.conversations.android.database.model.MessageWithContentReactions;
|
||||||
|
|
||||||
public class MessageComparator extends DiffUtil.ItemCallback<MessageWithContentReactions> {
|
public class MessageComparator extends DiffUtil.ItemCallback<MessageWithContentReactions> {
|
||||||
@Override
|
@Override
|
||||||
public boolean areItemsTheSame(@NonNull MessageWithContentReactions oldItem, @NonNull MessageWithContentReactions newItem) {
|
public boolean areItemsTheSame(
|
||||||
|
@NonNull MessageWithContentReactions oldItem,
|
||||||
|
@NonNull MessageWithContentReactions newItem) {
|
||||||
return oldItem.id == newItem.id;
|
return oldItem.id == newItem.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areContentsTheSame(@NonNull MessageWithContentReactions oldItem, @NonNull MessageWithContentReactions newItem) {
|
public boolean areContentsTheSame(
|
||||||
|
@NonNull MessageWithContentReactions oldItem,
|
||||||
|
@NonNull MessageWithContentReactions newItem) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,14 @@
|
||||||
package im.conversations.android.ui.fragment.main;
|
package im.conversations.android.ui.fragment.main;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.databinding.DataBindingUtil;
|
import androidx.databinding.DataBindingUtil;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
import androidx.lifecycle.Observer;
|
|
||||||
import androidx.lifecycle.Transformations;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.paging.CombinedLoadStates;
|
|
||||||
import androidx.paging.LoadState;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import im.conversations.android.R;
|
import im.conversations.android.R;
|
||||||
import im.conversations.android.databinding.FragmentChatBinding;
|
import im.conversations.android.databinding.FragmentChatBinding;
|
||||||
import im.conversations.android.ui.Activities;
|
import im.conversations.android.ui.Activities;
|
||||||
|
@ -26,14 +17,9 @@ import im.conversations.android.ui.RecyclerViewScroller;
|
||||||
import im.conversations.android.ui.adapter.MessageAdapter;
|
import im.conversations.android.ui.adapter.MessageAdapter;
|
||||||
import im.conversations.android.ui.adapter.MessageComparator;
|
import im.conversations.android.ui.adapter.MessageComparator;
|
||||||
import im.conversations.android.ui.model.ChatViewModel;
|
import im.conversations.android.ui.model.ChatViewModel;
|
||||||
import kotlin.Unit;
|
|
||||||
import kotlin.jvm.functions.Function1;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class ChatFragment extends Fragment {
|
public class ChatFragment extends Fragment {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ChatFragment.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(ChatFragment.class);
|
||||||
|
@ -56,7 +42,7 @@ public class ChatFragment extends Fragment {
|
||||||
this.binding.setChatViewModel(this.chatViewModel);
|
this.binding.setChatViewModel(this.chatViewModel);
|
||||||
this.binding.setLifecycleOwner(getViewLifecycleOwner());
|
this.binding.setLifecycleOwner(getViewLifecycleOwner());
|
||||||
final var linearLayoutManager = new LinearLayoutManager(requireContext());
|
final var linearLayoutManager = new LinearLayoutManager(requireContext());
|
||||||
//linearLayoutManager.setStackFromEnd(true);
|
// linearLayoutManager.setStackFromEnd(true);
|
||||||
linearLayoutManager.setReverseLayout(true);
|
linearLayoutManager.setReverseLayout(true);
|
||||||
this.binding.messages.setLayoutManager(linearLayoutManager);
|
this.binding.messages.setLayoutManager(linearLayoutManager);
|
||||||
this.recyclerViewScroller = new RecyclerViewScroller(this.binding.messages);
|
this.recyclerViewScroller = new RecyclerViewScroller(this.binding.messages);
|
||||||
|
@ -76,9 +62,9 @@ public class ChatFragment extends Fragment {
|
||||||
NavControllers.findNavController(requireActivity(), R.id.nav_host_fragment)
|
NavControllers.findNavController(requireActivity(), R.id.nav_host_fragment)
|
||||||
.popBackStack();
|
.popBackStack();
|
||||||
});
|
});
|
||||||
this.binding.addContent.setOnClickListener(v ->{
|
this.binding.addContent.setOnClickListener(
|
||||||
|
v -> {
|
||||||
scrollToPosition(messageAdapter.getItemCount() - 1);
|
scrollToPosition(messageAdapter.getItemCount() - 1);
|
||||||
|
|
||||||
});
|
});
|
||||||
this.binding.messageLayout.setEndIconOnClickListener(
|
this.binding.messageLayout.setEndIconOnClickListener(
|
||||||
v -> {
|
v -> {
|
||||||
|
@ -89,8 +75,7 @@ public class ChatFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scrollToPosition(final int position) {
|
private void scrollToPosition(final int position) {
|
||||||
LOGGER.info("scrollToPosition({})",position);
|
LOGGER.info("scrollToPosition({})", position);
|
||||||
this.recyclerViewScroller.scrollToPosition(position);
|
this.recyclerViewScroller.scrollToPosition(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,13 +11,9 @@ import androidx.paging.Pager;
|
||||||
import androidx.paging.PagingConfig;
|
import androidx.paging.PagingConfig;
|
||||||
import androidx.paging.PagingData;
|
import androidx.paging.PagingData;
|
||||||
import androidx.paging.PagingLiveData;
|
import androidx.paging.PagingLiveData;
|
||||||
|
|
||||||
import im.conversations.android.database.model.ChatInfo;
|
import im.conversations.android.database.model.ChatInfo;
|
||||||
import im.conversations.android.database.model.ChatOverviewItem;
|
|
||||||
import im.conversations.android.database.model.MessageWithContentReactions;
|
import im.conversations.android.database.model.MessageWithContentReactions;
|
||||||
import im.conversations.android.repository.ChatRepository;
|
import im.conversations.android.repository.ChatRepository;
|
||||||
import kotlinx.coroutines.CoroutineScope;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -37,7 +33,10 @@ public class ChatViewModel extends AndroidViewModel {
|
||||||
Transformations.switchMap(
|
Transformations.switchMap(
|
||||||
this.chatId,
|
this.chatId,
|
||||||
chatId -> chatId == null ? null : chatRepository.getChatInfo(chatId));
|
chatId -> chatId == null ? null : chatRepository.getChatInfo(chatId));
|
||||||
final var messages = Transformations.switchMap(this.chatId, chatId -> {
|
final var messages =
|
||||||
|
Transformations.switchMap(
|
||||||
|
this.chatId,
|
||||||
|
chatId -> {
|
||||||
final Pager<Integer, MessageWithContentReactions> pager =
|
final Pager<Integer, MessageWithContentReactions> pager =
|
||||||
new Pager<>(
|
new Pager<>(
|
||||||
new PagingConfig(30),
|
new PagingConfig(30),
|
||||||
|
@ -46,7 +45,6 @@ public class ChatViewModel extends AndroidViewModel {
|
||||||
});
|
});
|
||||||
final var viewModelScope = ViewModelKt.getViewModelScope(this);
|
final var viewModelScope = ViewModelKt.getViewModelScope(this);
|
||||||
this.messages = PagingLiveData.cachedIn(messages, viewModelScope);
|
this.messages = PagingLiveData.cachedIn(messages, viewModelScope);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChatId(final long chatId) {
|
public void setChatId(final long chatId) {
|
||||||
|
|
|
@ -157,7 +157,8 @@ public class AvatarManager extends AbstractManager {
|
||||||
final var photo = vcard.getExtension(Photo.class);
|
final var photo = vcard.getExtension(Photo.class);
|
||||||
final var binary = photo == null ? null : photo.getExtension(BinaryValue.class);
|
final var binary = photo == null ? null : photo.getExtension(BinaryValue.class);
|
||||||
if (binary == null) {
|
if (binary == null) {
|
||||||
throw new IllegalStateException("vCard did not have embedded photo");
|
throw new IllegalStateException(
|
||||||
|
String.format("vCard for %s did not have embedded photo", address));
|
||||||
}
|
}
|
||||||
return binary.asBytes();
|
return binary.asBytes();
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue