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.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<Forum, ForumPostItem, ThreadItemAdapter<ForumPostItem>>
ThreadListActivity<ForumPostItem, ThreadItemAdapter<ForumPostItem>>
implements ForumListener {
@Inject
@@ -55,12 +54,12 @@ public class ForumActivity extends
}
@Override
protected ThreadListController<Forum, ForumPostItem> getController() {
protected ThreadListController<ForumPostItem> getController() {
return forumController;
}
@Override
protected ThreadListViewModel<Forum, ForumPostItem> getViewModel() {
protected ThreadListViewModel<ForumPostItem> getViewModel() {
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.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<Forum, ForumPostItem> {
interface ForumController extends ThreadListController<ForumPostItem> {
interface ForumListener extends ThreadListListener<ForumPostItem> {
@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.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<Forum, ForumPostItem, ForumPostHeader, ForumPost, ForumListener>
ThreadListControllerImpl<ForumPostItem, ForumPostHeader, ForumPost, ForumListener>
implements ForumController {
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
protected void markRead(MessageId id) throws DbException {
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.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<Forum, ForumPostItem> {
class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
private static final Logger LOG = getLogger(ForumViewModel.class.getName());
@@ -54,12 +62,13 @@ class ForumViewModel extends ThreadListViewModel<Forum, ForumPostItem> {
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<Forum, ForumPostItem> {
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() {
runOnDbThread(() -> {
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.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<PrivateGroup, GroupMessageItem, GroupMessageAdapter>
ThreadListActivity<GroupMessageItem, GroupMessageAdapter>
implements GroupListener {
@Inject
@@ -65,12 +64,12 @@ public class GroupActivity extends
}
@Override
protected ThreadListController<PrivateGroup, GroupMessageItem> getController() {
protected ThreadListController<GroupMessageItem> getController() {
return controller;
}
@Override
protected ThreadListViewModel<PrivateGroup, GroupMessageItem> getViewModel() {
protected ThreadListViewModel<GroupMessageItem> 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<Boolean, DbException>(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

View File

@@ -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<PrivateGroup, GroupMessageItem> {
extends ThreadListController<GroupMessageItem> {
void isDissolved(
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.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<PrivateGroup, GroupMessageItem, GroupMessageHeader, GroupMessage, GroupListener>
ThreadListControllerImpl<GroupMessageItem, GroupMessageHeader, GroupMessage, GroupListener>
implements GroupController {
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
protected void markRead(MessageId id) throws DbException {
privateGroupManager.setReadFlag(getGroupId(), id, true);

View File

@@ -32,7 +32,7 @@ class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
@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<GroupMessageItem> {
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<GroupMessageItem> {
}
}
@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
}
}

View File

@@ -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<PrivateGroup, GroupMessageItem> {
class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
private static final Logger LOG = getLogger(GroupViewModel.class.getName());
private final PrivateGroupManager privateGroupManager;
private final GroupMessageFactory groupMessageFactory;
MutableLiveData<PrivateGroup> privateGroup = new MutableLiveData<>();
MutableLiveData<Boolean> isCreator = new MutableLiveData<>();
private final MutableLiveData<PrivateGroup> privateGroup =
new MutableLiveData<>();
private final MutableLiveData<Boolean> 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<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() {
runOnDbThread(() -> {
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;
}
@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.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<I extends ThreadItem>
extends RecyclerView.Adapter<BaseThreadItemViewHolder<I>>
implements VersionedAdapter, ItemReturningAdapter<I> {
extends ListAdapter<I, BaseThreadItemViewHolder<I>>
implements ItemReturningAdapter<I> {
static final int UNDEFINED = -1;
protected final NestedTreeList<I> items = new NestedTreeList<>();
private final ThreadItemListener<I> listener;
private final LinearLayoutManager layoutManager;
private volatile int revision = 0;
public ThreadItemAdapter(ThreadItemListener<I> listener,
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.layoutManager = layoutManager;
}
@@ -51,28 +62,14 @@ public class ThreadItemAdapter<I extends ThreadItem>
@Override
public void onBindViewHolder(@NonNull BaseThreadItemViewHolder<I> 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<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}
* and disables the highlight for a previously highlighted item, if any.
*
* <p>
* 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<I extends ThreadItem>
@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<I extends ThreadItem>
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<I extends ThreadItem>
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<I extends ThreadItem>
return NO_POSITION;
}
@Override
public I getItemAt(int position) {
return getItem(position);
}
public interface ThreadItemListener<I> {
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.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<G extends NamedGroup, I extends ThreadItem, A extends ThreadItemAdapter<I>>
public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadItemAdapter<I>>
extends BriarActivity
implements ThreadListListener<I>, SendListener, SharingListener,
ThreadItemListener<I>, ThreadListDataSource {
@@ -59,6 +58,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
getLogger(ThreadListActivity.class.getName());
protected A adapter;
private ThreadScrollListener<I> scrollListener;
protected BriarRecyclerView list;
private LinearLayoutManager layoutManager;
@@ -70,9 +70,9 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
@Nullable
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
protected SharingController sharingController;
@@ -127,6 +127,11 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
if (replyIdBytes != null) replyId = new MessageId(replyIdBytes);
}
getViewModel().getItems().observe(this, result -> result
.onError(this::handleException)
.onSuccess(this::displayItems)
);
sharingController.setSharingListener(this);
loadSharingContacts();
}
@@ -145,44 +150,22 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
protected abstract A createAdapter(LinearLayoutManager layoutManager);
protected void loadItems() {
int revision = adapter.getRevision();
getController().loadItems(
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
protected void displayItems(List<I> 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<G extends NamedGroup, I extends ThreadI
public void onStart() {
super.onStart();
sharingController.onStart();
loadItems();
list.startPeriodicUpdate();
}
@@ -296,7 +278,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
}
private void scrollToItemAtTop(I item) {
int position = adapter.findItemPosition(item);
int position = NO_POSITION;// adapter.findItemPosition(item);
if (position != NO_POSITION) {
layoutManager
.scrollToPositionWithOffset(position, 0);
@@ -357,15 +339,14 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
}
private void addItem(I item, boolean isLocal) {
adapter.incrementRevision();
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
// loaded when its parent has been loaded
LOG.info("Ignoring item with missing parent");
return;
}
adapter.add(item);
// TODO submit new list
if (isLocal) {
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.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.client.NamedGroup;
import java.util.Collection;
@@ -16,7 +15,7 @@ import javax.annotation.Nullable;
import androidx.annotation.UiThread;
@NotNullByDefault
public interface ThreadListController<G extends NamedGroup, I extends ThreadItem>
public interface ThreadListController<I extends ThreadItem>
extends ActivityLifecycleController {
void setGroupId(GroupId groupId);
@@ -24,9 +23,6 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
void loadSharingContacts(
ResultExceptionHandler<Collection<ContactId>, DbException> handler);
void loadItems(
ResultExceptionHandler<ThreadItemList<I>, DbException> handler);
void markItemRead(I item);
void markItemsRead(Collection<I> items);
@@ -48,7 +44,8 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
interface ThreadListDataSource {
@UiThread @Nullable
@UiThread
@Nullable
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.api.android.AndroidNotificationManager;
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.ThreadedMessage;
@@ -34,7 +33,6 @@ import java.util.logging.Logger;
import androidx.annotation.CallSuper;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -42,9 +40,9 @@ import static org.briarproject.bramble.util.LogUtils.now;
@MethodsNotNullByDefault
@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
implements ThreadListController<G, I>, EventListener {
implements ThreadListController<I>, EventListener {
private static final Logger LOG =
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
public void markItemRead(I item) {
markItemsRead(Collections.singletonList(item));
@@ -207,19 +169,6 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
@DatabaseExecutor
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 GroupId getGroupId() {

View File

@@ -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<G extends NamedGroup, I extends ThreadItem>
extends DbViewModel implements EventListener {
public abstract class ThreadListViewModel<I extends ThreadItem> 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<MessageId, String> textCache = new ConcurrentHashMap<>();
private final MutableLiveData<LiveResult<List<I>>> items =
new MutableLiveData<>();
protected volatile GroupId groupId;
public ThreadListViewModel(Application application,
@@ -51,12 +70,14 @@ public abstract class ThreadListViewModel<G extends NamedGroup, I extends Thread
AndroidNotificationManager notificationManager,
@CryptoExecutor Executor cryptoExecutor,
Clock clock,
MessageTracker messageTracker,
EventBus eventBus) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.identityManager = identityManager;
this.notificationManager = notificationManager;
this.cryptoExecutor = cryptoExecutor;
this.clock = clock;
this.messageTracker = messageTracker;
this.eventBus = eventBus;
this.eventBus.addListener(this);
}
@@ -74,6 +95,52 @@ public abstract class ThreadListViewModel<G extends NamedGroup, I extends Thread
@CallSuper
public void setGroupId(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 =
getLogger(ThreadScrollListener.class.getName());
private final ThreadListController<?, I> controller;
private final ThreadListController<I> controller;
private final UnreadMessageButton upButton, downButton;
ThreadScrollListener(ThreadItemAdapter<I> adapter,
ThreadListController<?, I> controller,
ThreadListController<I> controller,
UnreadMessageButton upButton,
UnreadMessageButton downButton) {
super(adapter);

View File

@@ -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<T extends MessageTree.MessageNode> {
void add(Collection<T> nodes);
@Deprecated
void add(T node);
void setComparator(Comparator<T> comparator);
@Deprecated
void clear();
Collection<T> depthFirstOrder();
List<T> depthFirstOrder();
@Deprecated
boolean contains(MessageId m);
@NotNullByDefault

View File

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