From f73f8ca7e7bb0974e07a2fa4c19c2227bfd51e3c Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 18 Dec 2018 12:50:59 -0200 Subject: [PATCH] [android] do not show two private conversation onboardings at the same time Checking for introduction onboarding is now done in the ViewModel together with the image onboarding. The latter has preference. If both could be shown, the introduction onboarding will be delayed to the next time the user enters the conversation. --- .../conversation/ConversationActivity.java | 75 ++++++------------- .../conversation/ConversationViewModel.java | 72 ++++++++++++++---- 2 files changed, 81 insertions(+), 66 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index 9fb93a657..e12d33a62 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -46,7 +46,6 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent; import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent; -import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.MessageId; @@ -126,7 +125,6 @@ import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHME import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION; import static org.briarproject.briar.android.conversation.ImageActivity.DATE; import static org.briarproject.briar.android.conversation.ImageActivity.NAME; -import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; 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; @@ -141,11 +139,10 @@ public class ConversationActivity extends BriarActivity TextCache, AttachmentCache, AttachImageListener { public static final String CONTACT_ID = "briar.CONTACT_ID"; + private static final int ONBOARDING_DELAY = 750; private static final Logger LOG = Logger.getLogger(ConversationActivity.class.getName()); - private static final String SHOW_ONBOARDING_INTRODUCTION = - "showOnboardingIntroduction"; @Inject AndroidNotificationManager notificationManager; @@ -355,10 +352,19 @@ public class ConversationActivity extends BriarActivity MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.conversation_actions, menu); - enableIntroductionActionIfAvailable( - menu.findItem(R.id.action_introduction)); - enableAliasActionIfAvailable( - menu.findItem(R.id.action_set_alias)); + // enable introduction action if available + observeOnce(viewModel.showIntroductionAction(), this, enable -> { + if (enable != null && enable) { + menu.findItem(R.id.action_introduction).setEnabled(true); + // show introduction onboarding, if needed + observeOnce(viewModel.showIntroductionOnboarding(), this, + this::showIntroductionOnboarding); + } + }); + // enable alias action if available + observeOnce(viewModel.getContact(), this, contact -> { + menu.findItem(R.id.action_set_alias).setEnabled(true); + }); return super.onCreateOptionsMenu(menu); } @@ -713,32 +719,6 @@ public class ConversationActivity extends BriarActivity }); } - private void enableIntroductionActionIfAvailable(MenuItem item) { - runOnDbThread(() -> { - try { - if (contactManager.getActiveContacts().size() > 1) { - enableIntroductionAction(item); - Settings settings = - settingsManager.getSettings(SETTINGS_NAMESPACE); - if (settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION, - true)) { - showIntroductionOnboarding(); - } - } - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); - } - - private void enableAliasActionIfAvailable(MenuItem item) { - observeOnce(viewModel.getContact(), this, c -> item.setEnabled(true)); - } - - private void enableIntroductionAction(MenuItem item) { - runOnUiThreadUnlessDestroyed(() -> item.setEnabled(true)); - } - private void showImageOnboarding(@Nullable Boolean show) { if (show == null || !show) return; // show onboarding only after the enter transition has ended @@ -747,12 +727,15 @@ public class ConversationActivity extends BriarActivity // remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS ((TextAttachmentController) sendController) .showImageOnboarding(this, () -> - viewModel.imageOnboardingSeen()); - }, 750); + viewModel.onImageOnboardingSeen()); + }, ONBOARDING_DELAY); } - private void showIntroductionOnboarding() { - runOnUiThreadUnlessDestroyed(() -> { + private void showIntroductionOnboarding(@Nullable Boolean show) { + if (show == null || !show) return; + // show onboarding only after the enter transition has ended + // otherwise the tap target animation won't play + textInputView.postDelayed(() -> { // find view of overflow icon View target = null; for (int i = 0; i < toolbar.getChildCount(); i++) { @@ -770,7 +753,7 @@ public class ConversationActivity extends BriarActivity PromptStateChangeListener listener = (prompt, state) -> { if (state == STATE_DISMISSED || state == STATE_FINISHED) { - introductionOnboardingSeen(); + viewModel.onIntroductionOnboardingSeen(); } }; new MaterialTapTargetPrompt.Builder(ConversationActivity.this, @@ -782,19 +765,7 @@ public class ConversationActivity extends BriarActivity ContextCompat.getColor(this, R.color.briar_primary)) .setPromptStateChangeListener(listener) .show(); - }); - } - - private void introductionOnboardingSeen() { - runOnDbThread(() -> { - try { - Settings settings = new Settings(); - settings.putBoolean(SHOW_ONBOARDING_INTRODUCTION, false); - settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); + }, ONBOARDING_DELAY); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java index 7ca580db8..61ba6ab9b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java @@ -38,6 +38,7 @@ import org.briarproject.briar.api.messaging.PrivateMessageHeader; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -62,6 +63,8 @@ public class ConversationViewModel extends AndroidViewModel { getLogger(ConversationViewModel.class.getName()); private static final String SHOW_ONBOARDING_IMAGE = "showOnboardingImage"; + private static final String SHOW_ONBOARDING_INTRODUCTION = + "showOnboardingIntroduction"; @DatabaseExecutor private final Executor dbExecutor; @@ -86,6 +89,10 @@ public class ConversationViewModel extends AndroidViewModel { new MutableLiveData<>(); private final MutableLiveData showImageOnboarding = new MutableLiveData<>(); + private final MutableLiveData showIntroductionOnboarding = + new MutableLiveData<>(); + private final MutableLiveData showIntroductionAction = + new MutableLiveData<>(); private final MutableLiveData contactDeleted = new MutableLiveData<>(); private final MutableLiveData messagingGroupId = @@ -115,25 +122,28 @@ public class ConversationViewModel extends AndroidViewModel { contactDeleted.setValue(false); } + /** + * Setting the {@link ContactId} automatically triggers loading of other + * data. + */ void setContactId(ContactId contactId) { if (this.contactId == null) { this.contactId = contactId; - loadContact(); + loadContact(contactId); } else if (!contactId.equals(this.contactId)) { throw new IllegalStateException(); } } - private void loadContact() { + private void loadContact(ContactId contactId) { dbExecutor.execute(() -> { try { long start = now(); - Contact c = - contactManager.getContact(requireNonNull(contactId)); + Contact c = contactManager.getContact(contactId); contact.postValue(c); logDuration(LOG, "Loading contact", start); start = now(); - checkImageSupport(c.getId()); + checkFeaturesAndOnboarding(contactId); logDuration(LOG, "Checking for image support", start); } catch (NoSuchContactException e) { contactDeleted.postValue(true); @@ -148,7 +158,7 @@ public class ConversationViewModel extends AndroidViewModel { try { contactManager.setContactAlias(requireNonNull(contactId), alias.isEmpty() ? null : alias); - loadContact(); + loadContact(contactId); } catch (DbException e) { logException(LOG, WARNING, e); } @@ -177,28 +187,54 @@ public class ConversationViewModel extends AndroidViewModel { } @DatabaseExecutor - private void checkImageSupport(ContactId c) throws DbException { + private void checkFeaturesAndOnboarding(ContactId c) throws DbException { + // check if images are supported int minorVersion = db.transactionWithResult(true, txn -> clientVersioningManager .getClientMinorVersion(txn, c, CLIENT_ID, 0)); // support was added in 0.1 boolean imagesSupported = minorVersion == 1; imageSupport.postValue(imagesSupported); - if (!imagesSupported) return; - // check if we should show onboarding, only if images are supported - Settings settings = - settingsManager.getSettings(SETTINGS_NAMESPACE); - if (settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) { + // check if introductions are supported + Collection contacts = contactManager.getActiveContacts(); + boolean introductionSupported = contacts.size() > 1; + showIntroductionAction.postValue(introductionSupported); + + Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE); + if (imagesSupported && + settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) { + // check if we should show onboarding, only if images are supported showImageOnboarding.postValue(true); + // allow observer to stop listening for changes + showIntroductionOnboarding.postValue(false); + } else { + // allow observer to stop listening for changes + showImageOnboarding.postValue(false); + // we only show one onboarding dialog at a time + if (introductionSupported && + settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION, true)) { + showIntroductionOnboarding.postValue(true); + } else { + // allow observer to stop listening for changes + showIntroductionOnboarding.postValue(false); + } } } - void imageOnboardingSeen() { + void onImageOnboardingSeen() { + onOnboardingSeen(SHOW_ONBOARDING_IMAGE); + } + + void onIntroductionOnboardingSeen() { + onOnboardingSeen(SHOW_ONBOARDING_INTRODUCTION); + } + + private void onOnboardingSeen(String key) { dbExecutor.execute(() -> { try { Settings settings = new Settings(); - settings.putBoolean(SHOW_ONBOARDING_IMAGE, false); + settings.putBoolean(key, false); settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE); } catch (DbException e) { logException(LOG, WARNING, e); @@ -322,6 +358,14 @@ public class ConversationViewModel extends AndroidViewModel { return showImageOnboarding; } + LiveData showIntroductionOnboarding() { + return showIntroductionOnboarding; + } + + LiveData showIntroductionAction() { + return showIntroductionAction; + } + LiveData isContactDeleted() { return contactDeleted; }