From d393b79ced2c7db2a80688a75d204aa3147779dc Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 7 Jan 2021 08:52:51 -0300 Subject: [PATCH] Submit thread list items to ListAdapter --- .../briar/android/forum/ForumActivity.java | 7 +- .../briar/android/forum/ForumController.java | 3 +- .../android/forum/ForumControllerImpl.java | 13 +-- .../briar/android/forum/ForumViewModel.java | 36 +++++- .../conversation/GroupActivity.java | 24 ++-- .../conversation/GroupController.java | 3 +- .../conversation/GroupControllerImpl.java | 18 +-- .../conversation/GroupMessageAdapter.java | 18 ++- .../conversation/GroupViewModel.java | 51 ++++++++- .../android/threaded/NestedTreeList.java | 54 --------- .../briar/android/threaded/ThreadItem.java | 10 ++ .../android/threaded/ThreadItemAdapter.java | 105 +++++++----------- .../android/threaded/ThreadListActivity.java | 73 +++++------- .../threaded/ThreadListController.java | 9 +- .../threaded/ThreadListControllerImpl.java | 55 +-------- .../android/threaded/ThreadListViewModel.java | 75 ++++++++++++- .../threaded/ThreadScrollListener.java | 4 +- .../briar/api/client/MessageTree.java | 9 +- .../briar/client/MessageTreeImpl.java | 20 ++-- 19 files changed, 276 insertions(+), 311 deletions(-) delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/threaded/NestedTreeList.java diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java index c9c7637ef..f117dd61f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java @@ -19,7 +19,6 @@ import org.briarproject.briar.android.threaded.ThreadItemAdapter; import org.briarproject.briar.android.threaded.ThreadListActivity; import org.briarproject.briar.android.threaded.ThreadListController; import org.briarproject.briar.android.threaded.ThreadListViewModel; -import org.briarproject.briar.api.forum.Forum; import javax.annotation.Nullable; import javax.inject.Inject; @@ -37,7 +36,7 @@ import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEX @MethodsNotNullByDefault @ParametersNotNullByDefault public class ForumActivity extends - ThreadListActivity> + ThreadListActivity> implements ForumListener { @Inject @@ -55,12 +54,12 @@ public class ForumActivity extends } @Override - protected ThreadListController getController() { + protected ThreadListController getController() { return forumController; } @Override - protected ThreadListViewModel getViewModel() { + protected ThreadListViewModel getViewModel() { return viewModel; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumController.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumController.java index 6658e60fc..83ec874a3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumController.java @@ -3,12 +3,11 @@ package org.briarproject.briar.android.forum; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.android.threaded.ThreadListController; -import org.briarproject.briar.api.forum.Forum; import androidx.annotation.UiThread; @NotNullByDefault -interface ForumController extends ThreadListController { +interface ForumController extends ThreadListController { interface ForumListener extends ThreadListListener { @UiThread diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java index 6a931f991..8cc883166 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java @@ -19,7 +19,6 @@ import org.briarproject.briar.android.threaded.ThreadListControllerImpl; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker.GroupCount; -import org.briarproject.briar.api.forum.Forum; import org.briarproject.briar.api.forum.ForumInvitationResponse; import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumPost; @@ -43,7 +42,7 @@ import static org.briarproject.bramble.util.LogUtils.logException; @NotNullByDefault class ForumControllerImpl extends - ThreadListControllerImpl + ThreadListControllerImpl implements ForumController { private static final Logger LOG = @@ -98,16 +97,6 @@ class ForumControllerImpl extends } } - @Override - protected Collection loadHeaders() throws DbException { - return forumManager.getPostHeaders(getGroupId()); - } - - @Override - protected String loadMessageText(ForumPostHeader h) throws DbException { - return forumManager.getPostText(h.getId()); - } - @Override protected void markRead(MessageId id) throws DbException { forumManager.setReadFlag(getGroupId(), id, true); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index 2e2045568..a378f00db 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -6,6 +6,7 @@ import android.widget.Toast; import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventBus; @@ -18,10 +19,15 @@ import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.R; import org.briarproject.briar.android.threaded.ThreadListViewModel; import org.briarproject.briar.api.android.AndroidNotificationManager; +import org.briarproject.briar.api.client.MessageTracker; +import org.briarproject.briar.api.client.PostHeader; import org.briarproject.briar.api.forum.Forum; import org.briarproject.briar.api.forum.ForumManager; +import org.briarproject.briar.api.forum.ForumPostHeader; import org.briarproject.briar.api.forum.ForumSharingManager; +import org.briarproject.briar.client.MessageTreeImpl; +import java.util.List; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -33,11 +39,13 @@ import androidx.lifecycle.MutableLiveData; import static android.widget.Toast.LENGTH_SHORT; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.LogUtils.now; @MethodsNotNullByDefault @ParametersNotNullByDefault -class ForumViewModel extends ThreadListViewModel { +class ForumViewModel extends ThreadListViewModel { private static final Logger LOG = getLogger(ForumViewModel.class.getName()); @@ -54,12 +62,13 @@ class ForumViewModel extends ThreadListViewModel { AndroidNotificationManager notificationManager, @CryptoExecutor Executor cryptoExecutor, Clock clock, + MessageTracker messageTracker, EventBus eventBus, ForumManager forumManager, ForumSharingManager forumSharingManager) { super(application, dbExecutor, lifecycleManager, db, androidExecutor, identityManager, notificationManager, cryptoExecutor, clock, - eventBus); + messageTracker, eventBus); this.forumManager = forumManager; this.forumSharingManager = forumSharingManager; } @@ -82,6 +91,29 @@ class ForumViewModel extends ThreadListViewModel { return forum; } + @Override + public void loadItems() { + loadList(txn -> { + long start = now(); + List headers = + forumManager.getPostHeaders(txn, groupId); + logDuration(LOG, "Loading headers", start); + List items = + buildItems(txn, headers, this::buildItem); + return new MessageTreeImpl<>(items).depthFirstOrder(); + }, this::setItems); + } + + private ForumPostItem buildItem(ForumPostHeader header, String text) { + return new ForumPostItem(header, text); + } + + @Override + protected String loadMessageText(Transaction txn, PostHeader header) + throws DbException { + return forumManager.getPostText(txn, header.getId()); + } + void deleteForum() { runOnDbThread(() -> { try { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java index bd0bcd8c2..fab1546cb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java @@ -21,7 +21,6 @@ import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity import org.briarproject.briar.android.threaded.ThreadListActivity; import org.briarproject.briar.android.threaded.ThreadListController; import org.briarproject.briar.android.threaded.ThreadListViewModel; -import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.Visibility; import javax.annotation.Nullable; @@ -41,7 +40,7 @@ import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_ @MethodsNotNullByDefault @ParametersNotNullByDefault public class GroupActivity extends - ThreadListActivity + ThreadListActivity implements GroupListener { @Inject @@ -65,12 +64,12 @@ public class GroupActivity extends } @Override - protected ThreadListController getController() { + protected ThreadListController getController() { return controller; } @Override - protected ThreadListViewModel getViewModel() { + protected ThreadListViewModel getViewModel() { return viewModel; } @@ -103,22 +102,11 @@ public class GroupActivity extends } setGroupEnabled(false); - } - - @Override - protected GroupMessageAdapter createAdapter( - LinearLayoutManager layoutManager) { - return new GroupMessageAdapter(this, layoutManager); - } - - @Override - protected void loadItems() { controller.isDissolved( new UiResultExceptionHandler(this) { @Override public void onResultUi(Boolean isDissolved) { setGroupEnabled(!isDissolved); - GroupActivity.super.loadItems(); } @Override @@ -128,6 +116,12 @@ public class GroupActivity extends }); } + @Override + protected GroupMessageAdapter createAdapter( + LinearLayoutManager layoutManager) { + return new GroupMessageAdapter(this, layoutManager); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu items for use in the action bar diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java index 5965d6c98..d777ce08f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java @@ -5,13 +5,12 @@ import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.threaded.ThreadListController; -import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.Visibility; import androidx.annotation.UiThread; public interface GroupController - extends ThreadListController { + extends ThreadListController { void isDissolved( ResultExceptionHandler handler); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java index e88f8be6a..fa5c72a60 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java @@ -24,7 +24,6 @@ import org.briarproject.briar.api.privategroup.GroupMessage; import org.briarproject.briar.api.privategroup.GroupMessageFactory; import org.briarproject.briar.api.privategroup.GroupMessageHeader; import org.briarproject.briar.api.privategroup.JoinMessageHeader; -import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.briarproject.briar.api.privategroup.event.ContactRelationshipRevealedEvent; import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent; @@ -47,7 +46,7 @@ import static org.briarproject.bramble.util.LogUtils.logException; @MethodsNotNullByDefault @ParametersNotNullByDefault class GroupControllerImpl extends - ThreadListControllerImpl + ThreadListControllerImpl implements GroupController { private static final Logger LOG = @@ -108,21 +107,6 @@ class GroupControllerImpl extends } } - @Override - protected Collection loadHeaders() throws DbException { - return privateGroupManager.getHeaders(getGroupId()); - } - - @Override - protected String loadMessageText(GroupMessageHeader header) - throws DbException { - if (header instanceof JoinMessageHeader) { - // will be looked up later - return ""; - } - return privateGroupManager.getMessageText(header.getId()); - } - @Override protected void markRead(MessageId id) throws DbException { privateGroupManager.setReadFlag(getGroupId(), id, true); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java index 3b67f8b94..d418f6178 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java @@ -32,7 +32,7 @@ class GroupMessageAdapter extends ThreadItemAdapter { @LayoutRes @Override public int getItemViewType(int position) { - GroupMessageItem item = items.get(position); + GroupMessageItem item = getItem(position); return item.getLayout(); } @@ -55,7 +55,7 @@ class GroupMessageAdapter extends ThreadItemAdapter { void updateVisibility(AuthorId memberId, Visibility v) { int position = findItemPosition(memberId); if (position != NO_POSITION) { - GroupMessageItem item = items.get(position); + GroupMessageItem item = getItem(position); if (item instanceof JoinMessageItem) { ((JoinMessageItem) item).setVisibility(v); notifyItemChanged(findItemPosition(item), item); @@ -63,14 +63,22 @@ class GroupMessageAdapter extends ThreadItemAdapter { } } + @Deprecated private int findItemPosition(AuthorId a) { - int count = items.size(); - for (int i = 0; i < count; i++) { - GroupMessageItem item = items.get(i); + for (int i = 0; i < getItemCount(); i++) { + GroupMessageItem item = getItem(i); if (item.getAuthor().getId().equals(a)) return i; } return NO_POSITION; // Not found } + @Deprecated + private int findItemPosition(GroupMessageItem itemToFind) { + for (int i = 0; i < getItemCount(); i++) { + if (getItem(i).equals(itemToFind)) return i; + } + return NO_POSITION; // Not found + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index d47e544df..335889861 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -5,6 +5,7 @@ import android.app.Application; import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventBus; @@ -18,10 +19,16 @@ import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.android.threaded.ThreadListViewModel; import org.briarproject.briar.api.android.AndroidNotificationManager; +import org.briarproject.briar.api.client.MessageTracker; +import org.briarproject.briar.api.client.PostHeader; import org.briarproject.briar.api.privategroup.GroupMessageFactory; +import org.briarproject.briar.api.privategroup.GroupMessageHeader; +import org.briarproject.briar.api.privategroup.JoinMessageHeader; import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.PrivateGroupManager; +import org.briarproject.briar.client.MessageTreeImpl; +import java.util.List; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -32,20 +39,22 @@ import androidx.lifecycle.MutableLiveData; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.LogUtils.now; @MethodsNotNullByDefault @ParametersNotNullByDefault -class GroupViewModel - extends ThreadListViewModel { +class GroupViewModel extends ThreadListViewModel { private static final Logger LOG = getLogger(GroupViewModel.class.getName()); private final PrivateGroupManager privateGroupManager; private final GroupMessageFactory groupMessageFactory; - MutableLiveData privateGroup = new MutableLiveData<>(); - MutableLiveData isCreator = new MutableLiveData<>(); + private final MutableLiveData privateGroup = + new MutableLiveData<>(); + private final MutableLiveData isCreator = new MutableLiveData<>(); @Inject GroupViewModel(Application application, @@ -58,11 +67,12 @@ class GroupViewModel AndroidNotificationManager notificationManager, @CryptoExecutor Executor cryptoExecutor, Clock clock, + MessageTracker messageTracker, PrivateGroupManager privateGroupManager, GroupMessageFactory groupMessageFactory) { super(application, dbExecutor, lifecycleManager, db, androidExecutor, identityManager, notificationManager, cryptoExecutor, clock, - eventBus); + messageTracker, eventBus); this.privateGroupManager = privateGroupManager; this.groupMessageFactory = groupMessageFactory; } @@ -91,6 +101,37 @@ class GroupViewModel }); } + @Override + public void loadItems() { + loadList(txn -> { + // TODO first check if group is dissolved + long start = now(); + List headers = + privateGroupManager.getHeaders(txn, groupId); + logDuration(LOG, "Loading headers", start); + List items = + buildItems(txn, headers, this::buildItem); + return new MessageTreeImpl<>(items).depthFirstOrder(); + }, this::setItems); + } + + private GroupMessageItem buildItem(GroupMessageHeader header, String text) { + if (header instanceof JoinMessageHeader) { + return new JoinMessageItem((JoinMessageHeader) header, text); + } + return new GroupMessageItem(header, text); + } + + @Override + protected String loadMessageText( + Transaction txn, PostHeader header) throws DbException { + if (header instanceof JoinMessageHeader) { + // will be looked up later + return ""; + } + return privateGroupManager.getMessageText(txn, header.getId()); + } + void deletePrivateGroup() { runOnDbThread(() -> { try { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/NestedTreeList.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/NestedTreeList.java deleted file mode 100644 index 40e211270..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/NestedTreeList.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.briarproject.briar.android.threaded; - -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.briar.api.client.MessageTree; -import org.briarproject.briar.api.client.MessageTree.MessageNode; -import org.briarproject.briar.client.MessageTreeImpl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import androidx.annotation.UiThread; - -@UiThread -@NotNullByDefault -public class NestedTreeList implements Iterable { - - private final MessageTree tree = new MessageTreeImpl<>(); - private List depthFirstCollection = new ArrayList<>(); - - public void addAll(Collection collection) { - tree.add(collection); - depthFirstCollection = new ArrayList<>(tree.depthFirstOrder()); - } - - public void add(T elem) { - tree.add(elem); - depthFirstCollection = new ArrayList<>(tree.depthFirstOrder()); - } - - public void clear() { - tree.clear(); - depthFirstCollection.clear(); - } - - public T get(int index) { - return depthFirstCollection.get(index); - } - - public int size() { - return depthFirstCollection.size(); - } - - public boolean contains(MessageId m) { - return tree.contains(m); - } - - @Override - public Iterator iterator() { - return depthFirstCollection.iterator(); - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItem.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItem.java index 0d3d69205..bde7c3e7d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItem.java @@ -99,4 +99,14 @@ public abstract class ThreadItem implements MessageNode { return highlighted; } + @Override + public int hashCode() { + return messageId.hashCode(); + } + + @Override + public boolean equals(@Nullable Object o) { + return o instanceof ThreadItem && + messageId.equals(((ThreadItem) o).messageId); + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java index c293f7858..63db2770e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java @@ -4,37 +4,48 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.R; import org.briarproject.briar.android.util.ItemReturningAdapter; -import org.briarproject.briar.android.util.VersionedAdapter; - -import java.util.Collection; import javax.annotation.Nullable; import androidx.annotation.NonNull; import androidx.annotation.UiThread; +import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.ListAdapter; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; @UiThread +@MethodsNotNullByDefault +@ParametersNotNullByDefault public class ThreadItemAdapter - extends RecyclerView.Adapter> - implements VersionedAdapter, ItemReturningAdapter { + extends ListAdapter> + implements ItemReturningAdapter { static final int UNDEFINED = -1; - protected final NestedTreeList items = new NestedTreeList<>(); private final ThreadItemListener listener; private final LinearLayoutManager layoutManager; - private volatile int revision = 0; - public ThreadItemAdapter(ThreadItemListener listener, LinearLayoutManager layoutManager) { + super(new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(I a, I b) { + return a.equals(b); + } + + @Override + public boolean areContentsTheSame(I a, I b) { + return a.isHighlighted() == b.isHighlighted() && + a.isRead() && b.isRead(); + } + }); this.listener = listener; this.layoutManager = layoutManager; } @@ -51,28 +62,14 @@ public class ThreadItemAdapter @Override public void onBindViewHolder(@NonNull BaseThreadItemViewHolder ui, int position) { - I item = items.get(position); + I item = getItem(position); ui.bind(item, listener); } - @Override - public int getItemCount() { - return items.size(); - } - - @Override - public int getRevision() { - return revision; - } - - @Override - public void incrementRevision() { - revision++; - } - void setItemWithIdVisible(MessageId messageId) { int pos = 0; - for (I item : items) { + for (int i = 0; i < getItemCount(); i++) { + I item = getItem(i); if (item.getId().equals(messageId)) { layoutManager.scrollToPosition(pos); break; @@ -81,46 +78,16 @@ public class ThreadItemAdapter } } - public void setItems(Collection items) { - this.items.clear(); - this.items.addAll(items); - notifyDataSetChanged(); - } - - public void add(I item) { - items.add(item); - notifyItemInserted(findItemPosition(item)); - } - - @Nullable - public I getItemAt(int position) { - if (position == NO_POSITION || position >= items.size()) { - return null; - } - return items.get(position); - } - - protected int findItemPosition(@Nullable I item) { - for (int i = 0; i < items.size(); i++) { - if (items.get(i).equals(item)) return i; - } - return NO_POSITION; // Not found - } - - boolean contains(MessageId m) { - return items.contains(m); - } - /** * Highlights the item with the given {@link MessageId} * and disables the highlight for a previously highlighted item, if any. - * + *

* Only one item can be highlighted at a time. */ void setHighlightedItem(@Nullable MessageId id) { - for (int i = 0; i < items.size(); i++) { - I item = items.get(i); - if (id != null && item.getId().equals(id)) { + for (int i = 0; i < getItemCount(); i++) { + I item = getItem(i); + if (item.getId().equals(id)) { item.setHighlighted(true); notifyItemChanged(i, item); } else if (item.isHighlighted()) { @@ -132,8 +99,9 @@ public class ThreadItemAdapter @Nullable I getHighlightedItem() { - for (I i : items) { - if (i.isHighlighted()) return i; + for (int i = 0; i < getItemCount(); i++) { + I item = getItem(i); + if (item.isHighlighted()) return item; } return null; } @@ -144,8 +112,8 @@ public class ThreadItemAdapter int getVisibleUnreadPosBottom() { int positionBottom = layoutManager.findLastVisibleItemPosition(); if (positionBottom == NO_POSITION) return NO_POSITION; - for (int i = positionBottom + 1; i < items.size(); i++) { - if (!items.get(i).isRead()) return i; + for (int i = positionBottom + 1; i < getItemCount(); i++) { + if (!getItem(i).isRead()) return i; } return NO_POSITION; } @@ -156,8 +124,8 @@ public class ThreadItemAdapter int getVisibleUnreadPosTop() { int positionTop = layoutManager.findFirstVisibleItemPosition(); int position = NO_POSITION; - for (int i = 0; i < items.size(); i++) { - if (i < positionTop && !items.get(i).isRead()) { + for (int i = 0; i < getItemCount(); i++) { + if (i < positionTop && !getItem(i).isRead()) { position = i; } else if (i >= positionTop) { return position; @@ -166,6 +134,11 @@ public class ThreadItemAdapter return NO_POSITION; } + @Override + public I getItemAt(int position) { + return getItem(position); + } + public interface ThreadItemListener { void onReplyClick(I item); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java index 67088f84b..1b3e0d202 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java @@ -28,7 +28,6 @@ import org.briarproject.briar.android.view.TextSendController; import org.briarproject.briar.android.view.TextSendController.SendListener; import org.briarproject.briar.android.view.UnreadMessageButton; import org.briarproject.briar.api.attachment.AttachmentHeader; -import org.briarproject.briar.api.client.NamedGroup; import java.util.Collection; import java.util.List; @@ -48,7 +47,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @MethodsNotNullByDefault @ParametersNotNullByDefault -public abstract class ThreadListActivity> +public abstract class ThreadListActivity> extends BriarActivity implements ThreadListListener, SendListener, SharingListener, ThreadItemListener, ThreadListDataSource { @@ -59,6 +58,7 @@ public abstract class ThreadListActivity scrollListener; protected BriarRecyclerView list; private LinearLayoutManager layoutManager; @@ -70,9 +70,9 @@ public abstract class ThreadListActivity getController(); + protected abstract ThreadListController getController(); - protected abstract ThreadListViewModel getViewModel(); + protected abstract ThreadListViewModel getViewModel(); @Inject protected SharingController sharingController; @@ -127,6 +127,11 @@ public abstract class ThreadListActivity result + .onError(this::handleException) + .onSuccess(this::displayItems) + ); + sharingController.setSharingListener(this); loadSharingContacts(); } @@ -145,44 +150,22 @@ public abstract class ThreadListActivity, DbException>( - this) { - @Override - public void onResultUi(ThreadItemList items) { - if (revision == adapter.getRevision()) { - adapter.incrementRevision(); - if (items.isEmpty()) { - list.showData(); - } else { - displayItems(items); - updateTextInput(); - } - } else { - LOG.info("Concurrent update, reloading"); - loadItems(); - } - } - - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }); - } - - private void displayItems(ThreadItemList items) { - adapter.setItems(items); - MessageId messageId = items.getFirstVisibleItemId(); - if (messageId != null) - adapter.setItemWithIdVisible(messageId); - list.showData(); - if (layoutManagerState == null) { - list.scrollToPosition(0); // Scroll to the top + protected void displayItems(List items) { + if (items.isEmpty()) { + list.showData(); } else { - layoutManager.onRestoreInstanceState(layoutManagerState); + adapter.submitList(items); + // TODO get this ID from elsewhere + MessageId messageId = null; // items.getFirstVisibleItemId(); + if (messageId != null) + adapter.setItemWithIdVisible(messageId); + list.showData(); + if (layoutManagerState == null) { + list.scrollToPosition(0); // Scroll to the top + } else { + layoutManager.onRestoreInstanceState(layoutManagerState); + } + updateTextInput(); } } @@ -209,7 +192,6 @@ public abstract class ThreadListActivity +public interface ThreadListController extends ActivityLifecycleController { void setGroupId(GroupId groupId); @@ -24,9 +23,6 @@ public interface ThreadListController, DbException> handler); - void loadItems( - ResultExceptionHandler, DbException> handler); - void markItemRead(I item); void markItemsRead(Collection items); @@ -48,7 +44,8 @@ public interface ThreadListController> +public abstract class ThreadListControllerImpl> extends DbControllerImpl - implements ThreadListController, EventListener { + implements ThreadListController, EventListener { private static final Logger LOG = Logger.getLogger(ThreadListControllerImpl.class.getName()); @@ -129,42 +127,6 @@ public abstract class ThreadListControllerImpl, DbException> handler) { - checkGroupId(); - runOnDbThread(() -> { - try { - // Load headers - long start = now(); - Collection headers = loadHeaders(); - logDuration(LOG, "Loading headers", start); - - // Load bodies into cache - start = now(); - for (H header : headers) { - if (!textCache.containsKey(header.getId())) { - textCache.put(header.getId(), - loadMessageText(header)); - } - } - logDuration(LOG, "Loading bodies", start); - - // Build and hand over items - handler.onResult(buildItems(headers)); - } catch (DbException e) { - logException(LOG, WARNING, e); - handler.onException(e); - } - }); - } - - @DatabaseExecutor - protected abstract Collection loadHeaders() throws DbException; - - @DatabaseExecutor - protected abstract String loadMessageText(H header) throws DbException; - @Override public void markItemRead(I item) { markItemsRead(Collections.singletonList(item)); @@ -207,19 +169,6 @@ public abstract class ThreadListControllerImpl buildItems(Collection headers) - throws DbException { - ThreadItemList items = new ThreadItemListImpl<>(); - for (H h : headers) { - items.add(buildItem(h, textCache.get(h.getId()))); - } - MessageId msgId = messageTracker.loadStoredMessageId(groupId); - if (LOG.isLoggable(INFO)) - LOG.info("Loaded last top visible message id " + msgId); - items.setFirstVisibleId(msgId); - return items; - } - protected abstract I buildItem(H header, String text); protected GroupId getGroupId() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java index dbdd4cf35..778796ce9 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -4,6 +4,8 @@ import android.app.Application; import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventListener; @@ -12,34 +14,51 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.android.viewmodel.DbViewModel; +import org.briarproject.briar.android.viewmodel.LiveResult; import org.briarproject.briar.api.android.AndroidNotificationManager; -import org.briarproject.briar.api.client.NamedGroup; +import org.briarproject.briar.api.client.MessageTracker; +import org.briarproject.briar.api.client.PostHeader; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.logging.Logger; import androidx.annotation.CallSuper; +import androidx.annotation.UiThread; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import static java.util.logging.Level.INFO; import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logDuration; +import static org.briarproject.bramble.util.LogUtils.now; @MethodsNotNullByDefault @ParametersNotNullByDefault -public abstract class ThreadListViewModel - extends DbViewModel implements EventListener { +public abstract class ThreadListViewModel extends DbViewModel + implements EventListener { private static final Logger LOG = getLogger(ThreadListViewModel.class.getName()); - protected final IdentityManager identityManager; protected final AndroidNotificationManager notificationManager; protected final Executor cryptoExecutor; protected final Clock clock; + private final MessageTracker messageTracker; private final EventBus eventBus; + private final Map textCache = new ConcurrentHashMap<>(); + private final MutableLiveData>> items = + new MutableLiveData<>(); + protected volatile GroupId groupId; public ThreadListViewModel(Application application, @@ -51,12 +70,14 @@ public abstract class ThreadListViewModel> items) { + this.items.setValue(items); + } + + @DatabaseExecutor + protected List buildItems( + Transaction txn, Collection headers, ItemGetter itemGetter) + throws DbException { + long start = now(); + ThreadItemList items = new ThreadItemListImpl<>(); + for (H header : headers) { + MessageId id = header.getId(); + String text = textCache.get(header.getId()); + if (text == null) { + text = loadMessageText(txn, header); + textCache.put(id, text); + } + items.add(itemGetter.getItem(header, text)); + } + logDuration(LOG, "Loading bodies and creating items", start); + + MessageId msgId = messageTracker.loadStoredMessageId(txn, groupId); + if (LOG.isLoggable(INFO)) { + LOG.info("Loaded last top visible message id " + msgId); + } + // TODO store this elsewhere + items.setFirstVisibleId(msgId); + return items; + } + + @DatabaseExecutor + protected abstract String loadMessageText(Transaction txn, + PostHeader header) throws DbException; + + LiveData>> getItems() { + return items; + } + + public interface ItemGetter { + I getItem(H header, String text); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadScrollListener.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadScrollListener.java index 2181d6de3..dca100459 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadScrollListener.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadScrollListener.java @@ -20,11 +20,11 @@ class ThreadScrollListener private static final Logger LOG = getLogger(ThreadScrollListener.class.getName()); - private final ThreadListController controller; + private final ThreadListController controller; private final UnreadMessageButton upButton, downButton; ThreadScrollListener(ThreadItemAdapter adapter, - ThreadListController controller, + ThreadListController controller, UnreadMessageButton upButton, UnreadMessageButton downButton) { super(adapter); diff --git a/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTree.java b/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTree.java index e850391d0..fabdd3308 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTree.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTree.java @@ -4,7 +4,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.MessageId; import java.util.Collection; -import java.util.Comparator; +import java.util.List; import javax.annotation.Nullable; @@ -13,14 +13,15 @@ public interface MessageTree { void add(Collection nodes); + @Deprecated void add(T node); - void setComparator(Comparator comparator); - + @Deprecated void clear(); - Collection depthFirstOrder(); + List depthFirstOrder(); + @Deprecated boolean contains(MessageId m); @NotNullByDefault diff --git a/briar-core/src/main/java/org/briarproject/briar/client/MessageTreeImpl.java b/briar-core/src/main/java/org/briarproject/briar/client/MessageTreeImpl.java index 8176e3969..49027d190 100644 --- a/briar-core/src/main/java/org/briarproject/briar/client/MessageTreeImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/client/MessageTreeImpl.java @@ -30,9 +30,14 @@ public class MessageTreeImpl private final List> unsortedLists = new ArrayList<>(); @SuppressWarnings("UseCompareMethod") - private Comparator comparator = (o1, o2) -> + private final Comparator comparator = (o1, o2) -> Long.valueOf(o1.getTimestamp()).compareTo(o2.getTimestamp()); + public MessageTreeImpl(Collection collection) { + super(); + add(collection); + } + @Override public synchronized void clear() { roots.clear(); @@ -79,6 +84,7 @@ public class MessageTreeImpl @GuardedBy("this") private void sortUnsorted() { for (List list : unsortedLists) { + //noinspection Java8ListSort Collections.sort(list, comparator); } unsortedLists.clear(); @@ -95,17 +101,7 @@ public class MessageTreeImpl } @Override - public synchronized void setComparator(Comparator comparator) { - this.comparator = comparator; - // Sort all lists with the new comparator - Collections.sort(roots, comparator); - for (Map.Entry> entry : nodeMap.entrySet()) { - Collections.sort(entry.getValue(), comparator); - } - } - - @Override - public synchronized Collection depthFirstOrder() { + public synchronized List depthFirstOrder() { List orderedList = new ArrayList<>(); for (T root : roots) { traverse(orderedList, root, 0);