Migrate GroupListController to a ViewModel

Use ListAdapter to calculate list diffs on a background thread
This commit is contained in:
Torsten Grote
2020-12-17 17:35:17 -03:00
parent fd86b73626
commit 015ecb1d99
9 changed files with 195 additions and 321 deletions

View File

@@ -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 {

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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());