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