mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Migrate GroupListController to a ViewModel
Use ListAdapter to calculate list diffs on a background thread
This commit is contained in:
@@ -31,6 +31,7 @@ import org.briarproject.briar.android.account.LockManagerImpl;
|
||||
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.list.GroupListModule;
|
||||
import org.briarproject.briar.android.reporting.DevReportModule;
|
||||
import org.briarproject.briar.android.viewmodel.ViewModelModule;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
@@ -65,7 +66,9 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
LoginModule.class,
|
||||
NavDrawerModule.class,
|
||||
ViewModelModule.class,
|
||||
DevReportModule.class
|
||||
DevReportModule.class,
|
||||
// below need to be within same scope as ViewModelProvider.Factory
|
||||
GroupListModule.class,
|
||||
})
|
||||
public class AppModule {
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
|
||||
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
|
||||
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationModule;
|
||||
import org.briarproject.briar.android.privategroup.list.GroupListFragment;
|
||||
import org.briarproject.briar.android.privategroup.list.GroupListModule;
|
||||
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity;
|
||||
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule;
|
||||
import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
|
||||
@@ -94,7 +93,6 @@ import dagger.Component;
|
||||
ForumModule.class,
|
||||
GroupInvitationModule.class,
|
||||
GroupConversationModule.class,
|
||||
GroupListModule.class,
|
||||
GroupMemberModule.class,
|
||||
GroupRevealModule.class,
|
||||
SharingModule.class
|
||||
|
||||
@@ -8,9 +8,11 @@ import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
// This class is not thread-safe
|
||||
@NotNullByDefault
|
||||
class GroupItem {
|
||||
class GroupItem implements Comparable<GroupItem> {
|
||||
|
||||
private final PrivateGroup privateGroup;
|
||||
private final AuthorInfo authorInfo;
|
||||
@@ -28,6 +30,15 @@ class GroupItem {
|
||||
this.dissolved = dissolved;
|
||||
}
|
||||
|
||||
GroupItem(GroupItem item) {
|
||||
this.privateGroup = item.privateGroup;
|
||||
this.authorInfo = item.authorInfo;
|
||||
this.messageCount = item.messageCount;
|
||||
this.unreadCount = item.unreadCount;
|
||||
this.timestamp = item.timestamp;
|
||||
this.dissolved = item.dissolved;
|
||||
}
|
||||
|
||||
void addMessageHeader(GroupMessageHeader header) {
|
||||
messageCount++;
|
||||
if (header.getTimestamp() > timestamp) {
|
||||
@@ -38,10 +49,6 @@ class GroupItem {
|
||||
}
|
||||
}
|
||||
|
||||
PrivateGroup getPrivateGroup() {
|
||||
return privateGroup;
|
||||
}
|
||||
|
||||
GroupId getId() {
|
||||
return privateGroup.getId();
|
||||
}
|
||||
@@ -82,4 +89,22 @@ class GroupItem {
|
||||
dissolved = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
return o instanceof GroupItem &&
|
||||
getId().equals(((GroupItem) o).getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(GroupItem o) {
|
||||
if (this == o) return 0;
|
||||
// The group with the latest message comes first
|
||||
long aTime = getTimestamp(), bTime = o.getTimestamp();
|
||||
if (aTime > bTime) return -1;
|
||||
if (aTime < bTime) return 1;
|
||||
// Break ties by group name
|
||||
String aName = getName();
|
||||
String bName = o.getName();
|
||||
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,52 @@
|
||||
package org.briarproject.briar.android.privategroup.list;
|
||||
|
||||
import android.content.Context;
|
||||
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.GroupId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener;
|
||||
import org.briarproject.briar.android.util.BriarAdapter;
|
||||
|
||||
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
|
||||
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class GroupListAdapter extends BriarAdapter<GroupItem, GroupViewHolder> {
|
||||
class GroupListAdapter extends ListAdapter<GroupItem, GroupViewHolder> {
|
||||
|
||||
private final OnGroupRemoveClickListener listener;
|
||||
|
||||
GroupListAdapter(Context ctx, OnGroupRemoveClickListener listener) {
|
||||
super(ctx, GroupItem.class);
|
||||
GroupListAdapter(OnGroupRemoveClickListener listener) {
|
||||
super(new GroupItemCallback());
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(ctx).inflate(
|
||||
R.layout.list_item_group, parent, false);
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.list_item_group, parent, false);
|
||||
return new GroupViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(GroupViewHolder ui, int position) {
|
||||
ui.bindView(ctx, items.get(position), listener);
|
||||
ui.bindView(getItem(position), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(GroupItem a, GroupItem b) {
|
||||
if (a == b) return 0;
|
||||
// The group with the latest message comes first
|
||||
long aTime = a.getTimestamp(), bTime = b.getTimestamp();
|
||||
if (aTime > bTime) return -1;
|
||||
if (aTime < bTime) return 1;
|
||||
// Break ties by group name
|
||||
String aName = a.getName();
|
||||
String bName = b.getName();
|
||||
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(GroupItem a, GroupItem b) {
|
||||
return a.getMessageCount() == b.getMessageCount() &&
|
||||
a.getTimestamp() == b.getTimestamp() &&
|
||||
a.getUnreadCount() == b.getUnreadCount() &&
|
||||
a.isDissolved() == b.isDissolved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(GroupItem a, GroupItem b) {
|
||||
return a.getId().equals(b.getId());
|
||||
}
|
||||
|
||||
int findItemPosition(GroupId g) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
GroupItem item = items.get(i);
|
||||
if (item.getId().equals(g)) {
|
||||
return i;
|
||||
}
|
||||
private static class GroupItemCallback extends ItemCallback<GroupItem> {
|
||||
@Override
|
||||
public boolean areItemsTheSame(GroupItem a, GroupItem b) {
|
||||
return a.equals(b);
|
||||
}
|
||||
return INVALID_POSITION;
|
||||
}
|
||||
|
||||
void removeItem(GroupId groupId) {
|
||||
int pos = findItemPosition(groupId);
|
||||
if (pos != INVALID_POSITION) items.removeItemAt(pos);
|
||||
@Override
|
||||
public boolean areContentsTheSame(GroupItem a, GroupItem b) {
|
||||
return a.getMessageCount() == b.getMessageCount() &&
|
||||
a.getTimestamp() == b.getTimestamp() &&
|
||||
a.getUnreadCount() == b.getUnreadCount() &&
|
||||
a.isDissolved() == b.isDissolved();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package org.briarproject.briar.android.privategroup.list;
|
||||
|
||||
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.DbController;
|
||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
@NotNullByDefault
|
||||
interface GroupListController extends DbController {
|
||||
|
||||
/**
|
||||
* The listener must be set right after the controller was injected
|
||||
*/
|
||||
@UiThread
|
||||
void setGroupListListener(GroupListListener listener);
|
||||
|
||||
@UiThread
|
||||
void unsetGroupListListener(GroupListListener listener);
|
||||
|
||||
@UiThread
|
||||
void onStart();
|
||||
|
||||
@UiThread
|
||||
void onStop();
|
||||
|
||||
void loadGroups(
|
||||
ResultExceptionHandler<Collection<GroupItem>, DbException> result);
|
||||
|
||||
void removeGroup(GroupId g, ExceptionHandler<DbException> result);
|
||||
|
||||
void loadAvailableGroups(
|
||||
ResultExceptionHandler<Integer, DbException> result);
|
||||
|
||||
interface GroupListListener {
|
||||
|
||||
@UiThread
|
||||
void onGroupMessageAdded(GroupMessageHeader header);
|
||||
|
||||
@UiThread
|
||||
void onGroupInvitationReceived();
|
||||
|
||||
@UiThread
|
||||
void onGroupAdded(GroupId groupId);
|
||||
|
||||
@UiThread
|
||||
void onGroupRemoved(GroupId groupId);
|
||||
|
||||
@UiThread
|
||||
void onGroupDissolved(GroupId groupId);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,54 +15,50 @@ import com.google.android.material.snackbar.Snackbar;
|
||||
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.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.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
|
||||
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
|
||||
import org.briarproject.briar.android.privategroup.list.GroupListController.GroupListListener;
|
||||
import org.briarproject.briar.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener;
|
||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import static com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class GroupListFragment extends BaseFragment implements
|
||||
GroupListListener, OnGroupRemoveClickListener, OnClickListener {
|
||||
OnGroupRemoveClickListener, OnClickListener {
|
||||
|
||||
public final static String TAG = GroupListFragment.class.getName();
|
||||
private static final Logger LOG = Logger.getLogger(TAG);
|
||||
|
||||
public static GroupListFragment newInstance() {
|
||||
return new GroupListFragment();
|
||||
}
|
||||
|
||||
@Inject
|
||||
GroupListController controller;
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private GroupListViewModel viewModel;
|
||||
private BriarRecyclerView list;
|
||||
private GroupListAdapter adapter;
|
||||
private Snackbar snackbar;
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
controller.setGroupListListener(this);
|
||||
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||
.get(GroupListViewModel.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -75,17 +71,35 @@ public class GroupListFragment extends BaseFragment implements
|
||||
|
||||
View v = inflater.inflate(R.layout.list, container, false);
|
||||
|
||||
adapter = new GroupListAdapter(getActivity(), this);
|
||||
adapter = new GroupListAdapter(this);
|
||||
list = v.findViewById(R.id.list);
|
||||
list.setEmptyImage(R.drawable.ic_empty_state_group_list);
|
||||
list.setEmptyText(R.string.groups_list_empty);
|
||||
list.setEmptyAction(R.string.groups_list_empty_action);
|
||||
list.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
list.setAdapter(adapter);
|
||||
viewModel.getGroupItems().observe(getViewLifecycleOwner(), result -> {
|
||||
List<GroupItem> items = result.getResultOrNull();
|
||||
if (items == null && result.getException() instanceof DbException) {
|
||||
handleDbException((DbException) result.getException());
|
||||
} else {
|
||||
adapter.submitList(items);
|
||||
if (requireNonNull(items).size() == 0) list.showData();
|
||||
}
|
||||
});
|
||||
|
||||
snackbar = new BriarSnackbarBuilder()
|
||||
Snackbar snackbar = new BriarSnackbarBuilder()
|
||||
.setAction(R.string.show, this)
|
||||
.make(list, "", LENGTH_INDEFINITE);
|
||||
viewModel.getNumInvitations().observe(getViewLifecycleOwner(), num -> {
|
||||
if (num == 0) {
|
||||
snackbar.dismiss();
|
||||
} else {
|
||||
snackbar.setText(getResources().getQuantityString(
|
||||
R.plurals.groups_invitations_open, num, num));
|
||||
if (!snackbar.isShownOrQueued()) snackbar.show();
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
@@ -93,25 +107,22 @@ public class GroupListFragment extends BaseFragment implements
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
controller.onStart();
|
||||
// TODO should we block all group message notifications as well?
|
||||
viewModel.clearAllGroupMessageNotifications();
|
||||
// The attributes and sorting of the groups may have changed while we
|
||||
// were stopped and we have no way finding out about them, so re-load
|
||||
// e.g. less unread messages in a group after viewing it.
|
||||
viewModel.loadGroups();
|
||||
// The number of invitations might have changed while we were stopped
|
||||
// e.g. because of accepting an invitation which does not trigger event
|
||||
viewModel.loadNumInvitations();
|
||||
list.startPeriodicUpdate();
|
||||
loadGroups();
|
||||
loadAvailableGroups();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
controller.onStop();
|
||||
list.stopPeriodicUpdate();
|
||||
adapter.clear();
|
||||
list.showProgressBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
controller.unsetGroupListListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,68 +133,18 @@ public class GroupListFragment extends BaseFragment implements
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_add_group:
|
||||
Intent i = new Intent(getContext(), CreateGroupActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
if (item.getItemId() == R.id.action_add_group) {
|
||||
Intent i = new Intent(getContext(), CreateGroupActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void onGroupRemoveClick(GroupItem item) {
|
||||
controller.removeGroup(item.getId(),
|
||||
new UiExceptionHandler<DbException>(this) {
|
||||
// result handled by GroupRemovedEvent and onGroupRemoved()
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void onGroupMessageAdded(GroupMessageHeader header) {
|
||||
adapter.incrementRevision();
|
||||
int position = adapter.findItemPosition(header.getGroupId());
|
||||
GroupItem item = adapter.getItemAt(position);
|
||||
if (item != null) {
|
||||
item.addMessageHeader(header);
|
||||
adapter.updateItemAt(position, item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGroupInvitationReceived() {
|
||||
loadAvailableGroups();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void onGroupAdded(GroupId groupId) {
|
||||
loadGroups();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void onGroupRemoved(GroupId groupId) {
|
||||
adapter.incrementRevision();
|
||||
adapter.removeItem(groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGroupDissolved(GroupId groupId) {
|
||||
adapter.incrementRevision();
|
||||
int position = adapter.findItemPosition(groupId);
|
||||
GroupItem item = adapter.getItemAt(position);
|
||||
if (item != null) {
|
||||
item.setDissolved();
|
||||
adapter.updateItemAt(position, item);
|
||||
}
|
||||
viewModel.removeGroup(item.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,57 +152,13 @@ public class GroupListFragment extends BaseFragment implements
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private void loadGroups() {
|
||||
int revision = adapter.getRevision();
|
||||
controller.loadGroups(
|
||||
new UiResultExceptionHandler<Collection<GroupItem>, DbException>(
|
||||
this) {
|
||||
@Override
|
||||
public void onResultUi(Collection<GroupItem> groups) {
|
||||
if (revision == adapter.getRevision()) {
|
||||
adapter.incrementRevision();
|
||||
if (groups.isEmpty()) list.showData();
|
||||
else adapter.replaceAll(groups);
|
||||
} else {
|
||||
LOG.info("Concurrent update, reloading");
|
||||
loadGroups();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadAvailableGroups() {
|
||||
controller.loadAvailableGroups(
|
||||
new UiResultExceptionHandler<Integer, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(Integer num) {
|
||||
if (num == 0) {
|
||||
snackbar.dismiss();
|
||||
} else {
|
||||
snackbar.setText(getResources().getQuantityString(
|
||||
R.plurals.groups_invitations_open, num,
|
||||
num));
|
||||
if (!snackbar.isShownOrQueued()) snackbar.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is handling the available groups snackbar action
|
||||
*/
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// The snackbar dismisses itself when this is called
|
||||
// and does not come back until the fragment gets recreated.
|
||||
Intent i = new Intent(getContext(), GroupInvitationActivity.class);
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package org.briarproject.briar.android.privategroup.list;
|
||||
|
||||
import org.briarproject.briar.android.activity.ActivityScope;
|
||||
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 GroupListModule {
|
||||
public abstract class GroupListModule {
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
GroupListController provideGroupListController(
|
||||
GroupListControllerImpl groupListController) {
|
||||
return groupListController;
|
||||
}
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(GroupListViewModel.class)
|
||||
abstract ViewModel bindGroupListViewModel(
|
||||
GroupListViewModel groupListViewModel);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.briarproject.briar.android.privategroup.list;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
@@ -18,11 +20,11 @@ import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.event.GroupAddedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
|
||||
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.viewmodel.DbViewModel;
|
||||
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent;
|
||||
@@ -32,6 +34,7 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -40,10 +43,13 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
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;
|
||||
@@ -51,113 +57,86 @@ import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class GroupListControllerImpl extends DbControllerImpl
|
||||
implements GroupListController, EventListener {
|
||||
class GroupListViewModel extends DbViewModel implements EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(GroupListControllerImpl.class.getName());
|
||||
getLogger(GroupListViewModel.class.getName());
|
||||
|
||||
private final TransactionManager db;
|
||||
private final PrivateGroupManager groupManager;
|
||||
private final GroupInvitationManager groupInvitationManager;
|
||||
private final ContactManager contactManager;
|
||||
private final AndroidNotificationManager notificationManager;
|
||||
private final EventBus eventBus;
|
||||
|
||||
// UI thread
|
||||
@Nullable
|
||||
private GroupListListener listener;
|
||||
private final MutableLiveData<LiveResult<List<GroupItem>>> groupItems =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Integer> numInvitations =
|
||||
new MutableLiveData<>();
|
||||
|
||||
@Inject
|
||||
GroupListControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
GroupListViewModel(Application application,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager,
|
||||
TransactionManager db,
|
||||
PrivateGroupManager groupManager,
|
||||
GroupInvitationManager groupInvitationManager,
|
||||
ContactManager contactManager,
|
||||
AndroidNotificationManager notificationManager, EventBus eventBus) {
|
||||
super(dbExecutor, lifecycleManager);
|
||||
this.db = db;
|
||||
super(application, dbExecutor, lifecycleManager, db);
|
||||
this.groupManager = groupManager;
|
||||
this.groupInvitationManager = groupInvitationManager;
|
||||
this.contactManager = contactManager;
|
||||
this.notificationManager = notificationManager;
|
||||
this.eventBus = eventBus;
|
||||
this.eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGroupListListener(GroupListListener listener) {
|
||||
this.listener = listener;
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsetGroupListListener(GroupListListener listener) {
|
||||
if (this.listener == listener) this.listener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onStart() {
|
||||
if (listener == null) throw new IllegalStateException();
|
||||
eventBus.addListener(this);
|
||||
void clearAllGroupMessageNotifications() {
|
||||
notificationManager.clearAllGroupMessageNotifications();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onStop() {
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void eventOccurred(Event e) {
|
||||
if (listener == null) throw new IllegalStateException();
|
||||
if (e instanceof GroupMessageAddedEvent) {
|
||||
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
||||
LOG.info("Private group message added");
|
||||
listener.onGroupMessageAdded(g.getHeader());
|
||||
onGroupMessageAdded(g.getHeader());
|
||||
} else if (e instanceof GroupInvitationRequestReceivedEvent) {
|
||||
LOG.info("Private group invitation received");
|
||||
listener.onGroupInvitationReceived();
|
||||
loadNumInvitations();
|
||||
} else if (e instanceof GroupAddedEvent) {
|
||||
GroupAddedEvent g = (GroupAddedEvent) e;
|
||||
ClientId id = g.getGroup().getClientId();
|
||||
if (id.equals(CLIENT_ID)) {
|
||||
LOG.info("Private group added");
|
||||
listener.onGroupAdded(g.getGroup().getId());
|
||||
loadGroups();
|
||||
}
|
||||
} else if (e instanceof GroupRemovedEvent) {
|
||||
GroupRemovedEvent g = (GroupRemovedEvent) e;
|
||||
ClientId id = g.getGroup().getClientId();
|
||||
if (id.equals(CLIENT_ID)) {
|
||||
LOG.info("Private group removed");
|
||||
listener.onGroupRemoved(g.getGroup().getId());
|
||||
onGroupRemoved(g.getGroup().getId());
|
||||
}
|
||||
} else if (e instanceof GroupDissolvedEvent) {
|
||||
GroupDissolvedEvent g = (GroupDissolvedEvent) e;
|
||||
LOG.info("Private group dissolved");
|
||||
listener.onGroupDissolved(g.getGroupId());
|
||||
onGroupDissolved(g.getGroupId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadGroups(
|
||||
ResultExceptionHandler<Collection<GroupItem>, DbException> handler) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
db.transaction(true, txn -> loadGroups(txn, handler));
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
handler.onException(e);
|
||||
}
|
||||
});
|
||||
void loadGroups() {
|
||||
loadList(this::loadGroups, groupItems::setValue);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void loadGroups(Transaction txn,
|
||||
ResultExceptionHandler<Collection<GroupItem>, DbException> handler)
|
||||
throws DbException {
|
||||
private List<GroupItem> loadGroups(Transaction txn) throws DbException {
|
||||
long start = now();
|
||||
Collection<PrivateGroup> groups = groupManager.getPrivateGroups(txn);
|
||||
List<GroupItem> items = new ArrayList<>(groups.size());
|
||||
@@ -168,7 +147,7 @@ class GroupListControllerImpl extends DbControllerImpl
|
||||
AuthorId authorId = g.getCreator().getId();
|
||||
AuthorInfo authorInfo;
|
||||
if (authorInfos.containsKey(authorId)) {
|
||||
authorInfo = authorInfos.get(authorId);
|
||||
authorInfo = requireNonNull(authorInfos.get(authorId));
|
||||
} else {
|
||||
authorInfo = contactManager.getAuthorInfo(txn, authorId);
|
||||
authorInfos.put(authorId, authorInfo);
|
||||
@@ -180,12 +159,49 @@ class GroupListControllerImpl extends DbControllerImpl
|
||||
// Continue
|
||||
}
|
||||
}
|
||||
Collections.sort(items);
|
||||
logDuration(LOG, "Loading groups", start);
|
||||
handler.onResult(items);
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGroup(GroupId g, ExceptionHandler<DbException> handler) {
|
||||
@UiThread
|
||||
private void onGroupMessageAdded(GroupMessageHeader header) {
|
||||
GroupId g = header.getGroupId();
|
||||
List<GroupItem> list = updateListItem(groupItems,
|
||||
itemToTest -> itemToTest.getId().equals(g),
|
||||
itemToUpdate -> {
|
||||
GroupItem newItem = new GroupItem(itemToUpdate);
|
||||
newItem.addMessageHeader(header);
|
||||
return newItem;
|
||||
});
|
||||
if (list == null) return;
|
||||
// re-sort as the order of items may have changed
|
||||
Collections.sort(list);
|
||||
groupItems.setValue(new LiveResult<>(list));
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onGroupDissolved(GroupId groupId) {
|
||||
List<GroupItem> list = updateListItem(groupItems,
|
||||
itemToTest -> itemToTest.getId().equals(groupId),
|
||||
itemToUpdate -> {
|
||||
GroupItem newItem = new GroupItem(itemToUpdate);
|
||||
newItem.setDissolved();
|
||||
return newItem;
|
||||
});
|
||||
if (list == null) return;
|
||||
groupItems.setValue(new LiveResult<>(list));
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onGroupRemoved(GroupId groupId) {
|
||||
List<GroupItem> list =
|
||||
removeListItem(groupItems, i -> i.getId().equals(groupId));
|
||||
if (list == null) return;
|
||||
groupItems.setValue(new LiveResult<>(list));
|
||||
}
|
||||
|
||||
void removeGroup(GroupId g) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
@@ -193,23 +209,26 @@ class GroupListControllerImpl extends DbControllerImpl
|
||||
logDuration(LOG, "Removing group", start);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
handler.onException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAvailableGroups(
|
||||
ResultExceptionHandler<Integer, DbException> handler) {
|
||||
void loadNumInvitations() {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
handler.onResult(
|
||||
groupInvitationManager.getInvitations().size());
|
||||
int i = groupInvitationManager.getInvitations().size();
|
||||
numInvitations.postValue(i);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
handler.onException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LiveData<LiveResult<List<GroupItem>>> getGroupItems() {
|
||||
return groupItems;
|
||||
}
|
||||
|
||||
LiveData<Integer> getNumInvitations() {
|
||||
return numInvitations;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final static float ALPHA = 0.42f;
|
||||
|
||||
private final Context ctx;
|
||||
private final ViewGroup layout;
|
||||
private final TextAvatarView avatar;
|
||||
private final TextView name;
|
||||
@@ -40,7 +41,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
GroupViewHolder(View v) {
|
||||
super(v);
|
||||
|
||||
ctx = v.getContext();
|
||||
layout = (ViewGroup) v;
|
||||
avatar = v.findViewById(R.id.avatarView);
|
||||
name = v.findViewById(R.id.nameView);
|
||||
@@ -51,8 +52,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
|
||||
remove = v.findViewById(R.id.removeButton);
|
||||
}
|
||||
|
||||
void bindView(Context ctx, GroupItem group,
|
||||
OnGroupRemoveClickListener listener) {
|
||||
void bindView(GroupItem group, OnGroupRemoveClickListener listener) {
|
||||
// Avatar
|
||||
avatar.setText(group.getName().substring(0, 1));
|
||||
avatar.setBackgroundBytes(group.getId().getBytes());
|
||||
|
||||
Reference in New Issue
Block a user