mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +01:00
list position save and restore now implemented for threaded lists
This commit is contained in:
@@ -32,6 +32,7 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
import org.briarproject.briar.api.blog.BlogManager;
|
||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.feed.FeedManager;
|
||||
import org.briarproject.briar.api.forum.ForumManager;
|
||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||
@@ -78,6 +79,8 @@ public interface AndroidComponent
|
||||
@DatabaseExecutor
|
||||
Executor databaseExecutor();
|
||||
|
||||
MessageTracker messageTracker();
|
||||
|
||||
LifecycleManager lifecycleManager();
|
||||
|
||||
IdentityManager identityManager();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
@@ -26,6 +27,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
protected final NestedTreeList<I> items = new NestedTreeList<>();
|
||||
private final ThreadItemListener<I> listener;
|
||||
private final LinearLayoutManager layoutManager;
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
private volatile int revision = 0;
|
||||
|
||||
@@ -64,6 +66,31 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
revision++;
|
||||
}
|
||||
|
||||
void setBottomItem(MessageId messageId) {
|
||||
if (messageId != null) {
|
||||
int pos = 0;
|
||||
for (I item : items) {
|
||||
if (item.getId().equals(messageId)) {
|
||||
scrollToPosition(pos);
|
||||
break;
|
||||
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scrollToPosition(final int pos) {
|
||||
// Post call ensures that the list scrolls AFTER it has been propagated
|
||||
// and the layout has been calculated.
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
layoutManager.scrollToPosition(pos);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setItems(Collection<I> items) {
|
||||
this.items.clear();
|
||||
this.items.addAll(items);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface ThreadItemList<I extends ThreadItem> extends List<I> {
|
||||
|
||||
@Nullable
|
||||
MessageId getBottomVisibleItemId();
|
||||
|
||||
void setBottomVisibleItemId(@Nullable MessageId bottomVisibleItemId);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ThreadItemListImpl<I extends ThreadItem> extends ArrayList<I>
|
||||
implements ThreadItemList<I> {
|
||||
|
||||
private MessageId bottomVisibleItemId;
|
||||
|
||||
@Override
|
||||
public MessageId getBottomVisibleItemId() {
|
||||
return bottomVisibleItemId;
|
||||
}
|
||||
|
||||
public void setBottomVisibleItemId(@Nullable MessageId bottomVisibleItemId) {
|
||||
this.bottomVisibleItemId = bottomVisibleItemId;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import org.briarproject.briar.android.controller.SharingController;
|
||||
import org.briarproject.briar.android.controller.SharingController.SharingListener;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListDataSource;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
@@ -51,7 +52,7 @@ import static org.briarproject.briar.android.threaded.ThreadItemAdapter.UnreadCo
|
||||
public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadItemAdapter<I>, I extends ThreadItem, H extends PostHeader>
|
||||
extends BriarActivity
|
||||
implements ThreadListListener<H>, TextInputListener, SharingListener,
|
||||
ThreadItemListener<I> {
|
||||
ThreadItemListener<I>, ThreadListDataSource {
|
||||
|
||||
protected static final String KEY_REPLY_ID = "replyId";
|
||||
|
||||
@@ -68,6 +69,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
private MessageId replyId;
|
||||
|
||||
protected abstract ThreadListController<G, I, H> getController();
|
||||
|
||||
@Inject
|
||||
protected SharingController sharingController;
|
||||
|
||||
@@ -104,6 +106,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
updateUnreadCount();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView,
|
||||
int newState) {
|
||||
@@ -144,6 +147,16 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
loadSharingContacts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageId getBottomVisibleMessageId() {
|
||||
if (layoutManager != null && adapter != null) {
|
||||
int position =
|
||||
layoutManager.findLastCompletelyVisibleItemPosition();
|
||||
return adapter.getItemAt(position).getId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract A createAdapter(LinearLayoutManager layoutManager);
|
||||
|
||||
protected void loadNamedGroup() {
|
||||
@@ -167,15 +180,18 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
protected void loadItems() {
|
||||
final int revision = adapter.getRevision();
|
||||
getController().loadItems(
|
||||
new UiResultExceptionHandler<Collection<I>, DbException>(this) {
|
||||
new UiResultExceptionHandler<ThreadItemList<I>, DbException>(
|
||||
this) {
|
||||
@Override
|
||||
public void onResultUi(Collection<I> items) {
|
||||
public void onResultUi(ThreadItemList<I> items) {
|
||||
if (revision == adapter.getRevision()) {
|
||||
adapter.incrementRevision();
|
||||
if (items.isEmpty()) {
|
||||
list.showData();
|
||||
} else {
|
||||
adapter.setItems(items);
|
||||
adapter.setBottomItem(
|
||||
items.getBottomVisibleItemId());
|
||||
list.showData();
|
||||
updateTextInput(replyId);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.android.DestroyableContext;
|
||||
import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||
@@ -30,7 +31,7 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
|
||||
|
||||
void loadItem(H header, ResultExceptionHandler<I, DbException> handler);
|
||||
|
||||
void loadItems(ResultExceptionHandler<Collection<I>, DbException> handler);
|
||||
void loadItems(ResultExceptionHandler<ThreadItemList<I>, DbException> handler);
|
||||
|
||||
void markItemRead(I item);
|
||||
|
||||
@@ -52,4 +53,10 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
|
||||
void onInvitationAccepted(ContactId c);
|
||||
}
|
||||
|
||||
interface ThreadListDataSource {
|
||||
|
||||
@UiThread @Nullable
|
||||
MessageId getBottomVisibleMessageId();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,19 +22,20 @@ import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||
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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@@ -56,6 +57,9 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
||||
protected final Executor cryptoExecutor;
|
||||
protected final Clock clock;
|
||||
protected volatile L listener;
|
||||
@Inject
|
||||
MessageTracker messageTracker;
|
||||
private ThreadListDataSource source;
|
||||
|
||||
protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager, IdentityManager identityManager,
|
||||
@@ -79,6 +83,13 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
||||
@Override
|
||||
public void onActivityCreate(Activity activity) {
|
||||
listener = (L) activity;
|
||||
if (activity instanceof ThreadListDataSource) {
|
||||
source = (ThreadListDataSource) activity;
|
||||
} else {
|
||||
throw new ClassCastException(
|
||||
"Activity " + activity.getClass().getSimpleName() +
|
||||
" must implement ThreadListDataSource");
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@@ -97,6 +108,13 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
||||
|
||||
@Override
|
||||
public void onActivityDestroy() {
|
||||
try {
|
||||
messageTracker
|
||||
.storeMessageId(groupId,
|
||||
source.getBottomVisibleMessageId());
|
||||
} catch (DbException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@@ -144,7 +162,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
||||
|
||||
@Override
|
||||
public void loadItems(
|
||||
final ResultExceptionHandler<Collection<I>, DbException> handler) {
|
||||
final ResultExceptionHandler<ThreadItemList<I>, DbException> handler) {
|
||||
checkGroupId();
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
@@ -293,11 +311,19 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
||||
@DatabaseExecutor
|
||||
protected abstract void deleteNamedGroup(G groupItem) throws DbException;
|
||||
|
||||
private List<I> buildItems(Collection<H> headers) {
|
||||
List<I> items = new ArrayList<>();
|
||||
private ThreadItemList<I> buildItems(Collection<H> headers) {
|
||||
ThreadItemList<I> items = new ThreadItemListImpl<>();
|
||||
for (H h : headers) {
|
||||
items.add(buildItem(h, bodyCache.get(h.getId())));
|
||||
}
|
||||
try {
|
||||
MessageId msgId = messageTracker.loadStoredMessageId(groupId);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loaded last top visible message id " + msgId);
|
||||
items.setBottomVisibleItemId(msgId);
|
||||
} catch (DbException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import org.briarproject.briar.BuildConfig;
|
||||
import org.briarproject.briar.android.TestBriarApplication;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemList;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemListImpl;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -23,10 +25,7 @@ import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricGradleTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
@@ -81,7 +80,7 @@ public class ForumActivityTest {
|
||||
|
||||
private TestForumActivity forumActivity;
|
||||
@Captor
|
||||
private ArgumentCaptor<UiResultExceptionHandler<Collection<ForumItem>, DbException>>
|
||||
private ArgumentCaptor<UiResultExceptionHandler<ThreadItemList<ForumItem>, DbException>>
|
||||
rc;
|
||||
|
||||
@Before
|
||||
@@ -93,7 +92,7 @@ public class ForumActivityTest {
|
||||
.withIntent(intent).create().resume().get();
|
||||
}
|
||||
|
||||
private List<ForumItem> getDummyData() {
|
||||
private ThreadItemList<ForumItem> getDummyData() {
|
||||
ForumItem[] forumItems = new ForumItem[6];
|
||||
for (int i = 0; i < forumItems.length; i++) {
|
||||
AuthorId authorId = new AuthorId(TestUtils.getRandomId());
|
||||
@@ -103,13 +102,15 @@ public class ForumActivityTest {
|
||||
AUTHORS[i], System.currentTimeMillis(), author, UNKNOWN);
|
||||
forumItems[i].setLevel(LEVELS[i]);
|
||||
}
|
||||
return new ArrayList<>(Arrays.asList(forumItems));
|
||||
ThreadItemList<ForumItem> list = new ThreadItemListImpl<>();
|
||||
list.addAll(Arrays.asList(forumItems));
|
||||
return list;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedEntries() {
|
||||
ForumController mc = forumActivity.getController();
|
||||
List<ForumItem> dummyData = getDummyData();
|
||||
ThreadItemList<ForumItem> dummyData = getDummyData();
|
||||
verify(mc, times(1)).loadItems(rc.capture());
|
||||
rc.getValue().onResult(dummyData);
|
||||
ThreadItemAdapter<ForumItem> adapter = forumActivity.getAdapter();
|
||||
|
||||
@@ -7,6 +7,8 @@ import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface MessageTracker {
|
||||
|
||||
@@ -38,6 +40,19 @@ public interface MessageTracker {
|
||||
void trackMessage(Transaction txn, GroupId g, long timestamp, boolean read)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Loads the stored message id for the respective group id or returns null
|
||||
* if none is available.
|
||||
*/
|
||||
@Nullable
|
||||
MessageId loadStoredMessageId(GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the message id for the respective group id. Exactly one message id
|
||||
* can be stored for any group id at any time, older values are overwritten.
|
||||
*/
|
||||
void storeMessageId(GroupId g, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks a message as read or unread and updates the group count.
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.client;
|
||||
|
||||
public interface MessageTrackerConstants {
|
||||
|
||||
String GROUP_KEY_STORED_MESSAGE_ID = "storedMessageId";
|
||||
String GROUP_KEY_MSG_COUNT = "messageCount";
|
||||
String GROUP_KEY_UNREAD_COUNT = "unreadCount";
|
||||
String GROUP_KEY_LATEST_MSG = "latestMessageTime";
|
||||
|
||||
@@ -13,11 +13,13 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_LATEST_MSG;
|
||||
import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_MSG_COUNT;
|
||||
import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_STORED_MESSAGE_ID;
|
||||
import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_UNREAD_COUNT;
|
||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||
|
||||
@@ -57,6 +59,30 @@ class MessageTrackerImpl implements MessageTracker {
|
||||
latestMsgTime));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MessageId loadStoredMessageId(GroupId g) throws DbException {
|
||||
try {
|
||||
BdfDictionary d = clientHelper.getGroupMetadataAsDictionary(g);
|
||||
byte[] msgBytes = d.getOptionalRaw(GROUP_KEY_STORED_MESSAGE_ID);
|
||||
return msgBytes != null? new MessageId(msgBytes) : null;
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeMessageId(GroupId g, MessageId m) throws DbException {
|
||||
BdfDictionary d = BdfDictionary.of(
|
||||
new BdfEntry(GROUP_KEY_STORED_MESSAGE_ID, m)
|
||||
);
|
||||
try {
|
||||
clientHelper.mergeGroupMetadata(g, d);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupCount getGroupCount(GroupId g) throws DbException {
|
||||
GroupCount count;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.briarproject.briar.forum;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.test.TestDatabaseModule;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.briar.api.forum.Forum;
|
||||
import org.briarproject.briar.api.forum.ForumManager;
|
||||
import org.briarproject.briar.api.forum.ForumPost;
|
||||
@@ -10,6 +13,7 @@ import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||
import org.briarproject.briar.test.BriarIntegrationTest;
|
||||
import org.briarproject.briar.test.BriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -223,4 +227,18 @@ public class ForumManagerTest
|
||||
assertEquals(1, forumManager1.getPostHeaders(g1).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageStoreAndLoad() {
|
||||
MessageId msgId = new MessageId(TestUtils.getRandomId());
|
||||
MessageId loadedId = null;
|
||||
try {
|
||||
messageTracker0.storeMessageId(groupId0, msgId);
|
||||
loadedId = messageTracker0.loadStoredMessageId(groupId0);
|
||||
} catch (DbException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Assert.assertNotNull(loadedId);
|
||||
Assert.assertTrue(msgId.equals(loadedId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user