From 1423ca7a150c4e519d66e2838ca566dba1be979b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 26 Oct 2018 18:48:11 -0300 Subject: [PATCH] [android] Add UI for changing and displaying contact alias Note that this commit only shows the alias where the Contact is available. In cases, where we only have the Author, only its name is shown. This also introduces the first ViewModel to share state between the ConversationActivity and the AliasDialogFragment. --- briar-android/build.gradle | 1 + .../briar/android/AndroidComponent.java | 3 + .../android/contact/AliasDialogFragment.java | 90 ++++++++++++++++++ .../contact/BaseContactListAdapter.java | 8 +- .../contact/ContactItemViewHolder.java | 5 +- .../android/contact/ConversationActivity.java | 93 +++++++++--------- .../contact/ConversationViewModel.java | 95 +++++++++++++++++++ .../IntroductionMessageFragment.java | 5 +- .../invitation/GroupInvitationViewHolder.java | 4 +- .../reveal/RevealableContactViewHolder.java | 3 +- .../sharing/SharingInvitationViewHolder.java | 4 +- .../briar/android/util/UiUtils.java | 8 ++ .../main/res/layout/fragment_alias_dialog.xml | 55 +++++++++++ .../main/res/menu/conversation_actions.xml | 6 ++ briar-android/src/main/res/values/strings.xml | 3 + briar-android/witness.gradle | 1 + 16 files changed, 328 insertions(+), 56 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/contact/AliasDialogFragment.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationViewModel.java create mode 100644 briar-android/src/main/res/layout/fragment_alias_dialog.xml diff --git a/briar-android/build.gradle b/briar-android/build.gradle index fd2145417..0ccadfcc8 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -105,6 +105,7 @@ dependencies { implementation "com.android.support:cardview-v7:$supportVersion" implementation "com.android.support:support-annotations:$supportVersion" implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation "android.arch.lifecycle:extensions:1.1.1" implementation('ch.acra:acra:4.11') { exclude module: 'support-v4' diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index 6971b49a6..a3834cce0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -26,6 +26,7 @@ import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.plugin.tor.CircumventionProvider; import org.briarproject.briar.BriarCoreEagerSingletons; import org.briarproject.briar.BriarCoreModule; +import org.briarproject.briar.android.contact.ConversationViewModel; import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.reporting.BriarReportSender; import org.briarproject.briar.android.view.TextInputView; @@ -164,6 +165,8 @@ public interface AndroidComponent void inject(TextInputView textInputView); + void inject(ConversationViewModel conversationViewModel); + // Eager singleton load void inject(AppModule.EagerSingletons init); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/AliasDialogFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/AliasDialogFragment.java new file mode 100644 index 000000000..407445912 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/AliasDialogFragment.java @@ -0,0 +1,90 @@ +package org.briarproject.briar.android.contact; + +import android.arch.lifecycle.ViewModelProviders; +import android.graphics.Point; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatDialogFragment; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.Button; +import android.widget.TextView; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.briar.R; + +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + +public class AliasDialogFragment extends AppCompatDialogFragment { + + final static String TAG = AliasDialogFragment.class.getName(); + + private ConversationViewModel viewModel; + private ContactId contactId; + private TextView aliasEditText; + + public static AliasDialogFragment newInstance(ContactId id) { + AliasDialogFragment f = new AliasDialogFragment(); + + Bundle args = new Bundle(); + args.putInt("contactId", id.getInt()); + f.setArguments(args); + + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments() == null) throw new IllegalArgumentException(); + int contactIdInt = getArguments().getInt("contactId", -1); + if (contactIdInt == -1) throw new IllegalArgumentException(); + contactId = new ContactId(contactIdInt); + + setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme); + + viewModel = + ViewModelProviders.of(getActivity()).get(ConversationViewModel.class); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + + View v = inflater.inflate(R.layout.fragment_alias_dialog, container, + false); + + aliasEditText = v.findViewById(R.id.aliasEditText); + + Button setButton = v.findViewById(R.id.setButton); + setButton.setOnClickListener(v1 -> { + viewModel.setContactAlias(contactId, + aliasEditText.getText().toString()); + getDialog().dismiss(); + }); + + Button cancelButton = v.findViewById(R.id.cancelButton); + cancelButton.setOnClickListener(v1 -> getDialog().cancel()); + + return v; + } + + @Override + public void onResume() { + Window window = getDialog().getWindow(); + if (window == null) { + super.onResume(); + return; + } + Point size = new Point(); + Display display = window.getWindowManager().getDefaultDisplay(); + display.getSize(size); + window.setLayout((int) (size.x * 0.75), WRAP_CONTENT); + super.onResume(); + } + +} 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 1f577c366..5e58597ce 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,6 +1,7 @@ package org.briarproject.briar.android.contact; import android.content.Context; +import android.support.annotation.NonNull; import android.view.View; import org.briarproject.bramble.api.contact.ContactId; @@ -9,6 +10,7 @@ import org.briarproject.briar.android.util.BriarAdapter; import javax.annotation.Nullable; import static android.support.v7.util.SortedList.INVALID_POSITION; +import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName; public abstract class BaseContactListAdapter> extends BriarAdapter { @@ -23,15 +25,15 @@ public abstract class BaseContactListAdapter @@ -41,8 +43,7 @@ public class ContactItemViewHolder Author author = item.getContact().getAuthor(); avatar.setImageDrawable( new IdenticonDrawable(author.getId().getBytes())); - String contactName = author.getName(); - name.setText(contactName); + name.setText(getContactDisplayName(item.getContact())); if (bulb != null) { // online/offline diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java index 423f47ec9..3f7deeb17 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java @@ -1,7 +1,8 @@ package org.briarproject.briar.android.contact; -import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.LiveData; import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; @@ -23,7 +24,6 @@ import android.widget.TextView; import android.widget.Toast; import org.briarproject.bramble.api.FormatException; -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.ContactRemovedEvent; @@ -34,7 +34,6 @@ 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.identity.AuthorId; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.ConnectionRegistry; @@ -131,8 +130,8 @@ public class ConversationActivity extends BriarActivity Executor cryptoExecutor; private final Map textCache = new ConcurrentHashMap<>(); - private final MutableLiveData contactName = new MutableLiveData<>(); + private ConversationViewModel viewModel; private ConversationVisitor visitor; private ConversationAdapter adapter; private Toolbar toolbar; @@ -166,8 +165,6 @@ public class ConversationActivity extends BriarActivity private volatile ContactId contactId; @Nullable - private volatile AuthorId contactAuthorId; - @Nullable private volatile GroupId messagingGroupId; @SuppressWarnings("ConstantConditions") @@ -176,6 +173,9 @@ public class ConversationActivity extends BriarActivity setSceneTransitionAnimation(); super.onCreate(state); + viewModel = + ViewModelProviders.of(this).get(ConversationViewModel.class); + Intent i = getIntent(); int id = i.getIntExtra(CONTACT_ID, -1); if (id == -1) throw new IllegalStateException(); @@ -185,16 +185,29 @@ public class ConversationActivity extends BriarActivity // Custom Toolbar toolbar = setUpCustomToolbar(true); - if (toolbar != null) { - toolbarAvatar = toolbar.findViewById(R.id.contactAvatar); - toolbarStatus = toolbar.findViewById(R.id.contactStatus); - toolbarTitle = toolbar.findViewById(R.id.contactName); - } + toolbarAvatar = toolbar.findViewById(R.id.contactAvatar); + toolbarStatus = toolbar.findViewById(R.id.contactStatus); + toolbarTitle = toolbar.findViewById(R.id.contactName); + + viewModel.getContactAuthorId().observe(this, authorId -> { + toolbarAvatar.setImageDrawable( + new IdenticonDrawable(authorId.getBytes())); + // we only need this once + viewModel.getContactAuthorId().removeObservers(this); + }); + viewModel.getContactDisplayName().observe(this, contactName -> { + toolbarTitle.setText(contactName); + }); + viewModel.isContactDeleted().observe(this, deleted -> { + if (deleted != null && deleted) finish(); + }); + viewModel.loadContactDetails(contactId); setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId)); setTransitionName(toolbarStatus, getBulbTransitionName(contactId)); - visitor = new ConversationVisitor(this, this, contactName); + visitor = new ConversationVisitor(this, this, + viewModel.getContactDisplayName()); adapter = new ConversationAdapter(this, this); list = findViewById(R.id.conversationView); list.setLayoutManager(new LinearLayoutManager(this)); @@ -229,7 +242,19 @@ public class ConversationActivity extends BriarActivity notificationManager.blockContactNotification(contactId); notificationManager.clearContactNotification(contactId); displayContactOnlineStatus(); - loadContactDetailsAndMessages(); + LiveData contactName = viewModel.getContactDisplayName(); + if (contactName.getValue() == null) { + // wait for contact name to be initialized + contactName.observe(this, new Observer() { + @Override + public void onChanged(@Nullable String cName) { + if (cName != null) { + loadMessages(); + contactName.removeObserver(this); + } + } + }); + } else loadMessages(); list.startPeriodicUpdate(); } @@ -266,6 +291,10 @@ public class ConversationActivity extends BriarActivity intent.putExtra(CONTACT_ID, contactId.getInt()); startActivityForResult(intent, REQUEST_INTRODUCTION); return true; + case R.id.action_set_alias: + AliasDialogFragment.newInstance(contactId).show( + getSupportFragmentManager(), AliasDialogFragment.TAG); + return true; case R.id.action_social_remove_person: askToRemoveContact(); return true; @@ -274,36 +303,6 @@ public class ConversationActivity extends BriarActivity } } - private void loadContactDetailsAndMessages() { - runOnDbThread(() -> { - try { - long start = now(); - if (contactAuthorId == null) { - Contact contact = contactManager.getContact(contactId); - contactName.postValue(contact.getAuthor().getName()); - contactAuthorId = contact.getAuthor().getId(); - } - logDuration(LOG, "Loading contact", start); - loadMessages(); - displayContactDetails(); - } catch (NoSuchContactException e) { - finishOnUiThread(); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); - } - - // contactAuthorId and contactName are expected to be set - private void displayContactDetails() { - runOnUiThreadUnlessDestroyed(() -> { - //noinspection ConstantConditions - toolbarAvatar.setImageDrawable( - new IdenticonDrawable(contactAuthorId.getBytes())); - toolbarTitle.setText(contactName.getValue()); - }); - } - private void displayContactOnlineStatus() { runOnUiThreadUnlessDestroyed(() -> { if (connectionRegistry.isConnected(contactId)) { @@ -453,15 +452,17 @@ public class ConversationActivity extends BriarActivity private void onNewPrivateMessage(PrivateMessageHeader h) { runOnUiThreadUnlessDestroyed(() -> { if (h instanceof PrivateRequest || h instanceof PrivateResponse) { - String cName = contactName.getValue(); + String cName = viewModel.getContactDisplayName().getValue(); if (cName == null) { // Wait for the contact name to be loaded - contactName.observe(this, new Observer() { + viewModel.getContactDisplayName() + .observe(this, new Observer() { @Override public void onChanged(@Nullable String cName) { if (cName != null) { addConversationItem(h.accept(visitor)); - contactName.removeObserver(this); + viewModel.getContactDisplayName() + .removeObserver(this); } } }); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationViewModel.java new file mode 100644 index 000000000..1c132216e --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationViewModel.java @@ -0,0 +1,95 @@ +package org.briarproject.briar.android.contact; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.support.annotation.NonNull; + +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.NoSuchContactException; +import org.briarproject.bramble.api.identity.AuthorId; +import org.briarproject.briar.android.AndroidComponent; +import org.briarproject.briar.android.BriarApplication; +import org.briarproject.briar.android.util.UiUtils; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logDuration; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.LogUtils.now; + +public class ConversationViewModel extends AndroidViewModel { + + private static Logger LOG = + Logger.getLogger(ConversationViewModel.class.getName()); + + @Inject + @DatabaseExecutor + Executor dbExecutor; + @Inject + ContactManager contactManager; + + private final MutableLiveData contactAuthorId = + new MutableLiveData<>(); + private final MutableLiveData contactDeleted = + new MutableLiveData<>(); + private final MutableLiveData contactName = new MutableLiveData<>(); + + public ConversationViewModel(@NonNull Application application) { + super(application); + AndroidComponent component = + ((BriarApplication) application).getApplicationComponent(); + component.inject(this); + contactDeleted.setValue(false); + } + + void loadContactDetails(ContactId contactId) { + dbExecutor.execute(() -> { + try { + long start = now(); + Contact contact = contactManager.getContact(contactId); + contactAuthorId.postValue(contact.getAuthor().getId()); + contactName.postValue(UiUtils.getContactDisplayName(contact)); + logDuration(LOG, "Loading contact", start); + } catch (NoSuchContactException e) { + contactDeleted.postValue(true); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + void setContactAlias(ContactId contactId, String alias) { + dbExecutor.execute(() -> { + try { + contactManager.setContactAlias(contactId, + alias.isEmpty() ? null : alias); + loadContactDetails(contactId); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + LiveData getContactAuthorId() { + return contactAuthorId; + } + + LiveData isContactDeleted() { + return contactDeleted; + } + + LiveData getContactDisplayName() { + return contactName; + } + +} 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 194641f07..ea7858e48 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 @@ -38,6 +38,7 @@ 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.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH; public class IntroductionMessageFragment extends BaseFragment @@ -148,8 +149,8 @@ public class IntroductionMessageFragment extends BaseFragment c2.getAuthor().getId().getBytes())); // set contact names - ui.contactName1.setText(c1.getAuthor().getName()); - ui.contactName2.setText(c2.getAuthor().getName()); + ui.contactName1.setText(getContactDisplayName(c1)); + ui.contactName2.setText(getContactDisplayName(c2)); // hide progress bar ui.progressBar.setVisibility(GONE); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/invitation/GroupInvitationViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/invitation/GroupInvitationViewHolder.java index fae57b85e..b205e2409 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/invitation/GroupInvitationViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/invitation/GroupInvitationViewHolder.java @@ -9,6 +9,8 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationItem; import javax.annotation.Nullable; +import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName; + class GroupInvitationViewHolder extends InvitationViewHolder { @@ -24,7 +26,7 @@ class GroupInvitationViewHolder sharedBy.setText( sharedBy.getContext().getString(R.string.groups_created_by, - item.getCreator().getAuthor().getName())); + getContactDisplayName(item.getCreator()))); } } \ No newline at end of file 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 ab7777f4b..f32c34522 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 @@ -14,6 +14,7 @@ import javax.annotation.Nullable; import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityIcon; import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityString; import static org.briarproject.briar.android.util.UiUtils.GREY_OUT; +import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName; @UiThread @NotNullByDefault @@ -36,7 +37,7 @@ class RevealableContactViewHolder icon.setImageResource(getVisibilityIcon(item.getVisibility())); info.setText( getVisibilityString(info.getContext(), item.getVisibility(), - item.getContact().getAuthor().getName())); + getContactDisplayName(item.getContact()))); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingInvitationViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingInvitationViewHolder.java index 6d4eae929..9b65e1c8c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingInvitationViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/sharing/SharingInvitationViewHolder.java @@ -13,6 +13,8 @@ import java.util.Collection; import javax.annotation.Nullable; +import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName; + class SharingInvitationViewHolder extends InvitationViewHolder { @@ -28,7 +30,7 @@ class SharingInvitationViewHolder Collection names = new ArrayList<>(); for (Contact c : item.getNewSharers()) - names.add(c.getAuthor().getName()); + names.add(getContactDisplayName(c)); sharedBy.setText( sharedBy.getContext().getString(R.string.shared_by_format, StringUtils.join(names, ", "))); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java index ff18520f4..56dc4666b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java @@ -33,6 +33,7 @@ import android.view.View; import android.widget.TextView; import org.acra.ACRA; +import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; @@ -74,6 +75,13 @@ public class UiUtils { public static final int TEASER_LENGTH = 320; public static final float GREY_OUT = 0.5f; + public static String getContactDisplayName(Contact c) { + String name = c.getAuthor().getName(); + String alias = c.getAlias(); + if (alias == null) return name; + else return String.format("%s (%s)", alias, name); + } + public static void setError(TextInputLayout til, @Nullable String error, boolean set) { if (set) { diff --git a/briar-android/src/main/res/layout/fragment_alias_dialog.xml b/briar-android/src/main/res/layout/fragment_alias_dialog.xml new file mode 100644 index 000000000..70920e522 --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_alias_dialog.xml @@ -0,0 +1,55 @@ + + + + + + + + + +