mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-21 07:09:56 +01:00
Merge branch '894-list-position-restore' into 'master'
save and restore list position for threaded lists Closes #894 and #946 See merge request !528
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.BlogManager;
|
||||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
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.feed.FeedManager;
|
||||||
import org.briarproject.briar.api.forum.ForumManager;
|
import org.briarproject.briar.api.forum.ForumManager;
|
||||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||||
@@ -78,6 +79,8 @@ public interface AndroidComponent
|
|||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
Executor databaseExecutor();
|
Executor databaseExecutor();
|
||||||
|
|
||||||
|
MessageTracker messageTracker();
|
||||||
|
|
||||||
LifecycleManager lifecycleManager();
|
LifecycleManager lifecycleManager();
|
||||||
|
|
||||||
IdentityManager identityManager();
|
IdentityManager identityManager();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class DbControllerImpl implements DbController {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(DbControllerImpl.class.getName());
|
Logger.getLogger(DbControllerImpl.class.getName());
|
||||||
|
|
||||||
private final Executor dbExecutor;
|
protected final Executor dbExecutor;
|
||||||
private final LifecycleManager lifecycleManager;
|
private final LifecycleManager lifecycleManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
|||||||
import org.briarproject.briar.android.forum.ForumController.ForumListener;
|
import org.briarproject.briar.android.forum.ForumController.ForumListener;
|
||||||
import org.briarproject.briar.android.threaded.ThreadListControllerImpl;
|
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.GroupCount;
|
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||||
import org.briarproject.briar.api.forum.Forum;
|
import org.briarproject.briar.api.forum.Forum;
|
||||||
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
||||||
@@ -55,10 +56,10 @@ class ForumControllerImpl extends
|
|||||||
LifecycleManager lifecycleManager, IdentityManager identityManager,
|
LifecycleManager lifecycleManager, IdentityManager identityManager,
|
||||||
@CryptoExecutor Executor cryptoExecutor,
|
@CryptoExecutor Executor cryptoExecutor,
|
||||||
ForumManager forumManager, ForumSharingManager forumSharingManager,
|
ForumManager forumManager, ForumSharingManager forumSharingManager,
|
||||||
EventBus eventBus, Clock clock,
|
EventBus eventBus, Clock clock, MessageTracker messageTracker,
|
||||||
AndroidNotificationManager notificationManager) {
|
AndroidNotificationManager notificationManager) {
|
||||||
super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
|
super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
|
||||||
eventBus, clock, notificationManager);
|
eventBus, clock, notificationManager, messageTracker);
|
||||||
this.forumManager = forumManager;
|
this.forumManager = forumManager;
|
||||||
this.forumSharingManager = forumSharingManager;
|
this.forumSharingManager = forumSharingManager;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
|||||||
import org.briarproject.briar.android.privategroup.conversation.GroupController.GroupListener;
|
import org.briarproject.briar.android.privategroup.conversation.GroupController.GroupListener;
|
||||||
import org.briarproject.briar.android.threaded.ThreadListControllerImpl;
|
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.GroupCount;
|
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||||
import org.briarproject.briar.api.privategroup.GroupMember;
|
import org.briarproject.briar.api.privategroup.GroupMember;
|
||||||
import org.briarproject.briar.api.privategroup.GroupMessage;
|
import org.briarproject.briar.api.privategroup.GroupMessage;
|
||||||
@@ -60,9 +61,10 @@ class GroupControllerImpl extends
|
|||||||
@CryptoExecutor Executor cryptoExecutor,
|
@CryptoExecutor Executor cryptoExecutor,
|
||||||
PrivateGroupManager privateGroupManager,
|
PrivateGroupManager privateGroupManager,
|
||||||
GroupMessageFactory groupMessageFactory, EventBus eventBus,
|
GroupMessageFactory groupMessageFactory, EventBus eventBus,
|
||||||
Clock clock, AndroidNotificationManager notificationManager) {
|
MessageTracker messageTracker, Clock clock,
|
||||||
|
AndroidNotificationManager notificationManager) {
|
||||||
super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
|
super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
|
||||||
eventBus, clock, notificationManager);
|
eventBus, clock, notificationManager, messageTracker);
|
||||||
this.privateGroupManager = privateGroupManager;
|
this.privateGroupManager = privateGroupManager;
|
||||||
this.groupMessageFactory = groupMessageFactory;
|
this.groupMessageFactory = groupMessageFactory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.briar.android.threaded;
|
package org.briarproject.briar.android.threaded;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
@@ -26,6 +27,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
|||||||
protected final NestedTreeList<I> items = new NestedTreeList<>();
|
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 final Handler handler = new Handler();
|
||||||
|
|
||||||
private volatile int revision = 0;
|
private volatile int revision = 0;
|
||||||
|
|
||||||
@@ -64,6 +66,17 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
|||||||
revision++;
|
revision++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setItemWithIdVisible(MessageId messageId) {
|
||||||
|
int pos = 0;
|
||||||
|
for (I item : items) {
|
||||||
|
if (item.getId().equals(messageId)) {
|
||||||
|
layoutManager.scrollToPosition(pos);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setItems(Collection<I> items) {
|
public void setItems(Collection<I> items) {
|
||||||
this.items.clear();
|
this.items.clear();
|
||||||
this.items.addAll(items);
|
this.items.addAll(items);
|
||||||
@@ -144,7 +157,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
|||||||
/**
|
/**
|
||||||
* Returns the position of the first unread item below the current viewport
|
* Returns the position of the first unread item below the current viewport
|
||||||
*/
|
*/
|
||||||
public int getVisibleUnreadPosBottom() {
|
int getVisibleUnreadPosBottom() {
|
||||||
final int positionBottom = layoutManager.findLastVisibleItemPosition();
|
final 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 < items.size(); i++) {
|
||||||
@@ -156,7 +169,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
|||||||
/**
|
/**
|
||||||
* Returns the position of the first unread item above the current viewport
|
* Returns the position of the first unread item above the current viewport
|
||||||
*/
|
*/
|
||||||
public int getVisibleUnreadPosTop() {
|
int getVisibleUnreadPosTop() {
|
||||||
final int positionTop = layoutManager.findFirstVisibleItemPosition();
|
final 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 < items.size(); i++) {
|
||||||
|
|||||||
@@ -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 getFirstVisibleItemId();
|
||||||
|
|
||||||
|
void setFirstVisibleId(@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 getFirstVisibleItemId() {
|
||||||
|
return bottomVisibleItemId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstVisibleId(@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.SharingController.SharingListener;
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener;
|
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.threaded.ThreadListController.ThreadListListener;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
import org.briarproject.briar.android.view.TextInputView;
|
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>
|
public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadItemAdapter<I>, I extends ThreadItem, H extends PostHeader>
|
||||||
extends BriarActivity
|
extends BriarActivity
|
||||||
implements ThreadListListener<H>, TextInputListener, SharingListener,
|
implements ThreadListListener<H>, TextInputListener, SharingListener,
|
||||||
ThreadItemListener<I> {
|
ThreadItemListener<I>, ThreadListDataSource {
|
||||||
|
|
||||||
protected static final String KEY_REPLY_ID = "replyId";
|
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;
|
private MessageId replyId;
|
||||||
|
|
||||||
protected abstract ThreadListController<G, I, H> getController();
|
protected abstract ThreadListController<G, I, H> getController();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected SharingController sharingController;
|
protected SharingController sharingController;
|
||||||
|
|
||||||
@@ -104,6 +106,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
|||||||
updateUnreadCount();
|
updateUnreadCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onScrollStateChanged(RecyclerView recyclerView,
|
public void onScrollStateChanged(RecyclerView recyclerView,
|
||||||
int newState) {
|
int newState) {
|
||||||
@@ -144,6 +147,18 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
|||||||
loadSharingContacts();
|
loadSharingContacts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public MessageId getFirstVisibleMessageId() {
|
||||||
|
if (layoutManager != null && adapter != null) {
|
||||||
|
int position =
|
||||||
|
layoutManager.findFirstVisibleItemPosition();
|
||||||
|
I i = adapter.getItemAt(position);
|
||||||
|
return i == null ? null : i.getId();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract A createAdapter(LinearLayoutManager layoutManager);
|
protected abstract A createAdapter(LinearLayoutManager layoutManager);
|
||||||
|
|
||||||
protected void loadNamedGroup() {
|
protected void loadNamedGroup() {
|
||||||
@@ -167,16 +182,16 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
|||||||
protected void loadItems() {
|
protected void loadItems() {
|
||||||
final int revision = adapter.getRevision();
|
final int revision = adapter.getRevision();
|
||||||
getController().loadItems(
|
getController().loadItems(
|
||||||
new UiResultExceptionHandler<Collection<I>, DbException>(this) {
|
new UiResultExceptionHandler<ThreadItemList<I>, DbException>(
|
||||||
|
this) {
|
||||||
@Override
|
@Override
|
||||||
public void onResultUi(Collection<I> items) {
|
public void onResultUi(ThreadItemList<I> items) {
|
||||||
if (revision == adapter.getRevision()) {
|
if (revision == adapter.getRevision()) {
|
||||||
adapter.incrementRevision();
|
adapter.incrementRevision();
|
||||||
if (items.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
list.showData();
|
list.showData();
|
||||||
} else {
|
} else {
|
||||||
adapter.setItems(items);
|
initList(items);
|
||||||
list.showData();
|
|
||||||
updateTextInput(replyId);
|
updateTextInput(replyId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -192,6 +207,15 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initList(final ThreadItemList<I> items) {
|
||||||
|
adapter.setItems(items);
|
||||||
|
MessageId messageId = items.getFirstVisibleItemId();
|
||||||
|
if (messageId != null)
|
||||||
|
adapter.setItemWithIdVisible(messageId);
|
||||||
|
updateUnreadCount();
|
||||||
|
list.showData();
|
||||||
|
}
|
||||||
|
|
||||||
protected void loadSharingContacts() {
|
protected void loadSharingContacts() {
|
||||||
getController().loadSharingContacts(
|
getController().loadSharingContacts(
|
||||||
new UiResultExceptionHandler<Collection<ContactId>, DbException>(
|
new UiResultExceptionHandler<Collection<ContactId>, DbException>(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.contact.ContactId;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
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.DestroyableContext;
|
||||||
import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
||||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
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 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);
|
void markItemRead(I item);
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
|
|||||||
|
|
||||||
void deleteNamedGroup(ExceptionHandler<DbException> handler);
|
void deleteNamedGroup(ExceptionHandler<DbException> handler);
|
||||||
|
|
||||||
interface ThreadListListener<H> extends DestroyableContext {
|
interface ThreadListListener<H> extends ThreadListDataSource {
|
||||||
@UiThread
|
@UiThread
|
||||||
void onHeaderReceived(H header);
|
void onHeaderReceived(H header);
|
||||||
|
|
||||||
@@ -52,4 +53,10 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
|
|||||||
void onInvitationAccepted(ContactId c);
|
void onInvitationAccepted(ContactId c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ThreadListDataSource extends DestroyableContext {
|
||||||
|
|
||||||
|
@UiThread @Nullable
|
||||||
|
MessageId getFirstVisibleMessageId();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,14 +22,13 @@ import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
|||||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
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.NamedGroup;
|
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;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -55,18 +54,21 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
|||||||
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;
|
||||||
protected volatile L listener;
|
protected volatile L listener;
|
||||||
|
|
||||||
protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||||
LifecycleManager lifecycleManager, IdentityManager identityManager,
|
LifecycleManager lifecycleManager, IdentityManager identityManager,
|
||||||
@CryptoExecutor Executor cryptoExecutor, EventBus eventBus,
|
@CryptoExecutor Executor cryptoExecutor, EventBus eventBus,
|
||||||
Clock clock, AndroidNotificationManager notificationManager) {
|
Clock clock, AndroidNotificationManager notificationManager,
|
||||||
|
MessageTracker messageTracker) {
|
||||||
super(dbExecutor, lifecycleManager);
|
super(dbExecutor, lifecycleManager);
|
||||||
this.identityManager = identityManager;
|
this.identityManager = identityManager;
|
||||||
this.cryptoExecutor = cryptoExecutor;
|
this.cryptoExecutor = cryptoExecutor;
|
||||||
this.notificationManager = notificationManager;
|
this.notificationManager = notificationManager;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
|
this.messageTracker = messageTracker;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -97,6 +99,19 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityDestroy() {
|
public void onActivityDestroy() {
|
||||||
|
dbExecutor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
messageTracker
|
||||||
|
.storeMessageId(groupId,
|
||||||
|
listener.getFirstVisibleMessageId());
|
||||||
|
} catch (DbException e) {
|
||||||
|
if (LOG.isLoggable(WARNING))
|
||||||
|
LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
@@ -144,7 +159,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadItems(
|
public void loadItems(
|
||||||
final ResultExceptionHandler<Collection<I>, DbException> handler) {
|
final ResultExceptionHandler<ThreadItemList<I>, DbException> handler) {
|
||||||
checkGroupId();
|
checkGroupId();
|
||||||
runOnDbThread(new Runnable() {
|
runOnDbThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -293,11 +308,16 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
|||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
protected abstract void deleteNamedGroup(G groupItem) throws DbException;
|
protected abstract void deleteNamedGroup(G groupItem) throws DbException;
|
||||||
|
|
||||||
private List<I> buildItems(Collection<H> headers) {
|
private ThreadItemList<I> buildItems(Collection<H> headers)
|
||||||
List<I> items = new ArrayList<>();
|
throws DbException {
|
||||||
|
ThreadItemList<I> items = new ThreadItemListImpl<>();
|
||||||
for (H h : headers) {
|
for (H h : headers) {
|
||||||
items.add(buildItem(h, bodyCache.get(h.getId())));
|
items.add(buildItem(h, bodyCache.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;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ public class UnreadMessageButton extends FrameLayout {
|
|||||||
|
|
||||||
LayoutInflater inflater = (LayoutInflater) context
|
LayoutInflater inflater = (LayoutInflater) context
|
||||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
inflater
|
inflater.inflate(R.layout.unread_message_button, this, true);
|
||||||
.inflate(R.layout.unread_message_button, this, true);
|
|
||||||
|
|
||||||
fab = (FloatingActionButton) findViewById(R.id.fab);
|
fab = (FloatingActionButton) findViewById(R.id.fab);
|
||||||
unread = (TextView) findViewById(R.id.unreadCountView);
|
unread = (TextView) findViewById(R.id.unreadCountView);
|
||||||
@@ -64,15 +63,11 @@ public class UnreadMessageButton extends FrameLayout {
|
|||||||
|
|
||||||
public void setUnreadCount(int count) {
|
public void setUnreadCount(int count) {
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
fab.setVisibility(GONE);
|
setVisibility(INVISIBLE);
|
||||||
// fab.hide();
|
|
||||||
unread.setVisibility(GONE);
|
|
||||||
} else {
|
} else {
|
||||||
// FIXME: Use animations when upgrading to support library 24.2.0
|
// FIXME: Use animations when upgrading to support library 24.2.0
|
||||||
// https://code.google.com/p/android/issues/detail?id=216469
|
// https://code.google.com/p/android/issues/detail?id=216469
|
||||||
fab.setVisibility(VISIBLE);
|
setVisibility(VISIBLE);
|
||||||
// if (!fab.isShown()) fab.show();
|
|
||||||
unread.setVisibility(VISIBLE);
|
|
||||||
unread.setText(String.valueOf(count));
|
unread.setText(String.valueOf(count));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import org.briarproject.briar.BuildConfig;
|
|||||||
import org.briarproject.briar.android.TestBriarApplication;
|
import org.briarproject.briar.android.TestBriarApplication;
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter;
|
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.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -23,10 +25,7 @@ import org.robolectric.Robolectric;
|
|||||||
import org.robolectric.RobolectricGradleTestRunner;
|
import org.robolectric.RobolectricGradleTestRunner;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static junit.framework.Assert.assertEquals;
|
import static junit.framework.Assert.assertEquals;
|
||||||
import static junit.framework.Assert.assertTrue;
|
import static junit.framework.Assert.assertTrue;
|
||||||
@@ -81,7 +80,7 @@ public class ForumActivityTest {
|
|||||||
|
|
||||||
private TestForumActivity forumActivity;
|
private TestForumActivity forumActivity;
|
||||||
@Captor
|
@Captor
|
||||||
private ArgumentCaptor<UiResultExceptionHandler<Collection<ForumItem>, DbException>>
|
private ArgumentCaptor<UiResultExceptionHandler<ThreadItemList<ForumItem>, DbException>>
|
||||||
rc;
|
rc;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -93,7 +92,7 @@ public class ForumActivityTest {
|
|||||||
.withIntent(intent).create().resume().get();
|
.withIntent(intent).create().resume().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ForumItem> getDummyData() {
|
private ThreadItemList<ForumItem> getDummyData() {
|
||||||
ForumItem[] forumItems = new ForumItem[6];
|
ForumItem[] forumItems = new ForumItem[6];
|
||||||
for (int i = 0; i < forumItems.length; i++) {
|
for (int i = 0; i < forumItems.length; i++) {
|
||||||
AuthorId authorId = new AuthorId(TestUtils.getRandomId());
|
AuthorId authorId = new AuthorId(TestUtils.getRandomId());
|
||||||
@@ -103,13 +102,15 @@ public class ForumActivityTest {
|
|||||||
AUTHORS[i], System.currentTimeMillis(), author, UNKNOWN);
|
AUTHORS[i], System.currentTimeMillis(), author, UNKNOWN);
|
||||||
forumItems[i].setLevel(LEVELS[i]);
|
forumItems[i].setLevel(LEVELS[i]);
|
||||||
}
|
}
|
||||||
return new ArrayList<>(Arrays.asList(forumItems));
|
ThreadItemList<ForumItem> list = new ThreadItemListImpl<>();
|
||||||
|
list.addAll(Arrays.asList(forumItems));
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNestedEntries() {
|
public void testNestedEntries() {
|
||||||
ForumController mc = forumActivity.getController();
|
ForumController mc = forumActivity.getController();
|
||||||
List<ForumItem> dummyData = getDummyData();
|
ThreadItemList<ForumItem> dummyData = getDummyData();
|
||||||
verify(mc, times(1)).loadItems(rc.capture());
|
verify(mc, times(1)).loadItems(rc.capture());
|
||||||
rc.getValue().onResult(dummyData);
|
rc.getValue().onResult(dummyData);
|
||||||
ThreadItemAdapter<ForumItem> adapter = forumActivity.getAdapter();
|
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.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface MessageTracker {
|
public interface MessageTracker {
|
||||||
|
|
||||||
@@ -38,6 +40,19 @@ public interface MessageTracker {
|
|||||||
void trackMessage(Transaction txn, GroupId g, long timestamp, boolean read)
|
void trackMessage(Transaction txn, GroupId g, long timestamp, boolean read)
|
||||||
throws DbException;
|
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.
|
* Marks a message as read or unread and updates the group count.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.client;
|
|||||||
|
|
||||||
public interface MessageTrackerConstants {
|
public interface MessageTrackerConstants {
|
||||||
|
|
||||||
|
String GROUP_KEY_STORED_MESSAGE_ID = "storedMessageId";
|
||||||
String GROUP_KEY_MSG_COUNT = "messageCount";
|
String GROUP_KEY_MSG_COUNT = "messageCount";
|
||||||
String GROUP_KEY_UNREAD_COUNT = "unreadCount";
|
String GROUP_KEY_UNREAD_COUNT = "unreadCount";
|
||||||
String GROUP_KEY_LATEST_MSG = "latestMessageTime";
|
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.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.api.client.MessageTracker;
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_LATEST_MSG;
|
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_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.GROUP_KEY_UNREAD_COUNT;
|
||||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||||
|
|
||||||
@@ -57,6 +59,30 @@ class MessageTrackerImpl implements MessageTracker {
|
|||||||
latestMsgTime));
|
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
|
@Override
|
||||||
public GroupCount getGroupCount(GroupId g) throws DbException {
|
public GroupCount getGroupCount(GroupId g) throws DbException {
|
||||||
GroupCount count;
|
GroupCount count;
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package org.briarproject.briar.client;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.data.BdfEntry;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_STORED_MESSAGE_ID;
|
||||||
|
|
||||||
|
public class MessageTrackerTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
protected final GroupId groupId = new GroupId(TestUtils.getRandomId());
|
||||||
|
protected final ClientHelper clientHelper =
|
||||||
|
context.mock(ClientHelper.class);
|
||||||
|
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
private final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||||
|
private final MessageTracker messageTracker =
|
||||||
|
new MessageTrackerImpl(db, clientHelper);
|
||||||
|
private final BdfDictionary dictionary = BdfDictionary.of(
|
||||||
|
new BdfEntry(GROUP_KEY_STORED_MESSAGE_ID, messageId)
|
||||||
|
);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMessageStore() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).mergeGroupMetadata(groupId, dictionary);
|
||||||
|
}});
|
||||||
|
messageTracker.storeMessageId(groupId, messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMessageLoad() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).getGroupMetadataAsDictionary(groupId);
|
||||||
|
will(returnValue(dictionary));
|
||||||
|
}});
|
||||||
|
MessageId loadedId = messageTracker.loadStoredMessageId(groupId);
|
||||||
|
Assert.assertNotNull(loadedId);
|
||||||
|
Assert.assertTrue(messageId.equals(loadedId));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user