Skip to content
Snippets Groups Projects
Unverified Commit 04471943 authored by Ernir Erlingsson's avatar Ernir Erlingsson
Browse files

list position save and restore now implemented for threaded lists

parent d1a929da
No related branches found
No related tags found
No related merge requests found
Showing
with 193 additions and 16 deletions
......@@ -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();
......
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);
......
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);
}
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;
......
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));
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment