Submit thread list items to ListAdapter

This commit is contained in:
Torsten Grote
2021-01-07 08:52:51 -03:00
parent 6611d7c02e
commit d393b79ced
19 changed files with 276 additions and 311 deletions

View File

@@ -19,7 +19,6 @@ import org.briarproject.briar.android.threaded.ThreadItemAdapter;
import org.briarproject.briar.android.threaded.ThreadListActivity; import org.briarproject.briar.android.threaded.ThreadListActivity;
import org.briarproject.briar.android.threaded.ThreadListController; import org.briarproject.briar.android.threaded.ThreadListController;
import org.briarproject.briar.android.threaded.ThreadListViewModel; import org.briarproject.briar.android.threaded.ThreadListViewModel;
import org.briarproject.briar.api.forum.Forum;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
@@ -37,7 +36,7 @@ import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEX
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class ForumActivity extends public class ForumActivity extends
ThreadListActivity<Forum, ForumPostItem, ThreadItemAdapter<ForumPostItem>> ThreadListActivity<ForumPostItem, ThreadItemAdapter<ForumPostItem>>
implements ForumListener { implements ForumListener {
@Inject @Inject
@@ -55,12 +54,12 @@ public class ForumActivity extends
} }
@Override @Override
protected ThreadListController<Forum, ForumPostItem> getController() { protected ThreadListController<ForumPostItem> getController() {
return forumController; return forumController;
} }
@Override @Override
protected ThreadListViewModel<Forum, ForumPostItem> getViewModel() { protected ThreadListViewModel<ForumPostItem> getViewModel() {
return viewModel; return viewModel;
} }

View File

@@ -3,12 +3,11 @@ package org.briarproject.briar.android.forum;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.threaded.ThreadListController; import org.briarproject.briar.android.threaded.ThreadListController;
import org.briarproject.briar.api.forum.Forum;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
@NotNullByDefault @NotNullByDefault
interface ForumController extends ThreadListController<Forum, ForumPostItem> { interface ForumController extends ThreadListController<ForumPostItem> {
interface ForumListener extends ThreadListListener<ForumPostItem> { interface ForumListener extends ThreadListListener<ForumPostItem> {
@UiThread @UiThread

View File

@@ -19,7 +19,6 @@ import org.briarproject.briar.android.threaded.ThreadListControllerImpl;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; 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.ForumInvitationResponse;
import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumPost; import org.briarproject.briar.api.forum.ForumPost;
@@ -43,7 +42,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
class ForumControllerImpl extends class ForumControllerImpl extends
ThreadListControllerImpl<Forum, ForumPostItem, ForumPostHeader, ForumPost, ForumListener> ThreadListControllerImpl<ForumPostItem, ForumPostHeader, ForumPost, ForumListener>
implements ForumController { implements ForumController {
private static final Logger LOG = private static final Logger LOG =
@@ -98,16 +97,6 @@ class ForumControllerImpl extends
} }
} }
@Override
protected Collection<ForumPostHeader> loadHeaders() throws DbException {
return forumManager.getPostHeaders(getGroupId());
}
@Override
protected String loadMessageText(ForumPostHeader h) throws DbException {
return forumManager.getPostText(h.getId());
}
@Override @Override
protected void markRead(MessageId id) throws DbException { protected void markRead(MessageId id) throws DbException {
forumManager.setReadFlag(getGroupId(), id, true); forumManager.setReadFlag(getGroupId(), id, true);

View File

@@ -6,6 +6,7 @@ import android.widget.Toast;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; 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.db.TransactionManager;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; 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.R;
import org.briarproject.briar.android.threaded.ThreadListViewModel; import org.briarproject.briar.android.threaded.ThreadListViewModel;
import org.briarproject.briar.api.android.AndroidNotificationManager; 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.Forum;
import org.briarproject.briar.api.forum.ForumManager; 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.api.forum.ForumSharingManager;
import org.briarproject.briar.client.MessageTreeImpl;
import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -33,11 +39,13 @@ import androidx.lifecycle.MutableLiveData;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; 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.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class ForumViewModel extends ThreadListViewModel<Forum, ForumPostItem> { class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
private static final Logger LOG = getLogger(ForumViewModel.class.getName()); private static final Logger LOG = getLogger(ForumViewModel.class.getName());
@@ -54,12 +62,13 @@ class ForumViewModel extends ThreadListViewModel<Forum, ForumPostItem> {
AndroidNotificationManager notificationManager, AndroidNotificationManager notificationManager,
@CryptoExecutor Executor cryptoExecutor, @CryptoExecutor Executor cryptoExecutor,
Clock clock, Clock clock,
MessageTracker messageTracker,
EventBus eventBus, EventBus eventBus,
ForumManager forumManager, ForumManager forumManager,
ForumSharingManager forumSharingManager) { ForumSharingManager forumSharingManager) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor, super(application, dbExecutor, lifecycleManager, db, androidExecutor,
identityManager, notificationManager, cryptoExecutor, clock, identityManager, notificationManager, cryptoExecutor, clock,
eventBus); messageTracker, eventBus);
this.forumManager = forumManager; this.forumManager = forumManager;
this.forumSharingManager = forumSharingManager; this.forumSharingManager = forumSharingManager;
} }
@@ -82,6 +91,29 @@ class ForumViewModel extends ThreadListViewModel<Forum, ForumPostItem> {
return forum; return forum;
} }
@Override
public void loadItems() {
loadList(txn -> {
long start = now();
List<ForumPostHeader> headers =
forumManager.getPostHeaders(txn, groupId);
logDuration(LOG, "Loading headers", start);
List<ForumPostItem> 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() { void deleteForum() {
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {

View File

@@ -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.ThreadListActivity;
import org.briarproject.briar.android.threaded.ThreadListController; import org.briarproject.briar.android.threaded.ThreadListController;
import org.briarproject.briar.android.threaded.ThreadListViewModel; import org.briarproject.briar.android.threaded.ThreadListViewModel;
import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.Visibility; import org.briarproject.briar.api.privategroup.Visibility;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -41,7 +40,7 @@ import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class GroupActivity extends public class GroupActivity extends
ThreadListActivity<PrivateGroup, GroupMessageItem, GroupMessageAdapter> ThreadListActivity<GroupMessageItem, GroupMessageAdapter>
implements GroupListener { implements GroupListener {
@Inject @Inject
@@ -65,12 +64,12 @@ public class GroupActivity extends
} }
@Override @Override
protected ThreadListController<PrivateGroup, GroupMessageItem> getController() { protected ThreadListController<GroupMessageItem> getController() {
return controller; return controller;
} }
@Override @Override
protected ThreadListViewModel<PrivateGroup, GroupMessageItem> getViewModel() { protected ThreadListViewModel<GroupMessageItem> getViewModel() {
return viewModel; return viewModel;
} }
@@ -103,22 +102,11 @@ public class GroupActivity extends
} }
setGroupEnabled(false); setGroupEnabled(false);
}
@Override
protected GroupMessageAdapter createAdapter(
LinearLayoutManager layoutManager) {
return new GroupMessageAdapter(this, layoutManager);
}
@Override
protected void loadItems() {
controller.isDissolved( controller.isDissolved(
new UiResultExceptionHandler<Boolean, DbException>(this) { new UiResultExceptionHandler<Boolean, DbException>(this) {
@Override @Override
public void onResultUi(Boolean isDissolved) { public void onResultUi(Boolean isDissolved) {
setGroupEnabled(!isDissolved); setGroupEnabled(!isDissolved);
GroupActivity.super.loadItems();
} }
@Override @Override
@@ -128,6 +116,12 @@ public class GroupActivity extends
}); });
} }
@Override
protected GroupMessageAdapter createAdapter(
LinearLayoutManager layoutManager) {
return new GroupMessageAdapter(this, layoutManager);
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar // Inflate the menu items for use in the action bar

View File

@@ -5,13 +5,12 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.android.threaded.ThreadListController; import org.briarproject.briar.android.threaded.ThreadListController;
import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.Visibility; import org.briarproject.briar.api.privategroup.Visibility;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
public interface GroupController public interface GroupController
extends ThreadListController<PrivateGroup, GroupMessageItem> { extends ThreadListController<GroupMessageItem> {
void isDissolved( void isDissolved(
ResultExceptionHandler<Boolean, DbException> handler); ResultExceptionHandler<Boolean, DbException> handler);

View File

@@ -24,7 +24,6 @@ import org.briarproject.briar.api.privategroup.GroupMessage;
import org.briarproject.briar.api.privategroup.GroupMessageFactory; import org.briarproject.briar.api.privategroup.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.GroupMessageHeader; import org.briarproject.briar.api.privategroup.GroupMessageHeader;
import org.briarproject.briar.api.privategroup.JoinMessageHeader; 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.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.event.ContactRelationshipRevealedEvent; import org.briarproject.briar.api.privategroup.event.ContactRelationshipRevealedEvent;
import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent; import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent;
@@ -47,7 +46,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class GroupControllerImpl extends class GroupControllerImpl extends
ThreadListControllerImpl<PrivateGroup, GroupMessageItem, GroupMessageHeader, GroupMessage, GroupListener> ThreadListControllerImpl<GroupMessageItem, GroupMessageHeader, GroupMessage, GroupListener>
implements GroupController { implements GroupController {
private static final Logger LOG = private static final Logger LOG =
@@ -108,21 +107,6 @@ class GroupControllerImpl extends
} }
} }
@Override
protected Collection<GroupMessageHeader> 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 @Override
protected void markRead(MessageId id) throws DbException { protected void markRead(MessageId id) throws DbException {
privateGroupManager.setReadFlag(getGroupId(), id, true); privateGroupManager.setReadFlag(getGroupId(), id, true);

View File

@@ -32,7 +32,7 @@ class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
@LayoutRes @LayoutRes
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
GroupMessageItem item = items.get(position); GroupMessageItem item = getItem(position);
return item.getLayout(); return item.getLayout();
} }
@@ -55,7 +55,7 @@ class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
void updateVisibility(AuthorId memberId, Visibility v) { void updateVisibility(AuthorId memberId, Visibility v) {
int position = findItemPosition(memberId); int position = findItemPosition(memberId);
if (position != NO_POSITION) { if (position != NO_POSITION) {
GroupMessageItem item = items.get(position); GroupMessageItem item = getItem(position);
if (item instanceof JoinMessageItem) { if (item instanceof JoinMessageItem) {
((JoinMessageItem) item).setVisibility(v); ((JoinMessageItem) item).setVisibility(v);
notifyItemChanged(findItemPosition(item), item); notifyItemChanged(findItemPosition(item), item);
@@ -63,14 +63,22 @@ class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
} }
} }
@Deprecated
private int findItemPosition(AuthorId a) { private int findItemPosition(AuthorId a) {
int count = items.size(); for (int i = 0; i < getItemCount(); i++) {
for (int i = 0; i < count; i++) { GroupMessageItem item = getItem(i);
GroupMessageItem item = items.get(i);
if (item.getAuthor().getId().equals(a)) if (item.getAuthor().getId().equals(a))
return i; return i;
} }
return NO_POSITION; // Not found 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
}
} }

View File

@@ -5,6 +5,7 @@ import android.app.Application;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; 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.db.TransactionManager;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; 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.bramble.api.system.Clock;
import org.briarproject.briar.android.threaded.ThreadListViewModel; import org.briarproject.briar.android.threaded.ThreadListViewModel;
import org.briarproject.briar.api.android.AndroidNotificationManager; 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.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.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupManager; 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.concurrent.Executor;
import java.util.logging.Logger; 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.Level.WARNING;
import static java.util.logging.Logger.getLogger; 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.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class GroupViewModel class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
extends ThreadListViewModel<PrivateGroup, GroupMessageItem> {
private static final Logger LOG = getLogger(GroupViewModel.class.getName()); private static final Logger LOG = getLogger(GroupViewModel.class.getName());
private final PrivateGroupManager privateGroupManager; private final PrivateGroupManager privateGroupManager;
private final GroupMessageFactory groupMessageFactory; private final GroupMessageFactory groupMessageFactory;
MutableLiveData<PrivateGroup> privateGroup = new MutableLiveData<>(); private final MutableLiveData<PrivateGroup> privateGroup =
MutableLiveData<Boolean> isCreator = new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveData<Boolean> isCreator = new MutableLiveData<>();
@Inject @Inject
GroupViewModel(Application application, GroupViewModel(Application application,
@@ -58,11 +67,12 @@ class GroupViewModel
AndroidNotificationManager notificationManager, AndroidNotificationManager notificationManager,
@CryptoExecutor Executor cryptoExecutor, @CryptoExecutor Executor cryptoExecutor,
Clock clock, Clock clock,
MessageTracker messageTracker,
PrivateGroupManager privateGroupManager, PrivateGroupManager privateGroupManager,
GroupMessageFactory groupMessageFactory) { GroupMessageFactory groupMessageFactory) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor, super(application, dbExecutor, lifecycleManager, db, androidExecutor,
identityManager, notificationManager, cryptoExecutor, clock, identityManager, notificationManager, cryptoExecutor, clock,
eventBus); messageTracker, eventBus);
this.privateGroupManager = privateGroupManager; this.privateGroupManager = privateGroupManager;
this.groupMessageFactory = groupMessageFactory; 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<GroupMessageHeader> headers =
privateGroupManager.getHeaders(txn, groupId);
logDuration(LOG, "Loading headers", start);
List<GroupMessageItem> 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() { void deletePrivateGroup() {
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {

View File

@@ -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<T extends MessageNode> implements Iterable<T> {
private final MessageTree<T> tree = new MessageTreeImpl<>();
private List<T> depthFirstCollection = new ArrayList<>();
public void addAll(Collection<T> 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<T> iterator() {
return depthFirstCollection.iterator();
}
}

View File

@@ -99,4 +99,14 @@ public abstract class ThreadItem implements MessageNode {
return highlighted; 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);
}
} }

View File

@@ -4,37 +4,48 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.bramble.api.sync.MessageId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.util.ItemReturningAdapter; import org.briarproject.briar.android.util.ItemReturningAdapter;
import org.briarproject.briar.android.util.VersionedAdapter;
import java.util.Collection;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.ListAdapter;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
@UiThread @UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ThreadItemAdapter<I extends ThreadItem> public class ThreadItemAdapter<I extends ThreadItem>
extends RecyclerView.Adapter<BaseThreadItemViewHolder<I>> extends ListAdapter<I, BaseThreadItemViewHolder<I>>
implements VersionedAdapter, ItemReturningAdapter<I> { implements ItemReturningAdapter<I> {
static final int UNDEFINED = -1; static final int UNDEFINED = -1;
protected final NestedTreeList<I> items = new NestedTreeList<>();
private final ThreadItemListener<I> listener; private final ThreadItemListener<I> listener;
private final LinearLayoutManager layoutManager; private final LinearLayoutManager layoutManager;
private volatile int revision = 0;
public ThreadItemAdapter(ThreadItemListener<I> listener, public ThreadItemAdapter(ThreadItemListener<I> listener,
LinearLayoutManager layoutManager) { LinearLayoutManager layoutManager) {
super(new DiffUtil.ItemCallback<I>() {
@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.listener = listener;
this.layoutManager = layoutManager; this.layoutManager = layoutManager;
} }
@@ -51,28 +62,14 @@ public class ThreadItemAdapter<I extends ThreadItem>
@Override @Override
public void onBindViewHolder(@NonNull BaseThreadItemViewHolder<I> ui, public void onBindViewHolder(@NonNull BaseThreadItemViewHolder<I> ui,
int position) { int position) {
I item = items.get(position); I item = getItem(position);
ui.bind(item, listener); 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) { void setItemWithIdVisible(MessageId messageId) {
int pos = 0; int pos = 0;
for (I item : items) { for (int i = 0; i < getItemCount(); i++) {
I item = getItem(i);
if (item.getId().equals(messageId)) { if (item.getId().equals(messageId)) {
layoutManager.scrollToPosition(pos); layoutManager.scrollToPosition(pos);
break; break;
@@ -81,46 +78,16 @@ public class ThreadItemAdapter<I extends ThreadItem>
} }
} }
public void setItems(Collection<I> 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} * Highlights the item with the given {@link MessageId}
* and disables the highlight for a previously highlighted item, if any. * and disables the highlight for a previously highlighted item, if any.
* * <p>
* Only one item can be highlighted at a time. * Only one item can be highlighted at a time.
*/ */
void setHighlightedItem(@Nullable MessageId id) { void setHighlightedItem(@Nullable MessageId id) {
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < getItemCount(); i++) {
I item = items.get(i); I item = getItem(i);
if (id != null && item.getId().equals(id)) { if (item.getId().equals(id)) {
item.setHighlighted(true); item.setHighlighted(true);
notifyItemChanged(i, item); notifyItemChanged(i, item);
} else if (item.isHighlighted()) { } else if (item.isHighlighted()) {
@@ -132,8 +99,9 @@ public class ThreadItemAdapter<I extends ThreadItem>
@Nullable @Nullable
I getHighlightedItem() { I getHighlightedItem() {
for (I i : items) { for (int i = 0; i < getItemCount(); i++) {
if (i.isHighlighted()) return i; I item = getItem(i);
if (item.isHighlighted()) return item;
} }
return null; return null;
} }
@@ -144,8 +112,8 @@ public class ThreadItemAdapter<I extends ThreadItem>
int getVisibleUnreadPosBottom() { int getVisibleUnreadPosBottom() {
int positionBottom = layoutManager.findLastVisibleItemPosition(); int positionBottom = layoutManager.findLastVisibleItemPosition();
if (positionBottom == NO_POSITION) return NO_POSITION; if (positionBottom == NO_POSITION) return NO_POSITION;
for (int i = positionBottom + 1; i < items.size(); i++) { for (int i = positionBottom + 1; i < getItemCount(); i++) {
if (!items.get(i).isRead()) return i; if (!getItem(i).isRead()) return i;
} }
return NO_POSITION; return NO_POSITION;
} }
@@ -156,8 +124,8 @@ public class ThreadItemAdapter<I extends ThreadItem>
int getVisibleUnreadPosTop() { int getVisibleUnreadPosTop() {
int positionTop = layoutManager.findFirstVisibleItemPosition(); int positionTop = layoutManager.findFirstVisibleItemPosition();
int position = NO_POSITION; int position = NO_POSITION;
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < getItemCount(); i++) {
if (i < positionTop && !items.get(i).isRead()) { if (i < positionTop && !getItem(i).isRead()) {
position = i; position = i;
} else if (i >= positionTop) { } else if (i >= positionTop) {
return position; return position;
@@ -166,6 +134,11 @@ public class ThreadItemAdapter<I extends ThreadItem>
return NO_POSITION; return NO_POSITION;
} }
@Override
public I getItemAt(int position) {
return getItem(position);
}
public interface ThreadItemListener<I> { public interface ThreadItemListener<I> {
void onReplyClick(I item); void onReplyClick(I item);
} }

View File

@@ -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.TextSendController.SendListener;
import org.briarproject.briar.android.view.UnreadMessageButton; import org.briarproject.briar.android.view.UnreadMessageButton;
import org.briarproject.briar.api.attachment.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.client.NamedGroup;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -48,7 +47,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadItem, A extends ThreadItemAdapter<I>> public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadItemAdapter<I>>
extends BriarActivity extends BriarActivity
implements ThreadListListener<I>, SendListener, SharingListener, implements ThreadListListener<I>, SendListener, SharingListener,
ThreadItemListener<I>, ThreadListDataSource { ThreadItemListener<I>, ThreadListDataSource {
@@ -59,6 +58,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
getLogger(ThreadListActivity.class.getName()); getLogger(ThreadListActivity.class.getName());
protected A adapter; protected A adapter;
private ThreadScrollListener<I> scrollListener; private ThreadScrollListener<I> scrollListener;
protected BriarRecyclerView list; protected BriarRecyclerView list;
private LinearLayoutManager layoutManager; private LinearLayoutManager layoutManager;
@@ -70,9 +70,9 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
@Nullable @Nullable
private MessageId replyId; private MessageId replyId;
protected abstract ThreadListController<G, I> getController(); protected abstract ThreadListController<I> getController();
protected abstract ThreadListViewModel<G, I> getViewModel(); protected abstract ThreadListViewModel<I> getViewModel();
@Inject @Inject
protected SharingController sharingController; protected SharingController sharingController;
@@ -127,6 +127,11 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
if (replyIdBytes != null) replyId = new MessageId(replyIdBytes); if (replyIdBytes != null) replyId = new MessageId(replyIdBytes);
} }
getViewModel().getItems().observe(this, result -> result
.onError(this::handleException)
.onSuccess(this::displayItems)
);
sharingController.setSharingListener(this); sharingController.setSharingListener(this);
loadSharingContacts(); loadSharingContacts();
} }
@@ -145,44 +150,22 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
protected abstract A createAdapter(LinearLayoutManager layoutManager); protected abstract A createAdapter(LinearLayoutManager layoutManager);
protected void loadItems() { protected void displayItems(List<I> items) {
int revision = adapter.getRevision(); if (items.isEmpty()) {
getController().loadItems( list.showData();
new UiResultExceptionHandler<ThreadItemList<I>, DbException>(
this) {
@Override
public void onResultUi(ThreadItemList<I> 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<I> 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
} else { } 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<G extends NamedGroup, I extends ThreadI
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
sharingController.onStart(); sharingController.onStart();
loadItems();
list.startPeriodicUpdate(); list.startPeriodicUpdate();
} }
@@ -296,7 +278,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
} }
private void scrollToItemAtTop(I item) { private void scrollToItemAtTop(I item) {
int position = adapter.findItemPosition(item); int position = NO_POSITION;// adapter.findItemPosition(item);
if (position != NO_POSITION) { if (position != NO_POSITION) {
layoutManager layoutManager
.scrollToPositionWithOffset(position, 0); .scrollToPositionWithOffset(position, 0);
@@ -357,15 +339,14 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
} }
private void addItem(I item, boolean isLocal) { private void addItem(I item, boolean isLocal) {
adapter.incrementRevision();
MessageId parent = item.getParentId(); MessageId parent = item.getParentId();
if (parent != null && !adapter.contains(parent)) { if (parent != null) {
// We've incremented the adapter's revision, so the item will be // We've incremented the adapter's revision, so the item will be
// loaded when its parent has been loaded // loaded when its parent has been loaded
LOG.info("Ignoring item with missing parent"); LOG.info("Ignoring item with missing parent");
return; return;
} }
adapter.add(item); // TODO submit new list
if (isLocal) { if (isLocal) {
scrollToItemAtTop(item); scrollToItemAtTop(item);

View File

@@ -7,7 +7,6 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.controller.ActivityLifecycleController; import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.client.NamedGroup;
import java.util.Collection; import java.util.Collection;
@@ -16,7 +15,7 @@ import javax.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
@NotNullByDefault @NotNullByDefault
public interface ThreadListController<G extends NamedGroup, I extends ThreadItem> public interface ThreadListController<I extends ThreadItem>
extends ActivityLifecycleController { extends ActivityLifecycleController {
void setGroupId(GroupId groupId); void setGroupId(GroupId groupId);
@@ -24,9 +23,6 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
void loadSharingContacts( void loadSharingContacts(
ResultExceptionHandler<Collection<ContactId>, DbException> handler); ResultExceptionHandler<Collection<ContactId>, DbException> handler);
void loadItems(
ResultExceptionHandler<ThreadItemList<I>, DbException> handler);
void markItemRead(I item); void markItemRead(I item);
void markItemsRead(Collection<I> items); void markItemsRead(Collection<I> items);
@@ -48,7 +44,8 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
interface ThreadListDataSource { interface ThreadListDataSource {
@UiThread @Nullable @UiThread
@Nullable
MessageId getFirstVisibleMessageId(); MessageId getFirstVisibleMessageId();
} }

View File

@@ -21,7 +21,6 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener; import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.NamedGroup;
import org.briarproject.briar.api.client.PostHeader; import org.briarproject.briar.api.client.PostHeader;
import org.briarproject.briar.api.client.ThreadedMessage; import org.briarproject.briar.api.client.ThreadedMessage;
@@ -34,7 +33,6 @@ import java.util.logging.Logger;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@@ -42,9 +40,9 @@ import static org.briarproject.bramble.util.LogUtils.now;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends ThreadItem, H extends PostHeader, M extends ThreadedMessage, L extends ThreadListListener<I>> public abstract class ThreadListControllerImpl<I extends ThreadItem, H extends PostHeader, M extends ThreadedMessage, L extends ThreadListListener<I>>
extends DbControllerImpl extends DbControllerImpl
implements ThreadListController<G, I>, EventListener { implements ThreadListController<I>, EventListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ThreadListControllerImpl.class.getName()); Logger.getLogger(ThreadListControllerImpl.class.getName());
@@ -129,42 +127,6 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
} }
} }
@Override
public void loadItems(
ResultExceptionHandler<ThreadItemList<I>, DbException> handler) {
checkGroupId();
runOnDbThread(() -> {
try {
// Load headers
long start = now();
Collection<H> 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<H> loadHeaders() throws DbException;
@DatabaseExecutor
protected abstract String loadMessageText(H header) throws DbException;
@Override @Override
public void markItemRead(I item) { public void markItemRead(I item) {
markItemsRead(Collections.singletonList(item)); markItemsRead(Collections.singletonList(item));
@@ -207,19 +169,6 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
@DatabaseExecutor @DatabaseExecutor
protected abstract H addLocalMessage(M message) throws DbException; protected abstract H addLocalMessage(M message) throws DbException;
private ThreadItemList<I> buildItems(Collection<H> headers)
throws DbException {
ThreadItemList<I> 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 abstract I buildItem(H header, String text);
protected GroupId getGroupId() { protected GroupId getGroupId() {

View File

@@ -4,6 +4,8 @@ import android.app.Application;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseExecutor; 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.db.TransactionManager;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; 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.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; 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.AndroidExecutor;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.viewmodel.DbViewModel; 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.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.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import androidx.annotation.CallSuper; 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 java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public abstract class ThreadListViewModel<G extends NamedGroup, I extends ThreadItem> public abstract class ThreadListViewModel<I extends ThreadItem> extends DbViewModel
extends DbViewModel implements EventListener { implements EventListener {
private static final Logger LOG = private static final Logger LOG =
getLogger(ThreadListViewModel.class.getName()); getLogger(ThreadListViewModel.class.getName());
protected final IdentityManager identityManager; protected final IdentityManager identityManager;
protected final AndroidNotificationManager notificationManager; protected final AndroidNotificationManager notificationManager;
protected final Executor cryptoExecutor; protected final Executor cryptoExecutor;
protected final Clock clock; protected final Clock clock;
private final MessageTracker messageTracker;
private final EventBus eventBus; private final EventBus eventBus;
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private final MutableLiveData<LiveResult<List<I>>> items =
new MutableLiveData<>();
protected volatile GroupId groupId; protected volatile GroupId groupId;
public ThreadListViewModel(Application application, public ThreadListViewModel(Application application,
@@ -51,12 +70,14 @@ public abstract class ThreadListViewModel<G extends NamedGroup, I extends Thread
AndroidNotificationManager notificationManager, AndroidNotificationManager notificationManager,
@CryptoExecutor Executor cryptoExecutor, @CryptoExecutor Executor cryptoExecutor,
Clock clock, Clock clock,
MessageTracker messageTracker,
EventBus eventBus) { EventBus eventBus) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor); super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.identityManager = identityManager; this.identityManager = identityManager;
this.notificationManager = notificationManager; this.notificationManager = notificationManager;
this.cryptoExecutor = cryptoExecutor; this.cryptoExecutor = cryptoExecutor;
this.clock = clock; this.clock = clock;
this.messageTracker = messageTracker;
this.eventBus = eventBus; this.eventBus = eventBus;
this.eventBus.addListener(this); this.eventBus.addListener(this);
} }
@@ -74,6 +95,52 @@ public abstract class ThreadListViewModel<G extends NamedGroup, I extends Thread
@CallSuper @CallSuper
public void setGroupId(GroupId groupId) { public void setGroupId(GroupId groupId) {
this.groupId = groupId; this.groupId = groupId;
loadItems();
}
public abstract void loadItems();
@UiThread
protected void setItems(LiveResult<List<I>> items) {
this.items.setValue(items);
}
@DatabaseExecutor
protected <H extends PostHeader> List<I> buildItems(
Transaction txn, Collection<H> headers, ItemGetter<H, I> itemGetter)
throws DbException {
long start = now();
ThreadItemList<I> 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<LiveResult<List<I>>> getItems() {
return items;
}
public interface ItemGetter<H extends PostHeader, I> {
I getItem(H header, String text);
} }
} }

View File

@@ -20,11 +20,11 @@ class ThreadScrollListener<I extends ThreadItem>
private static final Logger LOG = private static final Logger LOG =
getLogger(ThreadScrollListener.class.getName()); getLogger(ThreadScrollListener.class.getName());
private final ThreadListController<?, I> controller; private final ThreadListController<I> controller;
private final UnreadMessageButton upButton, downButton; private final UnreadMessageButton upButton, downButton;
ThreadScrollListener(ThreadItemAdapter<I> adapter, ThreadScrollListener(ThreadItemAdapter<I> adapter,
ThreadListController<?, I> controller, ThreadListController<I> controller,
UnreadMessageButton upButton, UnreadMessageButton upButton,
UnreadMessageButton downButton) { UnreadMessageButton downButton) {
super(adapter); super(adapter);

View File

@@ -4,7 +4,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -13,14 +13,15 @@ public interface MessageTree<T extends MessageTree.MessageNode> {
void add(Collection<T> nodes); void add(Collection<T> nodes);
@Deprecated
void add(T node); void add(T node);
void setComparator(Comparator<T> comparator); @Deprecated
void clear(); void clear();
Collection<T> depthFirstOrder(); List<T> depthFirstOrder();
@Deprecated
boolean contains(MessageId m); boolean contains(MessageId m);
@NotNullByDefault @NotNullByDefault

View File

@@ -30,9 +30,14 @@ public class MessageTreeImpl<T extends MessageTree.MessageNode>
private final List<List<T>> unsortedLists = new ArrayList<>(); private final List<List<T>> unsortedLists = new ArrayList<>();
@SuppressWarnings("UseCompareMethod") @SuppressWarnings("UseCompareMethod")
private Comparator<T> comparator = (o1, o2) -> private final Comparator<T> comparator = (o1, o2) ->
Long.valueOf(o1.getTimestamp()).compareTo(o2.getTimestamp()); Long.valueOf(o1.getTimestamp()).compareTo(o2.getTimestamp());
public MessageTreeImpl(Collection<T> collection) {
super();
add(collection);
}
@Override @Override
public synchronized void clear() { public synchronized void clear() {
roots.clear(); roots.clear();
@@ -79,6 +84,7 @@ public class MessageTreeImpl<T extends MessageTree.MessageNode>
@GuardedBy("this") @GuardedBy("this")
private void sortUnsorted() { private void sortUnsorted() {
for (List<T> list : unsortedLists) { for (List<T> list : unsortedLists) {
//noinspection Java8ListSort
Collections.sort(list, comparator); Collections.sort(list, comparator);
} }
unsortedLists.clear(); unsortedLists.clear();
@@ -95,17 +101,7 @@ public class MessageTreeImpl<T extends MessageTree.MessageNode>
} }
@Override @Override
public synchronized void setComparator(Comparator<T> comparator) { public synchronized List<T> depthFirstOrder() {
this.comparator = comparator;
// Sort all lists with the new comparator
Collections.sort(roots, comparator);
for (Map.Entry<MessageId, List<T>> entry : nodeMap.entrySet()) {
Collections.sort(entry.getValue(), comparator);
}
}
@Override
public synchronized Collection<T> depthFirstOrder() {
List<T> orderedList = new ArrayList<>(); List<T> orderedList = new ArrayList<>();
for (T root : roots) { for (T root : roots) {
traverse(orderedList, root, 0); traverse(orderedList, root, 0);