mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-19 06:09:55 +01:00
Introduce ViewModel for ContactListFragment
This commit is contained in:
@@ -179,6 +179,11 @@ public interface ContactManager {
|
|||||||
*/
|
*/
|
||||||
Collection<Contact> getContacts() throws DbException;
|
Collection<Contact> getContacts() throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all contacts.
|
||||||
|
*/
|
||||||
|
Collection<Contact> getContacts(Transaction txn) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a contact and all associated state.
|
* Removes a contact and all associated state.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -213,6 +213,11 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
|||||||
return db.transactionWithResult(true, db::getContacts);
|
return db.transactionWithResult(true, db::getContacts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Contact> getContacts(Transaction txn) throws DbException {
|
||||||
|
return db.getContacts(txn);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeContact(ContactId c) throws DbException {
|
public void removeContact(ContactId c) throws DbException {
|
||||||
db.transaction(false, txn -> removeContact(txn, c));
|
db.transaction(false, txn -> removeContact(txn, c));
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
|
|||||||
import org.briarproject.bramble.util.AndroidUtils;
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.briarproject.briar.android.account.LockManagerImpl;
|
import org.briarproject.briar.android.account.LockManagerImpl;
|
||||||
|
import org.briarproject.briar.android.contact.ContactListModule;
|
||||||
import org.briarproject.briar.android.forum.ForumModule;
|
import org.briarproject.briar.android.forum.ForumModule;
|
||||||
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
|
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
|
||||||
import org.briarproject.briar.android.login.LoginModule;
|
import org.briarproject.briar.android.login.LoginModule;
|
||||||
@@ -68,6 +69,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
|||||||
NavDrawerModule.class,
|
NavDrawerModule.class,
|
||||||
ViewModelModule.class,
|
ViewModelModule.class,
|
||||||
DevReportModule.class,
|
DevReportModule.class,
|
||||||
|
ContactListModule.class,
|
||||||
// below need to be within same scope as ViewModelProvider.Factory
|
// below need to be within same scope as ViewModelProvider.Factory
|
||||||
ForumModule.BindsModule.class,
|
ForumModule.BindsModule.class,
|
||||||
GroupListModule.class,
|
GroupListModule.class,
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import org.briarproject.briar.android.blog.RssFeedImportActivity;
|
|||||||
import org.briarproject.briar.android.blog.RssFeedManageActivity;
|
import org.briarproject.briar.android.blog.RssFeedManageActivity;
|
||||||
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
|
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
|
||||||
import org.briarproject.briar.android.contact.ContactListFragment;
|
import org.briarproject.briar.android.contact.ContactListFragment;
|
||||||
import org.briarproject.briar.android.contact.ContactModule;
|
|
||||||
import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
|
import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
|
||||||
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
|
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
|
||||||
import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
|
import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
|
||||||
@@ -88,7 +87,6 @@ import dagger.Component;
|
|||||||
@Component(modules = {
|
@Component(modules = {
|
||||||
ActivityModule.class,
|
ActivityModule.class,
|
||||||
BlogModule.class,
|
BlogModule.class,
|
||||||
ContactModule.class,
|
|
||||||
CreateGroupModule.class,
|
CreateGroupModule.class,
|
||||||
ForumModule.class,
|
ForumModule.class,
|
||||||
GroupInvitationModule.class,
|
GroupInvitationModule.class,
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package org.briarproject.briar.android.contact;
|
|||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import javax.annotation.concurrent.NotThreadSafe;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
@NotThreadSafe
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ContactItem {
|
public class ContactItem {
|
||||||
|
|
||||||
private final Contact contact;
|
private final Contact contact;
|
||||||
private boolean connected;
|
private final boolean connected;
|
||||||
|
|
||||||
public ContactItem(Contact contact) {
|
public ContactItem(Contact contact) {
|
||||||
this(contact, false);
|
this(contact, false);
|
||||||
@@ -29,8 +29,4 @@ public class ContactItem {
|
|||||||
return connected;
|
return connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setConnected(boolean connected) {
|
|
||||||
this.connected = connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,68 @@
|
|||||||
package org.briarproject.briar.android.contact;
|
package org.briarproject.briar.android.contact;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
|
||||||
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ContactListAdapter extends
|
public class ContactListAdapter extends
|
||||||
BaseContactListAdapter<ContactListItem, ContactListItemViewHolder> {
|
ListAdapter<ContactListItem, ContactListItemViewHolder> {
|
||||||
|
|
||||||
public ContactListAdapter(Context context,
|
// TODO: using the click listener interface from BaseContactListAdapter on
|
||||||
|
// purpose here because it is entangled with ContactListItemViewHolder. At
|
||||||
|
// some point we probably want to change that.
|
||||||
|
protected final OnContactClickListener<ContactListItem> listener;
|
||||||
|
|
||||||
|
public ContactListAdapter(
|
||||||
OnContactClickListener<ContactListItem> listener) {
|
OnContactClickListener<ContactListItem> listener) {
|
||||||
super(context, ContactListItem.class, listener);
|
super(new ContactListCallback());
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
private static class ContactListCallback
|
||||||
|
extends ItemCallback<ContactListItem> {
|
||||||
|
@Override
|
||||||
|
public boolean areItemsTheSame(ContactListItem c1, ContactListItem c2) {
|
||||||
|
return c1.getContact().equals(c2.getContact());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(ContactListItem c1,
|
||||||
|
ContactListItem c2) {
|
||||||
|
// check for all properties that influence visual
|
||||||
|
// representation of contact
|
||||||
|
if (c1.isEmpty() != c2.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (c1.getUnreadCount() != c2.getUnreadCount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (c1.getTimestamp() != c2.getTimestamp()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return c1.isConnected() == c2.isConnected();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
|
public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
|
||||||
int i) {
|
int viewType) {
|
||||||
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
|
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
|
||||||
R.layout.list_item_contact, viewGroup, false);
|
R.layout.list_item_contact, viewGroup, false);
|
||||||
|
|
||||||
return new ContactListItemViewHolder(v);
|
return new ContactListItemViewHolder(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) {
|
public void onBindViewHolder(ContactListItemViewHolder viewHolder,
|
||||||
// check for all properties that influence visual
|
int position) {
|
||||||
// representation of contact
|
viewHolder.bind(getItem(position), listener);
|
||||||
if (c1.isEmpty() != c2.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (c1.getUnreadCount() != c2.getUnreadCount()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (c1.getTimestamp() != c2.getTimestamp()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return c1.isConnected() == c2.isConnected();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(ContactListItem c1, ContactListItem c2) {
|
|
||||||
return Long.compare(c2.getTimestamp(), c1.getTimestamp());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,23 +10,9 @@ import android.widget.TextView;
|
|||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
|
||||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
|
||||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
|
||||||
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
|
|
||||||
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
|
||||||
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.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
||||||
@@ -37,55 +23,35 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
|||||||
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
|
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
|
||||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
|
||||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
|
||||||
import org.briarproject.briar.api.conversation.ConversationManager;
|
|
||||||
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
|
||||||
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.core.app.ActivityOptionsCompat;
|
|
||||||
import androidx.core.util.Pair;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
|
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
|
||||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
|
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
|
||||||
|
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
|
||||||
import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
|
||||||
import static androidx.core.view.ViewCompat.getTransitionName;
|
|
||||||
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
|
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
|
||||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class ContactListFragment extends BaseFragment implements EventListener,
|
public class ContactListFragment extends BaseFragment
|
||||||
OnMenuItemClickListener {
|
implements OnMenuItemClickListener,
|
||||||
|
OnContactClickListener<ContactListItem> {
|
||||||
|
|
||||||
public static final String TAG = ContactListFragment.class.getName();
|
public static final String TAG = ContactListFragment.class.getName();
|
||||||
private static final Logger LOG = Logger.getLogger(TAG);
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ConnectionRegistry connectionRegistry;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
@Inject
|
|
||||||
EventBus eventBus;
|
|
||||||
@Inject
|
|
||||||
AndroidNotificationManager notificationManager;
|
|
||||||
|
|
||||||
private ContactListAdapter adapter;
|
private ContactListViewModel viewModel;
|
||||||
|
private final ContactListAdapter adapter = new ContactListAdapter(this);
|
||||||
private BriarRecyclerView list;
|
private BriarRecyclerView list;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Snackbar is non-null when shown and null otherwise.
|
* The Snackbar is non-null when shown and null otherwise.
|
||||||
* Use {@link #showSnackBar()} and {@link #dismissSnackBar()} to interact.
|
* Use {@link #showSnackBar()} and {@link #dismissSnackBar()} to interact.
|
||||||
@@ -93,12 +59,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
|||||||
@Nullable
|
@Nullable
|
||||||
private Snackbar snackbar = null;
|
private Snackbar snackbar = null;
|
||||||
|
|
||||||
// Fields that are accessed from background threads must be volatile
|
|
||||||
@Inject
|
|
||||||
volatile ContactManager contactManager;
|
|
||||||
@Inject
|
|
||||||
volatile ConversationManager conversationManager;
|
|
||||||
|
|
||||||
public static ContactListFragment newInstance() {
|
public static ContactListFragment newInstance() {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
ContactListFragment fragment = new ContactListFragment();
|
ContactListFragment fragment = new ContactListFragment();
|
||||||
@@ -114,6 +74,8 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
|||||||
@Override
|
@Override
|
||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||||
|
.get(ContactListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -129,37 +91,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
|||||||
FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial);
|
FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial);
|
||||||
speedDial.addOnMenuItemClickListener(this);
|
speedDial.addOnMenuItemClickListener(this);
|
||||||
|
|
||||||
OnContactClickListener<ContactListItem> onContactClickListener =
|
|
||||||
(view, item) -> {
|
|
||||||
Intent i = new Intent(getActivity(),
|
|
||||||
ConversationActivity.class);
|
|
||||||
ContactId contactId = item.getContact().getId();
|
|
||||||
i.putExtra(CONTACT_ID, contactId.getInt());
|
|
||||||
|
|
||||||
if (SDK_INT >= 23 && !isSamsung7()) {
|
|
||||||
ContactListItemViewHolder holder =
|
|
||||||
(ContactListItemViewHolder) list
|
|
||||||
.getRecyclerView()
|
|
||||||
.findViewHolderForAdapterPosition(
|
|
||||||
adapter.findItemPosition(item));
|
|
||||||
Pair<View, String> avatar =
|
|
||||||
Pair.create(holder.avatar,
|
|
||||||
getTransitionName(holder.avatar));
|
|
||||||
Pair<View, String> bulb =
|
|
||||||
Pair.create(holder.bulb,
|
|
||||||
getTransitionName(holder.bulb));
|
|
||||||
ActivityOptionsCompat options =
|
|
||||||
makeSceneTransitionAnimation(getActivity(),
|
|
||||||
avatar, bulb);
|
|
||||||
ActivityCompat.startActivity(getActivity(), i,
|
|
||||||
options.toBundle());
|
|
||||||
} else {
|
|
||||||
// work-around for android bug #224270
|
|
||||||
startActivity(i);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
adapter = new ContactListAdapter(requireContext(),
|
|
||||||
onContactClickListener);
|
|
||||||
list = contentView.findViewById(R.id.list);
|
list = contentView.findViewById(R.id.list);
|
||||||
list.setLayoutManager(new LinearLayoutManager(requireContext()));
|
list.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
@@ -167,9 +98,30 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
|||||||
list.setEmptyText(getString(R.string.no_contacts));
|
list.setEmptyText(getString(R.string.no_contacts));
|
||||||
list.setEmptyAction(getString(R.string.no_contacts_action));
|
list.setEmptyAction(getString(R.string.no_contacts_action));
|
||||||
|
|
||||||
|
viewModel.getContactListItems()
|
||||||
|
.observe(getViewLifecycleOwner(), result -> {
|
||||||
|
result.onError(this::handleException).onSuccess(items -> {
|
||||||
|
adapter.submitList(items);
|
||||||
|
if (requireNonNull(items).size() == 0) list.showData();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
viewModel.getHasPendingContacts()
|
||||||
|
.observe(getViewLifecycleOwner(), hasPending -> {
|
||||||
|
if (hasPending) showSnackBar();
|
||||||
|
else dismissSnackBar();
|
||||||
|
});
|
||||||
|
|
||||||
return contentView;
|
return contentView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(View view, ContactListItem item) {
|
||||||
|
Intent i = new Intent(getActivity(), ConversationActivity.class);
|
||||||
|
ContactId contactId = item.getContact().getId();
|
||||||
|
i.putExtra(CONTACT_ID, contactId.getInt());
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v,
|
public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v,
|
||||||
int itemId) {
|
int itemId) {
|
||||||
@@ -188,131 +140,20 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
|||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
eventBus.addListener(this);
|
viewModel.clearAllContactNotifications();
|
||||||
notificationManager.clearAllContactNotifications();
|
viewModel.clearAllContactAddedNotifications();
|
||||||
notificationManager.clearAllContactAddedNotifications();
|
viewModel.loadContacts();
|
||||||
loadContacts();
|
viewModel.checkForPendingContacts();
|
||||||
checkForPendingContacts();
|
|
||||||
list.startPeriodicUpdate();
|
list.startPeriodicUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkForPendingContacts() {
|
|
||||||
listener.runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
if (contactManager.getPendingContacts().isEmpty()) {
|
|
||||||
runOnUiThreadUnlessDestroyed(this::dismissSnackBar);
|
|
||||||
} else {
|
|
||||||
runOnUiThreadUnlessDestroyed(this::showSnackBar);
|
|
||||||
}
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
eventBus.removeListener(this);
|
|
||||||
adapter.clear();
|
|
||||||
list.showProgressBar();
|
|
||||||
list.stopPeriodicUpdate();
|
list.stopPeriodicUpdate();
|
||||||
dismissSnackBar();
|
dismissSnackBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadContacts() {
|
|
||||||
int revision = adapter.getRevision();
|
|
||||||
listener.runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
long start = now();
|
|
||||||
List<ContactListItem> contacts = new ArrayList<>();
|
|
||||||
for (Contact c : contactManager.getContacts()) {
|
|
||||||
try {
|
|
||||||
ContactId id = c.getId();
|
|
||||||
GroupCount count =
|
|
||||||
conversationManager.getGroupCount(id);
|
|
||||||
boolean connected =
|
|
||||||
connectionRegistry.isConnected(c.getId());
|
|
||||||
contacts.add(new ContactListItem(c, connected, count));
|
|
||||||
} catch (NoSuchContactException e) {
|
|
||||||
// Continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logDuration(LOG, "Full load", start);
|
|
||||||
displayContacts(revision, contacts);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void displayContacts(int revision, List<ContactListItem> contacts) {
|
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
|
||||||
if (revision == adapter.getRevision()) {
|
|
||||||
adapter.incrementRevision();
|
|
||||||
if (contacts.isEmpty()) list.showData();
|
|
||||||
else adapter.replaceAll(contacts);
|
|
||||||
} else {
|
|
||||||
LOG.info("Concurrent update, reloading");
|
|
||||||
loadContacts();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(Event e) {
|
|
||||||
if (e instanceof ContactAddedEvent) {
|
|
||||||
LOG.info("Contact added, reloading");
|
|
||||||
loadContacts();
|
|
||||||
} else if (e instanceof ContactConnectedEvent) {
|
|
||||||
setConnected(((ContactConnectedEvent) e).getContactId(), true);
|
|
||||||
} else if (e instanceof ContactDisconnectedEvent) {
|
|
||||||
setConnected(((ContactDisconnectedEvent) e).getContactId(), false);
|
|
||||||
} else if (e instanceof ContactRemovedEvent) {
|
|
||||||
LOG.info("Contact removed, removing item");
|
|
||||||
removeItem(((ContactRemovedEvent) e).getContactId());
|
|
||||||
} else if (e instanceof ConversationMessageReceivedEvent) {
|
|
||||||
LOG.info("Conversation message received, updating item");
|
|
||||||
ConversationMessageReceivedEvent<?> p =
|
|
||||||
(ConversationMessageReceivedEvent<?>) e;
|
|
||||||
ConversationMessageHeader h = p.getMessageHeader();
|
|
||||||
updateItem(p.getContactId(), h);
|
|
||||||
} else if (e instanceof PendingContactAddedEvent ||
|
|
||||||
e instanceof PendingContactRemovedEvent) {
|
|
||||||
checkForPendingContacts();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private void updateItem(ContactId c, ConversationMessageHeader h) {
|
|
||||||
adapter.incrementRevision();
|
|
||||||
int position = adapter.findItemPosition(c);
|
|
||||||
ContactListItem item = adapter.getItemAt(position);
|
|
||||||
if (item != null) {
|
|
||||||
item.addMessage(h);
|
|
||||||
adapter.updateItemAt(position, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private void removeItem(ContactId c) {
|
|
||||||
adapter.incrementRevision();
|
|
||||||
int position = adapter.findItemPosition(c);
|
|
||||||
ContactListItem item = adapter.getItemAt(position);
|
|
||||||
if (item != null) adapter.remove(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private void setConnected(ContactId c, boolean connected) {
|
|
||||||
adapter.incrementRevision();
|
|
||||||
int position = adapter.findItemPosition(c);
|
|
||||||
ContactListItem item = adapter.getItemAt(position);
|
|
||||||
if (item != null) {
|
|
||||||
item.setConnected(connected);
|
|
||||||
adapter.updateItemAt(position, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void showSnackBar() {
|
private void showSnackBar() {
|
||||||
if (snackbar != null) return;
|
if (snackbar != null) return;
|
||||||
@@ -335,5 +176,4 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
|||||||
Intent i = new Intent(getContext(), PendingContactListActivity.class);
|
Intent i = new Intent(getContext(), PendingContactListActivity.class);
|
||||||
startActivity(i);
|
startActivity(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,16 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||||
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
||||||
|
|
||||||
import javax.annotation.concurrent.NotThreadSafe;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
@NotThreadSafe
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ContactListItem extends ContactItem {
|
public class ContactListItem extends ContactItem
|
||||||
|
implements Comparable<ContactListItem> {
|
||||||
|
|
||||||
private boolean empty;
|
private final boolean empty;
|
||||||
private long timestamp;
|
private final long timestamp;
|
||||||
private int unread;
|
private final int unread;
|
||||||
|
|
||||||
public ContactListItem(Contact contact, boolean connected,
|
public ContactListItem(Contact contact, boolean connected,
|
||||||
GroupCount count) {
|
GroupCount count) {
|
||||||
@@ -23,10 +24,23 @@ public class ContactListItem extends ContactItem {
|
|||||||
this.timestamp = count.getLatestMsgTime();
|
this.timestamp = count.getLatestMsgTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
void addMessage(ConversationMessageHeader h) {
|
private ContactListItem(Contact contact, boolean connected, boolean empty,
|
||||||
empty = false;
|
int unread, long timestamp) {
|
||||||
if (h.getTimestamp() > timestamp) timestamp = h.getTimestamp();
|
super(contact, connected);
|
||||||
if (!h.isRead()) unread++;
|
this.empty = empty;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.unread = unread;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactListItem(ContactListItem item, boolean connected) {
|
||||||
|
this(item.getContact(), connected, item.empty, item.unread,
|
||||||
|
item.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactListItem(ContactListItem item, ConversationMessageHeader h) {
|
||||||
|
this(item.getContact(), item.isConnected(), false,
|
||||||
|
h.isRead() ? item.unread : item.unread + 1,
|
||||||
|
Math.max(h.getTimestamp(), item.timestamp));
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isEmpty() {
|
boolean isEmpty() {
|
||||||
@@ -41,4 +55,8 @@ public class ContactListItem extends ContactItem {
|
|||||||
return unread;
|
return unread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(ContactListItem o) {
|
||||||
|
return Long.compare(o.getTimestamp(), timestamp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ package org.briarproject.briar.android.contact;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
@@ -15,7 +13,6 @@ import javax.annotation.Nullable;
|
|||||||
|
|
||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
|
|
||||||
import static androidx.core.view.ViewCompat.setTransitionName;
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.formatDate;
|
import static org.briarproject.briar.android.util.UiUtils.formatDate;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@@ -39,7 +36,8 @@ class ContactListItemViewHolder extends ContactItemViewHolder<ContactListItem> {
|
|||||||
// unread count
|
// unread count
|
||||||
int unreadCount = item.getUnreadCount();
|
int unreadCount = item.getUnreadCount();
|
||||||
if (unreadCount > 0) {
|
if (unreadCount > 0) {
|
||||||
unread.setText(String.format(Locale.getDefault(), "%d", unreadCount));
|
unread.setText(
|
||||||
|
String.format(Locale.getDefault(), "%d", unreadCount));
|
||||||
unread.setVisibility(View.VISIBLE);
|
unread.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
unread.setVisibility(View.INVISIBLE);
|
unread.setVisibility(View.INVISIBLE);
|
||||||
@@ -52,10 +50,6 @@ class ContactListItemViewHolder extends ContactItemViewHolder<ContactListItem> {
|
|||||||
long timestamp = item.getTimestamp();
|
long timestamp = item.getTimestamp();
|
||||||
date.setText(formatDate(date.getContext(), timestamp));
|
date.setText(formatDate(date.getContext(), timestamp));
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactId c = item.getContact().getId();
|
|
||||||
setTransitionName(avatar, UiUtils.getAvatarTransitionName(c));
|
|
||||||
setTransitionName(bulb, UiUtils.getBulbTransitionName(c));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.briarproject.briar.android.contact;
|
||||||
|
|
||||||
|
import org.briarproject.briar.android.viewmodel.ViewModelKey;
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.multibindings.IntoMap;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public abstract class ContactListModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(ContactListViewModel.class)
|
||||||
|
abstract ViewModel bindContactListViewModel(
|
||||||
|
ContactListViewModel contactListViewModel);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
package org.briarproject.briar.android.contact;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||||
|
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||||
|
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
|
||||||
|
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
|
||||||
|
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.lifecycle.LifecycleManager;
|
||||||
|
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 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.conversation.ConversationManager;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
||||||
|
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.arch.core.util.Function;
|
||||||
|
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.logDuration;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ContactListViewModel extends DbViewModel implements EventListener {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(ContactListViewModel.class.getName());
|
||||||
|
|
||||||
|
private final ContactManager contactManager;
|
||||||
|
private final ConversationManager conversationManager;
|
||||||
|
private final ConnectionRegistry connectionRegistry;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final AndroidNotificationManager notificationManager;
|
||||||
|
|
||||||
|
private final MutableLiveData<LiveResult<List<ContactListItem>>>
|
||||||
|
contactListItems = new MutableLiveData<>();
|
||||||
|
|
||||||
|
private final MutableLiveData<Boolean> hasPendingContacts =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ContactListViewModel(Application application,
|
||||||
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
|
LifecycleManager lifecycleManager, TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor, ContactManager contactManager,
|
||||||
|
ConversationManager conversationManager,
|
||||||
|
ConnectionRegistry connectionRegistry, EventBus eventBus,
|
||||||
|
AndroidNotificationManager notificationManager) {
|
||||||
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||||
|
this.contactManager = contactManager;
|
||||||
|
this.conversationManager = conversationManager;
|
||||||
|
this.connectionRegistry = connectionRegistry;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.notificationManager = notificationManager;
|
||||||
|
this.eventBus.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCleared() {
|
||||||
|
super.onCleared();
|
||||||
|
eventBus.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadContacts() {
|
||||||
|
loadList(this::loadContacts, contactListItems::setValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ContactListItem> loadContacts(Transaction txn)
|
||||||
|
throws DbException {
|
||||||
|
long start = now();
|
||||||
|
List<ContactListItem> contacts = new ArrayList<>();
|
||||||
|
for (Contact c : contactManager.getContacts(txn)) {
|
||||||
|
ContactId id = c.getId();
|
||||||
|
MessageTracker.GroupCount count =
|
||||||
|
conversationManager.getGroupCount(txn, id);
|
||||||
|
boolean connected = connectionRegistry.isConnected(c.getId());
|
||||||
|
contacts.add(new ContactListItem(c, connected, count));
|
||||||
|
}
|
||||||
|
Collections.sort(contacts);
|
||||||
|
logDuration(LOG, "Full load", start);
|
||||||
|
return contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventOccurred(Event e) {
|
||||||
|
if (e instanceof ContactAddedEvent) {
|
||||||
|
LOG.info("Contact added, reloading");
|
||||||
|
loadContacts();
|
||||||
|
} else if (e instanceof ContactConnectedEvent) {
|
||||||
|
updateItem(((ContactConnectedEvent) e).getContactId(),
|
||||||
|
item -> new ContactListItem(item, true), false);
|
||||||
|
} else if (e instanceof ContactDisconnectedEvent) {
|
||||||
|
updateItem(((ContactDisconnectedEvent) e).getContactId(),
|
||||||
|
item -> new ContactListItem(item, false), false);
|
||||||
|
} else if (e instanceof ContactRemovedEvent) {
|
||||||
|
LOG.info("Contact removed, removing item");
|
||||||
|
removeItem(((ContactRemovedEvent) e).getContactId());
|
||||||
|
} else if (e instanceof ConversationMessageReceivedEvent) {
|
||||||
|
LOG.info("Conversation message received, updating item");
|
||||||
|
ConversationMessageReceivedEvent<?> p =
|
||||||
|
(ConversationMessageReceivedEvent<?>) e;
|
||||||
|
ConversationMessageHeader h = p.getMessageHeader();
|
||||||
|
updateItem(p.getContactId(), item -> new ContactListItem(item, h),
|
||||||
|
true);
|
||||||
|
} else if (e instanceof PendingContactAddedEvent ||
|
||||||
|
e instanceof PendingContactRemovedEvent) {
|
||||||
|
checkForPendingContacts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<LiveResult<List<ContactListItem>>> getContactListItems() {
|
||||||
|
return contactListItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<Boolean> getHasPendingContacts() {
|
||||||
|
return hasPendingContacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateItem(ContactId c,
|
||||||
|
Function<ContactListItem, ContactListItem> replacer, boolean sort) {
|
||||||
|
List<ContactListItem> list = updateListItems(contactListItems,
|
||||||
|
itemToTest -> itemToTest.getContact().getId().equals(c),
|
||||||
|
replacer);
|
||||||
|
if (list == null) return;
|
||||||
|
if (sort) Collections.sort(list);
|
||||||
|
contactListItems.setValue(new LiveResult<>(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeItem(ContactId c) {
|
||||||
|
List<ContactListItem> list = removeListItems(contactListItems,
|
||||||
|
itemToTest -> itemToTest.getContact().getId().equals(c));
|
||||||
|
if (list == null) return;
|
||||||
|
contactListItems.setValue(new LiveResult<>(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkForPendingContacts() {
|
||||||
|
runOnDbThread(() -> {
|
||||||
|
try {
|
||||||
|
boolean hasPending =
|
||||||
|
!contactManager.getPendingContacts().isEmpty();
|
||||||
|
hasPendingContacts.postValue(hasPending);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearAllContactNotifications() {
|
||||||
|
notificationManager.clearAllContactNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearAllContactAddedNotifications() {
|
||||||
|
notificationManager.clearAllContactAddedNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package org.briarproject.briar.android.contact;
|
|
||||||
|
|
||||||
import dagger.Module;
|
|
||||||
|
|
||||||
@Module
|
|
||||||
public class ContactModule {
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package org.briarproject.briar.android.contact;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class LegacyContactListAdapter extends
|
||||||
|
BaseContactListAdapter<ContactListItem, ContactListItemViewHolder> {
|
||||||
|
|
||||||
|
public LegacyContactListAdapter(Context context,
|
||||||
|
OnContactClickListener<ContactListItem> listener) {
|
||||||
|
super(context, ContactListItem.class, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
|
||||||
|
int i) {
|
||||||
|
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
|
||||||
|
R.layout.list_item_contact, viewGroup, false);
|
||||||
|
|
||||||
|
return new ContactListItemViewHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) {
|
||||||
|
// check for all properties that influence visual
|
||||||
|
// representation of contact
|
||||||
|
if (c1.isEmpty() != c2.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (c1.getUnreadCount() != c2.getUnreadCount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (c1.getTimestamp() != c2.getTimestamp()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return c1.isConnected() == c2.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(ContactListItem c1, ContactListItem c2) {
|
||||||
|
return Long.compare(c2.getTimestamp(), c1.getTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -113,7 +113,6 @@ import static android.os.Build.VERSION.SDK_INT;
|
|||||||
import static android.view.Gravity.RIGHT;
|
import static android.view.Gravity.RIGHT;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
||||||
import static androidx.core.view.ViewCompat.setTransitionName;
|
|
||||||
import static androidx.lifecycle.Lifecycle.State.STARTED;
|
import static androidx.lifecycle.Lifecycle.State.STARTED;
|
||||||
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
|
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
|
||||||
import static java.util.Collections.sort;
|
import static java.util.Collections.sort;
|
||||||
@@ -134,8 +133,6 @@ import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHME
|
|||||||
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
|
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
|
||||||
import static org.briarproject.briar.android.conversation.ImageActivity.ITEM_ID;
|
import static org.briarproject.briar.android.conversation.ImageActivity.ITEM_ID;
|
||||||
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
|
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
||||||
@@ -252,9 +249,6 @@ public class ConversationActivity extends BriarActivity
|
|||||||
viewModel.getAddedPrivateMessage().observeEvent(this,
|
viewModel.getAddedPrivateMessage().observeEvent(this,
|
||||||
this::onAddedPrivateMessage);
|
this::onAddedPrivateMessage);
|
||||||
|
|
||||||
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
|
||||||
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
|
|
||||||
|
|
||||||
visitor = new ConversationVisitor(this, this, this,
|
visitor = new ConversationVisitor(this, this, this,
|
||||||
viewModel.getContactDisplayName());
|
viewModel.getContactDisplayName());
|
||||||
adapter = new ConversationAdapter(this, this);
|
adapter = new ConversationAdapter(this, this);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
||||||
import org.briarproject.briar.android.contact.ContactListAdapter;
|
|
||||||
import org.briarproject.briar.android.contact.ContactListItem;
|
import org.briarproject.briar.android.contact.ContactListItem;
|
||||||
|
import org.briarproject.briar.android.contact.LegacyContactListAdapter;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||||
@@ -45,7 +45,7 @@ public class ContactChooserFragment extends BaseFragment {
|
|||||||
private static final Logger LOG = Logger.getLogger(TAG);
|
private static final Logger LOG = Logger.getLogger(TAG);
|
||||||
|
|
||||||
private BriarRecyclerView list;
|
private BriarRecyclerView list;
|
||||||
private ContactListAdapter adapter;
|
private LegacyContactListAdapter adapter;
|
||||||
private ContactId contactId;
|
private ContactId contactId;
|
||||||
|
|
||||||
// Fields that are accessed from background threads must be volatile
|
// Fields that are accessed from background threads must be volatile
|
||||||
@@ -72,7 +72,8 @@ public class ContactChooserFragment extends BaseFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
public View onCreateView(LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
|
||||||
View contentView = inflater.inflate(R.layout.list, container, false);
|
View contentView = inflater.inflate(R.layout.list, container, false);
|
||||||
@@ -83,7 +84,7 @@ public class ContactChooserFragment extends BaseFragment {
|
|||||||
Contact c2 = item.getContact();
|
Contact c2 = item.getContact();
|
||||||
showMessageScreen(c1, c2);
|
showMessageScreen(c1, c2);
|
||||||
};
|
};
|
||||||
adapter = new ContactListAdapter(requireActivity(),
|
adapter = new LegacyContactListAdapter(requireActivity(),
|
||||||
onContactClickListener);
|
onContactClickListener);
|
||||||
|
|
||||||
list = contentView.findViewById(R.id.list);
|
list = contentView.findViewById(R.id.list);
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import android.widget.TextView;
|
|||||||
import com.google.android.material.textfield.TextInputLayout;
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
@@ -240,14 +239,6 @@ public class UiUtils {
|
|||||||
textView.setMovementMethod(new LinkMovementMethod());
|
textView.setMovementMethod(new LinkMovementMethod());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getAvatarTransitionName(ContactId c) {
|
|
||||||
return "avatar" + c.getInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getBulbTransitionName(ContactId c) {
|
|
||||||
return "bulb" + c.getInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static OnClickListener getGoToSettingsListener(Context context) {
|
public static OnClickListener getGoToSettingsListener(Context context) {
|
||||||
return (dialog, which) -> {
|
return (dialog, which) -> {
|
||||||
Intent i = new Intent();
|
Intent i = new Intent();
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ public interface ConversationManager {
|
|||||||
*/
|
*/
|
||||||
GroupCount getGroupCount(ContactId c) throws DbException;
|
GroupCount getGroupCount(ContactId c) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the unified group count for all private conversation messages.
|
||||||
|
*/
|
||||||
|
GroupCount getGroupCount(Transaction txn, ContactId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes all messages exchanged with the given contact.
|
* Deletes all messages exchanged with the given contact.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -57,20 +57,21 @@ class ConversationManagerImpl implements ConversationManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GroupCount getGroupCount(ContactId contactId) throws DbException {
|
public GroupCount getGroupCount(ContactId contactId) throws DbException {
|
||||||
|
return db.transactionWithResult(true,
|
||||||
|
txn -> getGroupCount(txn, contactId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupCount getGroupCount(Transaction txn, ContactId contactId)
|
||||||
|
throws DbException {
|
||||||
int msgCount = 0, unreadCount = 0;
|
int msgCount = 0, unreadCount = 0;
|
||||||
long latestTime = 0;
|
long latestTime = 0;
|
||||||
Transaction txn = db.startTransaction(true);
|
for (ConversationClient client : clients) {
|
||||||
try {
|
GroupCount count = client.getGroupCount(txn, contactId);
|
||||||
for (ConversationClient client : clients) {
|
msgCount += count.getMsgCount();
|
||||||
GroupCount count = client.getGroupCount(txn, contactId);
|
unreadCount += count.getUnreadCount();
|
||||||
msgCount += count.getMsgCount();
|
if (count.getLatestMsgTime() > latestTime)
|
||||||
unreadCount += count.getUnreadCount();
|
latestTime = count.getLatestMsgTime();
|
||||||
if (count.getLatestMsgTime() > latestTime)
|
|
||||||
latestTime = count.getLatestMsgTime();
|
|
||||||
}
|
|
||||||
db.commitTransaction(txn);
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
}
|
||||||
return new GroupCount(msgCount, unreadCount, latestTime);
|
return new GroupCount(msgCount, unreadCount, latestTime);
|
||||||
}
|
}
|
||||||
@@ -87,7 +88,8 @@ class ConversationManagerImpl implements ConversationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeletionResult deleteMessages(ContactId c, Collection<MessageId> toDelete)
|
public DeletionResult deleteMessages(ContactId c,
|
||||||
|
Collection<MessageId> toDelete)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
return db.transactionWithResult(false, txn -> {
|
return db.transactionWithResult(false, txn -> {
|
||||||
DeletionResult result = new DeletionResult();
|
DeletionResult result = new DeletionResult();
|
||||||
|
|||||||
Reference in New Issue
Block a user