rudimentary ChatOverviewAdapter
This commit is contained in:
parent
cfaf6162e6
commit
260654f171
|
@ -86,8 +86,11 @@ dependencies {
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:$rootProject.ext.roomVersion"
|
implementation "androidx.room:room-runtime:$rootProject.ext.roomVersion"
|
||||||
implementation "androidx.room:room-guava:$rootProject.ext.roomVersion"
|
implementation "androidx.room:room-guava:$rootProject.ext.roomVersion"
|
||||||
|
implementation "androidx.room:room-paging:$rootProject.ext.roomVersion"
|
||||||
annotationProcessor "androidx.room:room-compiler:$rootProject.ext.roomVersion"
|
annotationProcessor "androidx.room:room-compiler:$rootProject.ext.roomVersion"
|
||||||
|
|
||||||
|
implementation "androidx.paging:paging-runtime:$rootProject.ext.pagingVersion"
|
||||||
|
|
||||||
implementation "androidx.preference:preference:$rootProject.ext.preferenceVersion"
|
implementation "androidx.preference:preference:$rootProject.ext.preferenceVersion"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ public class MessageTransformationTest {
|
||||||
|
|
||||||
private static final BareJid ACCOUNT = JidCreate.bareFromOrThrowUnchecked("user@example.com");
|
private static final BareJid ACCOUNT = JidCreate.bareFromOrThrowUnchecked("user@example.com");
|
||||||
private static final BareJid REMOTE = JidCreate.bareFromOrThrowUnchecked("juliet@example.com");
|
private static final BareJid REMOTE = JidCreate.bareFromOrThrowUnchecked("juliet@example.com");
|
||||||
|
private static final BareJid REMOTE_2 = JidCreate.bareFromOrThrowUnchecked("romeo@example.com");
|
||||||
|
|
||||||
private static final String GREETING = "Hi Juliet. How are you?";
|
private static final String GREETING = "Hi Juliet. How are you?";
|
||||||
|
|
||||||
|
@ -551,4 +552,37 @@ public class MessageTransformationTest {
|
||||||
Assert.assertEquals(Modification.RETRACTION, message.modification);
|
Assert.assertEquals(Modification.RETRACTION, message.modification);
|
||||||
Assert.assertEquals(PartType.RETRACTION, Iterables.getOnlyElement(message.contents).type);
|
Assert.assertEquals(PartType.RETRACTION, Iterables.getOnlyElement(message.contents).type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void twoChatThreeMessages() throws XmppStringprepException {
|
||||||
|
final var m1 = new Message();
|
||||||
|
m1.setId("1");
|
||||||
|
m1.setTo(REMOTE);
|
||||||
|
m1.setFrom(JidCreate.fullFrom(ACCOUNT, Resourcepart.from("junit")));
|
||||||
|
m1.addExtension(new Body("Hi. How are you?"));
|
||||||
|
|
||||||
|
this.transformer.transform(
|
||||||
|
MessageTransformation.of(
|
||||||
|
m1, Instant.now(), REMOTE, null, m1.getFrom().asBareJid(), null));
|
||||||
|
|
||||||
|
final var m2 = new Message();
|
||||||
|
m2.setId("2");
|
||||||
|
m2.setTo(REMOTE);
|
||||||
|
m2.setFrom(JidCreate.fullFrom(ACCOUNT, Resourcepart.from("junit")));
|
||||||
|
m2.addExtension(new Body("Please answer"));
|
||||||
|
|
||||||
|
this.transformer.transform(
|
||||||
|
MessageTransformation.of(
|
||||||
|
m2, Instant.now(), REMOTE, null, m2.getFrom().asBareJid(), null));
|
||||||
|
|
||||||
|
final var m3 = new Message();
|
||||||
|
m3.setId("3");
|
||||||
|
m3.setTo(REMOTE_2);
|
||||||
|
m3.setFrom(JidCreate.fullFrom(ACCOUNT, Resourcepart.from("junit")));
|
||||||
|
m3.addExtension(new Body("Another message"));
|
||||||
|
|
||||||
|
this.transformer.transform(
|
||||||
|
MessageTransformation.of(
|
||||||
|
m3, Instant.now(), REMOTE, null, m3.getFrom().asBareJid(), null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package im.conversations.android.database.dao;
|
package im.conversations.android.database.dao;
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.paging.PagingSource;
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
import androidx.room.Insert;
|
import androidx.room.Insert;
|
||||||
import androidx.room.Query;
|
import androidx.room.Query;
|
||||||
|
@ -10,6 +11,7 @@ import im.conversations.android.database.entity.ChatEntity;
|
||||||
import im.conversations.android.database.entity.MucStatusCodeEntity;
|
import im.conversations.android.database.entity.MucStatusCodeEntity;
|
||||||
import im.conversations.android.database.model.Account;
|
import im.conversations.android.database.model.Account;
|
||||||
import im.conversations.android.database.model.ChatIdentifier;
|
import im.conversations.android.database.model.ChatIdentifier;
|
||||||
|
import im.conversations.android.database.model.ChatOverviewItem;
|
||||||
import im.conversations.android.database.model.ChatType;
|
import im.conversations.android.database.model.ChatType;
|
||||||
import im.conversations.android.database.model.GroupIdentifier;
|
import im.conversations.android.database.model.GroupIdentifier;
|
||||||
import im.conversations.android.database.model.MucState;
|
import im.conversations.android.database.model.MucState;
|
||||||
|
@ -129,7 +131,7 @@ public abstract class ChatDao {
|
||||||
protected abstract List<String> getChatsNotInBookmarks(long account, ChatType chatType);
|
protected abstract List<String> getChatsNotInBookmarks(long account, ChatType chatType);
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT bookmark.address FROM bookmark WHERE bookmark.accountId=accountId AND"
|
"SELECT bookmark.address FROM bookmark WHERE bookmark.accountId=:account AND"
|
||||||
+ " bookmark.autoJoin=1 EXCEPT SELECT chat.address FROM chat WHERE"
|
+ " bookmark.autoJoin=1 EXCEPT SELECT chat.address FROM chat WHERE"
|
||||||
+ " chat.accountId=:account AND chat.type=:chatType AND archived=0")
|
+ " chat.accountId=:account AND chat.type=:chatType AND archived=0")
|
||||||
protected abstract List<Jid> getBookmarksNotInChats(long account, ChatType chatType);
|
protected abstract List<Jid> getBookmarksNotInChats(long account, ChatType chatType);
|
||||||
|
@ -186,4 +188,25 @@ public abstract class ChatDao {
|
||||||
|
|
||||||
@Query("DELETE FROM muc_status_code WHERE chatId=:chatId")
|
@Query("DELETE FROM muc_status_code WHERE chatId=:chatId")
|
||||||
protected abstract void deleteStatusCodes(final long chatId);
|
protected abstract void deleteStatusCodes(final long chatId);
|
||||||
|
|
||||||
|
// TODO select vCardPhoto for c.type='MUC_PM'
|
||||||
|
@Transaction
|
||||||
|
@Query(
|
||||||
|
"SELECT c.id,c.accountId,c.address,c.type,m.sentAt,m.outgoing,m.latestVersion as"
|
||||||
|
+ " version,m.toBare,m.toResource,m.fromBare,m.fromResource,(SELECT count(id) FROM"
|
||||||
|
+ " message WHERE chatId=c.id) as unread,(SELECT name FROM roster WHERE"
|
||||||
|
+ " roster.address=c.address) as rosterName,(SELECT nick FROM nick WHERE"
|
||||||
|
+ " nick.address=c.address) as nick,(SELECT identity.name FROM disco_item JOIN"
|
||||||
|
+ " disco_identity identity ON disco_item.discoId=identity.discoId WHERE"
|
||||||
|
+ " disco_item.address=c.address LIMIT 1) as discoIdentityName,(SELECT name FROM"
|
||||||
|
+ " bookmark WHERE bookmark.address=c.address) as bookmarkName,(CASE WHEN"
|
||||||
|
+ " c.type='MUC' THEN (SELECT vCardPhoto FROM presence WHERE address=c.address AND"
|
||||||
|
+ " resource='') WHEN c.type='INDIVIDUAL' THEN (SELECT vCardPhoto FROM presence"
|
||||||
|
+ " WHERE address=c.address AND vCardPhoto NOT NULL LIMIT 1) ELSE NULL END) as"
|
||||||
|
+ " vCardPhoto,(SELECT thumb_id FROM avatar WHERE avatar.address=c.address) as"
|
||||||
|
+ " avatar FROM CHAT c LEFT JOIN message m ON (c.id=m.chatId) LEFT OUTER JOIN"
|
||||||
|
+ " message m2 ON (c.id = m2.chatId AND (m.receivedAt < m2.receivedAt OR"
|
||||||
|
+ " (m.receivedAt = m2.receivedAt AND m.id < m2.id))) WHERE c.archived=0 AND m2.id"
|
||||||
|
+ " IS NULL ORDER by m.receivedAt DESC")
|
||||||
|
public abstract PagingSource<Integer, ChatOverviewItem> getChatOverview();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
package im.conversations.android.database.model;
|
||||||
|
|
||||||
|
import androidx.room.Relation;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import im.conversations.android.database.entity.MessageContentEntity;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import org.jxmpp.jid.Jid;
|
||||||
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
|
||||||
|
public class ChatOverviewItem {
|
||||||
|
|
||||||
|
public long id;
|
||||||
|
public long accountId;
|
||||||
|
public String address;
|
||||||
|
public ChatType type;
|
||||||
|
|
||||||
|
public Instant sentAt;
|
||||||
|
|
||||||
|
public boolean outgoing;
|
||||||
|
|
||||||
|
public Jid toBare;
|
||||||
|
public String toResource;
|
||||||
|
public Jid fromBare;
|
||||||
|
public String fromResource;
|
||||||
|
public long version;
|
||||||
|
|
||||||
|
public String rosterName;
|
||||||
|
public String nick;
|
||||||
|
public String discoIdentityName;
|
||||||
|
public String bookmarkName;
|
||||||
|
|
||||||
|
public String vCardPhoto;
|
||||||
|
public String avatar;
|
||||||
|
|
||||||
|
public int unread;
|
||||||
|
|
||||||
|
@Relation(
|
||||||
|
entity = MessageContentEntity.class,
|
||||||
|
parentColumn = "version",
|
||||||
|
entityColumn = "messageVersionId")
|
||||||
|
public List<MessageContent> contents;
|
||||||
|
|
||||||
|
public String name() {
|
||||||
|
return switch (type) {
|
||||||
|
case MUC -> mucName();
|
||||||
|
case INDIVIDUAL -> individualName();
|
||||||
|
default -> address;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public String message() {
|
||||||
|
final var firstMessageContent = Iterables.getFirst(contents, null);
|
||||||
|
return firstMessageContent == null ? null : firstMessageContent.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sender getSender() {
|
||||||
|
if (outgoing) {
|
||||||
|
return new SenderYou();
|
||||||
|
} else if (type == ChatType.MUC) {
|
||||||
|
if (fromResource != null) {
|
||||||
|
return new SenderName(fromResource);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
if (notNullNotEmpty(this.bookmarkName)) {
|
||||||
|
return this.bookmarkName.trim();
|
||||||
|
}
|
||||||
|
if (notNullNotEmpty(this.discoIdentityName)) {
|
||||||
|
return this.discoIdentityName.trim();
|
||||||
|
}
|
||||||
|
return fallbackName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Jid getJidAddress() {
|
||||||
|
return address == null ? null : JidCreate.fromOrNull(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean notNullNotEmpty(final String value) {
|
||||||
|
return value != null && !value.trim().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed interface Sender permits SenderYou, SenderName {}
|
||||||
|
|
||||||
|
public static final class SenderYou implements Sender {}
|
||||||
|
|
||||||
|
public static final class SenderName implements Sender {
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
public SenderName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,8 @@ package im.conversations.android.repository;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.paging.PagingSource;
|
||||||
|
import im.conversations.android.database.model.ChatOverviewItem;
|
||||||
import im.conversations.android.database.model.GroupIdentifier;
|
import im.conversations.android.database.model.GroupIdentifier;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -14,4 +16,8 @@ public class ChatRepository extends AbstractRepository {
|
||||||
public LiveData<List<GroupIdentifier>> getGroups() {
|
public LiveData<List<GroupIdentifier>> getGroups() {
|
||||||
return this.database.chatDao().getGroups();
|
return this.database.chatDao().getGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PagingSource<Integer, ChatOverviewItem> getChatOverview() {
|
||||||
|
return this.database.chatDao().getChatOverview();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,29 @@
|
||||||
package im.conversations.android.ui;
|
package im.conversations.android.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.format.DateUtils;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.databinding.BindingAdapter;
|
import androidx.databinding.BindingAdapter;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import com.google.android.material.textfield.TextInputEditText;
|
import com.google.android.material.textfield.TextInputEditText;
|
||||||
import com.google.android.material.textfield.TextInputLayout;
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
|
import im.conversations.android.R;
|
||||||
|
import im.conversations.android.database.model.ChatOverviewItem;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
public class BindingAdapters {
|
public class BindingAdapters {
|
||||||
|
|
||||||
|
private static final Duration SIX_HOURS = Duration.ofHours(6);
|
||||||
|
private static final Duration THREE_MONTH = Duration.ofDays(90);
|
||||||
|
|
||||||
@BindingAdapter("errorText")
|
@BindingAdapter("errorText")
|
||||||
public static void setErrorText(
|
public static void setErrorText(
|
||||||
final TextInputLayout textInputLayout, final LiveData<String> error) {
|
final TextInputLayout textInputLayout, final LiveData<String> error) {
|
||||||
|
@ -28,4 +42,63 @@ public class BindingAdapters {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean sameYear(final Instant a, final Instant b) {
|
||||||
|
final ZoneId local = ZoneId.systemDefault();
|
||||||
|
return LocalDateTime.ofInstant(a, local).getYear()
|
||||||
|
== LocalDateTime.ofInstant(b, local).getYear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean sameDay(final Instant a, final Instant b) {
|
||||||
|
return a.truncatedTo(ChronoUnit.DAYS).equals(b.truncatedTo(ChronoUnit.DAYS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("instant")
|
||||||
|
public static void setInstant(final TextView textView, final Instant instant) {
|
||||||
|
if (instant == null || instant.getEpochSecond() <= 0) {
|
||||||
|
textView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
final Context context = textView.getContext();
|
||||||
|
final Instant now = Instant.now();
|
||||||
|
textView.setVisibility(View.VISIBLE);
|
||||||
|
if (sameDay(instant, now) || now.minus(SIX_HOURS).isBefore(instant)) {
|
||||||
|
textView.setText(
|
||||||
|
DateUtils.formatDateTime(
|
||||||
|
context,
|
||||||
|
instant.getEpochSecond() * 1000,
|
||||||
|
DateUtils.FORMAT_SHOW_TIME));
|
||||||
|
} else if (sameYear(instant, now) || now.minus(THREE_MONTH).isBefore(instant)) {
|
||||||
|
textView.setText(
|
||||||
|
DateUtils.formatDateTime(
|
||||||
|
context,
|
||||||
|
instant.getEpochSecond() * 1000,
|
||||||
|
DateUtils.FORMAT_SHOW_DATE
|
||||||
|
| DateUtils.FORMAT_NO_YEAR
|
||||||
|
| DateUtils.FORMAT_ABBREV_ALL));
|
||||||
|
} else {
|
||||||
|
textView.setText(
|
||||||
|
DateUtils.formatDateTime(
|
||||||
|
context,
|
||||||
|
instant.getEpochSecond() * 1000,
|
||||||
|
DateUtils.FORMAT_SHOW_DATE
|
||||||
|
| DateUtils.FORMAT_NO_MONTH_DAY
|
||||||
|
| DateUtils.FORMAT_ABBREV_ALL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("android:text")
|
||||||
|
public static void setSender(final TextView textView, final ChatOverviewItem.Sender sender) {
|
||||||
|
if (sender == null) {
|
||||||
|
textView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
if (sender instanceof ChatOverviewItem.SenderYou) {
|
||||||
|
textView.setText(
|
||||||
|
String.format("%s:", textView.getContext().getString(R.string.you)));
|
||||||
|
} else if (sender instanceof ChatOverviewItem.SenderName senderName) {
|
||||||
|
textView.setText(String.format("%s:", senderName.name));
|
||||||
|
}
|
||||||
|
textView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package im.conversations.android.ui.adapter;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.databinding.DataBindingUtil;
|
||||||
|
import androidx.paging.PagingDataAdapter;
|
||||||
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import im.conversations.android.R;
|
||||||
|
import im.conversations.android.database.model.ChatOverviewItem;
|
||||||
|
import im.conversations.android.databinding.ItemChatoverviewBinding;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class ChatOverviewAdapter
|
||||||
|
extends PagingDataAdapter<ChatOverviewItem, ChatOverviewAdapter.ChatOverviewViewHolder> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ChatOverviewAdapter.class);
|
||||||
|
|
||||||
|
public ChatOverviewAdapter(@NonNull DiffUtil.ItemCallback<ChatOverviewItem> diffCallback) {
|
||||||
|
super(diffCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ChatOverviewViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
return new ChatOverviewViewHolder(
|
||||||
|
DataBindingUtil.inflate(
|
||||||
|
LayoutInflater.from(parent.getContext()),
|
||||||
|
R.layout.item_chatoverview,
|
||||||
|
parent,
|
||||||
|
false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ChatOverviewViewHolder holder, int position) {
|
||||||
|
final var chatOverviewItem = getItem(position);
|
||||||
|
holder.binding.setChatOverviewItem(chatOverviewItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ChatOverviewViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
private final ItemChatoverviewBinding binding;
|
||||||
|
|
||||||
|
public ChatOverviewViewHolder(@NonNull ItemChatoverviewBinding binding) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package im.conversations.android.ui.adapter;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
|
import im.conversations.android.database.model.ChatOverviewItem;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class ChatOverviewComparator extends DiffUtil.ItemCallback<ChatOverviewItem> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ChatOverviewComparator.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areItemsTheSame(
|
||||||
|
@NonNull ChatOverviewItem oldItem, @NonNull ChatOverviewItem newItem) {
|
||||||
|
// LOGGER.info("areItemsTheSame({},{})", oldItem.id, newItem.id);
|
||||||
|
return oldItem.id == newItem.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(
|
||||||
|
@NonNull ChatOverviewItem oldItem, @NonNull ChatOverviewItem newItem) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,8 @@ import im.conversations.android.databinding.FragmentOverviewBinding;
|
||||||
import im.conversations.android.ui.Intents;
|
import im.conversations.android.ui.Intents;
|
||||||
import im.conversations.android.ui.activity.SettingsActivity;
|
import im.conversations.android.ui.activity.SettingsActivity;
|
||||||
import im.conversations.android.ui.activity.SetupActivity;
|
import im.conversations.android.ui.activity.SetupActivity;
|
||||||
|
import im.conversations.android.ui.adapter.ChatOverviewAdapter;
|
||||||
|
import im.conversations.android.ui.adapter.ChatOverviewComparator;
|
||||||
import im.conversations.android.ui.model.OverviewViewModel;
|
import im.conversations.android.ui.model.OverviewViewModel;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -82,6 +84,15 @@ public class OverviewFragment extends Fragment {
|
||||||
.getChatFilterAvailable()
|
.getChatFilterAvailable()
|
||||||
.observe(getViewLifecycleOwner(), this::onChatFilterAvailable);
|
.observe(getViewLifecycleOwner(), this::onChatFilterAvailable);
|
||||||
this.configureDrawerLayoutToCloseOnBackPress();
|
this.configureDrawerLayoutToCloseOnBackPress();
|
||||||
|
final var chatOverviewAdapter = new ChatOverviewAdapter(new ChatOverviewComparator());
|
||||||
|
binding.chats.setAdapter(chatOverviewAdapter);
|
||||||
|
this.overviewViewModel
|
||||||
|
.getChats()
|
||||||
|
.observe(
|
||||||
|
getViewLifecycleOwner(),
|
||||||
|
pagingData -> {
|
||||||
|
chatOverviewAdapter.submitData(getLifecycle(), pagingData);
|
||||||
|
});
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,19 @@ import androidx.lifecycle.AndroidViewModel;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MediatorLiveData;
|
import androidx.lifecycle.MediatorLiveData;
|
||||||
import androidx.lifecycle.Transformations;
|
import androidx.lifecycle.Transformations;
|
||||||
|
import androidx.lifecycle.ViewModelKt;
|
||||||
|
import androidx.paging.Pager;
|
||||||
|
import androidx.paging.PagingConfig;
|
||||||
|
import androidx.paging.PagingData;
|
||||||
|
import androidx.paging.PagingLiveData;
|
||||||
import im.conversations.android.database.model.AccountIdentifier;
|
import im.conversations.android.database.model.AccountIdentifier;
|
||||||
import im.conversations.android.database.model.ChatFilter;
|
import im.conversations.android.database.model.ChatFilter;
|
||||||
|
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.repository.AccountRepository;
|
import im.conversations.android.repository.AccountRepository;
|
||||||
import im.conversations.android.repository.ChatRepository;
|
import im.conversations.android.repository.ChatRepository;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import kotlinx.coroutines.CoroutineScope;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -65,4 +72,17 @@ public class OverviewViewModel extends AndroidViewModel {
|
||||||
this.chatFilter = chatFilter;
|
this.chatFilter = chatFilter;
|
||||||
LOGGER.info("Setting chat filter to {}", chatFilter);
|
LOGGER.info("Setting chat filter to {}", chatFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LiveData<PagingData<ChatOverviewItem>> getChats() {
|
||||||
|
final Pager<Integer, ChatOverviewItem> pager =
|
||||||
|
new Pager<>(
|
||||||
|
new PagingConfig(20),
|
||||||
|
() -> {
|
||||||
|
return this.chatRepository.getChatOverview();
|
||||||
|
});
|
||||||
|
|
||||||
|
LiveData<PagingData<ChatOverviewItem>> foo = PagingLiveData.getLiveData(pager);
|
||||||
|
CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(this);
|
||||||
|
return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), viewModelScope);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,11 @@
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/chats"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/item_chatoverview"
|
||||||
android:fillViewport="true"
|
android:fillViewport="true"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
|
|
76
app/src/main/res/layout/item_chatoverview.xml
Normal file
76
app/src/main/res/layout/item_chatoverview.xml
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="72dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/name"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/name">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@{chatOverviewItem.name}"
|
||||||
|
android:textAppearance="?textAppearanceTitleMedium"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/message"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/sentAt"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.45"
|
||||||
|
tools:text="The City of Verona" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sender"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="4sp"
|
||||||
|
android:text="@{chatOverviewItem.sender}"
|
||||||
|
android:textAppearance="?textAppearanceBodyMedium"
|
||||||
|
android:textColor="?colorTertiary"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:layout_constraintBaseline_toBaselineOf="@+id/message"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/message"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/name"
|
||||||
|
android:maxLines="1"
|
||||||
|
tools:text="You:" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/message"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@{chatOverviewItem.message}"
|
||||||
|
android:textAppearance="?textAppearanceBodyMedium"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/sentAt"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/sender"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/name"
|
||||||
|
tools:text="O Romeo, Romeo! Wherefore art thou Romeo?" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sentAt"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:textAppearance="?textAppearanceLabelMedium"
|
||||||
|
app:instant="@{chatOverviewItem.sentAt}"
|
||||||
|
app:layout_constraintBaseline_toBaselineOf="@+id/name"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
tools:text="23:24" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<import type="android.view.View" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="chatOverviewItem"
|
||||||
|
type="im.conversations.android.database.model.ChatOverviewItem" />
|
||||||
|
</data>
|
||||||
|
</layout>
|
|
@ -1046,5 +1046,6 @@
|
||||||
<string name="trust_certificate_instructions">To continue compare this SHA-256 fingerprint with that of the server certificate</string>
|
<string name="trust_certificate_instructions">To continue compare this SHA-256 fingerprint with that of the server certificate</string>
|
||||||
<string name="trust_certificate_warning">The server certificate is not trustworthy. If you don’t know what this means it’s best to go back!</string>
|
<string name="trust_certificate_warning">The server certificate is not trustworthy. If you don’t know what this means it’s best to go back!</string>
|
||||||
<string name="trust_cerficate">Trust certificate</string>
|
<string name="trust_cerficate">Trust certificate</string>
|
||||||
|
<string name="you">You</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -7,6 +7,7 @@ buildscript {
|
||||||
roomVersion = "2.5.0"
|
roomVersion = "2.5.0"
|
||||||
preferenceVersion = "1.2.0"
|
preferenceVersion = "1.2.0"
|
||||||
espressoVersion = "3.5.1"
|
espressoVersion = "3.5.1"
|
||||||
|
pagingVersion = "3.1.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|
Loading…
Reference in a new issue