From 36a917478146c94c29134dad6f18b8385b196541 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 6 Jan 2021 14:14:48 -0300 Subject: [PATCH 01/22] Perform thread list core access within a single transaction --- .../briar/api/forum/ForumManager.java | 12 ++++ .../api/privategroup/PrivateGroupManager.java | 12 ++++ .../briar/forum/ForumManagerImpl.java | 63 +++++++++++-------- .../privategroup/PrivateGroupManagerImpl.java | 22 +++++-- 4 files changed, 78 insertions(+), 31 deletions(-) diff --git a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java index 7d6bd42a9..30ee18e35 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java @@ -11,6 +11,7 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.api.client.MessageTracker.GroupCount; import java.util.Collection; +import java.util.List; import javax.annotation.Nullable; @@ -84,11 +85,22 @@ public interface ForumManager { */ String getPostText(MessageId m) throws DbException; + /** + * Returns the text of the forum post with the given ID. + */ + String getPostText(Transaction txn, MessageId m) throws DbException; + /** * Returns the headers of all posts in the given forum. */ Collection getPostHeaders(GroupId g) throws DbException; + /** + * Returns the headers of all posts in the given forum. + */ + List getPostHeaders(Transaction txn, GroupId g) + throws DbException; + /** * Registers a hook to be called whenever a forum is removed. */ diff --git a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java index 478b03975..df10ccdf5 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java @@ -12,6 +12,7 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.api.client.MessageTracker.GroupCount; import java.util.Collection; +import java.util.List; @NotNullByDefault public interface PrivateGroupManager { @@ -107,11 +108,22 @@ public interface PrivateGroupManager { */ String getMessageText(MessageId m) throws DbException; + /** + * Returns the text of the private group message with the given ID. + */ + String getMessageText(Transaction txn, MessageId m) throws DbException; + /** * Returns the headers of all messages in the given private group. */ Collection getHeaders(GroupId g) throws DbException; + /** + * Returns the headers of all messages in the given private group. + */ + List getHeaders(Transaction txn, GroupId g) + throws DbException; + /** * Returns all members of the given private group. */ diff --git a/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java index 4e4a63af3..673d1f3a3 100644 --- a/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java @@ -192,6 +192,15 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { } } + @Override + public String getPostText(Transaction txn, MessageId m) throws DbException { + try { + return getPostText(clientHelper.getMessageAsList(txn, m)); + } catch (FormatException e) { + throw new DbException(e); + } + } + private String getPostText(BdfList body) throws FormatException { // Parent ID, author, text, signature return body.getString(2); @@ -200,33 +209,35 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { @Override public Collection getPostHeaders(GroupId g) throws DbException { + return db.transactionWithResult(true, txn -> getPostHeaders(txn, g)); + } + + @Override + public List getPostHeaders(Transaction txn, GroupId g) + throws DbException { try { - return db.transactionWithResult(true, txn -> { - Collection headers = new ArrayList<>(); - Map metadata = - clientHelper.getMessageMetadataAsDictionary(txn, g); - // get all authors we need to get the info for - Set authors = new HashSet<>(); - for (Entry entry : - metadata.entrySet()) { - BdfList authorList = entry.getValue().getList(KEY_AUTHOR); - Author a = clientHelper.parseAndValidateAuthor(authorList); - authors.add(a.getId()); - } - // get information for all authors - Map authorInfos = new HashMap<>(); - for (AuthorId id : authors) { - authorInfos.put(id, authorManager.getAuthorInfo(txn, id)); - } - // Parse the metadata - for (Entry entry : - metadata.entrySet()) { - BdfDictionary meta = entry.getValue(); - headers.add(getForumPostHeader(txn, entry.getKey(), meta, - authorInfos)); - } - return headers; - }); + List headers = new ArrayList<>(); + Map metadata = + clientHelper.getMessageMetadataAsDictionary(txn, g); + // get all authors we need to get the info for + Set authors = new HashSet<>(); + for (Entry entry : metadata.entrySet()) { + BdfList authorList = entry.getValue().getList(KEY_AUTHOR); + Author a = clientHelper.parseAndValidateAuthor(authorList); + authors.add(a.getId()); + } + // get information for all authors + Map authorInfos = new HashMap<>(); + for (AuthorId id : authors) { + authorInfos.put(id, authorManager.getAuthorInfo(txn, id)); + } + // Parse the metadata + for (Entry entry : metadata.entrySet()) { + BdfDictionary meta = entry.getValue(); + headers.add(getForumPostHeader(txn, entry.getKey(), meta, + authorInfos)); + } + return headers; } catch (FormatException e) { throw new DbException(e); } diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java index 7bf044347..bff8fb14c 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java @@ -315,6 +315,16 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook } } + @Override + public String getMessageText(Transaction txn, MessageId m) + throws DbException { + try { + return getMessageText(clientHelper.getMessageAsList(txn, m)); + } catch (FormatException e) { + throw new DbException(e); + } + } + private String getMessageText(BdfList body) throws FormatException { // Message type (0), member (1), parent ID (2), previous message ID (3), // text (4), signature (5) @@ -324,8 +334,13 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook @Override public Collection getHeaders(GroupId g) throws DbException { - Collection headers = new ArrayList<>(); - Transaction txn = db.startTransaction(true); + return db.transactionWithResult(true, txn -> getHeaders(txn, g)); + } + + @Override + public List getHeaders(Transaction txn, GroupId g) + throws DbException { + List headers = new ArrayList<>(); try { Map metadata = clientHelper.getMessageMetadataAsDictionary(txn, g); @@ -354,12 +369,9 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook meta, authorInfos)); } } - db.commitTransaction(txn); return headers; } catch (FormatException e) { throw new DbException(e); - } finally { - db.endTransaction(txn); } } From ab43dd4986ac30385fdb9420312da1f727bb19cc Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 6 Jan 2021 10:59:20 -0300 Subject: [PATCH 02/22] Create ThreadListViewModels and move loading of named groups there --- .../briarproject/briar/android/AppModule.java | 2 + .../briar/android/forum/ForumActivity.java | 66 ++++++----- .../briar/android/forum/ForumModule.java | 5 + .../briar/android/forum/ForumViewModel.java | 82 +++++++++++++ .../conversation/GroupActivity.java | 109 +++++++++--------- .../conversation/GroupController.java | 4 - .../conversation/GroupControllerImpl.java | 14 --- .../conversation/GroupConversationModule.java | 12 ++ .../conversation/GroupViewModel.java | 102 ++++++++++++++++ .../android/threaded/ThreadListActivity.java | 35 ++---- .../threaded/ThreadListController.java | 2 - .../threaded/ThreadListControllerImpl.java | 17 --- .../android/threaded/ThreadListViewModel.java | 79 +++++++++++++ 13 files changed, 387 insertions(+), 142 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index f1904fdab..0163f415a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -35,6 +35,7 @@ import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.keyagreement.ContactExchangeModule; import org.briarproject.briar.android.login.LoginModule; import org.briarproject.briar.android.navdrawer.NavDrawerModule; +import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule; import org.briarproject.briar.android.settings.SettingsModule; import org.briarproject.briar.android.privategroup.list.GroupListModule; import org.briarproject.briar.android.reporting.DevReportModule; @@ -81,6 +82,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; // below need to be within same scope as ViewModelProvider.Factory ForumModule.BindsModule.class, GroupListModule.class, + GroupConversationModule.BindsModule.class, }) public class AppModule { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java index f77ac93e0..41ddde2cd 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java @@ -21,6 +21,7 @@ import org.briarproject.briar.android.sharing.ShareForumActivity; 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; @@ -28,11 +29,13 @@ import javax.inject.Inject; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.widget.Toast.LENGTH_SHORT; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_FORUM; +import static org.briarproject.briar.android.util.UiUtils.observeOnce; import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEXT_LENGTH; @MethodsNotNullByDefault @@ -41,12 +44,18 @@ public class ForumActivity extends ThreadListActivity> implements ForumListener { + @Inject + ViewModelProvider.Factory viewModelFactory; @Inject ForumController forumController; + private ForumViewModel viewModel; + @Override public void injectActivity(ActivityComponent component) { component.inject(this); + viewModel = new ViewModelProvider(this, viewModelFactory) + .get(ForumViewModel.class); } @Override @@ -54,6 +63,11 @@ public class ForumActivity extends return forumController; } + @Override + protected ThreadListViewModel getViewModel() { + return viewModel; + } + @Override public void onCreate(@Nullable Bundle state) { super.onCreate(state); @@ -62,8 +76,13 @@ public class ForumActivity extends Intent i = getIntent(); String groupName = i.getStringExtra(GROUP_NAME); - if (groupName != null) setTitle(groupName); - else loadNamedGroup(); + if (groupName != null) { + setTitle(groupName); + } else { + observeOnce(viewModel.loadForum(), this, forum -> + setTitle(forum.getName()) + ); + } // Open member list on Toolbar click if (toolbar != null) { @@ -76,11 +95,6 @@ public class ForumActivity extends } } - @Override - protected void onNamedGroupLoaded(Forum forum) { - setTitle(forum.getName()); - } - @Override protected ThreadItemAdapter createAdapter( LinearLayoutManager layoutManager) { @@ -88,7 +102,8 @@ public class ForumActivity extends } @Override - protected void onActivityResult(int request, int result, Intent data) { + protected void onActivityResult(int request, int result, + @Nullable Intent data) { super.onActivityResult(request, result, data); if (request == REQUEST_SHARE_FORUM && result == RESULT_OK) { @@ -108,25 +123,24 @@ public class ForumActivity extends @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle presses on the action bar items - switch (item.getItemId()) { - case R.id.action_forum_share: - Intent i2 = new Intent(this, ShareForumActivity.class); - i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP); - i2.putExtra(GROUP_ID, groupId.getBytes()); - startActivityForResult(i2, REQUEST_SHARE_FORUM); - return true; - case R.id.action_forum_sharing_status: - Intent i3 = new Intent(this, ForumSharingStatusActivity.class); - i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP); - i3.putExtra(GROUP_ID, groupId.getBytes()); - startActivity(i3); - return true; - case R.id.action_forum_delete: - showUnsubscribeDialog(); - return true; - default: - return super.onOptionsItemSelected(item); + int itemId = item.getItemId(); + if (itemId == R.id.action_forum_share) { + Intent i2 = new Intent(this, ShareForumActivity.class); + i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP); + i2.putExtra(GROUP_ID, groupId.getBytes()); + startActivityForResult(i2, REQUEST_SHARE_FORUM); + return true; + } else if (itemId == R.id.action_forum_sharing_status) { + Intent i3 = new Intent(this, ForumSharingStatusActivity.class); + i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP); + i3.putExtra(GROUP_ID, groupId.getBytes()); + startActivity(i3); + return true; + } else if (itemId == R.id.action_forum_delete) { + showUnsubscribeDialog(); + return true; } + return super.onOptionsItemSelected(item); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumModule.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumModule.java index 62394c76d..e2efac2d9 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumModule.java @@ -19,6 +19,11 @@ public class ForumModule { @IntoMap @ViewModelKey(ForumListViewModel.class) ViewModel bindForumListViewModel(ForumListViewModel forumListViewModel); + + @Binds + @IntoMap + @ViewModelKey(ForumViewModel.class) + ViewModel bindForumViewModel(ForumViewModel forumViewModel); } @ActivityScope diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java new file mode 100644 index 000000000..f469bec8b --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -0,0 +1,82 @@ +package org.briarproject.briar.android.forum; + +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.TransactionManager; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.identity.IdentityManager; +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.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.forum.Forum; +import org.briarproject.briar.api.forum.ForumManager; +import org.briarproject.briar.api.forum.ForumSharingManager; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import androidx.lifecycle.LiveData; +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.logException; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +class ForumViewModel extends ThreadListViewModel { + + private static final Logger LOG = getLogger(ForumViewModel.class.getName()); + + private final ForumManager forumManager; + private final ForumSharingManager forumSharingManager; + + @Inject + ForumViewModel(Application application, + @DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, + TransactionManager db, + AndroidExecutor androidExecutor, + IdentityManager identityManager, + AndroidNotificationManager notificationManager, + @CryptoExecutor Executor cryptoExecutor, + Clock clock, + EventBus eventBus, + ForumManager forumManager, + ForumSharingManager forumSharingManager) { + super(application, dbExecutor, lifecycleManager, db, androidExecutor, + identityManager, notificationManager, cryptoExecutor, clock, + eventBus); + this.forumManager = forumManager; + this.forumSharingManager = forumSharingManager; + } + + @Override + public void eventOccurred(Event e) { + + } + + LiveData loadForum() { + MutableLiveData forum = new MutableLiveData<>(); + runOnDbThread(() -> { + try { + Forum f = forumManager.getForum(groupId); + forum.postValue(f); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + return forum; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java index f81004ad2..a5079e5e0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java @@ -11,7 +11,6 @@ import android.view.MenuItem; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.identity.AuthorId; -import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; @@ -24,6 +23,7 @@ import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListAct 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; @@ -32,11 +32,13 @@ import javax.inject.Inject; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import static android.view.View.GONE; import static android.view.View.VISIBLE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_GROUP_INVITE; +import static org.briarproject.briar.android.util.UiUtils.observeOnce; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_TEXT_LENGTH; @MethodsNotNullByDefault @@ -45,9 +47,13 @@ public class GroupActivity extends ThreadListActivity implements GroupListener, OnClickListener { + @Inject + ViewModelProvider.Factory viewModelFactory; @Inject GroupController controller; + private GroupViewModel viewModel; + @Nullable private Boolean isCreator = null; private boolean isDissolved = false; @@ -57,6 +63,8 @@ public class GroupActivity extends @Override public void injectActivity(ActivityComponent component) { component.inject(this); + viewModel = new ViewModelProvider(this, viewModelFactory) + .get(GroupViewModel.class); } @Override @@ -64,6 +72,11 @@ public class GroupActivity extends return controller; } + @Override + protected ThreadListViewModel getViewModel() { + return viewModel; + } + @Override public void onCreate(@Nullable Bundle state) { super.onCreate(state); @@ -73,7 +86,14 @@ public class GroupActivity extends Intent i = getIntent(); String groupName = i.getStringExtra(GROUP_NAME); if (groupName != null) setTitle(groupName); - loadNamedGroup(); + observeOnce(viewModel.getPrivateGroup(), this, privateGroup -> + setTitle(privateGroup.getName()) + ); + observeOnce(viewModel.isCreator(), this, isCreator -> { + this.isCreator = isCreator; // TODO remove field + adapter.setPerspective(isCreator); + showMenuItems(); + }); // Open member list on Toolbar click if (toolbar != null) { @@ -111,25 +131,6 @@ public class GroupActivity extends }); } - @Override - protected void onNamedGroupLoaded(PrivateGroup group) { - setTitle(group.getName()); - controller.loadLocalAuthor( - new UiResultExceptionHandler(this) { - @Override - public void onResultUi(LocalAuthor author) { - isCreator = group.getCreator().equals(author); - adapter.setPerspective(isCreator); - showMenuItems(); - } - - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }); - } - @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu items for use in the action bar @@ -155,42 +156,44 @@ public class GroupActivity extends @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_group_member_list: - Intent i1 = new Intent(this, GroupMemberListActivity.class); - i1.putExtra(GROUP_ID, groupId.getBytes()); - startActivity(i1); - return true; - case R.id.action_group_reveal: - if (isCreator == null || isCreator) - throw new IllegalStateException(); - Intent i2 = new Intent(this, RevealContactsActivity.class); - i2.putExtra(GROUP_ID, groupId.getBytes()); - startActivity(i2); - return true; - case R.id.action_group_invite: - if (isCreator == null || !isCreator) - throw new IllegalStateException(); - Intent i3 = new Intent(this, GroupInviteActivity.class); - i3.putExtra(GROUP_ID, groupId.getBytes()); - startActivityForResult(i3, REQUEST_GROUP_INVITE); - return true; - case R.id.action_group_leave: - if (isCreator == null || isCreator) - throw new IllegalStateException(); - showLeaveGroupDialog(); - return true; - case R.id.action_group_dissolve: - if (isCreator == null || !isCreator) - throw new IllegalStateException(); - showDissolveGroupDialog(); - default: - return super.onOptionsItemSelected(item); + int itemId = item.getItemId(); + if (itemId == R.id.action_group_member_list) { + Intent i1 = new Intent(this, GroupMemberListActivity.class); + i1.putExtra(GROUP_ID, groupId.getBytes()); + startActivity(i1); + return true; + } else if (itemId == R.id.action_group_reveal) { + if (isCreator == null || isCreator) + throw new IllegalStateException(); + Intent i2 = new Intent(this, RevealContactsActivity.class); + i2.putExtra(GROUP_ID, groupId.getBytes()); + startActivity(i2); + return true; + } else if (itemId == R.id.action_group_invite) { + if (isCreator == null || !isCreator) + throw new IllegalStateException(); + Intent i3 = new Intent(this, GroupInviteActivity.class); + i3.putExtra(GROUP_ID, groupId.getBytes()); + startActivityForResult(i3, REQUEST_GROUP_INVITE); + return true; + } else if (itemId == R.id.action_group_leave) { + if (isCreator == null || isCreator) + throw new IllegalStateException(); + showLeaveGroupDialog(); + return true; + } else if (itemId == R.id.action_group_dissolve) { + if (isCreator == null || !isCreator) + throw new IllegalStateException(); + showDissolveGroupDialog(); + + return super.onOptionsItemSelected(item); } + return super.onOptionsItemSelected(item); } @Override - protected void onActivityResult(int request, int result, Intent data) { + protected void onActivityResult(int request, int result, + @Nullable Intent data) { if (request == REQUEST_GROUP_INVITE && result == RESULT_OK) { displaySnackbar(R.string.groups_invitation_sent); } else super.onActivityResult(request, result, data); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java index 571f55bf1..5965d6c98 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java @@ -3,7 +3,6 @@ package org.briarproject.briar.android.privategroup.conversation; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.identity.AuthorId; -import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.threaded.ThreadListController; import org.briarproject.briar.api.privategroup.PrivateGroup; @@ -14,9 +13,6 @@ import androidx.annotation.UiThread; public interface GroupController extends ThreadListController { - void loadLocalAuthor( - ResultExceptionHandler handler); - void isDissolved( ResultExceptionHandler handler); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java index 5b0b15ad8..e35d5b0c3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java @@ -210,20 +210,6 @@ class GroupControllerImpl extends return new GroupMessageItem(header, text); } - @Override - public void loadLocalAuthor( - ResultExceptionHandler handler) { - runOnDbThread(() -> { - try { - LocalAuthor author = identityManager.getLocalAuthor(); - handler.onResult(author); - } catch (DbException e) { - logException(LOG, WARNING, e); - handler.onException(e); - } - }); - } - @Override public void isDissolved( ResultExceptionHandler handler) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupConversationModule.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupConversationModule.java index 6fa102375..6dd2c65ab 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupConversationModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupConversationModule.java @@ -2,13 +2,25 @@ package org.briarproject.briar.android.privategroup.conversation; import org.briarproject.briar.android.activity.ActivityScope; import org.briarproject.briar.android.activity.BaseActivity; +import org.briarproject.briar.android.viewmodel.ViewModelKey; +import androidx.lifecycle.ViewModel; +import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.IntoMap; @Module public class GroupConversationModule { + @Module + public interface BindsModule { + @Binds + @IntoMap + @ViewModelKey(GroupViewModel.class) + ViewModel bindGroupViewModel(GroupViewModel groupViewModel); + } + @ActivityScope @Provides GroupController provideGroupController(BaseActivity activity, diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java new file mode 100644 index 000000000..6fb39aeb6 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -0,0 +1,102 @@ +package org.briarproject.briar.android.privategroup.conversation; + +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.TransactionManager; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.IdentityManager; +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.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.privategroup.GroupMessageFactory; +import org.briarproject.briar.api.privategroup.PrivateGroup; +import org.briarproject.briar.api.privategroup.PrivateGroupManager; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import androidx.lifecycle.LiveData; +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.logException; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +class GroupViewModel + extends ThreadListViewModel { + + private static final Logger LOG = getLogger(GroupViewModel.class.getName()); + + private final PrivateGroupManager privateGroupManager; + private final GroupMessageFactory groupMessageFactory; + + MutableLiveData privateGroup = new MutableLiveData<>(); + MutableLiveData isCreator = new MutableLiveData<>(); + + @Inject + GroupViewModel(Application application, + @DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, + TransactionManager db, + AndroidExecutor androidExecutor, + EventBus eventBus, + IdentityManager identityManager, + AndroidNotificationManager notificationManager, + @CryptoExecutor Executor cryptoExecutor, + Clock clock, + PrivateGroupManager privateGroupManager, + GroupMessageFactory groupMessageFactory) { + super(application, dbExecutor, lifecycleManager, db, androidExecutor, + identityManager, notificationManager, cryptoExecutor, clock, + eventBus); + this.privateGroupManager = privateGroupManager; + this.groupMessageFactory = groupMessageFactory; + } + + @Override + public void eventOccurred(Event e) { + + } + + @Override + public void setGroupId(GroupId groupId) { + super.setGroupId(groupId); + loadPrivateGroup(groupId); + } + + private void loadPrivateGroup(GroupId groupId) { + runOnDbThread(() -> { + try { + PrivateGroup g = privateGroupManager.getPrivateGroup(groupId); + privateGroup.postValue(g); + Author author = identityManager.getLocalAuthor(); + isCreator.postValue(g.getCreator().equals(author)); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + LiveData getPrivateGroup() { + return privateGroup; + } + + LiveData isCreator() { + return isCreator; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java index 828be5091..67088f84b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java @@ -39,11 +39,11 @@ import javax.inject.Inject; import androidx.annotation.CallSuper; import androidx.annotation.StringRes; -import androidx.annotation.UiThread; import androidx.appcompat.app.ActionBar; import androidx.recyclerview.widget.LinearLayoutManager; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; +import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @MethodsNotNullByDefault @@ -56,7 +56,7 @@ public abstract class ThreadListActivity scrollListener; @@ -72,6 +72,8 @@ public abstract class ThreadListActivity getController(); + protected abstract ThreadListViewModel getViewModel(); + @Inject protected SharingController sharingController; @@ -87,6 +89,7 @@ public abstract class ThreadListActivity(this) { - @Override - public void onResultUi(G groupItem) { - onNamedGroupLoaded(groupItem); - } - - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }); - } - - @UiThread - protected abstract void onNamedGroupLoaded(G groupItem); - protected void loadItems() { int revision = adapter.getRevision(); getController().loadItems( @@ -256,13 +241,11 @@ public abstract class ThreadListActivity handler); - void loadSharingContacts( ResultExceptionHandler, DbException> handler); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java index 61eeb2a9e..9435e3017 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java @@ -130,23 +130,6 @@ public abstract class ThreadListControllerImpl handler) { - checkGroupId(); - runOnDbThread(() -> { - try { - long start = now(); - G groupItem = loadNamedGroup(); - logDuration(LOG, "Loading group", start); - handler.onResult(groupItem); - } catch (DbException e) { - logException(LOG, WARNING, e); - handler.onException(e); - } - }); - } - @DatabaseExecutor protected abstract G loadNamedGroup() throws DbException; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java new file mode 100644 index 000000000..dbdd4cf35 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -0,0 +1,79 @@ +package org.briarproject.briar.android.threaded; + +import android.app.Application; + +import org.briarproject.bramble.api.crypto.CryptoExecutor; +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.db.TransactionManager; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.identity.IdentityManager; +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.system.AndroidExecutor; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.briar.android.viewmodel.DbViewModel; +import org.briarproject.briar.api.android.AndroidNotificationManager; +import org.briarproject.briar.api.client.NamedGroup; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import androidx.annotation.CallSuper; + +import static java.util.logging.Logger.getLogger; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public abstract class ThreadListViewModel + 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 EventBus eventBus; + + protected volatile GroupId groupId; + + public ThreadListViewModel(Application application, + @DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, + TransactionManager db, + AndroidExecutor androidExecutor, + IdentityManager identityManager, + AndroidNotificationManager notificationManager, + @CryptoExecutor Executor cryptoExecutor, + Clock clock, + EventBus eventBus) { + super(application, dbExecutor, lifecycleManager, db, androidExecutor); + this.identityManager = identityManager; + this.notificationManager = notificationManager; + this.cryptoExecutor = cryptoExecutor; + this.clock = clock; + this.eventBus = eventBus; + this.eventBus.addListener(this); + } + + @Override + protected void onCleared() { + super.onCleared(); + eventBus.removeListener(this); + } + + /** + * Needs to be called right after initialization, + * before calling other methods. + */ + @CallSuper + public void setGroupId(GroupId groupId) { + this.groupId = groupId; + } + +} From 6611d7c02ecaeb2bb52d0c15fb2be59a76103b34 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 6 Jan 2021 11:18:45 -0300 Subject: [PATCH 03/22] Move removal of named groups into ViewModel --- .../briar/android/forum/ForumActivity.java | 22 +--------------- .../android/forum/ForumControllerImpl.java | 10 ------- .../briar/android/forum/ForumViewModel.java | 16 ++++++++++++ .../conversation/GroupActivity.java | 26 +++++++------------ .../conversation/GroupControllerImpl.java | 10 ------- .../conversation/GroupViewModel.java | 10 +++++++ .../threaded/ThreadListController.java | 3 --- .../threaded/ThreadListControllerImpl.java | 22 ---------------- 8 files changed, 36 insertions(+), 83 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java index 41ddde2cd..c9c7637ef 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java @@ -6,15 +6,12 @@ import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.widget.Toast; import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.forum.ForumController.ForumListener; import org.briarproject.briar.android.sharing.ForumSharingStatusActivity; import org.briarproject.briar.android.sharing.ShareForumActivity; @@ -33,7 +30,6 @@ import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; -import static android.widget.Toast.LENGTH_SHORT; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_FORUM; import static org.briarproject.briar.android.util.UiUtils.observeOnce; import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEXT_LENGTH; @@ -149,7 +145,7 @@ public class ForumActivity extends } private void showUnsubscribeDialog() { - OnClickListener okListener = (dialog, which) -> deleteForum(); + OnClickListener okListener = (dialog, which) -> viewModel.deleteForum(); AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.BriarDialogTheme); builder.setTitle(getString(R.string.dialog_title_leave_forum)); @@ -159,22 +155,6 @@ public class ForumActivity extends builder.show(); } - private void deleteForum() { - forumController.deleteNamedGroup( - new UiResultExceptionHandler(this) { - @Override - public void onResultUi(Void v) { - Toast.makeText(ForumActivity.this, - R.string.forum_left_toast, LENGTH_SHORT).show(); - } - - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }); - } - @Override public void onForumLeft(ContactId c) { sharingController.remove(c); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java index 6ad325d5b..6a931f991 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java @@ -98,11 +98,6 @@ class ForumControllerImpl extends } } - @Override - protected Forum loadNamedGroup() throws DbException { - return forumManager.getForum(getGroupId()); - } - @Override protected Collection loadHeaders() throws DbException { return forumManager.getPostHeaders(getGroupId()); @@ -172,11 +167,6 @@ class ForumControllerImpl extends return forumManager.addLocalPost(p); } - @Override - protected void deleteNamedGroup(Forum forum) throws DbException { - forumManager.removeForum(forum); - } - @Override protected ForumPostItem buildItem(ForumPostHeader header, String text) { return new ForumPostItem(header, text); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index f469bec8b..2e2045568 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -1,6 +1,7 @@ package org.briarproject.briar.android.forum; import android.app.Application; +import android.widget.Toast; import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor; @@ -14,6 +15,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.system.AndroidExecutor; 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.forum.Forum; @@ -28,6 +30,7 @@ import javax.inject.Inject; import androidx.lifecycle.LiveData; 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.logException; @@ -79,4 +82,17 @@ class ForumViewModel extends ThreadListViewModel { return forum; } + void deleteForum() { + runOnDbThread(() -> { + try { + Forum f = forumManager.getForum(groupId); + forumManager.removeForum(f); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + Toast.makeText(getApplication(), R.string.forum_left_toast, + LENGTH_SHORT).show(); + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java index a5079e5e0..bd0bcd8c2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java @@ -1,7 +1,5 @@ package org.briarproject.briar.android.privategroup.conversation; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; import android.view.Menu; @@ -15,7 +13,6 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.controller.handler.UiExceptionHandler; import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.privategroup.conversation.GroupController.GroupListener; import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity; @@ -45,7 +42,7 @@ import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_ @ParametersNotNullByDefault public class GroupActivity extends ThreadListActivity - implements GroupListener, OnClickListener { + implements GroupListener { @Inject ViewModelProvider.Factory viewModelFactory; @@ -244,7 +241,8 @@ public class GroupActivity extends new AlertDialog.Builder(this, R.style.BriarDialogTheme); builder.setTitle(getString(R.string.groups_leave_dialog_title)); builder.setMessage(getString(R.string.groups_leave_dialog_message)); - builder.setNegativeButton(R.string.dialog_button_leave, this); + builder.setNegativeButton(R.string.dialog_button_leave, + (d, w) -> deleteGroup()); builder.setPositiveButton(R.string.cancel, null); builder.show(); } @@ -254,22 +252,16 @@ public class GroupActivity extends new AlertDialog.Builder(this, R.style.BriarDialogTheme); builder.setTitle(getString(R.string.groups_dissolve_dialog_title)); builder.setMessage(getString(R.string.groups_dissolve_dialog_message)); - builder.setNegativeButton(R.string.groups_dissolve_button, this); + builder.setNegativeButton(R.string.groups_dissolve_button, + (d, w) -> deleteGroup()); builder.setPositiveButton(R.string.cancel, null); builder.show(); } - @Override - public void onClick(DialogInterface dialog, int which) { - controller.deleteNamedGroup( - new UiExceptionHandler(this) { - // The activity is going to be destroyed by the - // GroupRemovedEvent being fired - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }); + private void deleteGroup() { + // The activity is going to be destroyed by the + // GroupRemovedEvent being fired + viewModel.deletePrivateGroup(); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java index e35d5b0c3..e88f8be6a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java @@ -108,11 +108,6 @@ class GroupControllerImpl extends } } - @Override - protected PrivateGroup loadNamedGroup() throws DbException { - return privateGroupManager.getPrivateGroup(getGroupId()); - } - @Override protected Collection loadHeaders() throws DbException { return privateGroupManager.getHeaders(getGroupId()); @@ -196,11 +191,6 @@ class GroupControllerImpl extends return privateGroupManager.addLocalMessage(message); } - @Override - protected void deleteNamedGroup(PrivateGroup group) throws DbException { - privateGroupManager.removePrivateGroup(group.getId()); - } - @Override protected GroupMessageItem buildItem(GroupMessageHeader header, String text) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index 6fb39aeb6..d47e544df 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -91,6 +91,16 @@ class GroupViewModel }); } + void deletePrivateGroup() { + runOnDbThread(() -> { + try { + privateGroupManager.removePrivateGroup(groupId); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + LiveData getPrivateGroup() { return privateGroup; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java index a1c30b901..4992722ff 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java @@ -6,7 +6,6 @@ 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.controller.ActivityLifecycleController; -import org.briarproject.briar.android.controller.handler.ExceptionHandler; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.api.client.NamedGroup; @@ -35,8 +34,6 @@ public interface ThreadListController handler); - void deleteNamedGroup(ExceptionHandler handler); - interface ThreadListListener extends ThreadListDataSource { @UiThread diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java index 9435e3017..021d4af75 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java @@ -17,7 +17,6 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.event.GroupRemovedEvent; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.android.controller.DbControllerImpl; -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; @@ -130,9 +129,6 @@ public abstract class ThreadListControllerImpl, DbException> handler) { @@ -211,24 +207,6 @@ public abstract class ThreadListControllerImpl handler) { - runOnDbThread(() -> { - try { - long start = now(); - G groupItem = loadNamedGroup(); - deleteNamedGroup(groupItem); - logDuration(LOG, "Removing group", start); - } catch (DbException e) { - logException(LOG, WARNING, e); - handler.onException(e); - } - }); - } - - @DatabaseExecutor - protected abstract void deleteNamedGroup(G groupItem) throws DbException; - private ThreadItemList buildItems(Collection headers) throws DbException { ThreadItemList items = new ThreadItemListImpl<>(); From d393b79ced2c7db2a80688a75d204aa3147779dc Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 7 Jan 2021 08:52:51 -0300 Subject: [PATCH 04/22] Submit thread list items to ListAdapter --- .../briar/android/forum/ForumActivity.java | 7 +- .../briar/android/forum/ForumController.java | 3 +- .../android/forum/ForumControllerImpl.java | 13 +-- .../briar/android/forum/ForumViewModel.java | 36 +++++- .../conversation/GroupActivity.java | 24 ++-- .../conversation/GroupController.java | 3 +- .../conversation/GroupControllerImpl.java | 18 +-- .../conversation/GroupMessageAdapter.java | 18 ++- .../conversation/GroupViewModel.java | 51 ++++++++- .../android/threaded/NestedTreeList.java | 54 --------- .../briar/android/threaded/ThreadItem.java | 10 ++ .../android/threaded/ThreadItemAdapter.java | 105 +++++++----------- .../android/threaded/ThreadListActivity.java | 73 +++++------- .../threaded/ThreadListController.java | 9 +- .../threaded/ThreadListControllerImpl.java | 55 +-------- .../android/threaded/ThreadListViewModel.java | 75 ++++++++++++- .../threaded/ThreadScrollListener.java | 4 +- .../briar/api/client/MessageTree.java | 9 +- .../briar/client/MessageTreeImpl.java | 20 ++-- 19 files changed, 276 insertions(+), 311 deletions(-) delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/threaded/NestedTreeList.java diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java index c9c7637ef..f117dd61f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java @@ -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> + ThreadListActivity> implements ForumListener { @Inject @@ -55,12 +54,12 @@ public class ForumActivity extends } @Override - protected ThreadListController getController() { + protected ThreadListController getController() { return forumController; } @Override - protected ThreadListViewModel getViewModel() { + protected ThreadListViewModel getViewModel() { return viewModel; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumController.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumController.java index 6658e60fc..83ec874a3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumController.java @@ -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 { +interface ForumController extends ThreadListController { interface ForumListener extends ThreadListListener { @UiThread diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java index 6a931f991..8cc883166 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java @@ -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 + ThreadListControllerImpl implements ForumController { private static final Logger LOG = @@ -98,16 +97,6 @@ class ForumControllerImpl extends } } - @Override - protected Collection 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); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index 2e2045568..a378f00db 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -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 { +class ForumViewModel extends ThreadListViewModel { private static final Logger LOG = getLogger(ForumViewModel.class.getName()); @@ -54,12 +62,13 @@ class ForumViewModel extends ThreadListViewModel { 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 { return forum; } + @Override + public void loadItems() { + loadList(txn -> { + long start = now(); + List headers = + forumManager.getPostHeaders(txn, groupId); + logDuration(LOG, "Loading headers", start); + List 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 { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java index bd0bcd8c2..fab1546cb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java @@ -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 + ThreadListActivity implements GroupListener { @Inject @@ -65,12 +64,12 @@ public class GroupActivity extends } @Override - protected ThreadListController getController() { + protected ThreadListController getController() { return controller; } @Override - protected ThreadListViewModel getViewModel() { + protected ThreadListViewModel 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(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 diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java index 5965d6c98..d777ce08f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java @@ -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 { + extends ThreadListController { void isDissolved( ResultExceptionHandler handler); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java index e88f8be6a..fa5c72a60 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java @@ -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 + ThreadListControllerImpl implements GroupController { private static final Logger LOG = @@ -108,21 +107,6 @@ class GroupControllerImpl extends } } - @Override - protected Collection 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); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java index 3b67f8b94..d418f6178 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java @@ -32,7 +32,7 @@ class GroupMessageAdapter extends ThreadItemAdapter { @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 { 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 { } } + @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 + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index d47e544df..335889861 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -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 { +class GroupViewModel extends ThreadListViewModel { private static final Logger LOG = getLogger(GroupViewModel.class.getName()); private final PrivateGroupManager privateGroupManager; private final GroupMessageFactory groupMessageFactory; - MutableLiveData privateGroup = new MutableLiveData<>(); - MutableLiveData isCreator = new MutableLiveData<>(); + private final MutableLiveData privateGroup = + new MutableLiveData<>(); + private final MutableLiveData 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 headers = + privateGroupManager.getHeaders(txn, groupId); + logDuration(LOG, "Loading headers", start); + List 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 { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/NestedTreeList.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/NestedTreeList.java deleted file mode 100644 index 40e211270..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/NestedTreeList.java +++ /dev/null @@ -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 implements Iterable { - - private final MessageTree tree = new MessageTreeImpl<>(); - private List depthFirstCollection = new ArrayList<>(); - - public void addAll(Collection 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 iterator() { - return depthFirstCollection.iterator(); - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItem.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItem.java index 0d3d69205..bde7c3e7d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItem.java @@ -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); + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java index c293f7858..63db2770e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java @@ -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 - extends RecyclerView.Adapter> - implements VersionedAdapter, ItemReturningAdapter { + extends ListAdapter> + implements ItemReturningAdapter { static final int UNDEFINED = -1; - protected final NestedTreeList items = new NestedTreeList<>(); private final ThreadItemListener listener; private final LinearLayoutManager layoutManager; - private volatile int revision = 0; - public ThreadItemAdapter(ThreadItemListener listener, LinearLayoutManager layoutManager) { + super(new DiffUtil.ItemCallback() { + @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 @Override public void onBindViewHolder(@NonNull BaseThreadItemViewHolder 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 } } - public void setItems(Collection 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. - * + *

* 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 @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 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 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 return NO_POSITION; } + @Override + public I getItemAt(int position) { + return getItem(position); + } + public interface ThreadItemListener { void onReplyClick(I item); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java index 67088f84b..1b3e0d202 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java @@ -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> +public abstract class ThreadListActivity> extends BriarActivity implements ThreadListListener, SendListener, SharingListener, ThreadItemListener, ThreadListDataSource { @@ -59,6 +58,7 @@ public abstract class ThreadListActivity scrollListener; protected BriarRecyclerView list; private LinearLayoutManager layoutManager; @@ -70,9 +70,9 @@ public abstract class ThreadListActivity getController(); + protected abstract ThreadListController getController(); - protected abstract ThreadListViewModel getViewModel(); + protected abstract ThreadListViewModel getViewModel(); @Inject protected SharingController sharingController; @@ -127,6 +127,11 @@ public abstract class ThreadListActivity result + .onError(this::handleException) + .onSuccess(this::displayItems) + ); + sharingController.setSharingListener(this); loadSharingContacts(); } @@ -145,44 +150,22 @@ public abstract class ThreadListActivity, DbException>( - this) { - @Override - public void onResultUi(ThreadItemList 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 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 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 +public interface ThreadListController extends ActivityLifecycleController { void setGroupId(GroupId groupId); @@ -24,9 +23,6 @@ public interface ThreadListController, DbException> handler); - void loadItems( - ResultExceptionHandler, DbException> handler); - void markItemRead(I item); void markItemsRead(Collection items); @@ -48,7 +44,8 @@ public interface ThreadListController> +public abstract class ThreadListControllerImpl> extends DbControllerImpl - implements ThreadListController, EventListener { + implements ThreadListController, EventListener { private static final Logger LOG = Logger.getLogger(ThreadListControllerImpl.class.getName()); @@ -129,42 +127,6 @@ public abstract class ThreadListControllerImpl, DbException> handler) { - checkGroupId(); - runOnDbThread(() -> { - try { - // Load headers - long start = now(); - Collection 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 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 buildItems(Collection headers) - throws DbException { - ThreadItemList 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() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java index dbdd4cf35..778796ce9 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -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 - extends DbViewModel implements EventListener { +public abstract class ThreadListViewModel 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 textCache = new ConcurrentHashMap<>(); + private final MutableLiveData>> items = + new MutableLiveData<>(); + protected volatile GroupId groupId; public ThreadListViewModel(Application application, @@ -51,12 +70,14 @@ public abstract class ThreadListViewModel> items) { + this.items.setValue(items); + } + + @DatabaseExecutor + protected List buildItems( + Transaction txn, Collection headers, ItemGetter itemGetter) + throws DbException { + long start = now(); + ThreadItemList 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>> getItems() { + return items; + } + + public interface ItemGetter { + I getItem(H header, String text); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadScrollListener.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadScrollListener.java index 2181d6de3..dca100459 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadScrollListener.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadScrollListener.java @@ -20,11 +20,11 @@ class ThreadScrollListener private static final Logger LOG = getLogger(ThreadScrollListener.class.getName()); - private final ThreadListController controller; + private final ThreadListController controller; private final UnreadMessageButton upButton, downButton; ThreadScrollListener(ThreadItemAdapter adapter, - ThreadListController controller, + ThreadListController controller, UnreadMessageButton upButton, UnreadMessageButton downButton) { super(adapter); diff --git a/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTree.java b/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTree.java index e850391d0..fabdd3308 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTree.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTree.java @@ -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 { void add(Collection nodes); + @Deprecated void add(T node); - void setComparator(Comparator comparator); - + @Deprecated void clear(); - Collection depthFirstOrder(); + List depthFirstOrder(); + @Deprecated boolean contains(MessageId m); @NotNullByDefault diff --git a/briar-core/src/main/java/org/briarproject/briar/client/MessageTreeImpl.java b/briar-core/src/main/java/org/briarproject/briar/client/MessageTreeImpl.java index 8176e3969..49027d190 100644 --- a/briar-core/src/main/java/org/briarproject/briar/client/MessageTreeImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/client/MessageTreeImpl.java @@ -30,9 +30,14 @@ public class MessageTreeImpl private final List> unsortedLists = new ArrayList<>(); @SuppressWarnings("UseCompareMethod") - private Comparator comparator = (o1, o2) -> + private final Comparator comparator = (o1, o2) -> Long.valueOf(o1.getTimestamp()).compareTo(o2.getTimestamp()); + public MessageTreeImpl(Collection collection) { + super(); + add(collection); + } + @Override public synchronized void clear() { roots.clear(); @@ -79,6 +84,7 @@ public class MessageTreeImpl @GuardedBy("this") private void sortUnsorted() { for (List list : unsortedLists) { + //noinspection Java8ListSort Collections.sort(list, comparator); } unsortedLists.clear(); @@ -95,17 +101,7 @@ public class MessageTreeImpl } @Override - public synchronized void setComparator(Comparator comparator) { - this.comparator = comparator; - // Sort all lists with the new comparator - Collections.sort(roots, comparator); - for (Map.Entry> entry : nodeMap.entrySet()) { - Collections.sort(entry.getValue(), comparator); - } - } - - @Override - public synchronized Collection depthFirstOrder() { + public synchronized List depthFirstOrder() { List orderedList = new ArrayList<>(); for (T root : roots) { traverse(orderedList, root, 0); From 21e56284fb95cccf1a56ad10e7db35306dbd2bb9 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 7 Jan 2021 16:57:12 -0300 Subject: [PATCH 05/22] Move adding new ThreadList items to ViewModel --- .../android/forum/ForumControllerImpl.java | 55 ++---------- .../briar/android/forum/ForumViewModel.java | 53 +++++++++++- .../conversation/GroupControllerImpl.java | 70 ++------------- .../conversation/GroupMessageAdapter.java | 10 +-- .../conversation/GroupViewModel.java | 57 ++++++++++++- .../android/threaded/ThreadItemAdapter.java | 11 +-- .../android/threaded/ThreadListActivity.java | 85 +++++++------------ .../threaded/ThreadListController.java | 3 - .../threaded/ThreadListControllerImpl.java | 54 ++---------- .../android/threaded/ThreadListViewModel.java | 76 ++++++++++++++--- .../briar/api/client/MessageTree.java | 3 - .../briar/api/forum/ForumManager.java | 1 - .../briar/client/MessageTreeImpl.java | 5 -- 13 files changed, 224 insertions(+), 259 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java index 8cc883166..5228d8763 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java @@ -8,20 +8,15 @@ import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.identity.IdentityManager; -import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; -import org.briarproject.briar.android.forum.ForumController.ForumListener; 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.ForumInvitationResponse; import org.briarproject.briar.api.forum.ForumManager; -import org.briarproject.briar.api.forum.ForumPost; import org.briarproject.briar.api.forum.ForumPostHeader; import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.forum.event.ForumInvitationResponseReceivedEvent; @@ -33,16 +28,13 @@ import java.util.Collection; import java.util.concurrent.Executor; import java.util.logging.Logger; -import javax.annotation.Nullable; import javax.inject.Inject; -import static java.lang.Math.max; import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.util.LogUtils.logException; @NotNullByDefault -class ForumControllerImpl extends - ThreadListControllerImpl +class ForumControllerImpl extends ThreadListControllerImpl implements ForumController { private static final Logger LOG = @@ -56,10 +48,10 @@ class ForumControllerImpl extends LifecycleManager lifecycleManager, IdentityManager identityManager, @CryptoExecutor Executor cryptoExecutor, ForumManager forumManager, ForumSharingManager forumSharingManager, - EventBus eventBus, Clock clock, MessageTracker messageTracker, + EventBus eventBus, Clock clock, AndroidNotificationManager notificationManager) { super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor, - eventBus, clock, notificationManager, messageTracker); + eventBus, clock, notificationManager); this.forumManager = forumManager; this.forumSharingManager = forumSharingManager; } @@ -74,6 +66,8 @@ class ForumControllerImpl extends public void eventOccurred(Event e) { super.eventOccurred(e); + ForumListener listener = (ForumListener) this.listener; + if (e instanceof ForumPostReceivedEvent) { ForumPostReceivedEvent f = (ForumPostReceivedEvent) e; if (f.getGroupId().equals(getGroupId())) { @@ -120,44 +114,7 @@ class ForumControllerImpl extends }); } - @Override - public void createAndStoreMessage(String text, - @Nullable ForumPostItem parentItem, - ResultExceptionHandler handler) { - runOnDbThread(() -> { - try { - LocalAuthor author = identityManager.getLocalAuthor(); - GroupCount count = forumManager.getGroupCount(getGroupId()); - long timestamp = max(count.getLatestMsgTime() + 1, - clock.currentTimeMillis()); - MessageId parentId = parentItem != null ? - parentItem.getId() : null; - createMessage(text, timestamp, parentId, author, handler); - } catch (DbException e) { - logException(LOG, WARNING, e); - handler.onException(e); - } - }); - } - - private void createMessage(String text, long timestamp, - @Nullable MessageId parentId, LocalAuthor author, - ResultExceptionHandler handler) { - cryptoExecutor.execute(() -> { - LOG.info("Creating forum post..."); - ForumPost msg = forumManager.createLocalPost(getGroupId(), text, - timestamp, parentId, author); - storePost(msg, text, handler); - }); - } - - @Override - protected ForumPostHeader addLocalMessage(ForumPost p) throws DbException { - return forumManager.addLocalPost(p); - } - - @Override - protected ForumPostItem buildItem(ForumPostHeader header, String text) { + private ForumPostItem buildItem(ForumPostHeader header, String text) { return new ForumPostItem(header, text); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index a378f00db..b6b24f790 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -11,32 +11,37 @@ import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.identity.LocalAuthor; 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.MessageId; import org.briarproject.bramble.api.system.AndroidExecutor; 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.MessageTracker.GroupCount; 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.ForumPost; 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; +import javax.annotation.Nullable; import javax.inject.Inject; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import static android.widget.Toast.LENGTH_SHORT; +import static java.lang.Math.max; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.LogUtils.logDuration; @@ -98,12 +103,52 @@ class ForumViewModel extends ThreadListViewModel { List headers = forumManager.getPostHeaders(txn, groupId); logDuration(LOG, "Loading headers", start); - List items = - buildItems(txn, headers, this::buildItem); - return new MessageTreeImpl<>(items).depthFirstOrder(); + return recreateItems(txn, headers, this::buildItem); }, this::setItems); } + @Override + public void createAndStoreMessage(String text, + @Nullable ForumPostItem parentItem) { + runOnDbThread(() -> { + try { + LocalAuthor author = identityManager.getLocalAuthor(); + GroupCount count = forumManager.getGroupCount(groupId); + long timestamp = max(count.getLatestMsgTime() + 1, + clock.currentTimeMillis()); + MessageId parentId = + parentItem != null ? parentItem.getId() : null; + createMessage(text, timestamp, parentId, author); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + private void createMessage(String text, long timestamp, + @Nullable MessageId parentId, LocalAuthor author) { + cryptoExecutor.execute(() -> { + LOG.info("Creating forum post..."); + ForumPost msg = forumManager.createLocalPost(groupId, text, + timestamp, parentId, author); + storePost(msg, text); + }); + } + + private void storePost(ForumPost msg, String text) { + runOnDbThread(() -> { + try { + long start = now(); + ForumPostHeader header = forumManager.addLocalPost(msg); + textCache.put(msg.getMessage().getId(), text); + addItem(buildItem(header, text), true); + logDuration(LOG, "Storing forum post", start); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + private ForumPostItem buildItem(ForumPostHeader header, String text) { return new ForumPostItem(header, text); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java index fa5c72a60..d83cd9b53 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java @@ -7,21 +7,15 @@ import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.identity.IdentityManager; -import org.briarproject.bramble.api.identity.LocalAuthor; 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.MessageId; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; -import org.briarproject.briar.android.privategroup.conversation.GroupController.GroupListener; 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.privategroup.GroupMember; -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.PrivateGroupManager; @@ -36,37 +30,32 @@ import java.util.Collection; import java.util.concurrent.Executor; import java.util.logging.Logger; -import javax.annotation.Nullable; import javax.inject.Inject; -import static java.lang.Math.max; import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.LogUtils.logException; @MethodsNotNullByDefault @ParametersNotNullByDefault -class GroupControllerImpl extends - ThreadListControllerImpl +class GroupControllerImpl extends ThreadListControllerImpl implements GroupController { private static final Logger LOG = - Logger.getLogger(GroupControllerImpl.class.getName()); + getLogger(GroupControllerImpl.class.getName()); private final PrivateGroupManager privateGroupManager; - private final GroupMessageFactory groupMessageFactory; @Inject GroupControllerImpl(@DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, IdentityManager identityManager, @CryptoExecutor Executor cryptoExecutor, PrivateGroupManager privateGroupManager, - GroupMessageFactory groupMessageFactory, EventBus eventBus, - MessageTracker messageTracker, Clock clock, + EventBus eventBus, Clock clock, AndroidNotificationManager notificationManager) { super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor, - eventBus, clock, notificationManager, messageTracker); + eventBus, clock, notificationManager); this.privateGroupManager = privateGroupManager; - this.groupMessageFactory = groupMessageFactory; } @Override @@ -79,6 +68,8 @@ class GroupControllerImpl extends public void eventOccurred(Event e) { super.eventOccurred(e); + GroupListener listener = (GroupListener) this.listener; + if (e instanceof GroupMessageAddedEvent) { GroupMessageAddedEvent g = (GroupMessageAddedEvent) e; if (!g.isLocal() && g.getGroupId().equals(getGroupId())) { @@ -132,52 +123,7 @@ class GroupControllerImpl extends }); } - @Override - public void createAndStoreMessage(String text, - @Nullable GroupMessageItem parentItem, - ResultExceptionHandler handler) { - runOnDbThread(() -> { - try { - LocalAuthor author = identityManager.getLocalAuthor(); - MessageId parentId = null; - MessageId previousMsgId = - privateGroupManager.getPreviousMsgId(getGroupId()); - GroupCount count = - privateGroupManager.getGroupCount(getGroupId()); - long timestamp = count.getLatestMsgTime(); - if (parentItem != null) parentId = parentItem.getId(); - timestamp = max(clock.currentTimeMillis(), timestamp + 1); - createMessage(text, timestamp, parentId, author, previousMsgId, - handler); - } catch (DbException e) { - logException(LOG, WARNING, e); - handler.onException(e); - } - }); - } - - private void createMessage(String text, long timestamp, - @Nullable MessageId parentId, LocalAuthor author, - MessageId previousMsgId, - ResultExceptionHandler handler) { - cryptoExecutor.execute(() -> { - LOG.info("Creating group message..."); - GroupMessage msg = groupMessageFactory - .createGroupMessage(getGroupId(), timestamp, - parentId, author, text, previousMsgId); - storePost(msg, text, handler); - }); - } - - @Override - protected GroupMessageHeader addLocalMessage(GroupMessage message) - throws DbException { - return privateGroupManager.addLocalMessage(message); - } - - @Override - protected GroupMessageItem buildItem(GroupMessageHeader header, - String text) { + private GroupMessageItem buildItem(GroupMessageHeader header, String text) { if (header instanceof JoinMessageHeader) { return new JoinMessageItem((JoinMessageHeader) header, text); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java index d418f6178..76c2dc564 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java @@ -58,7 +58,7 @@ class GroupMessageAdapter extends ThreadItemAdapter { GroupMessageItem item = getItem(position); if (item instanceof JoinMessageItem) { ((JoinMessageItem) item).setVisibility(v); - notifyItemChanged(findItemPosition(item), item); + notifyItemChanged(findItemPosition(item.getId()), item); } } } @@ -73,12 +73,4 @@ class GroupMessageAdapter extends ThreadItemAdapter { 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 - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index 335889861..4e0e33db5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -11,32 +11,37 @@ import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.identity.LocalAuthor; 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.threaded.ThreadListViewModel; 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.PostHeader; +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.client.MessageTreeImpl; import java.util.List; import java.util.concurrent.Executor; import java.util.logging.Logger; +import javax.annotation.Nullable; import javax.inject.Inject; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import static java.lang.Math.max; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.LogUtils.logDuration; @@ -109,9 +114,7 @@ class GroupViewModel extends ThreadListViewModel { List headers = privateGroupManager.getHeaders(txn, groupId); logDuration(LOG, "Loading headers", start); - List items = - buildItems(txn, headers, this::buildItem); - return new MessageTreeImpl<>(items).depthFirstOrder(); + return recreateItems(txn, headers, this::buildItem); }, this::setItems); } @@ -132,6 +135,52 @@ class GroupViewModel extends ThreadListViewModel { return privateGroupManager.getMessageText(txn, header.getId()); } + @Override + public void createAndStoreMessage(String text, + @Nullable GroupMessageItem parentItem) { + runOnDbThread(() -> { + try { + LocalAuthor author = identityManager.getLocalAuthor(); + MessageId parentId = null; + MessageId previousMsgId = + privateGroupManager.getPreviousMsgId(groupId); + GroupCount count = privateGroupManager.getGroupCount(groupId); + long timestamp = count.getLatestMsgTime(); + if (parentItem != null) parentId = parentItem.getId(); + timestamp = max(clock.currentTimeMillis(), timestamp + 1); + createMessage(text, timestamp, parentId, author, previousMsgId); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + private void createMessage(String text, long timestamp, + @Nullable MessageId parentId, LocalAuthor author, + MessageId previousMsgId) { + cryptoExecutor.execute(() -> { + LOG.info("Creating group message..."); + GroupMessage msg = groupMessageFactory.createGroupMessage(groupId, + timestamp, parentId, author, text, previousMsgId); + storePost(msg, text); + }); + } + + private void storePost(GroupMessage msg, String text) { + runOnDbThread(() -> { + try { + long start = now(); + GroupMessageHeader header = + privateGroupManager.addLocalMessage(msg); + textCache.put(msg.getMessage().getId(), text); + addItem(buildItem(header, text), true); + logDuration(LOG, "Storing group message", start); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + void deletePrivateGroup() { runOnDbThread(() -> { try { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java index 63db2770e..73865d875 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java @@ -66,16 +66,11 @@ public class ThreadItemAdapter ui.bind(item, listener); } - void setItemWithIdVisible(MessageId messageId) { - int pos = 0; + public int findItemPosition(MessageId id) { for (int i = 0; i < getItemCount(); i++) { - I item = getItem(i); - if (item.getId().equals(messageId)) { - layoutManager.scrollToPosition(pos); - break; - } - pos++; + if (id.equals(getItem(i).getId())) return i; } + return NO_POSITION; // Not found } /** diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java index 1b3e0d202..9aaee4dc0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java @@ -136,6 +136,12 @@ public abstract class ThreadListActivity, DbException>( @@ -206,10 +220,6 @@ public abstract class ThreadListActivity { - scrollToItemAtTop(item); + scrollToItemAtTop(item.getId()); textInput.setOnKeyboardShownListener(null); }); } @@ -277,11 +286,10 @@ public abstract class ThreadListActivity handler = - new UiResultExceptionHandler(this) { - @Override - public void onResultUi(I result) { - addItem(result, true); - } - - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }; - getController().createAndStoreMessage(text, replyItem, handler); + getViewModel().createAndStoreMessage(text, replyItem); textInput.hideSoftKeyboard(); textInput.clearText(); replyId = null; @@ -330,7 +326,7 @@ public abstract class ThreadListActivity void markItemsRead(Collection items); - void createAndStoreMessage(String text, @Nullable I parentItem, - ResultExceptionHandler handler); - interface ThreadListListener extends ThreadListDataSource { @UiThread diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java index 58e3f7d03..f998cf701 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java @@ -17,17 +17,10 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.event.GroupRemovedEvent; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.android.controller.DbControllerImpl; -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.PostHeader; -import org.briarproject.briar.api.client.ThreadedMessage; import java.util.Collection; import java.util.Collections; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -40,39 +33,34 @@ import static org.briarproject.bramble.util.LogUtils.now; @MethodsNotNullByDefault @ParametersNotNullByDefault -public abstract class ThreadListControllerImpl> +public abstract class ThreadListControllerImpl extends DbControllerImpl implements ThreadListController, EventListener { private static final Logger LOG = Logger.getLogger(ThreadListControllerImpl.class.getName()); - private final EventBus eventBus; - private final MessageTracker messageTracker; - private final Map textCache = new ConcurrentHashMap<>(); - private volatile GroupId groupId; + private final EventBus eventBus; protected final IdentityManager identityManager; protected final AndroidNotificationManager notificationManager; protected final Executor cryptoExecutor; protected final Clock clock; // UI thread - protected L listener; + protected ThreadListListener listener; protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, IdentityManager identityManager, @CryptoExecutor Executor cryptoExecutor, EventBus eventBus, - Clock clock, AndroidNotificationManager notificationManager, - MessageTracker messageTracker) { + Clock clock, AndroidNotificationManager notificationManager) { super(dbExecutor, lifecycleManager); this.identityManager = identityManager; this.cryptoExecutor = cryptoExecutor; this.notificationManager = notificationManager; this.clock = clock; this.eventBus = eventBus; - this.messageTracker = messageTracker; } @Override @@ -84,7 +72,7 @@ public abstract class ThreadListControllerImpl) activity; } @CallSuper @@ -103,16 +91,7 @@ public abstract class ThreadListControllerImpl { - try { - messageTracker.storeMessageId(groupId, messageId); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); - } + } @CallSuper @@ -150,27 +129,6 @@ public abstract class ThreadListControllerImpl resultHandler) { - runOnDbThread(() -> { - try { - long start = now(); - H header = addLocalMessage(msg); - textCache.put(msg.getMessage().getId(), text); - logDuration(LOG, "Storing message", start); - resultHandler.onResult(buildItem(header, text)); - } catch (DbException e) { - logException(LOG, WARNING, e); - resultHandler.onException(e); - } - }); - } - - @DatabaseExecutor - protected abstract H addLocalMessage(M message) throws DbException; - - protected abstract I buildItem(H header, String text); - protected GroupId getGroupId() { checkGroupId(); return groupId; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java index 778796ce9..733f38b4b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -21,28 +21,36 @@ 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.MessageTracker; +import org.briarproject.briar.api.client.MessageTree; import org.briarproject.briar.api.client.PostHeader; +import org.briarproject.briar.client.MessageTreeImpl; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Logger; +import javax.annotation.Nullable; + 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.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 -public abstract class ThreadListViewModel extends DbViewModel +public abstract class ThreadListViewModel + extends DbViewModel implements EventListener { private static final Logger LOG = @@ -55,11 +63,18 @@ public abstract class ThreadListViewModel extends DbViewMo private final MessageTracker messageTracker; private final EventBus eventBus; - private final Map textCache = new ConcurrentHashMap<>(); + @DatabaseExecutor + private final MessageTree messageTree = new MessageTreeImpl<>(); + protected final Map textCache = + new ConcurrentHashMap<>(); private final MutableLiveData>> items = new MutableLiveData<>(); + private final AtomicReference scrollToItem = + new AtomicReference<>(); protected volatile GroupId groupId; + private final AtomicReference storedMessageId = + new AtomicReference<>(); public ThreadListViewModel(Application application, @DatabaseExecutor Executor dbExecutor, @@ -95,18 +110,37 @@ public abstract class ThreadListViewModel extends DbViewMo @CallSuper public void setGroupId(GroupId groupId) { this.groupId = groupId; + loadStoredMessageId(); loadItems(); } + private void loadStoredMessageId() { + runOnDbThread(() -> { + try { + storedMessageId + .set(messageTracker.loadStoredMessageId(groupId)); + if (LOG.isLoggable(INFO)) { + LOG.info("Loaded last top visible message id " + + storedMessageId); + } + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + public abstract void loadItems(); + public abstract void createAndStoreMessage(String text, + @Nullable I parentItem); + @UiThread protected void setItems(LiveResult> items) { this.items.setValue(items); } @DatabaseExecutor - protected List buildItems( + protected List recreateItems( Transaction txn, Collection headers, ItemGetter itemGetter) throws DbException { long start = now(); @@ -122,23 +156,45 @@ public abstract class ThreadListViewModel extends DbViewMo } 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; + messageTree.clear(); + messageTree.add(items); + return messageTree.depthFirstOrder(); + } + + protected void addItem(I item, boolean local) { + messageTree.add(item); + if (local) scrollToItem.set(item.getId()); + items.postValue(new LiveResult<>(messageTree.depthFirstOrder())); } @DatabaseExecutor protected abstract String loadMessageText(Transaction txn, PostHeader header) throws DbException; + void storeMessageId(@Nullable MessageId messageId) { + if (messageId != null) runOnDbThread(() -> { + try { + messageTracker.storeMessageId(groupId, messageId); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + @Nullable + MessageId getAndResetRestoredMessageId() { + return storedMessageId.getAndSet(null); + } + LiveData>> getItems() { return items; } + @Nullable + MessageId getAndResetScrollToItem() { + return scrollToItem.getAndSet(null); + } + public interface ItemGetter { I getItem(H header, String text); } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTree.java b/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTree.java index fabdd3308..579059a01 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTree.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTree.java @@ -13,15 +13,12 @@ public interface MessageTree { void add(Collection nodes); - @Deprecated void add(T node); - @Deprecated void clear(); List depthFirstOrder(); - @Deprecated boolean contains(MessageId m); @NotNullByDefault diff --git a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java index 30ee18e35..1c98c62ac 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java @@ -109,7 +109,6 @@ public interface ForumManager { /** * Returns the group count for the given forum. */ - @Deprecated GroupCount getGroupCount(GroupId g) throws DbException; /** diff --git a/briar-core/src/main/java/org/briarproject/briar/client/MessageTreeImpl.java b/briar-core/src/main/java/org/briarproject/briar/client/MessageTreeImpl.java index 49027d190..caca96b11 100644 --- a/briar-core/src/main/java/org/briarproject/briar/client/MessageTreeImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/client/MessageTreeImpl.java @@ -33,11 +33,6 @@ public class MessageTreeImpl private final Comparator comparator = (o1, o2) -> Long.valueOf(o1.getTimestamp()).compareTo(o2.getTimestamp()); - public MessageTreeImpl(Collection collection) { - super(); - add(collection); - } - @Override public synchronized void clear() { roots.clear(); From db53e79d1d6584f89df7b16531d3edd711ccbf98 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 7 Jan 2021 17:00:57 -0300 Subject: [PATCH 06/22] Remove ForumActivityTest which provided little value anyway --- .../briar/android/activity/BaseActivity.java | 7 - .../android/forum/ForumActivityTest.java | 120 ------------------ .../android/forum/TestForumActivity.java | 65 ---------- 3 files changed, 192 deletions(-) delete mode 100644 briar-android/src/test/java/org/briarproject/briar/android/forum/TestForumActivity.java diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java index 594154f36..08f196e18 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java @@ -14,7 +14,6 @@ import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.DestroyableContext; import org.briarproject.briar.android.Localizer; import org.briarproject.briar.android.controller.ActivityLifecycleController; -import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment; import org.briarproject.briar.android.util.UiUtils; @@ -87,7 +86,6 @@ public abstract class BaseActivity extends AppCompatActivity activityComponent = DaggerActivityComponent.builder() .androidComponent(applicationComponent) .activityModule(getActivityModule()) - .forumModule(getForumModule()) .build(); injectActivity(activityComponent); super.onCreate(state); @@ -122,11 +120,6 @@ public abstract class BaseActivity extends AppCompatActivity return new ActivityModule(this); } - // TODO use a test module where this is used in tests - protected ForumModule getForumModule() { - return new ForumModule(); - } - @Override protected void onStart() { super.onStart(); diff --git a/briar-android/src/test/java/org/briarproject/briar/android/forum/ForumActivityTest.java b/briar-android/src/test/java/org/briarproject/briar/android/forum/ForumActivityTest.java index 50c4c80fc..e69de29bb 100644 --- a/briar-android/src/test/java/org/briarproject/briar/android/forum/ForumActivityTest.java +++ b/briar-android/src/test/java/org/briarproject/briar/android/forum/ForumActivityTest.java @@ -1,120 +0,0 @@ -package org.briarproject.briar.android.forum; - -import android.content.Intent; - -import junit.framework.Assert; - -import org.briarproject.bramble.api.db.DbException; -import org.briarproject.bramble.api.identity.Author; -import org.briarproject.briar.api.identity.AuthorInfo; -import org.briarproject.bramble.api.sync.MessageId; -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; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.MockitoAnnotations; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import java.util.Arrays; - -import static junit.framework.Assert.assertEquals; -import static org.briarproject.briar.api.identity.AuthorInfo.Status.UNKNOWN; -import static org.briarproject.bramble.test.TestUtils.getAuthor; -import static org.briarproject.bramble.test.TestUtils.getRandomId; -import static org.briarproject.bramble.util.StringUtils.getRandomString; -import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEXT_LENGTH; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -@RunWith(RobolectricTestRunner.class) -@Config(sdk = 21) -public class ForumActivityTest { - - private final static MessageId[] MESSAGE_IDS = new MessageId[6]; - - static { - for (int i = 0; i < MESSAGE_IDS.length; i++) - MESSAGE_IDS[i] = new MessageId(getRandomId()); - } - - private final static MessageId[] PARENT_IDS = { - null, - MESSAGE_IDS[0], - MESSAGE_IDS[1], - MESSAGE_IDS[2], - MESSAGE_IDS[0], - null - }; - - /* - 1 - -> 2 - -> 3 - -> 4 - 5 - 6 - */ - private final static int[] LEVELS = { - 0, 1, 2, 3, 1, 0 - }; - - private TestForumActivity forumActivity; - @Captor - private ArgumentCaptor, DbException>> - rc; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - Intent intent = new Intent(); - intent.putExtra("briar.GROUP_ID", getRandomId()); - forumActivity = Robolectric.buildActivity(TestForumActivity.class, - intent).create().start().resume().get(); - } - - private ThreadItemList getDummyData() { - ForumPostItem[] forumPostItems = new ForumPostItem[6]; - for (int i = 0; i < forumPostItems.length; i++) { - Author author = getAuthor(); - String text = getRandomString(MAX_FORUM_POST_TEXT_LENGTH); - forumPostItems[i] = new ForumPostItem(MESSAGE_IDS[i], PARENT_IDS[i], - text, System.currentTimeMillis(), author, - new AuthorInfo(UNKNOWN)); - forumPostItems[i].setLevel(LEVELS[i]); - } - ThreadItemList list = new ThreadItemListImpl<>(); - list.addAll(Arrays.asList(forumPostItems)); - return list; - } - - @Test - public void testNestedEntries() { - ForumController mc = forumActivity.getController(); - ThreadItemList dummyData = getDummyData(); - verify(mc, times(1)).loadItems(rc.capture()); - rc.getValue().onResult(dummyData); - ThreadItemAdapter adapter = forumActivity.getAdapter(); - Assert.assertNotNull(adapter); - assertEquals(6, adapter.getItemCount()); - assertEquals(dummyData.get(0).getText(), - adapter.getItemAt(0).getText()); - assertEquals(dummyData.get(1).getText(), - adapter.getItemAt(1).getText()); - assertEquals(dummyData.get(2).getText(), - adapter.getItemAt(2).getText()); - assertEquals(dummyData.get(3).getText(), - adapter.getItemAt(3).getText()); - assertEquals(dummyData.get(4).getText(), - adapter.getItemAt(4).getText()); - assertEquals(dummyData.get(5).getText(), - adapter.getItemAt(5).getText()); - } - -} diff --git a/briar-android/src/test/java/org/briarproject/briar/android/forum/TestForumActivity.java b/briar-android/src/test/java/org/briarproject/briar/android/forum/TestForumActivity.java deleted file mode 100644 index dd71be55f..000000000 --- a/briar-android/src/test/java/org/briarproject/briar/android/forum/TestForumActivity.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.briarproject.briar.android.forum; - -import android.os.Bundle; - -import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; -import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; -import org.briarproject.briar.R; -import org.briarproject.briar.android.activity.ActivityModule; -import org.briarproject.briar.android.activity.BaseActivity; -import org.briarproject.briar.android.controller.BriarController; -import org.briarproject.briar.android.controller.BriarControllerImpl; -import org.briarproject.briar.android.threaded.ThreadItemAdapter; -import org.mockito.Mockito; - -import javax.annotation.Nullable; - -/** - * This class exposes the ForumController and offers the possibility to - * override it. - */ -@MethodsNotNullByDefault -@ParametersNotNullByDefault -public class TestForumActivity extends ForumActivity { - - @Override - public ForumController getController() { - return forumController; - } - - public ThreadItemAdapter getAdapter() { - return adapter; - } - - @Override - public void onCreate(@Nullable Bundle state) { - setTheme(R.style.BriarTheme_NoActionBar); - super.onCreate(state); - } - - @Override - protected ActivityModule getActivityModule() { - return new ActivityModule(this) { - - @Override - protected BriarController provideBriarController( - BriarControllerImpl briarController) { - BriarController c = Mockito.mock(BriarController.class); - Mockito.when(c.accountSignedIn()).thenReturn(true); - return c; - } - - }; - } - - @Override - protected ForumModule getForumModule() { - return new ForumModule() { - @Override - ForumController provideForumController(BaseActivity activity, - ForumControllerImpl forumController) { - return Mockito.mock(ForumController.class); - } - }; - } -} From 1c107a851baa5b3c810d5eda6eb5fe9963719a6f Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 8 Jan 2021 11:14:11 -0300 Subject: [PATCH 07/22] Move thread list events, fields and notification handling into ViewModels --- .../briar/android/forum/ForumActivity.java | 12 +- .../android/forum/ForumControllerImpl.java | 15 +- .../briar/android/forum/ForumViewModel.java | 16 +- .../conversation/GroupActivity.java | 78 +++------ .../conversation/GroupController.java | 9 -- .../conversation/GroupControllerImpl.java | 40 +---- .../conversation/GroupMessageAdapter.java | 8 +- .../conversation/GroupViewModel.java | 40 ++++- .../android/threaded/ThreadItemAdapter.java | 16 +- .../android/threaded/ThreadListActivity.java | 152 +++++++----------- .../threaded/ThreadListController.java | 19 +-- .../threaded/ThreadListControllerImpl.java | 11 -- .../android/threaded/ThreadListViewModel.java | 62 ++++++- 13 files changed, 215 insertions(+), 263 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java index f117dd61f..f3e7ecb87 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java @@ -26,7 +26,6 @@ import javax.inject.Inject; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.LinearLayoutManager; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_FORUM; @@ -63,6 +62,11 @@ public class ForumActivity extends return viewModel; } + @Override + protected ThreadItemAdapter createAdapter() { + return new ThreadItemAdapter<>(this); + } + @Override public void onCreate(@Nullable Bundle state) { super.onCreate(state); @@ -91,9 +95,9 @@ public class ForumActivity extends } @Override - protected ThreadItemAdapter createAdapter( - LinearLayoutManager layoutManager) { - return new ThreadItemAdapter<>(this, layoutManager); + public void onStart() { + super.onStart(); + viewModel.clearForumPostNotification(); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java index 5228d8763..823c7ade0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java @@ -17,10 +17,8 @@ import org.briarproject.briar.android.threaded.ThreadListControllerImpl; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.forum.ForumInvitationResponse; import org.briarproject.briar.api.forum.ForumManager; -import org.briarproject.briar.api.forum.ForumPostHeader; import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.forum.event.ForumInvitationResponseReceivedEvent; -import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent; import org.briarproject.briar.api.sharing.event.ContactLeftShareableEvent; import java.util.ArrayList; @@ -59,7 +57,6 @@ class ForumControllerImpl extends ThreadListControllerImpl @Override public void onActivityStart() { super.onActivityStart(); - notificationManager.clearForumPostNotification(getGroupId()); } @Override @@ -68,13 +65,7 @@ class ForumControllerImpl extends ThreadListControllerImpl ForumListener listener = (ForumListener) this.listener; - if (e instanceof ForumPostReceivedEvent) { - ForumPostReceivedEvent f = (ForumPostReceivedEvent) e; - if (f.getGroupId().equals(getGroupId())) { - LOG.info("Forum post received, adding..."); - listener.onItemReceived(buildItem(f.getHeader(), f.getText())); - } - } else if (e instanceof ForumInvitationResponseReceivedEvent) { + if (e instanceof ForumInvitationResponseReceivedEvent) { ForumInvitationResponseReceivedEvent f = (ForumInvitationResponseReceivedEvent) e; ForumInvitationResponse r = f.getMessageHeader(); @@ -114,8 +105,4 @@ class ForumControllerImpl extends ThreadListControllerImpl }); } - private ForumPostItem buildItem(ForumPostHeader header, String text) { - return new ForumPostItem(header, text); - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index b6b24f790..281442cf3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -29,6 +29,7 @@ import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumPost; import org.briarproject.briar.api.forum.ForumPostHeader; import org.briarproject.briar.api.forum.ForumSharingManager; +import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent; import java.util.List; import java.util.concurrent.Executor; @@ -80,7 +81,20 @@ class ForumViewModel extends ThreadListViewModel { @Override public void eventOccurred(Event e) { + if (e instanceof ForumPostReceivedEvent) { + ForumPostReceivedEvent f = (ForumPostReceivedEvent) e; + if (f.getGroupId().equals(groupId)) { + LOG.info("Forum post received, adding..."); + ForumPostItem item = buildItem(f.getHeader(), f.getText()); + addItem(item); + } + } else { + super.eventOccurred(e); + } + } + void clearForumPostNotification() { + notificationManager.clearForumPostNotification(groupId); } LiveData loadForum() { @@ -141,7 +155,7 @@ class ForumViewModel extends ThreadListViewModel { long start = now(); ForumPostHeader header = forumManager.addLocalPost(msg); textCache.put(msg.getMessage().getId(), text); - addItem(buildItem(header, text), true); + addItemAsync(buildItem(header, text)); logDuration(LOG, "Storing forum post", start); } catch (DbException e) { logException(LOG, WARNING, e); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java index fab1546cb..799a0c5b1 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java @@ -7,13 +7,11 @@ import android.view.MenuInflater; import android.view.MenuItem; import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.privategroup.conversation.GroupController.GroupListener; import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity; import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity; @@ -29,7 +27,6 @@ import javax.inject.Inject; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.LinearLayoutManager; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -50,9 +47,6 @@ public class GroupActivity extends private GroupViewModel viewModel; - @Nullable - private Boolean isCreator = null; - private boolean isDissolved = false; private MenuItem revealMenuItem, inviteMenuItem, leaveMenuItem, dissolveMenuItem; @@ -73,6 +67,11 @@ public class GroupActivity extends return viewModel; } + @Override + protected GroupMessageAdapter createAdapter() { + return new GroupMessageAdapter(this); + } + @Override public void onCreate(@Nullable Bundle state) { super.onCreate(state); @@ -85,11 +84,7 @@ public class GroupActivity extends observeOnce(viewModel.getPrivateGroup(), this, privateGroup -> setTitle(privateGroup.getName()) ); - observeOnce(viewModel.isCreator(), this, isCreator -> { - this.isCreator = isCreator; // TODO remove field - adapter.setPerspective(isCreator); - showMenuItems(); - }); + observeOnce(viewModel.isCreator(), this, adapter::setIsCreator); // Open member list on Toolbar click if (toolbar != null) { @@ -101,25 +96,18 @@ public class GroupActivity extends }); } + // start with group disabled and enable when not dissolved setGroupEnabled(false); - controller.isDissolved( - new UiResultExceptionHandler(this) { - @Override - public void onResultUi(Boolean isDissolved) { - setGroupEnabled(!isDissolved); - } - - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }); + viewModel.isDissolved().observe(this, dissolved -> { + setGroupEnabled(!dissolved); + if (dissolved) onGroupDissolved(); + }); } @Override - protected GroupMessageAdapter createAdapter( - LinearLayoutManager layoutManager) { - return new GroupMessageAdapter(this, layoutManager); + public void onStart() { + super.onStart(); + viewModel.clearGroupMessageNotifications(); } @Override @@ -139,8 +127,13 @@ public class GroupActivity extends leaveMenuItem.setVisible(false); dissolveMenuItem.setVisible(false); - // show items based on role - showMenuItems(); + // show items based on role (which will not change, so observe once) + observeOnce(viewModel.isCreator(), this, isCreator -> { + revealMenuItem.setVisible(!isCreator); + inviteMenuItem.setVisible(isCreator); + leaveMenuItem.setVisible(!isCreator); + dissolveMenuItem.setVisible(isCreator); + }); return super.onCreateOptionsMenu(menu); } @@ -154,26 +147,26 @@ public class GroupActivity extends startActivity(i1); return true; } else if (itemId == R.id.action_group_reveal) { - if (isCreator == null || isCreator) + if (viewModel.isCreator().getValue()) throw new IllegalStateException(); Intent i2 = new Intent(this, RevealContactsActivity.class); i2.putExtra(GROUP_ID, groupId.getBytes()); startActivity(i2); return true; } else if (itemId == R.id.action_group_invite) { - if (isCreator == null || !isCreator) + if (!viewModel.isCreator().getValue()) throw new IllegalStateException(); Intent i3 = new Intent(this, GroupInviteActivity.class); i3.putExtra(GROUP_ID, groupId.getBytes()); startActivityForResult(i3, REQUEST_GROUP_INVITE); return true; } else if (itemId == R.id.action_group_leave) { - if (isCreator == null || isCreator) + if (viewModel.isCreator().getValue()) throw new IllegalStateException(); showLeaveGroupDialog(); return true; } else if (itemId == R.id.action_group_dissolve) { - if (isCreator == null || !isCreator) + if (!viewModel.isCreator().getValue()) throw new IllegalStateException(); showDissolveGroupDialog(); @@ -190,14 +183,6 @@ public class GroupActivity extends } else super.onActivityResult(request, result, data); } - @Override - public void onItemReceived(GroupMessageItem item) { - super.onItemReceived(item); - if (item instanceof JoinMessageItem) { - if (((JoinMessageItem) item).isInitial()) loadSharingContacts(); - } - } - @Override protected int getMaxTextLength() { return MAX_GROUP_POST_TEXT_LENGTH; @@ -205,11 +190,10 @@ public class GroupActivity extends @Override public void onReplyClick(GroupMessageItem item) { - if (!isDissolved) super.onReplyClick(item); + if (!viewModel.isDissolved().getValue()) super.onReplyClick(item); } private void setGroupEnabled(boolean enabled) { - isDissolved = !enabled; sendController.setReady(enabled); list.getRecyclerView().setAlpha(enabled ? 1f : 0.5f); @@ -221,15 +205,6 @@ public class GroupActivity extends } } - private void showMenuItems() { - // we need to have the menu items and know if we are the creator - if (leaveMenuItem == null || isCreator == null) return; - revealMenuItem.setVisible(!isCreator); - inviteMenuItem.setVisible(isCreator); - leaveMenuItem.setVisible(!isCreator); - dissolveMenuItem.setVisible(isCreator); - } - private void showLeaveGroupDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.BriarDialogTheme); @@ -268,7 +243,6 @@ public class GroupActivity extends sharingController.getOnlineCount()); } - @Override public void onGroupDissolved() { setGroupEnabled(false); AlertDialog.Builder builder = diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java index d777ce08f..210360ade 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java @@ -1,9 +1,7 @@ package org.briarproject.briar.android.privategroup.conversation; import org.briarproject.bramble.api.contact.ContactId; -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.Visibility; @@ -12,17 +10,10 @@ import androidx.annotation.UiThread; public interface GroupController extends ThreadListController { - void isDissolved( - ResultExceptionHandler handler); - interface GroupListener extends ThreadListListener { - @UiThread void onContactRelationshipRevealed(AuthorId memberId, ContactId contactId, Visibility v); - - @UiThread - void onGroupDissolved(); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java index d83cd9b53..e778cc01e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java @@ -16,13 +16,9 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.threaded.ThreadListControllerImpl; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.privategroup.GroupMember; -import org.briarproject.briar.api.privategroup.GroupMessageHeader; -import org.briarproject.briar.api.privategroup.JoinMessageHeader; import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.briarproject.briar.api.privategroup.event.ContactRelationshipRevealedEvent; -import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent; import org.briarproject.briar.api.privategroup.event.GroupInvitationResponseReceivedEvent; -import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent; import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse; import java.util.ArrayList; @@ -61,7 +57,6 @@ class GroupControllerImpl extends ThreadListControllerImpl @Override public void onActivityStart() { super.onActivityStart(); - notificationManager.clearGroupMessageNotification(getGroupId()); } @Override @@ -70,13 +65,7 @@ class GroupControllerImpl extends ThreadListControllerImpl GroupListener listener = (GroupListener) this.listener; - if (e instanceof GroupMessageAddedEvent) { - GroupMessageAddedEvent g = (GroupMessageAddedEvent) e; - if (!g.isLocal() && g.getGroupId().equals(getGroupId())) { - LOG.info("Group message received, adding..."); - listener.onItemReceived(buildItem(g.getHeader(), g.getText())); - } - } else if (e instanceof ContactRelationshipRevealedEvent) { + if (e instanceof ContactRelationshipRevealedEvent) { ContactRelationshipRevealedEvent c = (ContactRelationshipRevealedEvent) e; if (getGroupId().equals(c.getGroupId())) { @@ -90,11 +79,6 @@ class GroupControllerImpl extends ThreadListControllerImpl if (getGroupId().equals(r.getShareableId()) && r.wasAccepted()) { listener.onInvitationAccepted(g.getContactId()); } - } else if (e instanceof GroupDissolvedEvent) { - GroupDissolvedEvent g = (GroupDissolvedEvent) e; - if (getGroupId().equals(g.getGroupId())) { - listener.onGroupDissolved(); - } } } @@ -123,26 +107,4 @@ class GroupControllerImpl extends ThreadListControllerImpl }); } - private GroupMessageItem buildItem(GroupMessageHeader header, String text) { - if (header instanceof JoinMessageHeader) { - return new JoinMessageItem((JoinMessageHeader) header, text); - } - return new GroupMessageItem(header, text); - } - - @Override - public void isDissolved( - ResultExceptionHandler handler) { - runOnDbThread(() -> { - try { - boolean isDissolved = - privateGroupManager.isDissolved(getGroupId()); - handler.onResult(isDissolved); - } catch (DbException e) { - logException(LOG, WARNING, e); - handler.onException(e); - } - }); - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java index 76c2dc564..4d88a22ea 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java @@ -14,7 +14,6 @@ import org.briarproject.briar.api.privategroup.Visibility; import androidx.annotation.LayoutRes; import androidx.annotation.UiThread; -import androidx.recyclerview.widget.LinearLayoutManager; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; @@ -24,9 +23,8 @@ class GroupMessageAdapter extends ThreadItemAdapter { private boolean isCreator = false; - GroupMessageAdapter(ThreadItemListener listener, - LinearLayoutManager layoutManager) { - super(listener, layoutManager); + GroupMessageAdapter(ThreadItemListener listener) { + super(listener); } @LayoutRes @@ -47,7 +45,7 @@ class GroupMessageAdapter extends ThreadItemAdapter { return new ThreadPostViewHolder<>(v); } - void setPerspective(boolean isCreator) { + void setIsCreator(boolean isCreator) { this.isCreator = isCreator; notifyDataSetChanged(); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index 4e0e33db5..b501c38eb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -30,6 +30,8 @@ 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.GroupDissolvedEvent; +import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent; import java.util.List; import java.util.concurrent.Executor; @@ -60,6 +62,8 @@ class GroupViewModel extends ThreadListViewModel { private final MutableLiveData privateGroup = new MutableLiveData<>(); private final MutableLiveData isCreator = new MutableLiveData<>(); + private final MutableLiveData isDissolved = + new MutableLiveData<>(); @Inject GroupViewModel(Application application, @@ -84,7 +88,26 @@ class GroupViewModel extends ThreadListViewModel { @Override public void eventOccurred(Event e) { - + if (e instanceof GroupMessageAddedEvent) { + GroupMessageAddedEvent g = (GroupMessageAddedEvent) e; + // only act on non-local messages in this group + if (!g.isLocal() && g.getGroupId().equals(groupId)) { + LOG.info("Group message received, adding..."); + GroupMessageItem item = buildItem(g.getHeader(), g.getText()); + addItem(item); + if (item instanceof JoinMessageItem) { + // TODO +// if (((JoinMessageItem) item).isInitial()) loadSharingContacts(); + } + } + } else if (e instanceof GroupDissolvedEvent) { + GroupDissolvedEvent g = (GroupDissolvedEvent) e; + if (g.getGroupId().equals(groupId)) { + isDissolved.setValue(true); + } + } else { + super.eventOccurred(e); + } } @Override @@ -93,6 +116,10 @@ class GroupViewModel extends ThreadListViewModel { loadPrivateGroup(groupId); } + public void clearGroupMessageNotifications() { + notificationManager.clearGroupMessageNotification(groupId); + } + private void loadPrivateGroup(GroupId groupId) { runOnDbThread(() -> { try { @@ -109,7 +136,10 @@ class GroupViewModel extends ThreadListViewModel { @Override public void loadItems() { loadList(txn -> { - // TODO first check if group is dissolved + // check first if group is dissolved + isDissolved + .postValue(privateGroupManager.isDissolved(txn, groupId)); + // no continue to load the items long start = now(); List headers = privateGroupManager.getHeaders(txn, groupId); @@ -173,7 +203,7 @@ class GroupViewModel extends ThreadListViewModel { GroupMessageHeader header = privateGroupManager.addLocalMessage(msg); textCache.put(msg.getMessage().getId(), text); - addItem(buildItem(header, text), true); + addItemAsync(buildItem(header, text)); logDuration(LOG, "Storing group message", start); } catch (DbException e) { logException(LOG, WARNING, e); @@ -199,4 +229,8 @@ class GroupViewModel extends ThreadListViewModel { return isCreator; } + LiveData isDissolved() { + return isDissolved; + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java index 73865d875..a66969592 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java @@ -30,10 +30,8 @@ public class ThreadItemAdapter static final int UNDEFINED = -1; private final ThreadItemListener listener; - private final LinearLayoutManager layoutManager; - public ThreadItemAdapter(ThreadItemListener listener, - LinearLayoutManager layoutManager) { + public ThreadItemAdapter(ThreadItemListener listener) { super(new DiffUtil.ItemCallback() { @Override public boolean areItemsTheSame(I a, I b) { @@ -47,7 +45,6 @@ public class ThreadItemAdapter } }); this.listener = listener; - this.layoutManager = layoutManager; } @NonNull @@ -101,10 +98,17 @@ public class ThreadItemAdapter return null; } + @Nullable + MessageId getFirstVisibleMessageId(LinearLayoutManager layoutManager) { + int position = layoutManager.findFirstVisibleItemPosition(); + if (position == NO_POSITION) return null; + return getItemAt(position).getId(); + } + /** * Returns the position of the first unread item below the current viewport */ - int getVisibleUnreadPosBottom() { + int getVisibleUnreadPosBottom(LinearLayoutManager layoutManager) { int positionBottom = layoutManager.findLastVisibleItemPosition(); if (positionBottom == NO_POSITION) return NO_POSITION; for (int i = positionBottom + 1; i < getItemCount(); i++) { @@ -116,7 +120,7 @@ public class ThreadItemAdapter /** * Returns the position of the first unread item above the current viewport */ - int getVisibleUnreadPosTop() { + int getVisibleUnreadPosTop(LinearLayoutManager layoutManager) { int positionTop = layoutManager.findFirstVisibleItemPosition(); int position = NO_POSITION; for (int i = 0; i < getItemCount(); i++) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java index 9aaee4dc0..b3c6d34d1 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java @@ -2,7 +2,6 @@ package org.briarproject.briar.android.threaded; import android.content.Intent; import android.os.Bundle; -import android.os.Parcelable; import android.view.MenuItem; import com.google.android.material.snackbar.Snackbar; @@ -19,7 +18,6 @@ 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.util.BriarSnackbarBuilder; import org.briarproject.briar.android.view.BriarRecyclerView; @@ -31,7 +29,6 @@ import org.briarproject.briar.api.attachment.AttachmentHeader; import java.util.Collection; import java.util.List; -import java.util.logging.Logger; import javax.annotation.Nullable; import javax.inject.Inject; @@ -42,7 +39,6 @@ import androidx.appcompat.app.ActionBar; import androidx.recyclerview.widget.LinearLayoutManager; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; -import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @MethodsNotNullByDefault @@ -50,30 +46,22 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; public abstract class ThreadListActivity> extends BriarActivity implements ThreadListListener, SendListener, SharingListener, - ThreadItemListener, ThreadListDataSource { - - protected static final String KEY_REPLY_ID = "replyId"; - - private static final Logger LOG = - getLogger(ThreadListActivity.class.getName()); - - protected A adapter; + ThreadItemListener { + protected final A adapter = createAdapter(); private ThreadScrollListener scrollListener; protected BriarRecyclerView list; private LinearLayoutManager layoutManager; protected TextInputView textInput; protected TextSendController sendController; protected GroupId groupId; - @Nullable - private Parcelable layoutManagerState; - @Nullable - private MessageId replyId; protected abstract ThreadListController getController(); protected abstract ThreadListViewModel getViewModel(); + protected abstract A createAdapter(); + @Inject protected SharingController sharingController; @@ -103,59 +91,85 @@ public abstract class ThreadListActivity(adapter, getController(), upButton, downButton); list.getRecyclerView().addOnScrollListener(scrollListener); upButton.setOnClickListener(v -> { - int position = adapter.getVisibleUnreadPosTop(); + int position = adapter.getVisibleUnreadPosTop(layoutManager); if (position != NO_POSITION) { list.getRecyclerView().scrollToPosition(position); } }); downButton.setOnClickListener(v -> { - int position = adapter.getVisibleUnreadPosBottom(); + int position = adapter.getVisibleUnreadPosBottom(layoutManager); if (position != NO_POSITION) { list.getRecyclerView().scrollToPosition(position); } }); - if (state != null) { - byte[] replyIdBytes = state.getByteArray(KEY_REPLY_ID); - if (replyIdBytes != null) replyId = new MessageId(replyIdBytes); - } - getViewModel().getItems().observe(this, result -> result .onError(this::handleException) .onSuccess(this::displayItems) ); + getViewModel().getGroupRemoved().observe(this, removed -> { + if (removed) supportFinishAfterTransition(); + }); + sharingController.setSharingListener(this); loadSharingContacts(); } + @CallSuper + @Override + public void onStart() { + super.onStart(); + getViewModel().blockNotifications(); + sharingController.onStart(); + list.startPeriodicUpdate(); + } + + @CallSuper + @Override + public void onStop() { + super.onStop(); + getViewModel().unblockNotifications(); + sharingController.onStop(); + list.stopPeriodicUpdate(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + supportFinishAfterTransition(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onBackPressed() { + if (adapter.getHighlightedItem() != null) { + textInput.clearText(); + getViewModel().setReplyId(null); + updateTextInput(); + } else { + super.onBackPressed(); + } + } + @Override protected void onDestroy() { super.onDestroy(); - getViewModel().storeMessageId(getFirstVisibleMessageId()); - } - - @Override - @Nullable - public MessageId getFirstVisibleMessageId() { + // store list position, so we can restore it when coming back here if (layoutManager != null && adapter != null) { - int position = - layoutManager.findFirstVisibleItemPosition(); - I i = adapter.getItemAt(position); - return i == null ? null : i.getId(); + MessageId id = adapter.getFirstVisibleMessageId(layoutManager); + getViewModel().storeMessageId(id); } - return null; } - protected abstract A createAdapter(LinearLayoutManager layoutManager); - protected void displayItems(List items) { if (items.isEmpty()) { list.showData(); @@ -201,58 +215,9 @@ public abstract class ThreadListActivity void markItemsRead(Collection items); - interface ThreadListListener extends ThreadListDataSource { - - @UiThread - void onItemReceived(I item); - - @UiThread - void onGroupRemoved(); - + interface ThreadListListener { @UiThread void onInvitationAccepted(ContactId c); } - interface ThreadListDataSource { - - @UiThread - @Nullable - MessageId getFirstVisibleMessageId(); - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java index f998cf701..01d015d17 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java @@ -14,7 +14,6 @@ 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.sync.event.GroupRemovedEvent; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.android.controller.DbControllerImpl; import org.briarproject.briar.api.android.AndroidNotificationManager; @@ -78,14 +77,12 @@ public abstract class ThreadListControllerImpl @CallSuper @Override public void onActivityStart() { - notificationManager.blockNotification(getGroupId()); eventBus.addListener(this); } @CallSuper @Override public void onActivityStop() { - notificationManager.unblockNotification(getGroupId()); eventBus.removeListener(this); } @@ -94,16 +91,8 @@ public abstract class ThreadListControllerImpl } - @CallSuper @Override public void eventOccurred(Event e) { - if (e instanceof GroupRemovedEvent) { - GroupRemovedEvent s = (GroupRemovedEvent) e; - if (s.getGroup().getId().equals(getGroupId())) { - LOG.info("Group removed"); - listener.onGroupRemoved(); - } - } } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java index 733f38b4b..c17f70e26 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -7,6 +7,7 @@ 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; import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.identity.IdentityManager; @@ -15,6 +16,7 @@ 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.sync.event.GroupRemovedEvent; import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.android.viewmodel.DbViewModel; @@ -65,14 +67,18 @@ public abstract class ThreadListViewModel @DatabaseExecutor private final MessageTree messageTree = new MessageTreeImpl<>(); - protected final Map textCache = + protected final Map textCache = // TODO still needed? new ConcurrentHashMap<>(); private final MutableLiveData>> items = new MutableLiveData<>(); + private final MutableLiveData groupRemoved = + new MutableLiveData<>(); private final AtomicReference scrollToItem = new AtomicReference<>(); protected volatile GroupId groupId; + @Nullable + private MessageId replyId; private final AtomicReference storedMessageId = new AtomicReference<>(); @@ -114,6 +120,26 @@ public abstract class ThreadListViewModel loadItems(); } + public void blockNotifications() { + notificationManager.blockNotification(groupId); + } + + public void unblockNotifications() { + notificationManager.unblockNotification(groupId); + } + + @Override + @CallSuper + public void eventOccurred(Event e) { + if (e instanceof GroupRemovedEvent) { + GroupRemovedEvent s = (GroupRemovedEvent) e; + if (s.getGroup().getId().equals(groupId)) { + LOG.info("Group removed"); + groupRemoved.setValue(true); + } + } + } + private void loadStoredMessageId() { runOnDbThread(() -> { try { @@ -161,9 +187,24 @@ public abstract class ThreadListViewModel return messageTree.depthFirstOrder(); } - protected void addItem(I item, boolean local) { + /** + * Add a remote item on the UI thread. + * The list will not scroll, but show an unread indicator. + */ + @UiThread + protected void addItem(I item) { messageTree.add(item); - if (local) scrollToItem.set(item.getId()); + items.setValue(new LiveResult<>(messageTree.depthFirstOrder())); + } + + /** + * Add a local item from the DB thread. + * The list will scroll to the new item. + */ + @DatabaseExecutor + protected void addItemAsync(I item) { + messageTree.add(item); + scrollToItem.set(item.getId()); items.postValue(new LiveResult<>(messageTree.depthFirstOrder())); } @@ -171,6 +212,17 @@ public abstract class ThreadListViewModel protected abstract String loadMessageText(Transaction txn, PostHeader header) throws DbException; + @UiThread + public void setReplyId(@Nullable MessageId id) { + replyId = id; + } + + @UiThread + @Nullable + public MessageId getReplyId() { + return replyId; + } + void storeMessageId(@Nullable MessageId messageId) { if (messageId != null) runOnDbThread(() -> { try { @@ -190,6 +242,10 @@ public abstract class ThreadListViewModel return items; } + LiveData getGroupRemoved() { + return groupRemoved; + } + @Nullable MessageId getAndResetScrollToItem() { return scrollToItem.getAndSet(null); From 766718e75cb006471185d41083eec04310581566 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 8 Jan 2021 11:16:16 -0300 Subject: [PATCH 08/22] Remove text cache as it is no longer needed --- .../briar/android/forum/ForumViewModel.java | 3 +-- .../privategroup/conversation/GroupViewModel.java | 3 +-- .../briar/android/threaded/ThreadListViewModel.java | 13 ++----------- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index 281442cf3..086aaf00b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -117,7 +117,7 @@ class ForumViewModel extends ThreadListViewModel { List headers = forumManager.getPostHeaders(txn, groupId); logDuration(LOG, "Loading headers", start); - return recreateItems(txn, headers, this::buildItem); + return createItems(txn, headers, this::buildItem); }, this::setItems); } @@ -154,7 +154,6 @@ class ForumViewModel extends ThreadListViewModel { try { long start = now(); ForumPostHeader header = forumManager.addLocalPost(msg); - textCache.put(msg.getMessage().getId(), text); addItemAsync(buildItem(header, text)); logDuration(LOG, "Storing forum post", start); } catch (DbException e) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index b501c38eb..dc02d7390 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -144,7 +144,7 @@ class GroupViewModel extends ThreadListViewModel { List headers = privateGroupManager.getHeaders(txn, groupId); logDuration(LOG, "Loading headers", start); - return recreateItems(txn, headers, this::buildItem); + return createItems(txn, headers, this::buildItem); }, this::setItems); } @@ -202,7 +202,6 @@ class GroupViewModel extends ThreadListViewModel { long start = now(); GroupMessageHeader header = privateGroupManager.addLocalMessage(msg); - textCache.put(msg.getMessage().getId(), text); addItemAsync(buildItem(header, text)); logDuration(LOG, "Storing group message", start); } catch (DbException e) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java index c17f70e26..acd47e421 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -29,8 +29,6 @@ import org.briarproject.briar.client.MessageTreeImpl; import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Logger; @@ -67,8 +65,6 @@ public abstract class ThreadListViewModel @DatabaseExecutor private final MessageTree messageTree = new MessageTreeImpl<>(); - protected final Map textCache = // TODO still needed? - new ConcurrentHashMap<>(); private final MutableLiveData>> items = new MutableLiveData<>(); private final MutableLiveData groupRemoved = @@ -166,18 +162,13 @@ public abstract class ThreadListViewModel } @DatabaseExecutor - protected List recreateItems( + protected List createItems( Transaction txn, Collection headers, ItemGetter itemGetter) throws DbException { long start = now(); ThreadItemList 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); - } + String text = loadMessageText(txn, header); items.add(itemGetter.getItem(header, text)); } logDuration(LOG, "Loading bodies and creating items", start); From 8372bb01b2a62eb950d489a699d40e1a9a26556b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 8 Jan 2021 11:25:56 -0300 Subject: [PATCH 09/22] Move marking thread list items read to ViewModel --- .../android/forum/ForumControllerImpl.java | 6 ---- .../briar/android/forum/ForumViewModel.java | 11 ++++++ .../conversation/GroupControllerImpl.java | 6 ---- .../conversation/GroupViewModel.java | 11 ++++++ .../android/threaded/ThreadListActivity.java | 2 +- .../threaded/ThreadListController.java | 4 --- .../threaded/ThreadListControllerImpl.java | 36 ------------------- .../android/threaded/ThreadListViewModel.java | 2 ++ .../threaded/ThreadScrollListener.java | 8 ++--- 9 files changed, 29 insertions(+), 57 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java index 823c7ade0..4bfab3d74 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java @@ -10,7 +10,6 @@ import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.threaded.ThreadListControllerImpl; @@ -82,11 +81,6 @@ class ForumControllerImpl extends ThreadListControllerImpl } } - @Override - protected void markRead(MessageId id) throws DbException { - forumManager.setReadFlag(getGroupId(), id, true); - } - @Override public void loadSharingContacts( ResultExceptionHandler, DbException> handler) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index 086aaf00b..0c1f21aff 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -172,6 +172,17 @@ class ForumViewModel extends ThreadListViewModel { return forumManager.getPostText(txn, header.getId()); } + @Override + protected void markItemRead(ForumPostItem item) { + runOnDbThread(() -> { + try { + forumManager.setReadFlag(groupId, item.getId(), true); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + void deleteForum() { runOnDbThread(() -> { try { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java index e778cc01e..275ee3586 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java @@ -10,7 +10,6 @@ import org.briarproject.bramble.api.identity.IdentityManager; 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.MessageId; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.threaded.ThreadListControllerImpl; @@ -82,11 +81,6 @@ class GroupControllerImpl extends ThreadListControllerImpl } } - @Override - protected void markRead(MessageId id) throws DbException { - privateGroupManager.setReadFlag(getGroupId(), id, true); - } - @Override public void loadSharingContacts( ResultExceptionHandler, DbException> handler) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index dc02d7390..e79e862d7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -210,6 +210,17 @@ class GroupViewModel extends ThreadListViewModel { }); } + @Override + protected void markItemRead(GroupMessageItem item) { + runOnDbThread(() -> { + try { + privateGroupManager.setReadFlag(groupId, item.getId(), true); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + void deletePrivateGroup() { runOnDbThread(() -> { try { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java index b3c6d34d1..516eec20c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java @@ -92,7 +92,7 @@ public abstract class ThreadListActivity(adapter, getController(), + scrollListener = new ThreadScrollListener<>(adapter, getViewModel(), upButton, downButton); list.getRecyclerView().addOnScrollListener(scrollListener); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java index ff0513af1..211182d3c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java @@ -20,10 +20,6 @@ public interface ThreadListController void loadSharingContacts( ResultExceptionHandler, DbException> handler); - void markItemRead(I item); - - void markItemsRead(Collection items); - interface ThreadListListener { @UiThread void onInvitationAccepted(ContactId c); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java index 01d015d17..b96eed677 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java @@ -4,7 +4,6 @@ import android.app.Activity; 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.event.Event; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventListener; @@ -13,32 +12,20 @@ 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.Clock; import org.briarproject.briar.android.controller.DbControllerImpl; import org.briarproject.briar.api.android.AndroidNotificationManager; -import java.util.Collection; -import java.util.Collections; import java.util.concurrent.Executor; -import java.util.logging.Logger; import androidx.annotation.CallSuper; -import static java.util.logging.Level.WARNING; -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 public abstract class ThreadListControllerImpl extends DbControllerImpl implements ThreadListController, EventListener { - private static final Logger LOG = - Logger.getLogger(ThreadListControllerImpl.class.getName()); - private volatile GroupId groupId; private final EventBus eventBus; @@ -95,29 +82,6 @@ public abstract class ThreadListControllerImpl public void eventOccurred(Event e) { } - @Override - public void markItemRead(I item) { - markItemsRead(Collections.singletonList(item)); - } - - @Override - public void markItemsRead(Collection items) { - runOnDbThread(() -> { - try { - long start = now(); - for (I i : items) { - markRead(i.getId()); - } - logDuration(LOG, "Marking read", start); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); - } - - @DatabaseExecutor - protected abstract void markRead(MessageId id) throws DbException; - protected GroupId getGroupId() { checkGroupId(); return groupId; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java index acd47e421..ea7aac199 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -224,6 +224,8 @@ public abstract class ThreadListViewModel }); } + protected abstract void markItemRead(I item); + @Nullable MessageId getAndResetRestoredMessageId() { return storedMessageId.getAndSet(null); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadScrollListener.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadScrollListener.java index dca100459..e0f1ec327 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadScrollListener.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadScrollListener.java @@ -20,15 +20,15 @@ class ThreadScrollListener private static final Logger LOG = getLogger(ThreadScrollListener.class.getName()); - private final ThreadListController controller; + private final ThreadListViewModel viewModel; private final UnreadMessageButton upButton, downButton; ThreadScrollListener(ThreadItemAdapter adapter, - ThreadListController controller, + ThreadListViewModel viewModel, UnreadMessageButton upButton, UnreadMessageButton downButton) { super(adapter); - this.controller = controller; + this.viewModel = viewModel; this.upButton = upButton; this.downButton = downButton; } @@ -44,7 +44,7 @@ class ThreadScrollListener protected void onItemVisible(I item) { if (!item.isRead()) { item.setRead(true); - controller.markItemRead(item); + viewModel.markItemRead(item); } } From b78569119ae30adf0fffb79693da6af1e68831fe Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 8 Jan 2021 14:25:12 -0300 Subject: [PATCH 10/22] Remove Visibility from JoinMessageHeader and Item --- .../conversation/JoinMessageItem.java | 11 ---- .../api/privategroup/JoinMessageHeader.java | 9 +-- .../privategroup/PrivateGroupManagerImpl.java | 18 ++--- .../PrivateGroupManagerIntegrationTest.java | 66 +++---------------- 4 files changed, 16 insertions(+), 88 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/JoinMessageItem.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/JoinMessageItem.java index 592962371..bb92d8310 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/JoinMessageItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/JoinMessageItem.java @@ -2,7 +2,6 @@ package org.briarproject.briar.android.privategroup.conversation; import org.briarproject.briar.R; import org.briarproject.briar.api.privategroup.JoinMessageHeader; -import org.briarproject.briar.api.privategroup.Visibility; import javax.annotation.concurrent.NotThreadSafe; @@ -13,12 +12,10 @@ import androidx.annotation.UiThread; @NotThreadSafe class JoinMessageItem extends GroupMessageItem { - private Visibility visibility; private final boolean isInitial; JoinMessageItem(JoinMessageHeader h, String text) { super(h, text); - this.visibility = h.getVisibility(); this.isInitial = h.isInitial(); } @@ -33,14 +30,6 @@ class JoinMessageItem extends GroupMessageItem { return R.layout.list_item_group_join_notice; } - Visibility getVisibility() { - return visibility; - } - - void setVisibility(Visibility visibility) { - this.visibility = visibility; - } - boolean isInitial() { return isInitial; } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/JoinMessageHeader.java b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/JoinMessageHeader.java index dfcfeff5d..f84b59d82 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/JoinMessageHeader.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/JoinMessageHeader.java @@ -8,21 +8,14 @@ import javax.annotation.concurrent.Immutable; @NotNullByDefault public class JoinMessageHeader extends GroupMessageHeader { - private final Visibility visibility; private final boolean isInitial; - public JoinMessageHeader(GroupMessageHeader h, Visibility visibility, - boolean isInitial) { + public JoinMessageHeader(GroupMessageHeader h, boolean isInitial) { super(h.getGroupId(), h.getId(), h.getParentId(), h.getTimestamp(), h.getAuthor(), h.getAuthorInfo(), h.isRead()); - this.visibility = visibility; this.isInitial = isInitial; } - public Visibility getVisibility() { - return visibility; - } - public boolean isInitial() { return isInitial; } diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java index bff8fb14c..ad776aa58 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java @@ -145,7 +145,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook addMember(txn, m.getMessage().getGroupId(), m.getMember(), VISIBLE); setPreviousMsgId(txn, m.getMessage().getGroupId(), m.getMessage().getId()); - attachJoinMessageAddedEvent(txn, m.getMessage(), meta, true, VISIBLE); + attachJoinMessageAddedEvent(txn, m.getMessage(), meta, true); } @Override @@ -354,16 +354,12 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook for (AuthorId id : authors) { authorInfos.put(id, authorManager.getAuthorInfo(txn, id)); } - // get current visibilities for join messages - Map visibilities = getMembers(txn, g); // parse the metadata for (Entry entry : metadata.entrySet()) { BdfDictionary meta = entry.getValue(); if (meta.getLong(KEY_TYPE) == JOIN.getInt()) { - Author member = getAuthor(meta); - Visibility v = visibilities.get(member); headers.add(getJoinMessageHeader(txn, g, entry.getKey(), - meta, authorInfos, v)); + meta, authorInfos)); } else { headers.add(getGroupMessageHeader(txn, g, entry.getKey(), meta, authorInfos)); @@ -401,13 +397,13 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook private JoinMessageHeader getJoinMessageHeader(Transaction txn, GroupId g, MessageId id, BdfDictionary meta, - Map authorInfos, Visibility v) + Map authorInfos) throws DbException, FormatException { GroupMessageHeader header = getGroupMessageHeader(txn, g, id, meta, authorInfos); boolean creator = meta.getBoolean(KEY_INITIAL_JOIN_MSG); - return new JoinMessageHeader(header, v, creator); + return new JoinMessageHeader(header, creator); } @Override @@ -556,7 +552,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook addMember(txn, m.getGroupId(), member, v); // track message and broadcast event messageTracker.trackIncomingMessage(txn, m); - attachJoinMessageAddedEvent(txn, m, meta, false, v); + attachJoinMessageAddedEvent(txn, m, meta, false); } private void handleGroupMessage(Transaction txn, Message m, @@ -606,10 +602,10 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook } private void attachJoinMessageAddedEvent(Transaction txn, Message m, - BdfDictionary meta, boolean local, Visibility v) + BdfDictionary meta, boolean local) throws DbException, FormatException { JoinMessageHeader header = getJoinMessageHeader(txn, m.getGroupId(), - m.getId(), meta, Collections.emptyMap(), v); + m.getId(), meta, Collections.emptyMap()); txn.attach(new GroupMessageAddedEvent(m.getGroupId(), header, "", local)); } diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/PrivateGroupManagerIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/PrivateGroupManagerIntegrationTest.java index 8a87fbe20..9dae9d6b5 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/PrivateGroupManagerIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/PrivateGroupManagerIntegrationTest.java @@ -124,10 +124,8 @@ public class PrivateGroupManagerIntegrationTest addGroup(); // create and add test message with no previousMsgId - @SuppressWarnings("ConstantConditions") - GroupMessage msg = groupMessageFactory - .createGroupMessage(groupId0, clock.currentTimeMillis(), null, - author0, "test", null); + GroupMessage msg = groupMessageFactory.createGroupMessage(groupId0, + clock.currentTimeMillis(), null, author0, "test", null); groupManager0.addLocalMessage(msg); // sync test message @@ -342,23 +340,19 @@ public class PrivateGroupManagerIntegrationTest Collection members0 = groupManager0.getMembers(groupId0); assertEquals(2, members0.size()); for (GroupMember m : members0) { - if (m.getAuthor().equals(author0)) { - assertEquals(VISIBLE, m.getVisibility()); - } else { + if (!m.getAuthor().equals(author0)) { assertEquals(author1, m.getAuthor()); - assertEquals(VISIBLE, m.getVisibility()); } + assertEquals(VISIBLE, m.getVisibility()); } Collection members1 = groupManager1.getMembers(groupId0); assertEquals(2, members1.size()); for (GroupMember m : members1) { - if (m.getAuthor().equals(author1)) { - assertEquals(VISIBLE, m.getVisibility()); - } else { + if (!m.getAuthor().equals(author1)) { assertEquals(author0, m.getAuthor()); - assertEquals(VISIBLE, m.getVisibility()); } + assertEquals(VISIBLE, m.getVisibility()); } } @@ -368,27 +362,11 @@ public class PrivateGroupManagerIntegrationTest Collection headers0 = groupManager0.getHeaders(groupId0); - for (GroupMessageHeader h : headers0) { - if (h instanceof JoinMessageHeader) { - JoinMessageHeader j = (JoinMessageHeader) h; - // all relationships of the creator are visible - assertEquals(VISIBLE, j.getVisibility()); - } - } + assertEquals(2, headers0.size()); Collection headers1 = groupManager1.getHeaders(groupId0); - for (GroupMessageHeader h : headers1) { - if (h instanceof JoinMessageHeader) { - JoinMessageHeader j = (JoinMessageHeader) h; - if (h.getAuthor().equals(author1)) - // we are visible to ourselves - assertEquals(VISIBLE, j.getVisibility()); - else - // our relationship to the creator is visible - assertEquals(VISIBLE, j.getVisibility()); - } - } + assertEquals(2, headers1.size()); } @Test @@ -463,34 +441,6 @@ public class PrivateGroupManagerIntegrationTest assertEquals(REVEALED_BY_CONTACT, m.getVisibility()); } } - - // assert that join messages reflect revealed relationship - Collection headers1 = - groupManager1.getHeaders(groupId0); - for (GroupMessageHeader h : headers1) { - if (h instanceof JoinMessageHeader) { - JoinMessageHeader j = (JoinMessageHeader) h; - if (h.getAuthor().equals(author2)) - // 1 revealed the relationship to 2 - assertEquals(REVEALED_BY_US, j.getVisibility()); - else - // 1's other relationship (to 1 and creator) are visible - assertEquals(VISIBLE, j.getVisibility()); - } - } - Collection headers2 = - groupManager2.getHeaders(groupId0); - for (GroupMessageHeader h : headers2) { - if (h instanceof JoinMessageHeader) { - JoinMessageHeader j = (JoinMessageHeader) h; - if (h.getAuthor().equals(author1)) - // 2's relationship was revealed by 1 - assertEquals(REVEALED_BY_CONTACT, j.getVisibility()); - else - // 2's other relationship (to 2 and creator) are visible - assertEquals(VISIBLE, j.getVisibility()); - } - } } @Test From 635008fb606f5a0c3541cd239e5c2c32337c9213 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 8 Jan 2021 14:52:49 -0300 Subject: [PATCH 11/22] Introduce SharingController with LiveData and get rid of ThreadList controllers --- .../briarproject/briar/android/AppModule.java | 6 +- .../android/activity/ActivityComponent.java | 6 +- .../briar/android/activity/BriarActivity.java | 1 - .../android/controller/SharingController.java | 1 + .../controller/SharingControllerImpl.java | 1 + .../briar/android/forum/ForumActivity.java | 44 ++----- .../briar/android/forum/ForumController.java | 17 --- .../android/forum/ForumControllerImpl.java | 102 ---------------- .../briar/android/forum/ForumModule.java | 32 ++--- .../briar/android/forum/ForumPostItem.java | 6 - .../briar/android/forum/ForumViewModel.java | 46 +++++++- .../conversation/GroupActivity.java | 93 ++++----------- .../conversation/GroupController.java | 19 --- .../conversation/GroupControllerImpl.java | 104 ----------------- .../conversation/GroupConversationModule.java | 23 +--- .../conversation/GroupMessageAdapter.java | 25 ---- .../conversation/GroupViewModel.java | 55 +++++++-- .../android/sharing/SharingController.java | 55 +++++++++ .../sharing/SharingControllerImpl.java | 109 ++++++++++++++++++ .../briar/android/sharing/SharingModule.java | 65 ++++++----- .../android/threaded/ThreadItemList.java | 15 --- .../android/threaded/ThreadItemListImpl.java | 23 ---- .../android/threaded/ThreadListActivity.java | 91 ++++----------- .../threaded/ThreadListController.java | 28 ----- .../threaded/ThreadListControllerImpl.java | 94 --------------- .../android/threaded/ThreadListViewModel.java | 24 +++- .../src/main/res/menu/group_actions.xml | 24 ++-- 27 files changed, 406 insertions(+), 703 deletions(-) delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/forum/ForumController.java delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingController.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingControllerImpl.java delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemList.java delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemListImpl.java delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 0163f415a..d2c4bac64 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -39,6 +39,7 @@ import org.briarproject.briar.android.privategroup.conversation.GroupConversatio import org.briarproject.briar.android.settings.SettingsModule; import org.briarproject.briar.android.privategroup.list.GroupListModule; import org.briarproject.briar.android.reporting.DevReportModule; +import org.briarproject.briar.android.sharing.SharingModule; import org.briarproject.briar.android.test.TestAvatarCreatorImpl; import org.briarproject.briar.android.viewmodel.ViewModelModule; import org.briarproject.briar.api.android.AndroidNotificationManager; @@ -80,9 +81,10 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; DevReportModule.class, ContactListModule.class, // below need to be within same scope as ViewModelProvider.Factory - ForumModule.BindsModule.class, + ForumModule.class, GroupListModule.class, - GroupConversationModule.BindsModule.class, + GroupConversationModule.class, + SharingModule.class, }) public class AppModule { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java index 3835a7aaa..3b1e91ef9 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java @@ -32,7 +32,6 @@ import org.briarproject.briar.android.conversation.ImageFragment; import org.briarproject.briar.android.forum.CreateForumActivity; import org.briarproject.briar.android.forum.ForumActivity; import org.briarproject.briar.android.forum.ForumListFragment; -import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment; import org.briarproject.briar.android.introduction.ContactChooserFragment; import org.briarproject.briar.android.introduction.IntroductionActivity; @@ -50,7 +49,6 @@ import org.briarproject.briar.android.navdrawer.TransportsActivity; import org.briarproject.briar.android.panic.PanicPreferencesActivity; import org.briarproject.briar.android.panic.PanicResponderActivity; import org.briarproject.briar.android.privategroup.conversation.GroupActivity; -import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule; import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity; import org.briarproject.briar.android.privategroup.creation.CreateGroupFragment; import org.briarproject.briar.android.privategroup.creation.CreateGroupModule; @@ -89,12 +87,10 @@ import dagger.Component; ActivityModule.class, BlogModule.class, CreateGroupModule.class, - ForumModule.class, GroupInvitationModule.class, - GroupConversationModule.class, GroupMemberModule.class, GroupRevealModule.class, - SharingModule.class + SharingModule.SharingLegacyModule.class }, dependencies = AndroidComponent.class) public interface ActivityComponent { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java index 8957706f7..baf49a711 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java @@ -160,7 +160,6 @@ public abstract class BriarActivity extends BaseActivity { * @param ownLayout true if the custom toolbar brings its own layout * @return the Toolbar object or null if content view did not contain one */ - @Nullable protected Toolbar setUpCustomToolbar(boolean ownLayout) { // Custom Toolbar Toolbar toolbar = findViewById(R.id.toolbar); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/SharingController.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/SharingController.java index 01f885578..3ebe7024f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/controller/SharingController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/SharingController.java @@ -7,6 +7,7 @@ import java.util.Collection; import androidx.annotation.UiThread; +@Deprecated @NotNullByDefault public interface SharingController { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/SharingControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/SharingControllerImpl.java index 858c94c53..b49eb6aa8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/controller/SharingControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/SharingControllerImpl.java @@ -18,6 +18,7 @@ import javax.inject.Inject; import androidx.annotation.UiThread; +@Deprecated @NotNullByDefault public class SharingControllerImpl implements SharingController, EventListener { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java index f3e7ecb87..6a204dc23 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java @@ -7,17 +7,14 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.forum.ForumController.ForumListener; import org.briarproject.briar.android.sharing.ForumSharingStatusActivity; import org.briarproject.briar.android.sharing.ShareForumActivity; 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 javax.annotation.Nullable; @@ -35,13 +32,10 @@ import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEX @MethodsNotNullByDefault @ParametersNotNullByDefault public class ForumActivity extends - ThreadListActivity> - implements ForumListener { + ThreadListActivity> { @Inject ViewModelProvider.Factory viewModelFactory; - @Inject - ForumController forumController; private ForumViewModel viewModel; @@ -52,11 +46,6 @@ public class ForumActivity extends .get(ForumViewModel.class); } - @Override - protected ThreadListController getController() { - return forumController; - } - @Override protected ThreadListViewModel getViewModel() { return viewModel; @@ -72,9 +61,15 @@ public class ForumActivity extends super.onCreate(state); Toolbar toolbar = setUpCustomToolbar(false); + // Open member list on Toolbar click + toolbar.setOnClickListener(v -> { + Intent i = new Intent(ForumActivity.this, + ForumSharingStatusActivity.class); + i.putExtra(GROUP_ID, groupId.getBytes()); + startActivity(i); + }); - Intent i = getIntent(); - String groupName = i.getStringExtra(GROUP_NAME); + String groupName = getIntent().getStringExtra(GROUP_NAME); if (groupName != null) { setTitle(groupName); } else { @@ -82,16 +77,6 @@ public class ForumActivity extends setTitle(forum.getName()) ); } - - // Open member list on Toolbar click - if (toolbar != null) { - toolbar.setOnClickListener(v -> { - Intent i1 = new Intent(ForumActivity.this, - ForumSharingStatusActivity.class); - i1.putExtra(GROUP_ID, groupId.getBytes()); - startActivity(i1); - }); - } } @Override @@ -115,8 +100,8 @@ public class ForumActivity extends // Inflate the menu items for use in the action bar MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.forum_actions, menu); - - return super.onCreateOptionsMenu(menu); + super.onCreateOptionsMenu(menu); + return true; } @Override @@ -158,11 +143,4 @@ public class ForumActivity extends builder.show(); } - @Override - public void onForumLeft(ContactId c) { - sharingController.remove(c); - setToolbarSubTitle(sharingController.getTotalCount(), - sharingController.getOnlineCount()); - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumController.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumController.java deleted file mode 100644 index 83ec874a3..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumController.java +++ /dev/null @@ -1,17 +0,0 @@ -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 androidx.annotation.UiThread; - -@NotNullByDefault -interface ForumController extends ThreadListController { - - interface ForumListener extends ThreadListListener { - @UiThread - void onForumLeft(ContactId c); - } - -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java deleted file mode 100644 index 4bfab3d74..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.briarproject.briar.android.forum; - -import org.briarproject.bramble.api.contact.Contact; -import org.briarproject.bramble.api.contact.ContactId; -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.event.Event; -import org.briarproject.bramble.api.event.EventBus; -import org.briarproject.bramble.api.identity.IdentityManager; -import org.briarproject.bramble.api.lifecycle.LifecycleManager; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.system.Clock; -import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; -import org.briarproject.briar.android.threaded.ThreadListControllerImpl; -import org.briarproject.briar.api.android.AndroidNotificationManager; -import org.briarproject.briar.api.forum.ForumInvitationResponse; -import org.briarproject.briar.api.forum.ForumManager; -import org.briarproject.briar.api.forum.ForumSharingManager; -import org.briarproject.briar.api.forum.event.ForumInvitationResponseReceivedEvent; -import org.briarproject.briar.api.sharing.event.ContactLeftShareableEvent; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.concurrent.Executor; -import java.util.logging.Logger; - -import javax.inject.Inject; - -import static java.util.logging.Level.WARNING; -import static org.briarproject.bramble.util.LogUtils.logException; - -@NotNullByDefault -class ForumControllerImpl extends ThreadListControllerImpl - implements ForumController { - - private static final Logger LOG = - Logger.getLogger(ForumControllerImpl.class.getName()); - - private final ForumManager forumManager; - private final ForumSharingManager forumSharingManager; - - @Inject - ForumControllerImpl(@DatabaseExecutor Executor dbExecutor, - LifecycleManager lifecycleManager, IdentityManager identityManager, - @CryptoExecutor Executor cryptoExecutor, - ForumManager forumManager, ForumSharingManager forumSharingManager, - EventBus eventBus, Clock clock, - AndroidNotificationManager notificationManager) { - super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor, - eventBus, clock, notificationManager); - this.forumManager = forumManager; - this.forumSharingManager = forumSharingManager; - } - - @Override - public void onActivityStart() { - super.onActivityStart(); - } - - @Override - public void eventOccurred(Event e) { - super.eventOccurred(e); - - ForumListener listener = (ForumListener) this.listener; - - if (e instanceof ForumInvitationResponseReceivedEvent) { - ForumInvitationResponseReceivedEvent f = - (ForumInvitationResponseReceivedEvent) e; - ForumInvitationResponse r = f.getMessageHeader(); - if (r.getShareableId().equals(getGroupId()) && r.wasAccepted()) { - LOG.info("Forum invitation was accepted"); - listener.onInvitationAccepted(f.getContactId()); - } - } else if (e instanceof ContactLeftShareableEvent) { - ContactLeftShareableEvent c = (ContactLeftShareableEvent) e; - if (c.getGroupId().equals(getGroupId())) { - LOG.info("Forum left by contact"); - listener.onForumLeft(c.getContactId()); - } - } - } - - @Override - public void loadSharingContacts( - ResultExceptionHandler, DbException> handler) { - runOnDbThread(() -> { - try { - Collection contacts = - forumSharingManager.getSharedWith(getGroupId()); - Collection contactIds = - new ArrayList<>(contacts.size()); - for (Contact c : contacts) contactIds.add(c.getId()); - handler.onResult(contactIds); - } catch (DbException e) { - logException(LOG, WARNING, e); - handler.onException(e); - } - }); - } - -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumModule.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumModule.java index e2efac2d9..2f2aba2cd 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumModule.java @@ -1,37 +1,23 @@ package org.briarproject.briar.android.forum; -import org.briarproject.briar.android.activity.ActivityScope; -import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.viewmodel.ViewModelKey; import androidx.lifecycle.ViewModel; import dagger.Binds; import dagger.Module; -import dagger.Provides; import dagger.multibindings.IntoMap; @Module -public class ForumModule { +public interface ForumModule { - @Module - public interface BindsModule { - @Binds - @IntoMap - @ViewModelKey(ForumListViewModel.class) - ViewModel bindForumListViewModel(ForumListViewModel forumListViewModel); + @Binds + @IntoMap + @ViewModelKey(ForumListViewModel.class) + ViewModel bindForumListViewModel(ForumListViewModel forumListViewModel); - @Binds - @IntoMap - @ViewModelKey(ForumViewModel.class) - ViewModel bindForumViewModel(ForumViewModel forumViewModel); - } - - @ActivityScope - @Provides - ForumController provideForumController(BaseActivity activity, - ForumControllerImpl forumController) { - activity.addLifecycleController(forumController); - return forumController; - } + @Binds + @IntoMap + @ViewModelKey(ForumViewModel.class) + ViewModel bindForumViewModel(ForumViewModel forumViewModel); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumPostItem.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumPostItem.java index 7366725de..4973a5d0b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumPostItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumPostItem.java @@ -6,7 +6,6 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.android.threaded.ThreadItem; import org.briarproject.briar.api.forum.ForumPostHeader; -import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; @NotThreadSafe @@ -17,9 +16,4 @@ class ForumPostItem extends ThreadItem { h.getAuthorInfo(), h.isRead()); } - ForumPostItem(MessageId messageId, @Nullable MessageId parentId, - String text, long timestamp, Author author, AuthorInfo authorInfo) { - super(messageId, parentId, text, timestamp, author, authorInfo, true); - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index 0c1f21aff..48dfb7640 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -3,6 +3,8 @@ package org.briarproject.briar.android.forum; import android.app.Application; import android.widget.Toast; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; @@ -19,18 +21,24 @@ 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.R; +import org.briarproject.briar.android.sharing.SharingController; 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.MessageTracker.GroupCount; import org.briarproject.briar.api.client.PostHeader; 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; import org.briarproject.briar.api.forum.ForumPostHeader; import org.briarproject.briar.api.forum.ForumSharingManager; +import org.briarproject.briar.api.forum.event.ForumInvitationResponseReceivedEvent; import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent; +import org.briarproject.briar.api.sharing.event.ContactLeftShareableEvent; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -66,6 +74,7 @@ class ForumViewModel extends ThreadListViewModel { AndroidExecutor androidExecutor, IdentityManager identityManager, AndroidNotificationManager notificationManager, + SharingController sharingController, @CryptoExecutor Executor cryptoExecutor, Clock clock, MessageTracker messageTracker, @@ -73,8 +82,8 @@ class ForumViewModel extends ThreadListViewModel { ForumManager forumManager, ForumSharingManager forumSharingManager) { super(application, dbExecutor, lifecycleManager, db, androidExecutor, - identityManager, notificationManager, cryptoExecutor, clock, - messageTracker, eventBus); + identityManager, notificationManager, sharingController, + cryptoExecutor, clock, messageTracker, eventBus); this.forumManager = forumManager; this.forumSharingManager = forumSharingManager; } @@ -88,6 +97,20 @@ class ForumViewModel extends ThreadListViewModel { ForumPostItem item = buildItem(f.getHeader(), f.getText()); addItem(item); } + } else if (e instanceof ForumInvitationResponseReceivedEvent) { + ForumInvitationResponseReceivedEvent f = + (ForumInvitationResponseReceivedEvent) e; + ForumInvitationResponse r = f.getMessageHeader(); + if (r.getShareableId().equals(groupId) && r.wasAccepted()) { + LOG.info("Forum invitation was accepted"); + sharingController.add(f.getContactId()); + } + } else if (e instanceof ContactLeftShareableEvent) { + ContactLeftShareableEvent c = (ContactLeftShareableEvent) e; + if (c.getGroupId().equals(groupId)) { + LOG.info("Forum left by contact"); + sharingController.remove(c.getContactId()); + } } else { super.eventOccurred(e); } @@ -123,15 +146,13 @@ class ForumViewModel extends ThreadListViewModel { @Override public void createAndStoreMessage(String text, - @Nullable ForumPostItem parentItem) { + @Nullable MessageId parentId) { runOnDbThread(() -> { try { LocalAuthor author = identityManager.getLocalAuthor(); GroupCount count = forumManager.getGroupCount(groupId); long timestamp = max(count.getLatestMsgTime() + 1, clock.currentTimeMillis()); - MessageId parentId = - parentItem != null ? parentItem.getId() : null; createMessage(text, timestamp, parentId, author); } catch (DbException e) { logException(LOG, WARNING, e); @@ -183,6 +204,21 @@ class ForumViewModel extends ThreadListViewModel { }); } + public void loadSharingContacts() { + runOnDbThread(() -> { + try { + Collection contacts = + forumSharingManager.getSharedWith(groupId); + Collection contactIds = + new ArrayList<>(contacts.size()); + for (Contact c : contacts) contactIds.add(c.getId()); + sharingController.addAll(contactIds); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + void deleteForum() { runOnDbThread(() -> { try { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java index 799a0c5b1..a5dac9f34 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java @@ -6,20 +6,15 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.privategroup.conversation.GroupController.GroupListener; import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity; import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity; 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.Visibility; import javax.annotation.Nullable; import javax.inject.Inject; @@ -37,19 +32,13 @@ import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_ @MethodsNotNullByDefault @ParametersNotNullByDefault public class GroupActivity extends - ThreadListActivity - implements GroupListener { + ThreadListActivity { @Inject ViewModelProvider.Factory viewModelFactory; - @Inject - GroupController controller; private GroupViewModel viewModel; - private MenuItem revealMenuItem, inviteMenuItem, leaveMenuItem, - dissolveMenuItem; - @Override public void injectActivity(ActivityComponent component) { component.inject(this); @@ -57,11 +46,6 @@ public class GroupActivity extends .get(GroupViewModel.class); } - @Override - protected ThreadListController getController() { - return controller; - } - @Override protected ThreadListViewModel getViewModel() { return viewModel; @@ -77,25 +61,21 @@ public class GroupActivity extends super.onCreate(state); Toolbar toolbar = setUpCustomToolbar(false); + // Open member list on Toolbar click + toolbar.setOnClickListener(v -> { + Intent i = new Intent(GroupActivity.this, + GroupMemberListActivity.class); + i.putExtra(GROUP_ID, groupId.getBytes()); + startActivity(i); + }); - Intent i = getIntent(); - String groupName = i.getStringExtra(GROUP_NAME); + String groupName = getIntent().getStringExtra(GROUP_NAME); if (groupName != null) setTitle(groupName); observeOnce(viewModel.getPrivateGroup(), this, privateGroup -> setTitle(privateGroup.getName()) ); observeOnce(viewModel.isCreator(), this, adapter::setIsCreator); - // Open member list on Toolbar click - if (toolbar != null) { - toolbar.setOnClickListener(v -> { - Intent i1 = new Intent(GroupActivity.this, - GroupMemberListActivity.class); - i1.putExtra(GROUP_ID, groupId.getBytes()); - startActivity(i1); - }); - } - // start with group disabled and enable when not dissolved setGroupEnabled(false); viewModel.isDissolved().observe(this, dissolved -> { @@ -116,49 +96,38 @@ public class GroupActivity extends MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.group_actions, menu); - revealMenuItem = menu.findItem(R.id.action_group_reveal); - inviteMenuItem = menu.findItem(R.id.action_group_invite); - leaveMenuItem = menu.findItem(R.id.action_group_leave); - dissolveMenuItem = menu.findItem(R.id.action_group_dissolve); - - // all role-dependent items are invisible until we know our role - revealMenuItem.setVisible(false); - inviteMenuItem.setVisible(false); - leaveMenuItem.setVisible(false); - dissolveMenuItem.setVisible(false); - // show items based on role (which will not change, so observe once) observeOnce(viewModel.isCreator(), this, isCreator -> { - revealMenuItem.setVisible(!isCreator); - inviteMenuItem.setVisible(isCreator); - leaveMenuItem.setVisible(!isCreator); - dissolveMenuItem.setVisible(isCreator); + menu.findItem(R.id.action_group_reveal).setVisible(!isCreator); + menu.findItem(R.id.action_group_invite).setVisible(isCreator); + menu.findItem(R.id.action_group_leave).setVisible(!isCreator); + menu.findItem(R.id.action_group_dissolve).setVisible(isCreator); }); - - return super.onCreateOptionsMenu(menu); + super.onCreateOptionsMenu(menu); + return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); if (itemId == R.id.action_group_member_list) { - Intent i1 = new Intent(this, GroupMemberListActivity.class); - i1.putExtra(GROUP_ID, groupId.getBytes()); - startActivity(i1); + Intent i = new Intent(this, GroupMemberListActivity.class); + i.putExtra(GROUP_ID, groupId.getBytes()); + startActivity(i); return true; } else if (itemId == R.id.action_group_reveal) { if (viewModel.isCreator().getValue()) throw new IllegalStateException(); - Intent i2 = new Intent(this, RevealContactsActivity.class); - i2.putExtra(GROUP_ID, groupId.getBytes()); - startActivity(i2); + Intent i = new Intent(this, RevealContactsActivity.class); + i.putExtra(GROUP_ID, groupId.getBytes()); + startActivity(i); return true; } else if (itemId == R.id.action_group_invite) { if (!viewModel.isCreator().getValue()) throw new IllegalStateException(); - Intent i3 = new Intent(this, GroupInviteActivity.class); - i3.putExtra(GROUP_ID, groupId.getBytes()); - startActivityForResult(i3, REQUEST_GROUP_INVITE); + Intent i = new Intent(this, GroupInviteActivity.class); + i.putExtra(GROUP_ID, groupId.getBytes()); + startActivityForResult(i, REQUEST_GROUP_INVITE); return true; } else if (itemId == R.id.action_group_leave) { if (viewModel.isCreator().getValue()) @@ -169,8 +138,7 @@ public class GroupActivity extends if (!viewModel.isCreator().getValue()) throw new IllegalStateException(); showDissolveGroupDialog(); - - return super.onOptionsItemSelected(item); + return true; } return super.onOptionsItemSelected(item); } @@ -233,18 +201,7 @@ public class GroupActivity extends viewModel.deletePrivateGroup(); } - @Override - public void onContactRelationshipRevealed(AuthorId memberId, ContactId c, - Visibility v) { - adapter.updateVisibility(memberId, v); - - sharingController.add(c); - setToolbarSubTitle(sharingController.getTotalCount(), - sharingController.getOnlineCount()); - } - public void onGroupDissolved() { - setGroupEnabled(false); AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.BriarDialogTheme); builder.setTitle(getString(R.string.groups_dissolved_dialog_title)); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java deleted file mode 100644 index 210360ade..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupController.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.briarproject.briar.android.privategroup.conversation; - -import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.identity.AuthorId; -import org.briarproject.briar.android.threaded.ThreadListController; -import org.briarproject.briar.api.privategroup.Visibility; - -import androidx.annotation.UiThread; - -public interface GroupController - extends ThreadListController { - - interface GroupListener extends ThreadListListener { - @UiThread - void onContactRelationshipRevealed(AuthorId memberId, - ContactId contactId, Visibility v); - } - -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java deleted file mode 100644 index 275ee3586..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.briarproject.briar.android.privategroup.conversation; - -import org.briarproject.bramble.api.contact.ContactId; -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.event.Event; -import org.briarproject.bramble.api.event.EventBus; -import org.briarproject.bramble.api.identity.IdentityManager; -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.system.Clock; -import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; -import org.briarproject.briar.android.threaded.ThreadListControllerImpl; -import org.briarproject.briar.api.android.AndroidNotificationManager; -import org.briarproject.briar.api.privategroup.GroupMember; -import org.briarproject.briar.api.privategroup.PrivateGroupManager; -import org.briarproject.briar.api.privategroup.event.ContactRelationshipRevealedEvent; -import org.briarproject.briar.api.privategroup.event.GroupInvitationResponseReceivedEvent; -import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.concurrent.Executor; -import java.util.logging.Logger; - -import javax.inject.Inject; - -import static java.util.logging.Level.WARNING; -import static java.util.logging.Logger.getLogger; -import static org.briarproject.bramble.util.LogUtils.logException; - -@MethodsNotNullByDefault -@ParametersNotNullByDefault -class GroupControllerImpl extends ThreadListControllerImpl - implements GroupController { - - private static final Logger LOG = - getLogger(GroupControllerImpl.class.getName()); - - private final PrivateGroupManager privateGroupManager; - - @Inject - GroupControllerImpl(@DatabaseExecutor Executor dbExecutor, - LifecycleManager lifecycleManager, IdentityManager identityManager, - @CryptoExecutor Executor cryptoExecutor, - PrivateGroupManager privateGroupManager, - EventBus eventBus, Clock clock, - AndroidNotificationManager notificationManager) { - super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor, - eventBus, clock, notificationManager); - this.privateGroupManager = privateGroupManager; - } - - @Override - public void onActivityStart() { - super.onActivityStart(); - } - - @Override - public void eventOccurred(Event e) { - super.eventOccurred(e); - - GroupListener listener = (GroupListener) this.listener; - - if (e instanceof ContactRelationshipRevealedEvent) { - ContactRelationshipRevealedEvent c = - (ContactRelationshipRevealedEvent) e; - if (getGroupId().equals(c.getGroupId())) { - listener.onContactRelationshipRevealed(c.getMemberId(), - c.getContactId(), c.getVisibility()); - } - } else if (e instanceof GroupInvitationResponseReceivedEvent) { - GroupInvitationResponseReceivedEvent g = - (GroupInvitationResponseReceivedEvent) e; - GroupInvitationResponse r = g.getMessageHeader(); - if (getGroupId().equals(r.getShareableId()) && r.wasAccepted()) { - listener.onInvitationAccepted(g.getContactId()); - } - } - } - - @Override - public void loadSharingContacts( - ResultExceptionHandler, DbException> handler) { - runOnDbThread(() -> { - try { - Collection members = - privateGroupManager.getMembers(getGroupId()); - Collection contactIds = new ArrayList<>(); - for (GroupMember m : members) { - if (m.getContactId() != null) - contactIds.add(m.getContactId()); - } - handler.onResult(contactIds); - } catch (DbException e) { - logException(LOG, WARNING, e); - handler.onException(e); - } - }); - } - -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupConversationModule.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupConversationModule.java index 6dd2c65ab..88e43cf59 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupConversationModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupConversationModule.java @@ -1,31 +1,18 @@ package org.briarproject.briar.android.privategroup.conversation; -import org.briarproject.briar.android.activity.ActivityScope; -import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.viewmodel.ViewModelKey; import androidx.lifecycle.ViewModel; import dagger.Binds; import dagger.Module; -import dagger.Provides; import dagger.multibindings.IntoMap; @Module -public class GroupConversationModule { +public interface GroupConversationModule { - @Module - public interface BindsModule { - @Binds - @IntoMap - @ViewModelKey(GroupViewModel.class) - ViewModel bindGroupViewModel(GroupViewModel groupViewModel); - } + @Binds + @IntoMap + @ViewModelKey(GroupViewModel.class) + ViewModel bindGroupViewModel(GroupViewModel groupViewModel); - @ActivityScope - @Provides - GroupController provideGroupController(BaseActivity activity, - GroupControllerImpl groupController) { - activity.addLifecycleController(groupController); - return groupController; - } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java index 4d88a22ea..4eedbd580 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java @@ -4,19 +4,15 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.threaded.BaseThreadItemViewHolder; import org.briarproject.briar.android.threaded.ThreadItemAdapter; import org.briarproject.briar.android.threaded.ThreadPostViewHolder; -import org.briarproject.briar.api.privategroup.Visibility; import androidx.annotation.LayoutRes; import androidx.annotation.UiThread; -import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; - @UiThread @NotNullByDefault class GroupMessageAdapter extends ThreadItemAdapter { @@ -50,25 +46,4 @@ class GroupMessageAdapter extends ThreadItemAdapter { notifyDataSetChanged(); } - void updateVisibility(AuthorId memberId, Visibility v) { - int position = findItemPosition(memberId); - if (position != NO_POSITION) { - GroupMessageItem item = getItem(position); - if (item instanceof JoinMessageItem) { - ((JoinMessageItem) item).setVisibility(v); - notifyItemChanged(findItemPosition(item.getId()), item); - } - } - } - - @Deprecated - private int findItemPosition(AuthorId a) { - for (int i = 0; i < getItemCount(); i++) { - GroupMessageItem item = getItem(i); - if (item.getAuthor().getId().equals(a)) - return i; - } - return NO_POSITION; // Not found - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index e79e862d7..bf7b574b6 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -2,6 +2,7 @@ package org.briarproject.briar.android.privategroup.conversation; import android.app.Application; +import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; @@ -19,20 +20,27 @@ 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.sharing.SharingController; 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.MessageTracker.GroupCount; import org.briarproject.briar.api.client.PostHeader; +import org.briarproject.briar.api.privategroup.GroupMember; 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; +import org.briarproject.briar.api.privategroup.event.GroupInvitationResponseReceivedEvent; import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent; +import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -74,14 +82,15 @@ class GroupViewModel extends ThreadListViewModel { EventBus eventBus, IdentityManager identityManager, AndroidNotificationManager notificationManager, + SharingController sharingController, @CryptoExecutor Executor cryptoExecutor, Clock clock, MessageTracker messageTracker, PrivateGroupManager privateGroupManager, GroupMessageFactory groupMessageFactory) { super(application, dbExecutor, lifecycleManager, db, androidExecutor, - identityManager, notificationManager, cryptoExecutor, clock, - messageTracker, eventBus); + identityManager, notificationManager, sharingController, + cryptoExecutor, clock, messageTracker, eventBus); this.privateGroupManager = privateGroupManager; this.groupMessageFactory = groupMessageFactory; } @@ -95,11 +104,24 @@ class GroupViewModel extends ThreadListViewModel { LOG.info("Group message received, adding..."); GroupMessageItem item = buildItem(g.getHeader(), g.getText()); addItem(item); - if (item instanceof JoinMessageItem) { - // TODO -// if (((JoinMessageItem) item).isInitial()) loadSharingContacts(); + if (item instanceof JoinMessageItem && + (((JoinMessageItem) item).isInitial())) { + loadSharingContacts(); } } + } else if (e instanceof GroupInvitationResponseReceivedEvent) { + GroupInvitationResponseReceivedEvent g = + (GroupInvitationResponseReceivedEvent) e; + GroupInvitationResponse r = g.getMessageHeader(); + if (r.getShareableId().equals(groupId) && r.wasAccepted()) { + sharingController.add(g.getContactId()); + } + } else if (e instanceof ContactRelationshipRevealedEvent) { + ContactRelationshipRevealedEvent c = + (ContactRelationshipRevealedEvent) e; + if (c.getGroupId().equals(groupId)) { + sharingController.add(c.getContactId()); + } } else if (e instanceof GroupDissolvedEvent) { GroupDissolvedEvent g = (GroupDissolvedEvent) e; if (g.getGroupId().equals(groupId)) { @@ -139,7 +161,7 @@ class GroupViewModel extends ThreadListViewModel { // check first if group is dissolved isDissolved .postValue(privateGroupManager.isDissolved(txn, groupId)); - // no continue to load the items + // now continue to load the items long start = now(); List headers = privateGroupManager.getHeaders(txn, groupId); @@ -167,16 +189,14 @@ class GroupViewModel extends ThreadListViewModel { @Override public void createAndStoreMessage(String text, - @Nullable GroupMessageItem parentItem) { + @Nullable MessageId parentId) { runOnDbThread(() -> { try { LocalAuthor author = identityManager.getLocalAuthor(); - MessageId parentId = null; MessageId previousMsgId = privateGroupManager.getPreviousMsgId(groupId); GroupCount count = privateGroupManager.getGroupCount(groupId); long timestamp = count.getLatestMsgTime(); - if (parentItem != null) parentId = parentItem.getId(); timestamp = max(clock.currentTimeMillis(), timestamp + 1); createMessage(text, timestamp, parentId, author, previousMsgId); } catch (DbException e) { @@ -221,6 +241,23 @@ class GroupViewModel extends ThreadListViewModel { }); } + public void loadSharingContacts() { + runOnDbThread(() -> { + try { + Collection members = + privateGroupManager.getMembers(groupId); + Collection contactIds = new ArrayList<>(); + for (GroupMember m : members) { + if (m.getContactId() != null) + contactIds.add(m.getContactId()); + } + sharingController.addAll(contactIds); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + void deletePrivateGroup() { runOnDbThread(() -> { try { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingController.java b/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingController.java new file mode 100644 index 000000000..9a175534e --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingController.java @@ -0,0 +1,55 @@ +package org.briarproject.briar.android.sharing; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.Collection; + +import androidx.annotation.UiThread; +import androidx.lifecycle.LiveData; + +@NotNullByDefault +public interface SharingController { + + /** + * Call this when the owning ViewModel gets cleared, + * so the {@link EventBus} can get unregistered. + */ + void onCleared(); + + /** + * Adds one contact to be tracked. + */ + @UiThread + void add(ContactId c); + + /** + * Adds a collection of contacts to be tracked. + */ + @DatabaseExecutor + void addAll(Collection contacts); + + /** + * Call this when the contact identified by c is no longer sharing + * the given group identified by GroupId g. + */ + @UiThread + void remove(ContactId c); + + /** + * Returns the total number of contacts that have been added. + */ + LiveData getSharingInfo(); + + class SharingInfo { + public final int total, online; + + SharingInfo(int total, int online) { + this.total = total; + this.online = online; + } + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingControllerImpl.java new file mode 100644 index 000000000..55a4149a8 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingControllerImpl.java @@ -0,0 +1,109 @@ +package org.briarproject.briar.android.sharing; + +import org.briarproject.bramble.api.connection.ConnectionRegistry; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent; +import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent; +import org.briarproject.bramble.api.system.AndroidExecutor; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.inject.Inject; + +import androidx.annotation.UiThread; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +@NotNullByDefault +public class SharingControllerImpl implements SharingController, EventListener { + + private final EventBus eventBus; + private final ConnectionRegistry connectionRegistry; + private final AndroidExecutor executor; + + // UI thread + private final Set contacts = new HashSet<>(); + private final MutableLiveData sharingInfo = + new MutableLiveData<>(); + + @Inject + SharingControllerImpl(EventBus eventBus, + ConnectionRegistry connectionRegistry, + AndroidExecutor executor) { + this.eventBus = eventBus; + this.connectionRegistry = connectionRegistry; + this.executor = executor; + eventBus.addListener(this); + } + + @Override + public void onCleared() { + eventBus.removeListener(this); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof ContactConnectedEvent) { + setConnected(((ContactConnectedEvent) e).getContactId()); + } else if (e instanceof ContactDisconnectedEvent) { + setConnected(((ContactDisconnectedEvent) e).getContactId()); + } + } + + @UiThread + private void setConnected(ContactId c) { + if (contacts.contains(c)) { + updateLiveData(); + } + } + + @UiThread + private void updateLiveData() { + int online = getOnlineCount(); + sharingInfo.setValue(new SharingInfo(contacts.size(), online)); + } + + private int getOnlineCount() { + int online = 0; + for (ContactId c : contacts) { + if (connectionRegistry.isConnected(c)) online++; + } + return online; + } + + @Override + @DatabaseExecutor + public void addAll(Collection c) { + executor.runOnUiThread(() -> { + contacts.addAll(c); + updateLiveData(); + }); + } + + @UiThread + @Override + public void add(ContactId c) { + contacts.add(c); + updateLiveData(); + } + + @UiThread + @Override + public void remove(ContactId c) { + contacts.remove(c); + updateLiveData(); + } + + @Override + public LiveData getSharingInfo() { + return sharingInfo; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingModule.java b/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingModule.java index 50bcceb2b..137aa28e4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingModule.java @@ -9,36 +9,47 @@ import dagger.Provides; @Module public class SharingModule { - @ActivityScope - @Provides - ShareForumController provideShareForumController( - ShareForumControllerImpl shareForumController) { - return shareForumController; + @Module + @Deprecated + public static class SharingLegacyModule { + + @ActivityScope + @Provides + ShareForumController provideShareForumController( + ShareForumControllerImpl shareForumController) { + return shareForumController; + } + + @ActivityScope + @Provides + BlogInvitationController provideInvitationBlogController( + BaseActivity activity, + BlogInvitationControllerImpl blogInvitationController) { + activity.addLifecycleController(blogInvitationController); + return blogInvitationController; + } + + @ActivityScope + @Provides + ForumInvitationController provideInvitationForumController( + BaseActivity activity, + ForumInvitationControllerImpl forumInvitationController) { + activity.addLifecycleController(forumInvitationController); + return forumInvitationController; + } + + @ActivityScope + @Provides + ShareBlogController provideShareBlogController( + ShareBlogControllerImpl shareBlogController) { + return shareBlogController; + } } - @ActivityScope @Provides - BlogInvitationController provideInvitationBlogController( - BaseActivity activity, - BlogInvitationControllerImpl blogInvitationController) { - activity.addLifecycleController(blogInvitationController); - return blogInvitationController; - } - - @ActivityScope - @Provides - ForumInvitationController provideInvitationForumController( - BaseActivity activity, - ForumInvitationControllerImpl forumInvitationController) { - activity.addLifecycleController(forumInvitationController); - return forumInvitationController; - } - - @ActivityScope - @Provides - ShareBlogController provideShareBlogController( - ShareBlogControllerImpl shareBlogController) { - return shareBlogController; + SharingController provideSharingController( + SharingControllerImpl sharingController) { + return sharingController; } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemList.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemList.java deleted file mode 100644 index f517e8e9c..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemList.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.briarproject.briar.android.threaded; - -import org.briarproject.bramble.api.sync.MessageId; - -import java.util.List; - -import javax.annotation.Nullable; - -public interface ThreadItemList extends List { - - @Nullable - MessageId getFirstVisibleItemId(); - - void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId); -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemListImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemListImpl.java deleted file mode 100644 index bfd63fa5b..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemListImpl.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.briarproject.briar.android.threaded; - -import org.briarproject.bramble.api.sync.MessageId; - -import java.util.ArrayList; - -import javax.annotation.Nullable; - -public class ThreadItemListImpl extends ArrayList - implements ThreadItemList { - - private MessageId bottomVisibleItemId; - - @Override - public MessageId getFirstVisibleItemId() { - return bottomVisibleItemId; - } - - @Override - public void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId) { - this.bottomVisibleItemId = bottomVisibleItemId; - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java index 516eec20c..de23e0f2a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java @@ -6,19 +6,14 @@ import android.view.MenuItem; import com.google.android.material.snackbar.Snackbar; -import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.db.DbException; 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.briar.R; import org.briarproject.briar.android.activity.BriarActivity; -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.sharing.SharingController.SharingInfo; import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener; -import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener; import org.briarproject.briar.android.util.BriarSnackbarBuilder; import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.TextInputView; @@ -27,11 +22,9 @@ import org.briarproject.briar.android.view.TextSendController.SendListener; import org.briarproject.briar.android.view.UnreadMessageButton; import org.briarproject.briar.api.attachment.AttachmentHeader; -import java.util.Collection; import java.util.List; import javax.annotation.Nullable; -import javax.inject.Inject; import androidx.annotation.CallSuper; import androidx.annotation.StringRes; @@ -44,26 +37,18 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @MethodsNotNullByDefault @ParametersNotNullByDefault public abstract class ThreadListActivity> - extends BriarActivity - implements ThreadListListener, SendListener, SharingListener, - ThreadItemListener { + extends BriarActivity implements SendListener, ThreadItemListener { protected final A adapter = createAdapter(); - private ThreadScrollListener scrollListener; + protected abstract ThreadListViewModel getViewModel(); + protected abstract A createAdapter(); protected BriarRecyclerView list; - private LinearLayoutManager layoutManager; protected TextInputView textInput; protected TextSendController sendController; protected GroupId groupId; - protected abstract ThreadListController getController(); - - protected abstract ThreadListViewModel getViewModel(); - - protected abstract A createAdapter(); - - @Inject - protected SharingController sharingController; + private LinearLayoutManager layoutManager; + private ThreadScrollListener scrollListener; @CallSuper @Override @@ -76,8 +61,8 @@ public abstract class ThreadListActivity viewModel = getViewModel(); + viewModel.setGroupId(groupId); textInput = findViewById(R.id.text_input_container); sendController = new TextSendController(textInput, this, false); @@ -92,7 +77,7 @@ public abstract class ThreadListActivity(adapter, getViewModel(), + scrollListener = new ThreadScrollListener<>(adapter, viewModel, upButton, downButton); list.getRecyclerView().addOnScrollListener(scrollListener); @@ -109,17 +94,16 @@ public abstract class ThreadListActivity result + viewModel.getItems().observe(this, result -> result .onError(this::handleException) .onSuccess(this::displayItems) ); - getViewModel().getGroupRemoved().observe(this, removed -> { + viewModel.getSharingInfo().observe(this, this::setToolbarSubTitle); + + viewModel.getGroupRemoved().observe(this, removed -> { if (removed) supportFinishAfterTransition(); }); - - sharingController.setSharingListener(this); - loadSharingContacts(); } @CallSuper @@ -127,7 +111,6 @@ public abstract class ThreadListActivity { + // do stuff *after* list had been updated + scrollAfterListCommitted(); + updateTextInput(); + }); } } @@ -197,24 +182,6 @@ public abstract class ThreadListActivity, DbException>( - this) { - @Override - public void onResultUi(Collection contacts) { - sharingController.addAll(contacts); - int online = sharingController.getOnlineCount(); - setToolbarSubTitle(contacts.size(), online); - } - - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }); - } - @Override public void onReplyClick(I item) { getViewModel().setReplyId(item.getId()); @@ -231,23 +198,11 @@ public abstract class ThreadListActivity headers) { if (isNullOrEmpty(text)) throw new AssertionError(); - I replyItem = adapter.getHighlightedItem(); - getViewModel().createAndStoreMessage(text, replyItem); + MessageId replyId = getViewModel().getReplyId(); + getViewModel().createAndStoreMessage(text, replyId); textInput.hideSoftKeyboard(); textInput.clearText(); getViewModel().setReplyId(null); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java deleted file mode 100644 index 211182d3c..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.briarproject.briar.android.threaded; - -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.briar.android.controller.ActivityLifecycleController; -import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; - -import java.util.Collection; - -import androidx.annotation.UiThread; - -@NotNullByDefault -public interface ThreadListController - extends ActivityLifecycleController { - - void setGroupId(GroupId groupId); - - void loadSharingContacts( - ResultExceptionHandler, DbException> handler); - - interface ThreadListListener { - @UiThread - void onInvitationAccepted(ContactId c); - } - -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java deleted file mode 100644 index b96eed677..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.briarproject.briar.android.threaded; - -import android.app.Activity; - -import org.briarproject.bramble.api.crypto.CryptoExecutor; -import org.briarproject.bramble.api.db.DatabaseExecutor; -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.event.EventBus; -import org.briarproject.bramble.api.event.EventListener; -import org.briarproject.bramble.api.identity.IdentityManager; -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.system.Clock; -import org.briarproject.briar.android.controller.DbControllerImpl; -import org.briarproject.briar.api.android.AndroidNotificationManager; - -import java.util.concurrent.Executor; - -import androidx.annotation.CallSuper; - -@MethodsNotNullByDefault -@ParametersNotNullByDefault -public abstract class ThreadListControllerImpl - extends DbControllerImpl - implements ThreadListController, EventListener { - - private volatile GroupId groupId; - - private final EventBus eventBus; - protected final IdentityManager identityManager; - protected final AndroidNotificationManager notificationManager; - protected final Executor cryptoExecutor; - protected final Clock clock; - - // UI thread - protected ThreadListListener listener; - - protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor, - LifecycleManager lifecycleManager, IdentityManager identityManager, - @CryptoExecutor Executor cryptoExecutor, EventBus eventBus, - Clock clock, AndroidNotificationManager notificationManager) { - super(dbExecutor, lifecycleManager); - this.identityManager = identityManager; - this.cryptoExecutor = cryptoExecutor; - this.notificationManager = notificationManager; - this.clock = clock; - this.eventBus = eventBus; - } - - @Override - public void setGroupId(GroupId groupId) { - this.groupId = groupId; - } - - @CallSuper - @SuppressWarnings("unchecked") - @Override - public void onActivityCreate(Activity activity) { - listener = (ThreadListListener) activity; - } - - @CallSuper - @Override - public void onActivityStart() { - eventBus.addListener(this); - } - - @CallSuper - @Override - public void onActivityStop() { - eventBus.removeListener(this); - } - - @Override - public void onActivityDestroy() { - - } - - @Override - public void eventOccurred(Event e) { - } - - protected GroupId getGroupId() { - checkGroupId(); - return groupId; - } - - private void checkGroupId() { - if (groupId == null) throw new IllegalStateException(); - } - -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java index ea7aac199..422f0a564 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -19,6 +19,8 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.event.GroupRemovedEvent; import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.briar.android.sharing.SharingController; +import org.briarproject.briar.android.sharing.SharingController.SharingInfo; import org.briarproject.briar.android.viewmodel.DbViewModel; import org.briarproject.briar.android.viewmodel.LiveResult; import org.briarproject.briar.api.android.AndroidNotificationManager; @@ -27,6 +29,7 @@ import org.briarproject.briar.api.client.MessageTree; import org.briarproject.briar.api.client.PostHeader; import org.briarproject.briar.client.MessageTreeImpl; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; @@ -58,6 +61,7 @@ public abstract class ThreadListViewModel protected final IdentityManager identityManager; protected final AndroidNotificationManager notificationManager; + protected final SharingController sharingController; protected final Executor cryptoExecutor; protected final Clock clock; private final MessageTracker messageTracker; @@ -85,6 +89,7 @@ public abstract class ThreadListViewModel AndroidExecutor androidExecutor, IdentityManager identityManager, AndroidNotificationManager notificationManager, + SharingController sharingController, @CryptoExecutor Executor cryptoExecutor, Clock clock, MessageTracker messageTracker, @@ -94,6 +99,7 @@ public abstract class ThreadListViewModel this.notificationManager = notificationManager; this.cryptoExecutor = cryptoExecutor; this.clock = clock; + this.sharingController = sharingController; this.messageTracker = messageTracker; this.eventBus = eventBus; this.eventBus.addListener(this); @@ -103,17 +109,19 @@ public abstract class ThreadListViewModel protected void onCleared() { super.onCleared(); eventBus.removeListener(this); + sharingController.onCleared(); } /** * Needs to be called right after initialization, - * before calling other methods. + * before calling any other methods. */ @CallSuper public void setGroupId(GroupId groupId) { this.groupId = groupId; loadStoredMessageId(); loadItems(); + loadSharingContacts(); } public void blockNotifications() { @@ -154,7 +162,13 @@ public abstract class ThreadListViewModel public abstract void loadItems(); public abstract void createAndStoreMessage(String text, - @Nullable I parentItem); + @Nullable MessageId parentMessageId); + + /** + * Loads the ContactIds of all contacts the group is shared with + * and adds them to {@link SharingController}. + */ + public abstract void loadSharingContacts(); @UiThread protected void setItems(LiveResult> items) { @@ -166,7 +180,7 @@ public abstract class ThreadListViewModel Transaction txn, Collection headers, ItemGetter itemGetter) throws DbException { long start = now(); - ThreadItemList items = new ThreadItemListImpl<>(); + List items = new ArrayList<>(); for (H header : headers) { String text = loadMessageText(txn, header); items.add(itemGetter.getItem(header, text)); @@ -235,6 +249,10 @@ public abstract class ThreadListViewModel return items; } + LiveData getSharingInfo() { + return sharingController.getSharingInfo(); + } + LiveData getGroupRemoved() { return groupRemoved; } diff --git a/briar-android/src/main/res/menu/group_actions.xml b/briar-android/src/main/res/menu/group_actions.xml index 000e6e38b..b5bed53b6 100644 --- a/briar-android/src/main/res/menu/group_actions.xml +++ b/briar-android/src/main/res/menu/group_actions.xml @@ -1,36 +1,44 @@ -

+ + android:visible="false" + app:showAsAction="ifRoom" + tools:visible="true" /> + app:showAsAction="never" /> + android:visible="false" + app:showAsAction="never" + tools:visible="true" /> + android:visible="false" + app:showAsAction="never" + tools:visible="true" /> + android:visible="false" + app:showAsAction="never" + tools:visible="true" /> \ No newline at end of file From e5d78a858d5d4bb33414cb993e1e325923ebc62e Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 26 Jan 2021 15:42:18 -0300 Subject: [PATCH 12/22] Clear thread notification automatically after blocking new ones --- .../org/briarproject/briar/android/forum/ForumActivity.java | 6 ------ .../briarproject/briar/android/forum/ForumViewModel.java | 2 +- .../android/privategroup/conversation/GroupActivity.java | 6 ------ .../android/privategroup/conversation/GroupViewModel.java | 2 +- .../briar/android/threaded/ThreadListActivity.java | 2 +- .../briar/android/threaded/ThreadListViewModel.java | 5 ++++- 6 files changed, 7 insertions(+), 16 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java index 6a204dc23..5cf01d818 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java @@ -79,12 +79,6 @@ public class ForumActivity extends } } - @Override - public void onStart() { - super.onStart(); - viewModel.clearForumPostNotification(); - } - @Override protected void onActivityResult(int request, int result, @Nullable Intent data) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index 48dfb7640..d811bf36a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -116,7 +116,7 @@ class ForumViewModel extends ThreadListViewModel { } } - void clearForumPostNotification() { + protected void clearNotifications() { notificationManager.clearForumPostNotification(groupId); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java index a5dac9f34..053358caa 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java @@ -84,12 +84,6 @@ public class GroupActivity extends }); } - @Override - public void onStart() { - super.onStart(); - viewModel.clearGroupMessageNotifications(); - } - @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu items for use in the action bar diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index bf7b574b6..ada485e47 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -138,7 +138,7 @@ class GroupViewModel extends ThreadListViewModel { loadPrivateGroup(groupId); } - public void clearGroupMessageNotifications() { + protected void clearNotifications() { notificationManager.clearGroupMessageNotification(groupId); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java index de23e0f2a..ac045316b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java @@ -110,7 +110,7 @@ public abstract class ThreadListActivity loadSharingContacts(); } - public void blockNotifications() { + protected abstract void clearNotifications(); + + public void blockAndClearNotifications() { notificationManager.blockNotification(groupId); + clearNotifications(); } public void unblockNotifications() { From 239c4a27adab6036411f12354e3b6acc590ddd53 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 26 Jan 2021 16:56:43 -0300 Subject: [PATCH 13/22] Address first round of review feedback for thread list view model migration --- .../briar/android/forum/ForumActivity.java | 16 ++++++------ .../conversation/GroupActivity.java | 14 ++++++----- .../conversation/GroupViewModel.java | 3 +++ .../conversation/JoinMessageItem.java | 2 +- .../android/threaded/ThreadItemAdapter.java | 13 ++++------ .../android/threaded/ThreadListViewModel.java | 25 ++++++++++--------- .../PrivateGroupManagerIntegrationTest.java | 13 ---------- 7 files changed, 38 insertions(+), 48 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java index 5cf01d818..abb30abc5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java @@ -103,16 +103,16 @@ public class ForumActivity extends // Handle presses on the action bar items int itemId = item.getItemId(); if (itemId == R.id.action_forum_share) { - Intent i2 = new Intent(this, ShareForumActivity.class); - i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP); - i2.putExtra(GROUP_ID, groupId.getBytes()); - startActivityForResult(i2, REQUEST_SHARE_FORUM); + Intent i = new Intent(this, ShareForumActivity.class); + i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); + i.putExtra(GROUP_ID, groupId.getBytes()); + startActivityForResult(i, REQUEST_SHARE_FORUM); return true; } else if (itemId == R.id.action_forum_sharing_status) { - Intent i3 = new Intent(this, ForumSharingStatusActivity.class); - i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP); - i3.putExtra(GROUP_ID, groupId.getBytes()); - startActivity(i3); + Intent i = new Intent(this, ForumSharingStatusActivity.class); + i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); + i.putExtra(GROUP_ID, groupId.getBytes()); + startActivity(i); return true; } else if (itemId == R.id.action_forum_delete) { showUnsubscribeDialog(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java index 053358caa..93b9cbd23 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java @@ -25,6 +25,7 @@ import androidx.lifecycle.ViewModelProvider; import static android.view.View.GONE; import static android.view.View.VISIBLE; +import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_GROUP_INVITE; import static org.briarproject.briar.android.util.UiUtils.observeOnce; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_TEXT_LENGTH; @@ -110,26 +111,26 @@ public class GroupActivity extends startActivity(i); return true; } else if (itemId == R.id.action_group_reveal) { - if (viewModel.isCreator().getValue()) + if (requireNonNull(viewModel.isCreator().getValue())) throw new IllegalStateException(); Intent i = new Intent(this, RevealContactsActivity.class); i.putExtra(GROUP_ID, groupId.getBytes()); startActivity(i); return true; } else if (itemId == R.id.action_group_invite) { - if (!viewModel.isCreator().getValue()) + if (!requireNonNull(viewModel.isCreator().getValue())) throw new IllegalStateException(); Intent i = new Intent(this, GroupInviteActivity.class); i.putExtra(GROUP_ID, groupId.getBytes()); startActivityForResult(i, REQUEST_GROUP_INVITE); return true; } else if (itemId == R.id.action_group_leave) { - if (viewModel.isCreator().getValue()) + if (requireNonNull(viewModel.isCreator().getValue())) throw new IllegalStateException(); showLeaveGroupDialog(); return true; } else if (itemId == R.id.action_group_dissolve) { - if (!viewModel.isCreator().getValue()) + if (!requireNonNull(viewModel.isCreator().getValue())) throw new IllegalStateException(); showDissolveGroupDialog(); return true; @@ -152,7 +153,8 @@ public class GroupActivity extends @Override public void onReplyClick(GroupMessageItem item) { - if (!viewModel.isDissolved().getValue()) super.onReplyClick(item); + Boolean isDissolved = viewModel.isDissolved().getValue(); + if (isDissolved != null && !isDissolved) super.onReplyClick(item); } private void setGroupEnabled(boolean enabled) { @@ -195,7 +197,7 @@ public class GroupActivity extends viewModel.deletePrivateGroup(); } - public void onGroupDissolved() { + private void onGroupDissolved() { AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.BriarDialogTheme); builder.setTitle(getString(R.string.groups_dissolved_dialog_title)); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index ada485e47..557eb2865 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -104,6 +104,9 @@ class GroupViewModel extends ThreadListViewModel { LOG.info("Group message received, adding..."); GroupMessageItem item = buildItem(g.getHeader(), g.getText()); addItem(item); + // In case the join message comes from the creator, + // we need to reload the sharing contacts + // in case it was delayed and the sharing count is wrong (#850). if (item instanceof JoinMessageItem && (((JoinMessageItem) item).isInitial())) { loadSharingContacts(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/JoinMessageItem.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/JoinMessageItem.java index bb92d8310..234fcccfb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/JoinMessageItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/JoinMessageItem.java @@ -16,7 +16,7 @@ class JoinMessageItem extends GroupMessageItem { JoinMessageItem(JoinMessageHeader h, String text) { super(h, text); - this.isInitial = h.isInitial(); + isInitial = h.isInitial(); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java index a66969592..7e5bf01ae 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java @@ -4,8 +4,7 @@ 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.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.R; import org.briarproject.briar.android.util.ItemReturningAdapter; @@ -21,8 +20,7 @@ import androidx.recyclerview.widget.ListAdapter; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; @UiThread -@MethodsNotNullByDefault -@ParametersNotNullByDefault +@NotNullByDefault public class ThreadItemAdapter extends ListAdapter> implements ItemReturningAdapter { @@ -41,7 +39,7 @@ public class ThreadItemAdapter @Override public boolean areContentsTheSame(I a, I b) { return a.isHighlighted() == b.isHighlighted() && - a.isRead() && b.isRead(); + a.isRead() == b.isRead(); } }); this.listener = listener; @@ -63,7 +61,7 @@ public class ThreadItemAdapter ui.bind(item, listener); } - public int findItemPosition(MessageId id) { + int findItemPosition(MessageId id) { for (int i = 0; i < getItemCount(); i++) { if (id.equals(getItem(i).getId())) return i; } @@ -91,8 +89,7 @@ public class ThreadItemAdapter @Nullable I getHighlightedItem() { - for (int i = 0; i < getItemCount(); i++) { - I item = getItem(i); + for (I item : getCurrentList()) { if (item.isHighlighted()) return item; } return null; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java index 946629760..a655dc605 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -67,7 +67,6 @@ public abstract class ThreadListViewModel private final MessageTracker messageTracker; private final EventBus eventBus; - @DatabaseExecutor private final MessageTree messageTree = new MessageTreeImpl<>(); private final MutableLiveData>> items = new MutableLiveData<>(); @@ -126,12 +125,12 @@ public abstract class ThreadListViewModel protected abstract void clearNotifications(); - public void blockAndClearNotifications() { + void blockAndClearNotifications() { notificationManager.blockNotification(groupId); clearNotifications(); } - public void unblockNotifications() { + void unblockNotifications() { notificationManager.unblockNotification(groupId); } @@ -221,24 +220,26 @@ public abstract class ThreadListViewModel PostHeader header) throws DbException; @UiThread - public void setReplyId(@Nullable MessageId id) { + void setReplyId(@Nullable MessageId id) { replyId = id; } @UiThread @Nullable - public MessageId getReplyId() { + MessageId getReplyId() { return replyId; } void storeMessageId(@Nullable MessageId messageId) { - if (messageId != null) runOnDbThread(() -> { - try { - messageTracker.storeMessageId(groupId, messageId); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); + if (messageId != null) { + runOnDbThread(() -> { + try { + messageTracker.storeMessageId(groupId, messageId); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } } protected abstract void markItemRead(I item); diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/PrivateGroupManagerIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/PrivateGroupManagerIntegrationTest.java index 9dae9d6b5..8dd6e855b 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/PrivateGroupManagerIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/PrivateGroupManagerIntegrationTest.java @@ -356,19 +356,6 @@ public class PrivateGroupManagerIntegrationTest } } - @Test - public void testJoinMessages() throws Exception { - addGroup(); - - Collection headers0 = - groupManager0.getHeaders(groupId0); - assertEquals(2, headers0.size()); - - Collection headers1 = - groupManager1.getHeaders(groupId0); - assertEquals(2, headers1.size()); - } - @Test public void testRevealingRelationships() throws Exception { addGroup(); From c62a57e8b28da67d9ab431fa7fa03f0d9ef91b0a Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 27 Jan 2021 15:11:46 +0000 Subject: [PATCH 14/22] Add transactional helper method to DbViewModel. --- .../briar/android/viewmodel/DbViewModel.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java index b6966c95a..e237b3962 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java @@ -5,6 +5,7 @@ import android.app.Application; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbCallable; import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.DbRunnable; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; @@ -23,6 +24,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.arch.core.util.Function; +import androidx.core.util.Consumer; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.recyclerview.widget.RecyclerView; @@ -57,8 +59,8 @@ public abstract class DbViewModel extends AndroidViewModel { } /** - * Runs the given task on the {@link DatabaseExecutor} - * and waits for the DB to open. + * Waits for the DB to open and runs the given task on the + * {@link DatabaseExecutor}. *

* If you need a list of items to be displayed in a * {@link RecyclerView.Adapter}, @@ -76,6 +78,29 @@ public abstract class DbViewModel extends AndroidViewModel { }); } + /** + * Waits for the DB to open and runs the given task on the + * {@link DatabaseExecutor}. + *

+ * If you need a list of items to be displayed in a + * {@link RecyclerView.Adapter}, + * use {@link #loadList(DbCallable, UiConsumer)} instead. + */ + protected void runOnDbThread(boolean readOnly, + DbRunnable task, Consumer err) { + dbExecutor.execute(() -> { + try { + lifecycleManager.waitForDatabase(); + db.transaction(readOnly, task); + } catch (InterruptedException e) { + LOG.warning("Interrupted while waiting for database"); + Thread.currentThread().interrupt(); + } catch (Exception e) { + err.accept(e); + } + }); + } + /** * Loads a list of items on the {@link DatabaseExecutor} within a single * {@link Transaction} and publishes it as a {@link LiveResult} From f2eca0fdb6ac0e36c707df042aa3ec0910709130 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 27 Jan 2021 15:12:29 +0000 Subject: [PATCH 15/22] Add transactional getMembers() method to PrivateGroupManager. --- .../api/privategroup/PrivateGroupManager.java | 6 ++ .../privategroup/PrivateGroupManagerImpl.java | 55 +++++++++---------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java index df10ccdf5..df8e0b1e7 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java @@ -129,6 +129,12 @@ public interface PrivateGroupManager { */ Collection getMembers(GroupId g) throws DbException; + /** + * Returns all members of the given private group. + */ + Collection getMembers(Transaction txn, GroupId g) + throws DbException; + /** * Returns true if the given author is a member of the given private group. */ diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java index ad776aa58..f1e7dbe85 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java @@ -408,35 +408,34 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook @Override public Collection getMembers(GroupId g) throws DbException { - Transaction txn = db.startTransaction(true); - try { - Collection members = new ArrayList<>(); - Map authors = getMembers(txn, g); - LocalAuthor la = identityManager.getLocalAuthor(txn); - PrivateGroup privateGroup = getPrivateGroup(txn, g); - for (Entry m : authors.entrySet()) { - Author a = m.getKey(); - AuthorInfo authorInfo = - authorManager.getAuthorInfo(txn, a.getId()); - Status status = authorInfo.getStatus(); - Visibility v = m.getValue(); - ContactId c = null; - if (v != INVISIBLE && - (status == VERIFIED || status == UNVERIFIED)) { - c = contactManager.getContact(txn, a.getId(), la.getId()) - .getId(); - } - boolean isCreator = privateGroup.getCreator().equals(a); - members.add(new GroupMember(a, authorInfo, isCreator, c, v)); - } - db.commitTransaction(txn); - return members; - } finally { - db.endTransaction(txn); - } + return db.transactionWithResult(true, txn -> getMembers(txn, g)); } - private Map getMembers(Transaction txn, GroupId g) + @Override + public Collection getMembers(Transaction txn, GroupId g) + throws DbException { + Collection members = new ArrayList<>(); + Map authors = getMemberAuthors(txn, g); + LocalAuthor la = identityManager.getLocalAuthor(txn); + PrivateGroup privateGroup = getPrivateGroup(txn, g); + for (Entry m : authors.entrySet()) { + Author a = m.getKey(); + AuthorInfo authorInfo = authorManager.getAuthorInfo(txn, a.getId()); + Status status = authorInfo.getStatus(); + Visibility v = m.getValue(); + ContactId c = null; + if (v != INVISIBLE && + (status == VERIFIED || status == UNVERIFIED)) { + c = contactManager.getContact(txn, a.getId(), la.getId()) + .getId(); + } + boolean isCreator = privateGroup.getCreator().equals(a); + members.add(new GroupMember(a, authorInfo, isCreator, c, v)); + } + return members; + } + + private Map getMemberAuthors(Transaction txn, GroupId g) throws DbException { try { BdfDictionary meta = @@ -458,7 +457,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook @Override public boolean isMember(Transaction txn, GroupId g, Author a) throws DbException { - for (Author member : getMembers(txn, g).keySet()) { + for (Author member : getMemberAuthors(txn, g).keySet()) { if (member.equals(a)) return true; } return false; From 98619df867d0d24b76ec234bc38093a1338148c6 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 27 Jan 2021 15:13:20 +0000 Subject: [PATCH 16/22] Use commit action to add contacts to SharingController. --- .../conversation/GroupViewModel.java | 22 ++++++++----------- .../android/sharing/SharingController.java | 3 +-- .../sharing/SharingControllerImpl.java | 15 ++++--------- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index 557eb2865..b64dad46a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -245,20 +245,16 @@ class GroupViewModel extends ThreadListViewModel { } public void loadSharingContacts() { - runOnDbThread(() -> { - try { - Collection members = - privateGroupManager.getMembers(groupId); - Collection contactIds = new ArrayList<>(); - for (GroupMember m : members) { - if (m.getContactId() != null) - contactIds.add(m.getContactId()); - } - sharingController.addAll(contactIds); - } catch (DbException e) { - logException(LOG, WARNING, e); + runOnDbThread(true, txn -> { + Collection members = + privateGroupManager.getMembers(txn, groupId); + Collection contactIds = new ArrayList<>(); + for (GroupMember m : members) { + if (m.getContactId() != null) + contactIds.add(m.getContactId()); } - }); + txn.attach(() -> sharingController.addAll(contactIds)); + }, e -> logException(LOG, WARNING, e)); } void deletePrivateGroup() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingController.java b/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingController.java index 9a175534e..3fd3f4e96 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingController.java @@ -1,7 +1,6 @@ package org.briarproject.briar.android.sharing; import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @@ -28,7 +27,7 @@ public interface SharingController { /** * Adds a collection of contacts to be tracked. */ - @DatabaseExecutor + @UiThread void addAll(Collection contacts); /** diff --git a/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingControllerImpl.java index 55a4149a8..b58ab1a08 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingControllerImpl.java @@ -2,14 +2,12 @@ package org.briarproject.briar.android.sharing; import org.briarproject.bramble.api.connection.ConnectionRegistry; import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent; import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent; -import org.briarproject.bramble.api.system.AndroidExecutor; import java.util.Collection; import java.util.HashSet; @@ -26,7 +24,6 @@ public class SharingControllerImpl implements SharingController, EventListener { private final EventBus eventBus; private final ConnectionRegistry connectionRegistry; - private final AndroidExecutor executor; // UI thread private final Set contacts = new HashSet<>(); @@ -35,11 +32,9 @@ public class SharingControllerImpl implements SharingController, EventListener { @Inject SharingControllerImpl(EventBus eventBus, - ConnectionRegistry connectionRegistry, - AndroidExecutor executor) { + ConnectionRegistry connectionRegistry) { this.eventBus = eventBus; this.connectionRegistry = connectionRegistry; - this.executor = executor; eventBus.addListener(this); } @@ -78,13 +73,11 @@ public class SharingControllerImpl implements SharingController, EventListener { return online; } + @UiThread @Override - @DatabaseExecutor public void addAll(Collection c) { - executor.runOnUiThread(() -> { - contacts.addAll(c); - updateLiveData(); - }); + contacts.addAll(c); + updateLiveData(); } @UiThread From d69406dfe3705fa6733c80d11b5c28214ae90c6c Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 27 Jan 2021 15:33:57 +0000 Subject: [PATCH 17/22] Add transactional getSharedWith() method to SharingManager. --- .../briar/api/sharing/SharingManager.java | 7 +++++++ .../briar/sharing/SharingManagerImpl.java | 18 +++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingManager.java b/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingManager.java index 3e6adc2dc..2170744ed 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingManager.java @@ -3,6 +3,7 @@ package org.briarproject.briar.api.sharing; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.briar.api.client.SessionId; @@ -45,6 +46,12 @@ public interface SharingManager */ Collection getSharedWith(GroupId g) throws DbException; + /** + * Returns all contacts with whom the given group is shared. + */ + Collection getSharedWith(Transaction txn, GroupId g) + throws DbException; + /** * Returns true if the group not already shared and no invitation is open */ diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java index 2b3abbd64..4a24ece90 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java @@ -426,17 +426,17 @@ abstract class SharingManagerImpl @Override public Collection getSharedWith(GroupId g) throws DbException { + return db.transactionWithResult(true, txn -> getSharedWith(txn, g)); + } + + @Override + public Collection getSharedWith(Transaction txn, GroupId g) + throws DbException { // TODO report also pending invitations Collection contacts = new ArrayList<>(); - Transaction txn = db.startTransaction(true); - try { - for (Contact c : db.getContacts(txn)) { - if (db.getGroupVisibility(txn, c.getId(), g) == SHARED) - contacts.add(c); - } - db.commitTransaction(txn); - } finally { - db.endTransaction(txn); + for (Contact c : db.getContacts(txn)) { + if (db.getGroupVisibility(txn, c.getId(), g) == SHARED) + contacts.add(c); } return contacts; } From 70532732c895388dd0eb43ec31466a8decad9b51 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 27 Jan 2021 15:34:42 +0000 Subject: [PATCH 18/22] Use commit action to add contacts on UI thread. --- .../briar/android/forum/ForumViewModel.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index d811bf36a..5f5c51f1b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -205,18 +205,13 @@ class ForumViewModel extends ThreadListViewModel { } public void loadSharingContacts() { - runOnDbThread(() -> { - try { - Collection contacts = - forumSharingManager.getSharedWith(groupId); - Collection contactIds = - new ArrayList<>(contacts.size()); - for (Contact c : contacts) contactIds.add(c.getId()); - sharingController.addAll(contactIds); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); + runOnDbThread(true, txn -> { + Collection contacts = + forumSharingManager.getSharedWith(txn, groupId); + Collection contactIds = new ArrayList<>(contacts.size()); + for (Contact c : contacts) contactIds.add(c.getId()); + txn.attach(() -> sharingController.addAll(contactIds)); + }, e -> logException(LOG, WARNING, e)); } void deleteForum() { From 4a0327a62bd7175ef1b6babe5072a0c812c9f79b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 27 Jan 2021 14:30:13 -0300 Subject: [PATCH 19/22] thread list: fix redundant load and dissolved dialog showing again after screen rotation --- .../privategroup/conversation/GroupActivity.java | 4 ++-- .../conversation/GroupViewModel.java | 16 +++++++++------- .../android/threaded/ThreadListViewModel.java | 9 +++++++-- .../briar/android/viewmodel/LiveEvent.java | 11 +++++++++++ 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java index 93b9cbd23..47e4cb476 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java @@ -79,7 +79,7 @@ public class GroupActivity extends // start with group disabled and enable when not dissolved setGroupEnabled(false); - viewModel.isDissolved().observe(this, dissolved -> { + viewModel.isDissolved().observeEvent(this, dissolved -> { setGroupEnabled(!dissolved); if (dissolved) onGroupDissolved(); }); @@ -153,7 +153,7 @@ public class GroupActivity extends @Override public void onReplyClick(GroupMessageItem item) { - Boolean isDissolved = viewModel.isDissolved().getValue(); + Boolean isDissolved = viewModel.isDissolved().getLastValue(); if (isDissolved != null && !isDissolved) super.onReplyClick(item); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index b64dad46a..9020efe1b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -22,6 +22,8 @@ import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.android.sharing.SharingController; import org.briarproject.briar.android.threaded.ThreadListViewModel; +import org.briarproject.briar.android.viewmodel.LiveEvent; +import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker.GroupCount; @@ -70,8 +72,8 @@ class GroupViewModel extends ThreadListViewModel { private final MutableLiveData privateGroup = new MutableLiveData<>(); private final MutableLiveData isCreator = new MutableLiveData<>(); - private final MutableLiveData isDissolved = - new MutableLiveData<>(); + private final MutableLiveEvent isDissolved = + new MutableLiveEvent<>(); @Inject GroupViewModel(Application application, @@ -128,7 +130,7 @@ class GroupViewModel extends ThreadListViewModel { } else if (e instanceof GroupDissolvedEvent) { GroupDissolvedEvent g = (GroupDissolvedEvent) e; if (g.getGroupId().equals(groupId)) { - isDissolved.setValue(true); + isDissolved.setEvent(true); } } else { super.eventOccurred(e); @@ -136,8 +138,8 @@ class GroupViewModel extends ThreadListViewModel { } @Override - public void setGroupId(GroupId groupId) { - super.setGroupId(groupId); + protected void performInitialLoad() { + super.performInitialLoad(); loadPrivateGroup(groupId); } @@ -163,7 +165,7 @@ class GroupViewModel extends ThreadListViewModel { loadList(txn -> { // check first if group is dissolved isDissolved - .postValue(privateGroupManager.isDissolved(txn, groupId)); + .postEvent(privateGroupManager.isDissolved(txn, groupId)); // now continue to load the items long start = now(); List headers = @@ -275,7 +277,7 @@ class GroupViewModel extends ThreadListViewModel { return isCreator; } - LiveData isDissolved() { + LiveEvent isDissolved() { return isDissolved; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java index a655dc605..c28847107 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -115,9 +115,14 @@ public abstract class ThreadListViewModel * Needs to be called right after initialization, * before calling any other methods. */ - @CallSuper - public void setGroupId(GroupId groupId) { + public final void setGroupId(GroupId groupId) { + boolean needsInitialLoad = this.groupId == null; this.groupId = groupId; + if (needsInitialLoad) performInitialLoad(); + } + + @CallSuper + protected void performInitialLoad() { loadStoredMessageId(); loadItems(); loadSharingContacts(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/LiveEvent.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/LiveEvent.java index 15eb83e8e..e4e8e8308 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/LiveEvent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/LiveEvent.java @@ -39,6 +39,17 @@ public class LiveEvent extends LiveData> { super.observeForever(observer); } + /** + * Returns the last value of the event (even if already consumed) + * or null if there hasn't been any value so far. + */ + @Nullable + public T getLastValue() { + ConsumableEvent event = getValue(); + if (event == null) return null; + return event.content; + } + static class ConsumableEvent { private final T content; From 998c435b13ba898a1b4e6899c1ff96506c7a3b99 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 27 Jan 2021 15:06:55 -0300 Subject: [PATCH 20/22] Allow to add forum/group posts in transaction --- .../briar/api/forum/ForumManager.java | 6 +++ .../api/privategroup/PrivateGroupManager.java | 6 +++ .../briar/forum/ForumManagerImpl.java | 43 +++++++-------- .../privategroup/PrivateGroupManagerImpl.java | 53 ++++++++++--------- 4 files changed, 61 insertions(+), 47 deletions(-) diff --git a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java index 1c98c62ac..cb1e0878b 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java @@ -60,6 +60,12 @@ public interface ForumManager { */ ForumPostHeader addLocalPost(ForumPost p) throws DbException; + /** + * Stores a local forum post. + */ + ForumPostHeader addLocalPost(Transaction txn, ForumPost p) + throws DbException; + /** * Returns the forum with the given ID. */ diff --git a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java index df8e0b1e7..a6873b52a 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java @@ -82,6 +82,12 @@ public interface PrivateGroupManager { */ GroupMessageHeader addLocalMessage(GroupMessage p) throws DbException; + /** + * Stores and sends a local private group message. + */ + GroupMessageHeader addLocalMessage(Transaction txn, GroupMessage p) + throws DbException; + /** * Returns the private group with the given ID. */ diff --git a/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java index 673d1f3a3..6dacf6c4d 100644 --- a/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java @@ -126,29 +126,30 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { @Override public ForumPostHeader addLocalPost(ForumPost p) throws DbException { - return db.transactionWithResult(false, txn -> { - try { - return addLocalPost(txn, p); - } catch (FormatException e) { - throw new AssertionError(e); - } - }); + return db.transactionWithResult(false, txn -> addLocalPost(txn, p)); } - private ForumPostHeader addLocalPost(Transaction txn, ForumPost p) - throws DbException, FormatException { - BdfDictionary meta = new BdfDictionary(); - meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp()); - if (p.getParent() != null) meta.put(KEY_PARENT, p.getParent()); - Author a = p.getAuthor(); - meta.put(KEY_AUTHOR, clientHelper.toList(a)); - meta.put(KEY_LOCAL, true); - meta.put(MSG_KEY_READ, true); - clientHelper.addLocalMessage(txn, p.getMessage(), meta, true, false); - messageTracker.trackOutgoingMessage(txn, p.getMessage()); - AuthorInfo authorInfo = authorManager.getMyAuthorInfo(txn); - return new ForumPostHeader(p.getMessage().getId(), p.getParent(), - p.getMessage().getTimestamp(), p.getAuthor(), authorInfo, true); + @Override + public ForumPostHeader addLocalPost(Transaction txn, ForumPost p) + throws DbException { + try { + BdfDictionary meta = new BdfDictionary(); + meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp()); + if (p.getParent() != null) meta.put(KEY_PARENT, p.getParent()); + Author a = p.getAuthor(); + meta.put(KEY_AUTHOR, clientHelper.toList(a)); + meta.put(KEY_LOCAL, true); + meta.put(MSG_KEY_READ, true); + clientHelper + .addLocalMessage(txn, p.getMessage(), meta, true, false); + messageTracker.trackOutgoingMessage(txn, p.getMessage()); + AuthorInfo authorInfo = authorManager.getMyAuthorInfo(txn); + return new ForumPostHeader(p.getMessage().getId(), p.getParent(), + p.getMessage().getTimestamp(), p.getAuthor(), authorInfo, + true); + } catch (FormatException e) { + throw new AssertionError(e); + } } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java index f1e7dbe85..4f0a101f5 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java @@ -210,34 +210,35 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook @Override public GroupMessageHeader addLocalMessage(GroupMessage m) throws DbException { - return db.transactionWithResult(false, txn -> { - try { - return addLocalMessage(txn, m); - } catch (FormatException e) { - throw new DbException(e); - } - }); + return db.transactionWithResult(false, txn -> addLocalMessage(txn, m)); } - private GroupMessageHeader addLocalMessage(Transaction txn, GroupMessage m) - throws DbException, FormatException { - // store message and metadata - BdfDictionary meta = new BdfDictionary(); - meta.put(KEY_TYPE, POST.getInt()); - if (m.getParent() != null) - meta.put(KEY_PARENT_MSG_ID, m.getParent()); - addMessageMetadata(meta, m); - GroupId g = m.getMessage().getGroupId(); - clientHelper.addLocalMessage(txn, m.getMessage(), meta, true, false); - // track message - setPreviousMsgId(txn, g, m.getMessage().getId()); - messageTracker.trackOutgoingMessage(txn, m.getMessage()); - // broadcast event - attachGroupMessageAddedEvent(txn, m.getMessage(), meta, true); - AuthorInfo authorInfo = authorManager.getMyAuthorInfo(txn); - return new GroupMessageHeader(m.getMessage().getGroupId(), - m.getMessage().getId(), m.getParent(), - m.getMessage().getTimestamp(), m.getMember(), authorInfo, true); + @Override + public GroupMessageHeader addLocalMessage(Transaction txn, GroupMessage m) + throws DbException { + try { + // store message and metadata + BdfDictionary meta = new BdfDictionary(); + meta.put(KEY_TYPE, POST.getInt()); + if (m.getParent() != null) + meta.put(KEY_PARENT_MSG_ID, m.getParent()); + addMessageMetadata(meta, m); + GroupId g = m.getMessage().getGroupId(); + clientHelper + .addLocalMessage(txn, m.getMessage(), meta, true, false); + // track message + setPreviousMsgId(txn, g, m.getMessage().getId()); + messageTracker.trackOutgoingMessage(txn, m.getMessage()); + // broadcast event + attachGroupMessageAddedEvent(txn, m.getMessage(), meta, true); + AuthorInfo authorInfo = authorManager.getMyAuthorInfo(txn); + return new GroupMessageHeader(m.getMessage().getGroupId(), + m.getMessage().getId(), m.getParent(), + m.getMessage().getTimestamp(), m.getMember(), authorInfo, + true); + } catch (FormatException e) { + throw new DbException(e); + } } private void addMessageMetadata(BdfDictionary meta, GroupMessage m) { From d670179e30ce3032a641e0957eb3906255a11645 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 27 Jan 2021 15:18:09 -0300 Subject: [PATCH 21/22] Access MessageTree only on UiThread and improve code in the process --- .../briar/android/forum/ForumViewModel.java | 49 ++++++++------- .../conversation/GroupViewModel.java | 53 ++++++++-------- .../android/threaded/ThreadListViewModel.java | 60 +++++-------------- 3 files changed, 70 insertions(+), 92 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index 5f5c51f1b..87aca3fb1 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -94,8 +94,9 @@ class ForumViewModel extends ThreadListViewModel { ForumPostReceivedEvent f = (ForumPostReceivedEvent) e; if (f.getGroupId().equals(groupId)) { LOG.info("Forum post received, adding..."); - ForumPostItem item = buildItem(f.getHeader(), f.getText()); - addItem(item); + ForumPostItem item = + new ForumPostItem(f.getHeader(), f.getText()); + addItem(item, false); } } else if (e instanceof ForumInvitationResponseReceivedEvent) { ForumInvitationResponseReceivedEvent f = @@ -140,10 +141,23 @@ class ForumViewModel extends ThreadListViewModel { List headers = forumManager.getPostHeaders(txn, groupId); logDuration(LOG, "Loading headers", start); - return createItems(txn, headers, this::buildItem); + start = now(); + List items = new ArrayList<>(); + for (ForumPostHeader header : headers) { + items.add(loadItem(txn, header)); + } + logDuration(LOG, "Loading bodies and creating items", start); + return items; }, this::setItems); } + private ForumPostItem loadItem(Transaction txn, PostHeader header) + throws DbException { + if (!(header instanceof ForumPostHeader)) throw new AssertionError(); + String text = forumManager.getPostText(txn, header.getId()); + return new ForumPostItem((ForumPostHeader) header, text); + } + @Override public void createAndStoreMessage(String text, @Nullable MessageId parentId) { @@ -171,26 +185,15 @@ class ForumViewModel extends ThreadListViewModel { } private void storePost(ForumPost msg, String text) { - runOnDbThread(() -> { - try { - long start = now(); - ForumPostHeader header = forumManager.addLocalPost(msg); - addItemAsync(buildItem(header, text)); - logDuration(LOG, "Storing forum post", start); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); - } - - 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()); + runOnDbThread(false, txn -> { + long start = now(); + ForumPostHeader header = forumManager.addLocalPost(txn, msg); + logDuration(LOG, "Storing forum post", start); + txn.attach(() -> { + ForumPostItem item = new ForumPostItem(header, text); + addItem(item, true); + }); + }, e -> logException(LOG, WARNING, e)); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index 9020efe1b..2b25ee75e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -27,7 +27,6 @@ import org.briarproject.briar.android.viewmodel.MutableLiveEvent; 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.PostHeader; import org.briarproject.briar.api.privategroup.GroupMember; import org.briarproject.briar.api.privategroup.GroupMessage; import org.briarproject.briar.api.privategroup.GroupMessageFactory; @@ -105,7 +104,7 @@ class GroupViewModel extends ThreadListViewModel { if (!g.isLocal() && g.getGroupId().equals(groupId)) { LOG.info("Group message received, adding..."); GroupMessageItem item = buildItem(g.getHeader(), g.getText()); - addItem(item); + addItem(item, false); // In case the join message comes from the creator, // we need to reload the sharing contacts // in case it was delayed and the sharing count is wrong (#850). @@ -171,10 +170,28 @@ class GroupViewModel extends ThreadListViewModel { List headers = privateGroupManager.getHeaders(txn, groupId); logDuration(LOG, "Loading headers", start); - return createItems(txn, headers, this::buildItem); + start = now(); + List items = new ArrayList<>(); + for (GroupMessageHeader header : headers) { + items.add(loadItem(txn, header)); + } + logDuration(LOG, "Loading bodies and creating items", start); + return items; }, this::setItems); } + private GroupMessageItem loadItem(Transaction txn, + GroupMessageHeader header) throws DbException { + String text; + if (header instanceof JoinMessageHeader) { + // will be looked up later + text = ""; + } else { + text = privateGroupManager.getMessageText(txn, header.getId()); + } + return buildItem(header, text); + } + private GroupMessageItem buildItem(GroupMessageHeader header, String text) { if (header instanceof JoinMessageHeader) { return new JoinMessageItem((JoinMessageHeader) header, text); @@ -182,16 +199,6 @@ class GroupViewModel extends ThreadListViewModel { 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()); - } - @Override public void createAndStoreMessage(String text, @Nullable MessageId parentId) { @@ -222,17 +229,15 @@ class GroupViewModel extends ThreadListViewModel { } private void storePost(GroupMessage msg, String text) { - runOnDbThread(() -> { - try { - long start = now(); - GroupMessageHeader header = - privateGroupManager.addLocalMessage(msg); - addItemAsync(buildItem(header, text)); - logDuration(LOG, "Storing group message", start); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); + runOnDbThread(false, txn -> { + long start = now(); + GroupMessageHeader header = + privateGroupManager.addLocalMessage(txn, msg); + logDuration(LOG, "Storing group message", start); + txn.attach(() -> + addItem(buildItem(header, text), true) + ); + }, e -> logException(LOG, WARNING, e)); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java index c28847107..3e3adfeba 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -5,7 +5,6 @@ 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; @@ -26,11 +25,8 @@ import org.briarproject.briar.android.viewmodel.LiveResult; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTree; -import org.briarproject.briar.api.client.PostHeader; import org.briarproject.briar.client.MessageTreeImpl; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; @@ -46,9 +42,7 @@ import androidx.lifecycle.MutableLiveData; import static java.util.logging.Level.INFO; 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 @@ -67,6 +61,7 @@ public abstract class ThreadListViewModel private final MessageTracker messageTracker; private final EventBus eventBus; + // UIThread private final MessageTree messageTree = new MessageTreeImpl<>(); private final MutableLiveData>> items = new MutableLiveData<>(); @@ -175,55 +170,34 @@ public abstract class ThreadListViewModel * Loads the ContactIds of all contacts the group is shared with * and adds them to {@link SharingController}. */ - public abstract void loadSharingContacts(); + protected abstract void loadSharingContacts(); @UiThread protected void setItems(LiveResult> items) { - this.items.setValue(items); - } - - @DatabaseExecutor - protected List createItems( - Transaction txn, Collection headers, ItemGetter itemGetter) - throws DbException { - long start = now(); - List items = new ArrayList<>(); - for (H header : headers) { - String text = loadMessageText(txn, header); - items.add(itemGetter.getItem(header, text)); + if (items.hasError()) { + this.items.setValue(items); + } else { + messageTree.clear(); + // not null, because hasError() is false + messageTree.add(items.getResultOrNull()); + LiveResult> result = + new LiveResult<>(messageTree.depthFirstOrder()); + this.items.setValue(result); } - logDuration(LOG, "Loading bodies and creating items", start); - - messageTree.clear(); - messageTree.add(items); - return messageTree.depthFirstOrder(); } /** * Add a remote item on the UI thread. - * The list will not scroll, but show an unread indicator. + * + * @param scrollToItem whether the list will scroll to the newly added item */ @UiThread - protected void addItem(I item) { + protected void addItem(I item, boolean scrollToItem) { messageTree.add(item); + if (scrollToItem) this.scrollToItem.set(item.getId()); items.setValue(new LiveResult<>(messageTree.depthFirstOrder())); } - /** - * Add a local item from the DB thread. - * The list will scroll to the new item. - */ - @DatabaseExecutor - protected void addItemAsync(I item) { - messageTree.add(item); - scrollToItem.set(item.getId()); - items.postValue(new LiveResult<>(messageTree.depthFirstOrder())); - } - - @DatabaseExecutor - protected abstract String loadMessageText(Transaction txn, - PostHeader header) throws DbException; - @UiThread void setReplyId(@Nullable MessageId id) { replyId = id; @@ -271,8 +245,4 @@ public abstract class ThreadListViewModel return scrollToItem.getAndSet(null); } - public interface ItemGetter { - I getItem(H header, String text); - } - } From ae4a04bada8001c48d83b74f058bae1d1bcc56fb Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 29 Jan 2021 08:33:28 -0300 Subject: [PATCH 22/22] Finishing touches of ThreadListViewModel migration docs and minor improvements --- .../briar/android/forum/ForumViewModel.java | 6 ++---- .../android/threaded/ThreadListViewModel.java | 14 +++++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index 87aca3fb1..af9254714 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -26,7 +26,6 @@ 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.MessageTracker.GroupCount; -import org.briarproject.briar.api.client.PostHeader; import org.briarproject.briar.api.forum.Forum; import org.briarproject.briar.api.forum.ForumInvitationResponse; import org.briarproject.briar.api.forum.ForumManager; @@ -151,11 +150,10 @@ class ForumViewModel extends ThreadListViewModel { }, this::setItems); } - private ForumPostItem loadItem(Transaction txn, PostHeader header) + private ForumPostItem loadItem(Transaction txn, ForumPostHeader header) throws DbException { - if (!(header instanceof ForumPostHeader)) throw new AssertionError(); String text = forumManager.getPostText(txn, header.getId()); - return new ForumPostItem((ForumPostHeader) header, text); + return new ForumPostItem(header, text); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java index 3e3adfeba..06100d636 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -39,6 +39,7 @@ import androidx.annotation.UiThread; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import static java.util.Objects.requireNonNull; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; @@ -47,8 +48,7 @@ import static org.briarproject.bramble.util.LogUtils.logException; @MethodsNotNullByDefault @ParametersNotNullByDefault public abstract class ThreadListViewModel - extends DbViewModel - implements EventListener { + extends DbViewModel implements EventListener { private static final Logger LOG = getLogger(ThreadListViewModel.class.getName()); @@ -73,6 +73,9 @@ public abstract class ThreadListViewModel protected volatile GroupId groupId; @Nullable private MessageId replyId; + /** + * Stored list position. Needs to be loaded and set before the list itself. + */ private final AtomicReference storedMessageId = new AtomicReference<>(); @@ -118,6 +121,7 @@ public abstract class ThreadListViewModel @CallSuper protected void performInitialLoad() { + // load stored MessageId (last list position) before the list itself loadStoredMessageId(); loadItems(); loadSharingContacts(); @@ -179,7 +183,7 @@ public abstract class ThreadListViewModel } else { messageTree.clear(); // not null, because hasError() is false - messageTree.add(items.getResultOrNull()); + messageTree.add(requireNonNull(items.getResultOrNull())); LiveResult> result = new LiveResult<>(messageTree.depthFirstOrder()); this.items.setValue(result); @@ -223,6 +227,10 @@ public abstract class ThreadListViewModel protected abstract void markItemRead(I item); + /** + * Returns the {@link MessageId} of the item that was at the top of the + * list last time or null if there has been nothing stored, yet. + */ @Nullable MessageId getAndResetRestoredMessageId() { return storedMessageId.getAndSet(null);