diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 760c3af82..0729aa0f2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -32,6 +32,7 @@ import org.briarproject.briar.android.account.LockManagerImpl; import org.briarproject.briar.android.account.SetupModule; import org.briarproject.briar.android.contact.ContactListModule; import org.briarproject.briar.android.forum.ForumModule; +import org.briarproject.briar.android.introduction.IntroductionModule; import org.briarproject.briar.android.keyagreement.ContactExchangeModule; import org.briarproject.briar.android.logging.LoggingModule; import org.briarproject.briar.android.login.LoginModule; @@ -82,6 +83,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; SettingsModule.class, DevReportModule.class, ContactListModule.class, + IntroductionModule.class, // below need to be within same scope as ViewModelProvider.Factory ForumModule.class, GroupListModule.class, diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/BaseContactListAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/BaseContactListAdapter.java index 056d4c77a..d2e63e174 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/BaseContactListAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/BaseContactListAdapter.java @@ -1,7 +1,6 @@ package org.briarproject.briar.android.contact; import android.content.Context; -import android.view.View; import org.briarproject.briar.android.util.BriarAdapter; @@ -45,8 +44,4 @@ public abstract class BaseContactListAdapter { - void onItemClick(View view, I item); - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactItemViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactItemViewHolder.java index ffdf9d6ec..622bfe007 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactItemViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactItemViewHolder.java @@ -7,7 +7,6 @@ import android.widget.TextView; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; -import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import javax.annotation.Nullable; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListAdapter.java index 13b64815e..103bc46c4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListAdapter.java @@ -7,7 +7,6 @@ import android.view.ViewGroup; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NullSafety; import org.briarproject.briar.R; -import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import androidx.recyclerview.widget.DiffUtil.ItemCallback; import androidx.recyclerview.widget.ListAdapter; @@ -16,9 +15,6 @@ import androidx.recyclerview.widget.ListAdapter; public class ContactListAdapter extends ListAdapter { - // 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 listener; public ContactListAdapter( diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java index b8b743935..9d32e0cab 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java @@ -15,7 +15,6 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.add.remote.AddContactActivity; import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity; import org.briarproject.briar.android.conversation.ConversationActivity; @@ -101,8 +100,6 @@ public class ContactListFragment extends BaseFragment .observe(getViewLifecycleOwner(), result -> { result.onError(this::handleException).onSuccess(items -> { adapter.submitList(items); - // TODO remove when BriarRecyclerView was adapted - list.showData(); }); }); viewModel.getHasPendingContacts() diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListItemViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListItemViewHolder.java index 5b5fa172a..149a26972 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListItemViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListItemViewHolder.java @@ -5,7 +5,6 @@ import android.widget.TextView; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; -import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import java.util.Locale; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListViewModel.java index 71a1c5740..f8e0184e3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListViewModel.java @@ -3,70 +3,41 @@ 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.avatar.event.AvatarUpdatedEvent; -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 org.briarproject.briar.api.identity.AuthorInfo; import org.briarproject.briar.api.identity.AuthorManager; -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 { +class ContactListViewModel extends ContactsViewModel { private static final Logger LOG = getLogger(ContactListViewModel.class.getName()); - private final ContactManager contactManager; - private final AuthorManager authorManager; - private final ConversationManager conversationManager; - private final ConnectionRegistry connectionRegistry; - private final EventBus eventBus; private final AndroidNotificationManager notificationManager; - private final MutableLiveData>> - contactListItems = new MutableLiveData<>(); - private final MutableLiveData hasPendingContacts = new MutableLiveData<>(); @@ -79,99 +50,25 @@ class ContactListViewModel extends DbViewModel implements EventListener { ConversationManager conversationManager, ConnectionRegistry connectionRegistry, EventBus eventBus, AndroidNotificationManager notificationManager) { - super(application, dbExecutor, lifecycleManager, db, androidExecutor); - this.contactManager = contactManager; - this.authorManager = authorManager; - this.conversationManager = conversationManager; - this.connectionRegistry = connectionRegistry; - this.eventBus = eventBus; + super(application, dbExecutor, lifecycleManager, db, androidExecutor, + contactManager, authorManager, conversationManager, + connectionRegistry, 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 loadContacts(Transaction txn) - throws DbException { - long start = now(); - List contacts = new ArrayList<>(); - for (Contact c : contactManager.getContacts(txn)) { - ContactId id = c.getId(); - AuthorInfo authorInfo = authorManager.getAuthorInfo(txn, c); - MessageTracker.GroupCount count = - conversationManager.getGroupCount(txn, id); - boolean connected = connectionRegistry.isConnected(c.getId()); - contacts.add(new ContactListItem(c, authorInfo, 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 || + super.eventOccurred(e); + if (e instanceof PendingContactAddedEvent || e instanceof PendingContactRemovedEvent) { checkForPendingContacts(); - } else if (e instanceof AvatarUpdatedEvent) { - AvatarUpdatedEvent a = (AvatarUpdatedEvent) e; - updateItem(a.getContactId(), item -> new ContactListItem(item, - a.getAttachmentHeader()), false); } } - LiveData>> getContactListItems() { - return contactListItems; - } - LiveData getHasPendingContacts() { return hasPendingContacts; } - private void updateItem(ContactId c, - Function replacer, boolean sort) { - List 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 list = removeListItems(contactListItems, - itemToTest -> itemToTest.getContact().getId().equals(c)); - if (list == null) return; - contactListItems.setValue(new LiveResult<>(list)); - } - void checkForPendingContacts() { runOnDbThread(() -> { try { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactsViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactsViewModel.java new file mode 100644 index 000000000..e0b1ab910 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactsViewModel.java @@ -0,0 +1,170 @@ +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.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.avatar.event.AvatarUpdatedEvent; +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 org.briarproject.briar.api.identity.AuthorInfo; +import org.briarproject.briar.api.identity.AuthorManager; + +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.annotation.UiThread; +import androidx.arch.core.util.Function; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logDuration; +import static org.briarproject.bramble.util.LogUtils.now; + +@NotNullByDefault +public class ContactsViewModel extends DbViewModel implements EventListener { + + private static final Logger LOG = + getLogger(ContactsViewModel.class.getName()); + + protected final ContactManager contactManager; + private final AuthorManager authorManager; + private final ConversationManager conversationManager; + private final ConnectionRegistry connectionRegistry; + private final EventBus eventBus; + + private final MutableLiveData>> + contactListItems = new MutableLiveData<>(); + + @Inject + public ContactsViewModel(Application application, + @DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, TransactionManager db, + AndroidExecutor androidExecutor, ContactManager contactManager, + AuthorManager authorManager, + ConversationManager conversationManager, + ConnectionRegistry connectionRegistry, EventBus eventBus) { + super(application, dbExecutor, lifecycleManager, db, androidExecutor); + this.contactManager = contactManager; + this.authorManager = authorManager; + this.conversationManager = conversationManager; + this.connectionRegistry = connectionRegistry; + this.eventBus = eventBus; + this.eventBus.addListener(this); + } + + @Override + protected void onCleared() { + super.onCleared(); + eventBus.removeListener(this); + } + + protected void loadContacts() { + loadList(this::loadContacts, contactListItems::setValue); + } + + private List loadContacts(Transaction txn) + throws DbException { + long start = now(); + List contacts = new ArrayList<>(); + for (Contact c : contactManager.getContacts(txn)) { + ContactId id = c.getId(); + if (!displayContact(id)) { + continue; + } + AuthorInfo authorInfo = authorManager.getAuthorInfo(txn, c); + MessageTracker.GroupCount count = + conversationManager.getGroupCount(txn, id); + boolean connected = connectionRegistry.isConnected(c.getId()); + contacts.add(new ContactListItem(c, authorInfo, connected, count)); + } + Collections.sort(contacts); + logDuration(LOG, "Full load", start); + return contacts; + } + + /** + * Override this method to display only a subset of contacts. + */ + protected boolean displayContact(ContactId contactId) { + return true; + } + + @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 AvatarUpdatedEvent) { + AvatarUpdatedEvent a = (AvatarUpdatedEvent) e; + updateItem(a.getContactId(), item -> new ContactListItem(item, + a.getAttachmentHeader()), false); + } + } + + public LiveData>> getContactListItems() { + return contactListItems; + } + + @UiThread + private void updateItem(ContactId c, + Function replacer, boolean sort) { + List list = updateListItems(contactListItems, + itemToTest -> itemToTest.getContact().getId().equals(c), + replacer); + if (list == null) return; + if (sort) Collections.sort(list); + contactListItems.setValue(new LiveResult<>(list)); + } + + @UiThread + private void removeItem(ContactId c) { + List list = removeListItems(contactListItems, + itemToTest -> itemToTest.getContact().getId().equals(c)); + if (list == null) return; + contactListItems.setValue(new LiveResult<>(list)); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/LegacyContactListAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/LegacyContactListAdapter.java deleted file mode 100644 index e8777bbdb..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/LegacyContactListAdapter.java +++ /dev/null @@ -1,50 +0,0 @@ -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 { - - public LegacyContactListAdapter(Context context, - OnContactClickListener 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()); - } - -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/OnContactClickListener.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/OnContactClickListener.java new file mode 100644 index 000000000..8381c1923 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/OnContactClickListener.java @@ -0,0 +1,9 @@ +package org.briarproject.briar.android.contact; + +import android.view.View; + +public interface OnContactClickListener { + + void onItemClick(View view, I item); + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorAdapter.java index f6252717e..6f9f7ddca 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorAdapter.java @@ -6,6 +6,7 @@ import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.android.contact.BaseContactListAdapter; import org.briarproject.briar.android.contact.ContactItemViewHolder; +import org.briarproject.briar.android.contact.OnContactClickListener; import java.util.ArrayList; import java.util.Collection; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorFragment.java index 0d7558f02..5d5ce1df3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorFragment.java @@ -12,8 +12,8 @@ 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.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.ContactItemViewHolder; +import org.briarproject.briar.android.contact.OnContactClickListener; import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.view.BriarRecyclerView; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseSelectableContactHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseSelectableContactHolder.java index df10f082c..1e4388c26 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseSelectableContactHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseSelectableContactHolder.java @@ -6,8 +6,8 @@ import android.widget.TextView; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; -import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.ContactItemViewHolder; +import org.briarproject.briar.android.contact.OnContactClickListener; import javax.annotation.Nullable; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorAdapter.java index 39144d3d6..97c45b5c5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorAdapter.java @@ -7,6 +7,7 @@ import android.view.ViewGroup; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; +import org.briarproject.briar.android.contact.OnContactClickListener; @NotNullByDefault class ContactSelectorAdapter extends diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorFragment.java index ec6cd389b..25c2ef5fc 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorFragment.java @@ -8,7 +8,7 @@ import android.view.MenuItem; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; -import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; +import org.briarproject.briar.android.contact.OnContactClickListener; @MethodsNotNullByDefault @ParametersNotNullByDefault diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/SelectableContactHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/SelectableContactHolder.java index cf0d2cdab..5ec6c782c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/SelectableContactHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/SelectableContactHolder.java @@ -3,7 +3,7 @@ package org.briarproject.briar.android.contactselection; import android.view.View; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; +import org.briarproject.briar.android.contact.OnContactClickListener; import javax.annotation.Nullable; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/ContactChooserFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/ContactChooserFragment.java index 7a655f2af..389a30fb6 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/introduction/ContactChooserFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/ContactChooserFragment.java @@ -5,74 +5,41 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -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.db.DbException; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; +import org.briarproject.briar.android.contact.ContactListAdapter; import org.briarproject.briar.android.contact.ContactListItem; -import org.briarproject.briar.android.contact.LegacyContactListAdapter; +import org.briarproject.briar.android.contact.OnContactClickListener; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.view.BriarRecyclerView; -import org.briarproject.briar.api.client.MessageTracker.GroupCount; -import org.briarproject.briar.api.conversation.ConversationManager; -import org.briarproject.briar.api.identity.AuthorInfo; -import org.briarproject.briar.api.identity.AuthorManager; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; import javax.inject.Inject; import androidx.annotation.Nullable; -import androidx.annotation.UiThread; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; -import static java.util.logging.Level.WARNING; -import static org.briarproject.bramble.util.LogUtils.logException; -import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID; - -@UiThread @MethodsNotNullByDefault @ParametersNotNullByDefault -public class ContactChooserFragment extends BaseFragment { +public class ContactChooserFragment extends BaseFragment + implements OnContactClickListener { - public static final String TAG = ContactChooserFragment.class.getName(); - private static final Logger LOG = Logger.getLogger(TAG); + private static final String TAG = ContactChooserFragment.class.getName(); + @Inject + ViewModelProvider.Factory viewModelFactory; + + private IntroductionViewModel viewModel; + private final ContactListAdapter adapter = new ContactListAdapter(this); private BriarRecyclerView list; - private LegacyContactListAdapter adapter; - private ContactId contactId; - - // Fields that are accessed from background threads must be volatile - private volatile Contact c1; - @Inject - volatile ContactManager contactManager; - @Inject - volatile AuthorManager authorManager; - @Inject - volatile ConversationManager conversationManager; - @Inject - volatile ConnectionRegistry connectionRegistry; - - public static ContactChooserFragment newInstance(ContactId id) { - Bundle args = new Bundle(); - - ContactChooserFragment fragment = new ContactChooserFragment(); - args.putInt(CONTACT_ID, id.getInt()); - fragment.setArguments(args); - return fragment; - } @Override public void injectFragment(ActivityComponent component) { component.inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(IntroductionViewModel.class); } @Override @@ -80,23 +47,20 @@ public class ContactChooserFragment extends BaseFragment { @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View contentView = inflater.inflate(R.layout.list, container, false); + // change toolbar text (relevant when navigating back to this fragment) + requireActivity().setTitle(R.string.introduction_activity_title); - OnContactClickListener onContactClickListener = - (view, item) -> { - if (c1 == null) throw new IllegalStateException(); - Contact c2 = item.getContact(); - showMessageScreen(c1, c2); - }; - adapter = new LegacyContactListAdapter(requireActivity(), - onContactClickListener); + View contentView = inflater.inflate(R.layout.list, container, false); list = contentView.findViewById(R.id.list); list.setLayoutManager(new LinearLayoutManager(getActivity())); list.setAdapter(adapter); list.setEmptyText(R.string.no_contacts); - contactId = new ContactId(requireArguments().getInt(CONTACT_ID)); + viewModel.getContactListItems().observe(getViewLifecycleOwner(), + result -> result.onError(this::handleException) + .onSuccess(adapter::submitList) + ); return contentView; } @@ -104,14 +68,13 @@ public class ContactChooserFragment extends BaseFragment { @Override public void onStart() { super.onStart(); - loadContacts(); + list.startPeriodicUpdate(); } @Override public void onStop() { super.onStop(); - adapter.clear(); - list.showProgressBar(); + list.stopPeriodicUpdate(); } @Override @@ -119,43 +82,9 @@ public class ContactChooserFragment extends BaseFragment { return TAG; } - private void loadContacts() { - listener.runOnDbThread(() -> { - try { - List contacts = new ArrayList<>(); - for (Contact c : contactManager.getContacts()) { - if (c.getId().equals(contactId)) { - c1 = c; - } else { - AuthorInfo authorInfo = authorManager.getAuthorInfo(c); - ContactId id = c.getId(); - GroupCount count = - conversationManager.getGroupCount(id); - boolean connected = - connectionRegistry.isConnected(c.getId()); - contacts.add(new ContactListItem(c, authorInfo, - connected, count)); - } - } - displayContacts(contacts); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); + @Override + public void onItemClick(View view, ContactListItem item) { + viewModel.setSecondContactId(item.getContact().getId()); + viewModel.triggerContactSelected(); } - - private void displayContacts(List contacts) { - runOnUiThreadUnlessDestroyed(() -> { - if (contacts.isEmpty()) list.showData(); - else adapter.addAll(contacts); - }); - } - - private void showMessageScreen(Contact c1, Contact c2) { - IntroductionMessageFragment messageFragment = - IntroductionMessageFragment - .newInstance(c1.getId().getInt(), c2.getId().getInt()); - showNextFragment(messageFragment); - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/ContactListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/ContactListViewModel.java new file mode 100644 index 000000000..e69de29bb diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionActivity.java index 137d0c585..f2307545d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionActivity.java @@ -9,30 +9,67 @@ import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModelProvider; + import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID; public class IntroductionActivity extends BriarActivity implements BaseFragmentListener { + @Inject + ViewModelProvider.Factory viewModelFactory; + + private IntroductionViewModel viewModel; + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + viewModel = new ViewModelProvider(this, viewModelFactory) + .get(IntroductionViewModel.class); + } + + private static final String BUNDLE_CONTACT2 = "contact2"; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); - int id = intent.getIntExtra(CONTACT_ID, -1); - if (id == -1) throw new IllegalStateException("No ContactId"); - ContactId contactId = new ContactId(id); + int contactId1 = intent.getIntExtra(CONTACT_ID, -1); + if (contactId1 == -1) + throw new IllegalStateException("No ContactId"); + ContactId firstContactId = new ContactId(contactId1); + + viewModel.setFirstContactId(firstContactId); setContentView(R.layout.activity_fragment_container); if (savedInstanceState == null) { - showInitialFragment(ContactChooserFragment.newInstance(contactId)); + showInitialFragment(new ContactChooserFragment()); + } else { + int contactId2 = savedInstanceState.getInt(BUNDLE_CONTACT2); + ContactId secondContactId = new ContactId(contactId2); + viewModel.setSecondContactId(secondContactId); } + + viewModel.getSecondContactSelected().observeEvent(this, e -> { + IntroductionMessageFragment fragment = + new IntroductionMessageFragment(); + showNextFragment(fragment); + }); } @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + + ContactId secondContactId = viewModel.getSecondContactId(); + if (secondContactId != null) { + outState.putInt(BUNDLE_CONTACT2, secondContactId.getInt()); + } } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionInfo.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionInfo.java new file mode 100644 index 000000000..b8061b83d --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionInfo.java @@ -0,0 +1,28 @@ +package org.briarproject.briar.android.introduction; + +import org.briarproject.briar.android.contact.ContactItem; + +class IntroductionInfo { + private final ContactItem c1; + private final ContactItem c2; + private final boolean possible; + + IntroductionInfo(ContactItem c1, ContactItem c2, + boolean possible) { + this.c1 = c1; + this.c2 = c2; + this.possible = possible; + } + + ContactItem getContact1() { + return c1; + } + + ContactItem getContact2() { + return c2; + } + + boolean isPossible() { + return possible; + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java index 28ed4bc8b..8c2efe46f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java @@ -1,6 +1,5 @@ package org.briarproject.briar.android.introduction; -import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.MenuItem; @@ -8,12 +7,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.TextView; -import android.widget.Toast; -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.db.DbException; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; @@ -24,25 +18,19 @@ import org.briarproject.briar.android.view.TextInputView; import org.briarproject.briar.android.view.TextSendController; import org.briarproject.briar.android.view.TextSendController.SendListener; import org.briarproject.briar.api.attachment.AttachmentHeader; -import org.briarproject.briar.api.identity.AuthorInfo; -import org.briarproject.briar.api.identity.AuthorManager; -import org.briarproject.briar.api.introduction.IntroductionManager; import java.util.List; -import java.util.logging.Logger; import javax.inject.Inject; import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; +import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModelProvider; import de.hdodenhof.circleimageview.CircleImageView; import static android.app.Activity.RESULT_OK; import static android.view.View.GONE; import static android.view.View.VISIBLE; -import static android.widget.Toast.LENGTH_SHORT; -import static java.util.logging.Level.WARNING; -import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName; import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard; import static org.briarproject.briar.android.view.AuthorView.setAvatar; @@ -53,45 +41,21 @@ import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_ public class IntroductionMessageFragment extends BaseFragment implements SendListener { - public static final String TAG = + private static final String TAG = IntroductionMessageFragment.class.getName(); - private static final Logger LOG = Logger.getLogger(TAG); - private final static String CONTACT_ID_1 = "contact1"; - private final static String CONTACT_ID_2 = "contact2"; + @Inject + ViewModelProvider.Factory viewModelFactory; + + private IntroductionViewModel viewModel; - private IntroductionActivity introductionActivity; private ViewHolder ui; - private Contact contact1, contact2; - - // Fields that are accessed from background threads must be volatile - @Inject - protected volatile ContactManager contactManager; - @Inject - protected volatile AuthorManager authorManager; - @Inject - protected volatile IntroductionManager introductionManager; - - public static IntroductionMessageFragment newInstance(int contactId1, - int contactId2) { - Bundle args = new Bundle(); - args.putInt(CONTACT_ID_1, contactId1); - args.putInt(CONTACT_ID_2, contactId2); - IntroductionMessageFragment fragment = - new IntroductionMessageFragment(); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - introductionActivity = (IntroductionActivity) context; - } @Override public void injectFragment(ActivityComponent component) { component.inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(IntroductionViewModel.class); } @Override @@ -100,18 +64,7 @@ public class IntroductionMessageFragment extends BaseFragment @Nullable Bundle savedInstanceState) { // change toolbar text - ActionBar actionBar = introductionActivity.getSupportActionBar(); - if (actionBar != null) { - actionBar.setTitle(R.string.introduction_message_title); - } - - // get contact IDs from fragment arguments - Bundle args = requireArguments(); - int contactId1 = args.getInt(CONTACT_ID_1, -1); - int contactId2 = args.getInt(CONTACT_ID_2, -1); - if (contactId1 == -1 || contactId2 == -1) { - throw new AssertionError("Use newInstance() to instantiate"); - } + requireActivity().setTitle(R.string.introduction_message_title); // inflate view View v = inflater.inflate(R.layout.introduction_message, container, @@ -123,69 +76,44 @@ public class IntroductionMessageFragment extends BaseFragment ui.message.setMaxTextLength(MAX_INTRODUCTION_TEXT_LENGTH); ui.message.setReady(false); - // get contacts and then show view - prepareToSetUpViews(contactId1, contactId2); + viewModel.getIntroductionInfo().observe(getViewLifecycleOwner(), ii -> { + if (ii == null) { + return; + } + setUpViews(ii.getContact1(), ii.getContact2(), + ii.isPossible()); + }); return v; } - @Override - public void onStart() { - super.onStart(); - } - @Override public String getUniqueTag() { return TAG; } - private void prepareToSetUpViews(int contactId1, int contactId2) { - introductionActivity.runOnDbThread(() -> { - try { - Contact contact1 = - contactManager.getContact(new ContactId(contactId1)); - Contact contact2 = - contactManager.getContact(new ContactId(contactId2)); - AuthorInfo a1 = authorManager.getAuthorInfo(contact1); - AuthorInfo a2 = authorManager.getAuthorInfo(contact2); - boolean possible = - introductionManager.canIntroduce(contact1, contact2); - ContactItem c1 = new ContactItem(contact1, a1); - ContactItem c2 = new ContactItem(contact2, a2); - setUpViews(c1, c2, possible); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); - } - private void setUpViews(ContactItem c1, ContactItem c2, boolean possible) { - introductionActivity.runOnUiThreadUnlessDestroyed(() -> { - contact1 = c1.getContact(); - contact2 = c2.getContact(); + // set avatars + setAvatar(ui.avatar1, c1); + setAvatar(ui.avatar2, c2); - // set avatars - setAvatar(ui.avatar1, c1); - setAvatar(ui.avatar2, c2); + // set contact names + ui.contactName1.setText(getContactDisplayName(c1.getContact())); + ui.contactName2.setText(getContactDisplayName(c2.getContact())); - // set contact names - ui.contactName1.setText(getContactDisplayName(c1.getContact())); - ui.contactName2.setText(getContactDisplayName(c2.getContact())); + // hide progress bar + ui.progressBar.setVisibility(GONE); - // hide progress bar - ui.progressBar.setVisibility(GONE); - - if (possible) { - // show views - ui.notPossible.setVisibility(GONE); - ui.message.setVisibility(VISIBLE); - ui.message.setReady(true); - ui.message.showSoftKeyboard(); - } else { - ui.notPossible.setVisibility(VISIBLE); - ui.message.setVisibility(GONE); - } - }); + if (possible) { + // show views + ui.notPossible.setVisibility(GONE); + ui.message.setVisibility(VISIBLE); + ui.message.setReady(true); + ui.message.showSoftKeyboard(); + } else { + ui.notPossible.setVisibility(VISIBLE); + ui.message.setVisibility(GONE); + } } @Override @@ -193,7 +121,7 @@ public class IntroductionMessageFragment extends BaseFragment switch (item.getItemId()) { case android.R.id.home: hideSoftKeyboard(ui.message); - introductionActivity.onBackPressed(); + requireActivity().onBackPressed(); return true; default: return super.onOptionsItemSelected(item); @@ -206,32 +134,13 @@ public class IntroductionMessageFragment extends BaseFragment // disable button to prevent accidental double invitations ui.message.setReady(false); - makeIntroduction(contact1, contact2, text); + viewModel.makeIntroduction(text); // don't wait for the introduction to be made before finishing activity hideSoftKeyboard(ui.message); - introductionActivity.setResult(RESULT_OK); - introductionActivity.supportFinishAfterTransition(); - } - - private void makeIntroduction(Contact c1, Contact c2, - @Nullable String text) { - introductionActivity.runOnDbThread(() -> { - // actually make the introduction - try { - long timestamp = System.currentTimeMillis(); - introductionManager.makeIntroduction(c1, c2, text, timestamp); - } catch (DbException e) { - logException(LOG, WARNING, e); - introductionError(); - } - }); - } - - private void introductionError() { - introductionActivity.runOnUiThreadUnlessDestroyed( - () -> Toast.makeText(introductionActivity, - R.string.introduction_error, LENGTH_SHORT).show()); + FragmentActivity activity = requireActivity(); + activity.setResult(RESULT_OK); + activity.supportFinishAfterTransition(); } private static class ViewHolder { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionModule.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionModule.java new file mode 100644 index 000000000..7b3678ee7 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionModule.java @@ -0,0 +1,19 @@ +package org.briarproject.briar.android.introduction; + +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 IntroductionModule { + + @Binds + @IntoMap + @ViewModelKey(IntroductionViewModel.class) + abstract ViewModel bindIntroductionViewModel( + IntroductionViewModel introductionViewModel); + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionViewModel.java new file mode 100644 index 000000000..94ea3a4f3 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionViewModel.java @@ -0,0 +1,181 @@ +package org.briarproject.briar.android.introduction; + +import android.app.Application; +import android.widget.Toast; + +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.db.DatabaseExecutor; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.TransactionManager; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.briar.R; +import org.briarproject.briar.android.contact.ContactItem; +import org.briarproject.briar.android.contact.ContactsViewModel; +import org.briarproject.briar.android.viewmodel.LiveEvent; +import org.briarproject.briar.android.viewmodel.MutableLiveEvent; +import org.briarproject.briar.api.conversation.ConversationManager; +import org.briarproject.briar.api.identity.AuthorInfo; +import org.briarproject.briar.api.identity.AuthorManager; +import org.briarproject.briar.api.introduction.IntroductionManager; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import androidx.annotation.Nullable; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import static android.widget.Toast.LENGTH_SHORT; +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.logException; + +@NotNullByDefault +class IntroductionViewModel extends ContactsViewModel { + + private static final Logger LOG = + getLogger(IntroductionViewModel.class.getName()); + + private final ContactManager contactManager; + private final AuthorManager authorManager; + private final IntroductionManager introductionManager; + + @Inject + IntroductionViewModel(Application application, + @DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, TransactionManager db, + AndroidExecutor androidExecutor, ContactManager contactManager, + AuthorManager authorManager, + ConversationManager conversationManager, + ConnectionRegistry connectionRegistry, EventBus eventBus, + IntroductionManager introductionManager) { + super(application, dbExecutor, lifecycleManager, db, androidExecutor, + contactManager, authorManager, conversationManager, + connectionRegistry, eventBus); + this.contactManager = contactManager; + this.authorManager = authorManager; + this.introductionManager = introductionManager; + } + + /* + * This is the contact from whose conversation we started the introduction + * using the menu item. + */ + @Nullable + private ContactId firstContactId; + /* + * This is the contact we selected from the list of contacts as a second + * contact for the introduction. + */ + @Nullable + private ContactId secondContactId; + + private final MutableLiveEvent secondContactSelected = + new MutableLiveEvent<>(); + + private final MutableLiveData introductionInfo = + new MutableLiveData<>(); + + void setFirstContactId(ContactId contactId) { + this.firstContactId = contactId; + loadContacts(); + } + + @Nullable + ContactId getSecondContactId() { + return secondContactId; + } + + void setSecondContactId(ContactId contactId) { + secondContactId = contactId; + // Setting this to null here so that IntroductionMessageFragment can + // tell whether the correct value has been loaded from the database when + // selecting a second contact repeatedly. + introductionInfo.setValue(null); + loadIntroductionInfo(); + } + + /** + * Trigger the event that the second contact has been selected from the + * contact list by the user. + */ + void triggerContactSelected() { + secondContactSelected.setEvent(true); + } + + /** + * This event will be triggered once the second contact has been selected + * from the list of contacts displayed. It is not fired when the second + * contact gets restored from the saved instance state. + */ + LiveEvent getSecondContactSelected() { + return secondContactSelected; + } + + /** + * Holder for the introduction info object with data about both contacts + * and whether the introduction is possible. May wrap null if the data + * is not available yet. This happens when it is reset by selecting a + * contact with the same view model instance more than once. + */ + LiveData getIntroductionInfo() { + return introductionInfo; + } + + @Override + protected boolean displayContact(ContactId contactId) { + return !requireNonNull(firstContactId).equals(contactId); + } + + private void loadIntroductionInfo() { + final ContactId firstContactId = requireNonNull(this.firstContactId); + final ContactId secondContactId = requireNonNull(this.secondContactId); + runOnDbThread(() -> { + try { + Contact firstContact = + contactManager.getContact(firstContactId); + Contact secondContact = + contactManager.getContact(secondContactId); + AuthorInfo a1 = authorManager.getAuthorInfo(firstContact); + AuthorInfo a2 = authorManager.getAuthorInfo(secondContact); + boolean possible = introductionManager + .canIntroduce(firstContact, secondContact); + ContactItem c1 = new ContactItem(firstContact, a1); + ContactItem c2 = new ContactItem(secondContact, a2); + introductionInfo.postValue( + new IntroductionInfo(c1, c2, possible)); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + void makeIntroduction(@Nullable String text) { + final IntroductionInfo info = + requireNonNull(introductionInfo.getValue()); + runOnDbThread(() -> { + // actually make the introduction + try { + long timestamp = System.currentTimeMillis(); + introductionManager.makeIntroduction( + info.getContact1().getContact(), + info.getContact2().getContact(), text, timestamp); + } catch (DbException e) { + logException(LOG, WARNING, e); + androidExecutor.runOnUiThread(() -> Toast.makeText( + getApplication(), R.string.introduction_error, + LENGTH_SHORT).show()); + } + }); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealContactsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealContactsFragment.java index f2648ed12..4cf47c5f2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealContactsFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealContactsFragment.java @@ -8,7 +8,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; +import org.briarproject.briar.android.contact.OnContactClickListener; import org.briarproject.briar.android.contactselection.BaseContactSelectorFragment; import org.briarproject.briar.android.contactselection.ContactSelectorController; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactAdapter.java index 1ac55740e..271b97ebb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactAdapter.java @@ -8,6 +8,7 @@ import android.view.ViewGroup; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; +import org.briarproject.briar.android.contact.OnContactClickListener; import org.briarproject.briar.android.contactselection.BaseContactSelectorAdapter; import java.util.ArrayList; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactViewHolder.java index 87edf059c..02bfa90a9 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactViewHolder.java @@ -5,7 +5,7 @@ import android.widget.ImageView; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; -import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; +import org.briarproject.briar.android.contact.OnContactClickListener; import org.briarproject.briar.android.contactselection.BaseSelectableContactHolder; import javax.annotation.Nullable; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/BriarRecyclerView.java b/briar-android/src/main/java/org/briarproject/briar/android/view/BriarRecyclerView.java index c54b3ea17..9e01848fe 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/view/BriarRecyclerView.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/BriarRecyclerView.java @@ -52,7 +52,8 @@ public class BriarRecyclerView extends FrameLayout { R.styleable.BriarRecyclerView); isScrollingToEnd = attributes .getBoolean(R.styleable.BriarRecyclerView_scrollToEnd, true); - int drawableRes = attributes.getResourceId(R.styleable.BriarRecyclerView_emptyImage, -1); + int drawableRes = attributes + .getResourceId(R.styleable.BriarRecyclerView_emptyImage, -1); if (drawableRes != -1) setEmptyImage(drawableRes); String emtpyText = attributes.getString(R.styleable.BriarRecyclerView_emptyText); @@ -87,10 +88,30 @@ public class BriarRecyclerView extends FrameLayout { } emptyObserver = new RecyclerView.AdapterDataObserver() { + + @Override + public void onChanged() { + super.onChanged(); + showData(); + } + + @Override + public void onItemRangeChanged(int positionStart, int itemCount) { + super.onItemRangeChanged(positionStart, itemCount); + if (itemCount > 0) showData(); + } + + @Override + public void onItemRangeMoved(int fromPosition, int toPosition, + int itemCount) { + super.onItemRangeMoved(fromPosition, toPosition, itemCount); + if (itemCount > 0) showData(); + } + @Override public void onItemRangeInserted(int positionStart, int itemCount) { super.onItemRangeInserted(positionStart, itemCount); - if (itemCount > 0) showData(); + showData(); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java index e237b3962..7d3875723 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java @@ -43,7 +43,7 @@ public abstract class DbViewModel extends AndroidViewModel { private final Executor dbExecutor; private final LifecycleManager lifecycleManager; private final TransactionManager db; - private final AndroidExecutor androidExecutor; + protected final AndroidExecutor androidExecutor; public DbViewModel( @NonNull Application application,