From 16c701a71a288f86dac073c0416495a9615a5688 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 17 Dec 2018 15:41:47 -0200 Subject: [PATCH 1/9] [android] only enable image feature if contact supports it Also show an onboarding the first time, the feature gets activiated --- briar-android/build.gradle | 4 +- .../conversation/ConversationActivity.java | 25 +++++++ .../conversation/ConversationViewModel.java | 68 +++++++++++++++++-- .../view/TextAttachmentController.java | 50 ++++++++++++++ .../src/main/res/drawable/ic_image_off.xml | 11 +++ .../src/main/res/layout/text_input_view.xml | 2 +- briar-android/src/main/res/values/strings.xml | 4 ++ briar-android/witness.gradle | 8 +-- 8 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 briar-android/src/main/res/drawable/ic_image_off.xml diff --git a/briar-android/build.gradle b/briar-android/build.gradle index c52aced57..c44c35efe 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -116,7 +116,7 @@ dependencies { implementation 'info.guardianproject.trustedintents:trustedintents:0.2' implementation 'de.hdodenhof:circleimageview:2.2.0' implementation 'com.google.zxing:core:3.3.3' - implementation 'uk.co.samuelwall:material-tap-target-prompt:2.12.4' + implementation 'uk.co.samuelwall:material-tap-target-prompt:2.14.0' implementation 'com.vanniktech:emoji-google:0.5.1' def glideVersion = '4.8.0' implementation("com.github.bumptech.glide:glide:$glideVersion") { @@ -134,7 +134,7 @@ dependencies { testImplementation project(path: ':bramble-core', configuration: 'testOutput') testImplementation 'org.robolectric:robolectric:4.0.1' testImplementation 'org.robolectric:shadows-support-v4:3.3.2' - testImplementation 'org.mockito:mockito-core:2.13.0' + testImplementation 'org.mockito:mockito-core:2.19.0' testImplementation 'junit:junit:4.12' testImplementation "org.jmock:jmock:2.8.2" testImplementation "org.jmock:jmock-junit4:2.8.2" 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 2c0c5c72f..9fb93a657 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 @@ -263,6 +263,13 @@ public class ConversationActivity extends BriarActivity ImagePreview imagePreview = findViewById(R.id.imagePreview); sendController = new TextAttachmentController(textInputView, imagePreview, this, this); + observeOnce(viewModel.hasImageSupport(), this, hasSupport -> { + if (hasSupport != null && hasSupport) { + // remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS + ((TextAttachmentController) sendController) + .setImagesSupported(); + } + }); } else { sendController = new TextSendController(textInputView, this, false); } @@ -461,6 +468,10 @@ public class ConversationActivity extends BriarActivity if (revision == adapter.getRevision()) { adapter.incrementRevision(); textInputView.setEnabled(true); + // start observing onboarding after enabling (only once, because + // we only update this when an onboarding should be shown) + observeOnce(viewModel.showImageOnboarding(), this, + this::showImageOnboarding); List items = createItems(headers); adapter.addAll(items); list.showData(); @@ -728,6 +739,18 @@ public class ConversationActivity extends BriarActivity runOnUiThreadUnlessDestroyed(() -> item.setEnabled(true)); } + private void showImageOnboarding(@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(() -> { + // remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS + ((TextAttachmentController) sendController) + .showImageOnboarding(this, () -> + viewModel.imageOnboardingSeen()); + }, 750); + } + private void showIntroductionOnboarding() { runOnUiThreadUnlessDestroyed(() -> { // find view of overflow icon @@ -755,6 +778,8 @@ public class ConversationActivity extends BriarActivity .setPrimaryText(R.string.introduction_onboarding_title) .setSecondaryText(R.string.introduction_onboarding_text) .setIcon(R.drawable.ic_more_vert_accent) + .setBackgroundColour( + ContextCompat.getColor(this, R.color.briar_primary)) .setPromptStateChangeListener(listener) .show(); }); 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 732c9fb7e..7ca580db8 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 @@ -16,13 +16,17 @@ 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.crypto.CryptoExecutor; +import org.briarproject.bramble.api.db.DatabaseComponent; 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.bramble.api.nullsafety.NotNullByDefault; +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.Message; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.AttachmentHeader; @@ -47,21 +51,28 @@ import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.now; +import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; +import static org.briarproject.briar.api.messaging.MessagingManager.CLIENT_ID; @NotNullByDefault public class ConversationViewModel extends AndroidViewModel { private static Logger LOG = getLogger(ConversationViewModel.class.getName()); + private static final String SHOW_ONBOARDING_IMAGE = + "showOnboardingImage"; @DatabaseExecutor private final Executor dbExecutor; @CryptoExecutor private final Executor cryptoExecutor; + private final DatabaseComponent db; private final MessagingManager messagingManager; private final ContactManager contactManager; + private final SettingsManager settingsManager; private final PrivateMessageFactory privateMessageFactory; + private final ClientVersioningManager clientVersioningManager; private final AttachmentController attachmentController; @Nullable @@ -71,6 +82,10 @@ public class ConversationViewModel extends AndroidViewModel { Transformations.map(contact, c -> c.getAuthor().getId()); private final LiveData contactName = Transformations.map(contact, UiUtils::getContactDisplayName); + private final MutableLiveData imageSupport = + new MutableLiveData<>(); + private final MutableLiveData showImageOnboarding = + new MutableLiveData<>(); private final MutableLiveData contactDeleted = new MutableLiveData<>(); private final MutableLiveData messagingGroupId = @@ -81,16 +96,20 @@ public class ConversationViewModel extends AndroidViewModel { @Inject ConversationViewModel(Application application, @DatabaseExecutor Executor dbExecutor, - @CryptoExecutor Executor cryptoExecutor, - MessagingManager messagingManager, - ContactManager contactManager, - PrivateMessageFactory privateMessageFactory) { + @CryptoExecutor Executor cryptoExecutor, DatabaseComponent db, + MessagingManager messagingManager, ContactManager contactManager, + SettingsManager settingsManager, + PrivateMessageFactory privateMessageFactory, + ClientVersioningManager clientVersioningManager) { super(application); this.dbExecutor = dbExecutor; this.cryptoExecutor = cryptoExecutor; + this.db = db; this.messagingManager = messagingManager; this.contactManager = contactManager; + this.settingsManager = settingsManager; this.privateMessageFactory = privateMessageFactory; + this.clientVersioningManager = clientVersioningManager; this.attachmentController = new AttachmentController(messagingManager, application.getResources()); contactDeleted.setValue(false); @@ -113,6 +132,9 @@ public class ConversationViewModel extends AndroidViewModel { contactManager.getContact(requireNonNull(contactId)); contact.postValue(c); logDuration(LOG, "Loading contact", start); + start = now(); + checkImageSupport(c.getId()); + logDuration(LOG, "Checking for image support", start); } catch (NoSuchContactException e) { contactDeleted.postValue(true); } catch (DbException e) { @@ -154,6 +176,36 @@ public class ConversationViewModel extends AndroidViewModel { }); } + @DatabaseExecutor + private void checkImageSupport(ContactId c) throws DbException { + 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)) { + showImageOnboarding.postValue(true); + } + } + + void imageOnboardingSeen() { + dbExecutor.execute(() -> { + try { + Settings settings = new Settings(); + settings.putBoolean(SHOW_ONBOARDING_IMAGE, false); + settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + private void storeAttachments(GroupId groupId, @Nullable String text, List uris, long timestamp) { dbExecutor.execute(() -> { @@ -262,6 +314,14 @@ public class ConversationViewModel extends AndroidViewModel { return contactName; } + LiveData hasImageSupport() { + return imageSupport; + } + + LiveData showImageOnboarding() { + return showImageOnboarding; + } + LiveData isContactDeleted() { return contactDeleted; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java b/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java index f7d60de2c..79fc6eb7a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java @@ -1,6 +1,8 @@ package org.briarproject.briar.android.view; +import android.app.Activity; import android.content.ClipData; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Parcel; @@ -8,6 +10,7 @@ import android.os.Parcelable; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v4.view.AbsSavedState; +import android.support.v7.app.AlertDialog.Builder; import android.support.v7.widget.AppCompatImageButton; import android.widget.Toast; @@ -18,11 +21,15 @@ import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener; import java.util.ArrayList; import java.util.List; +import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; +import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener; + import static android.content.Intent.ACTION_GET_CONTENT; import static android.content.Intent.ACTION_OPEN_DOCUMENT; import static android.content.Intent.CATEGORY_OPENABLE; import static android.content.Intent.EXTRA_ALLOW_MULTIPLE; import static android.os.Build.VERSION.SDK_INT; +import static android.support.v4.content.ContextCompat.getColor; import static android.support.v4.view.AbsSavedState.EMPTY_STATE; import static android.view.View.GONE; import static android.view.View.INVISIBLE; @@ -30,6 +37,9 @@ import static android.view.View.VISIBLE; import static android.widget.Toast.LENGTH_LONG; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; +import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute; +import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED; +import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED; @UiThread @NotNullByDefault @@ -42,6 +52,7 @@ public class TextAttachmentController extends TextSendController private final AttachImageListener imageListener; private CharSequence textHint; + private boolean hasImageSupport = false; private List imageUris = emptyList(); public TextAttachmentController(TextInputView v, ImagePreview imagePreview, @@ -78,7 +89,28 @@ public class TextAttachmentController extends TextSendController return !imageUris.isEmpty(); } + /*** + * By default, image support is disabled. + * Once you know that it is supported in the current context, + * call this method to enable it. + */ + public void setImagesSupported() { + hasImageSupport = true; + imageButton.setImageResource(R.drawable.ic_image); + } + private void onImageButtonClicked() { + if (!hasImageSupport) { + Context ctx = imageButton.getContext(); + Builder builder = new Builder(ctx, R.style.OnboardingDialogTheme); + builder.setTitle( + ctx.getString(R.string.dialog_title_no_image_support)); + builder.setMessage( + ctx.getString(R.string.dialog_message_no_image_support)); + builder.setPositiveButton(R.string.got_it, null); + builder.show(); + return; + } Intent intent = new Intent(SDK_INT >= 19 ? ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT); intent.addCategory(CATEGORY_OPENABLE); @@ -187,6 +219,24 @@ public class TextAttachmentController extends TextSendController reset(); } + public void showImageOnboarding(Activity activity, Runnable onOnboardingSeen) { + PromptStateChangeListener listener = (prompt, state) -> { + if (state == STATE_DISMISSED || state == STATE_FINISHED) { + onOnboardingSeen.run(); + } + }; + int color = resolveColorAttribute(activity, R.attr.colorControlNormal); + MaterialTapTargetPrompt p = new MaterialTapTargetPrompt.Builder(activity, + R.style.OnboardingDialogTheme).setTarget(imageButton) + .setPrimaryText(R.string.dialog_title_image_support) + .setSecondaryText(R.string.dialog_message_image_support) + .setBackgroundColour(getColor(activity, R.color.briar_primary)) + .setIcon(R.drawable.ic_image) + .setIconDrawableColourFilter(color) + .setPromptStateChangeListener(listener) + .show(); + } + private static class SavedState extends AbsSavedState { @Nullable diff --git a/briar-android/src/main/res/drawable/ic_image_off.xml b/briar-android/src/main/res/drawable/ic_image_off.xml new file mode 100644 index 000000000..4c3cd2341 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_image_off.xml @@ -0,0 +1,11 @@ + + + diff --git a/briar-android/src/main/res/layout/text_input_view.xml b/briar-android/src/main/res/layout/text_input_view.xml index 3ca815785..474ce90c1 100644 --- a/briar-android/src/main/res/layout/text_input_view.xml +++ b/briar-android/src/main/res/layout/text_input_view.xml @@ -40,7 +40,7 @@ android:focusable="true" android:padding="4dp" android:scaleType="center" - android:src="@drawable/ic_image" + android:src="@drawable/ic_image_off" android:visibility="invisible" app:tint="?attr/colorControlNormal"/> diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index dd4b0f96a..4e4e6c6b2 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -144,6 +144,10 @@ Saving this image will allow other apps to access it.\n\nAre you sure you want to save? Image was saved Could not save image + Images Unavailable + Your contact\'s Briar does not yet support image attachments. Once they upgrade you\'ll see a different icon. + You can now send images to this contact + Tap this icon to attach images. Add a Contact diff --git a/briar-android/witness.gradle b/briar-android/witness.gradle index ee7273494..4aed44ea8 100644 --- a/briar-android/witness.gradle +++ b/briar-android/witness.gradle @@ -134,8 +134,8 @@ dependencyVerification { 'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a', 'nekohtml:nekohtml:1.9.6.2:nekohtml-1.9.6.2.jar:fdff6cfa9ed9cc911c842a5d2395f209ec621ef1239d46810e9e495809d3ae09', 'nekohtml:xercesMinimal:1.9.6.2:xercesMinimal-1.9.6.2.jar:95b8b357d19f63797dd7d67622fd3f18374d64acbc6584faba1c7759a31e8438', - 'net.bytebuddy:byte-buddy-agent:1.7.9:byte-buddy-agent-1.7.9.jar:ac1a993befb528c3271a83a9ad9c42d363d399e9deb26e0470e3c4962066c550', - 'net.bytebuddy:byte-buddy:1.7.9:byte-buddy-1.7.9.jar:2ea2ada12b790d16ac7f6e6c065cb55cbcdb6ba519355f5958851159cad3b16a', + 'net.bytebuddy:byte-buddy-agent:1.8.10:byte-buddy-agent-1.8.10.jar:f7403b1126137eb68a5cc3beaf543d965bafca87fad7d7d30082617748c19e05', + 'net.bytebuddy:byte-buddy:1.8.10:byte-buddy-1.8.10.jar:8c29e0118256acf9fbadcd75143df2d8bd9bfb07623ccf95a14646be5a92380c', 'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5', 'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2', 'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', @@ -186,7 +186,7 @@ dependencyVerification { 'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760', 'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16', 'org.jvnet.staxex:stax-ex:1.7.7:stax-ex-1.7.7.jar:a31ff7d77163c0deb09e7fee59ad35ae44c2cee2cc8552a116ccd1583d813fb4', - 'org.mockito:mockito-core:2.13.0:mockito-core-2.13.0.jar:92a746b37cf8c5730a5e7b35fd7d8cd72700089435ff92ee03ed8384d4eb3377', + 'org.mockito:mockito-core:2.19.0:mockito-core-2.19.0.jar:d6ac2e04164c5d5c89e73838dc1c8b3856ca6582d3f2daf91816fd9d7ba3c9a9', 'org.objenesis:objenesis:2.6:objenesis-2.6.jar:5e168368fbc250af3c79aa5fef0c3467a2d64e5a7bd74005f25d8399aeb0708d', 'org.ow2.asm:asm-analysis:6.0:asm-analysis-6.0.jar:2f1a6387219c3a6cc4856481f221b03bd9f2408a326d416af09af5d6f608c1f4', 'org.ow2.asm:asm-commons:6.0:asm-commons-6.0.jar:f1bce5c648a96a017bdcd01fe5d59af9845297fd7b79b81c015a6fbbd9719abf', @@ -203,6 +203,6 @@ dependencyVerification { 'org.robolectric:shadows-support-v4:3.3.2:shadows-support-v4-3.3.2.jar:6f689264738266e70fe08db7c04b7b5a75155994f4e3f7f311960d90486bf005', 'org.robolectric:utils:4.0.1:utils-4.0.1.jar:ee923ed66847271009ebeb246286b7206b160c2b6d1347fe820c00be06c280cb', 'tools.fastlane:screengrab:1.2.0:screengrab-1.2.0.aar:af4ee23bb06f94404d3ab18e2ea69db8265539fc8da29f9ee45b7e472684ba83', - 'uk.co.samuelwall:material-tap-target-prompt:2.12.4:material-tap-target-prompt-2.12.4.aar:6c0990ab3aa22de9f7d09dcb0a944e671128c31634ac8429012faa5c508202fb', + 'uk.co.samuelwall:material-tap-target-prompt:2.14.0:material-tap-target-prompt-2.14.0.aar:12ab447ba97019adbecb20e048921ca30ed7a9f72a37b83f39a4333bd759b518', ] } From f73f8ca7e7bb0974e07a2fa4c19c2227bfd51e3c Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 18 Dec 2018 12:50:59 -0200 Subject: [PATCH 2/9] [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; } From ad71d69149fadc6a230c6e823533df494c29c5e8 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 18 Dec 2018 13:01:26 -0200 Subject: [PATCH 3/9] Create and use method in MessagingManager for checking for image support --- .../conversation/ConversationViewModel.java | 15 ++++----------- .../briar/api/messaging/MessagingManager.java | 10 ++++++++++ .../briar/messaging/MessagingManagerImpl.java | 9 +++++++++ 3 files changed, 23 insertions(+), 11 deletions(-) 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 61ba6ab9b..9c998e9eb 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 @@ -26,7 +26,6 @@ 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.Message; -import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.AttachmentHeader; @@ -54,7 +53,6 @@ import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; -import static org.briarproject.briar.api.messaging.MessagingManager.CLIENT_ID; @NotNullByDefault public class ConversationViewModel extends AndroidViewModel { @@ -70,12 +68,12 @@ public class ConversationViewModel extends AndroidViewModel { private final Executor dbExecutor; @CryptoExecutor private final Executor cryptoExecutor; + // TODO replace with TransactionManager once it exists private final DatabaseComponent db; private final MessagingManager messagingManager; private final ContactManager contactManager; private final SettingsManager settingsManager; private final PrivateMessageFactory privateMessageFactory; - private final ClientVersioningManager clientVersioningManager; private final AttachmentController attachmentController; @Nullable @@ -106,8 +104,7 @@ public class ConversationViewModel extends AndroidViewModel { @CryptoExecutor Executor cryptoExecutor, DatabaseComponent db, MessagingManager messagingManager, ContactManager contactManager, SettingsManager settingsManager, - PrivateMessageFactory privateMessageFactory, - ClientVersioningManager clientVersioningManager) { + PrivateMessageFactory privateMessageFactory) { super(application); this.dbExecutor = dbExecutor; this.cryptoExecutor = cryptoExecutor; @@ -116,7 +113,6 @@ public class ConversationViewModel extends AndroidViewModel { this.contactManager = contactManager; this.settingsManager = settingsManager; this.privateMessageFactory = privateMessageFactory; - this.clientVersioningManager = clientVersioningManager; this.attachmentController = new AttachmentController(messagingManager, application.getResources()); contactDeleted.setValue(false); @@ -189,11 +185,8 @@ public class ConversationViewModel extends AndroidViewModel { @DatabaseExecutor 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; + boolean imagesSupported = db.transactionWithResult(true, txn -> + messagingManager.contactSupportsImages(txn, c)); imageSupport.postValue(imagesSupported); // check if introductions are supported diff --git a/briar-api/src/main/java/org/briarproject/briar/api/messaging/MessagingManager.java b/briar-api/src/main/java/org/briarproject/briar/api/messaging/MessagingManager.java index 03bc693eb..a4f91af5c 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/messaging/MessagingManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/messaging/MessagingManager.java @@ -2,6 +2,7 @@ package org.briarproject.briar.api.messaging; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.GroupId; @@ -60,4 +61,13 @@ public interface MessagingManager extends ConversationClient { */ Attachment getAttachment(MessageId m) throws DbException; + /** + * Returns true if the contact with the given {@link ContactId} does support + * image attachments. + * + * Added: 2019-01-01 + */ + boolean contactSupportsImages(Transaction txn, ContactId c) + throws DbException; + } diff --git a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java index 223c980a2..4f20d13a9 100644 --- a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java @@ -248,4 +248,13 @@ class MessagingManagerImpl extends ConversationClientImpl return new Attachment(new ByteArrayInputStream(bytes)); } + @Override + public boolean contactSupportsImages(Transaction txn, ContactId c) + throws DbException { + int minorVersion = clientVersioningManager + .getClientMinorVersion(txn, c, CLIENT_ID, 0); + // support was added in 0.1 + return minorVersion == 1; + } + } From 3620edbfc97b222f93dc93512736b264e6915ff9 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 18 Dec 2018 13:14:15 -0200 Subject: [PATCH 4/9] [android] set a transition animation duration for ConversationActivity so we know better for how long to delay the onboarding dialogs --- .../briar/android/conversation/ConversationActivity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 e12d33a62..ca647a656 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 @@ -139,7 +139,8 @@ 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 int TRANSITION_DURATION = 500; + private static final int ONBOARDING_DELAY = TRANSITION_DURATION + 1; private static final Logger LOG = Logger.getLogger(ConversationActivity.class.getName()); @@ -206,6 +207,7 @@ public class ConversationActivity extends BriarActivity // Spurious lint warning - using END causes a crash @SuppressLint("RtlHardcoded") Transition slide = new Slide(RIGHT); + slide.setDuration(TRANSITION_DURATION); // should be default setSceneTransitionAnimation(slide, null, slide); } super.onCreate(state); From 232c2129a7ae2d3846158ddcbaabea8f9a6f2c5f Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 24 Dec 2018 13:53:00 -0200 Subject: [PATCH 5/9] [android] use a LiveData in ConversationActivity to get notified when transition ended --- .../conversation/ConversationActivity.java | 53 ++++++++++++++++--- 1 file changed, 45 insertions(+), 8 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 ca647a656..13ba6bffb 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 @@ -1,6 +1,7 @@ package org.briarproject.briar.android.conversation; import android.annotation.SuppressLint; +import android.arch.lifecycle.MutableLiveData; import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProvider; import android.arch.lifecycle.ViewModelProviders; @@ -10,6 +11,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; import android.support.annotation.UiThread; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; @@ -139,8 +141,6 @@ public class ConversationActivity extends BriarActivity TextCache, AttachmentCache, AttachImageListener { public static final String CONTACT_ID = "briar.CONTACT_ID"; - private static final int TRANSITION_DURATION = 500; - private static final int ONBOARDING_DELAY = TRANSITION_DURATION + 1; private static final Logger LOG = Logger.getLogger(ConversationActivity.class.getName()); @@ -195,6 +195,8 @@ public class ConversationActivity extends BriarActivity private volatile ContactId contactId; @Nullable private Parcelable layoutManagerState; + private final MutableLiveData canShowOnboarding = + new MutableLiveData<>(); private final Observer contactNameObserver = name -> { requireNonNull(name); @@ -207,7 +209,6 @@ public class ConversationActivity extends BriarActivity // Spurious lint warning - using END causes a crash @SuppressLint("RtlHardcoded") Transition slide = new Slide(RIGHT); - slide.setDuration(TRANSITION_DURATION); // should be default setSceneTransitionAnimation(slide, null, slide); } super.onCreate(state); @@ -289,7 +290,8 @@ public class ConversationActivity extends BriarActivity } @Override - protected void onActivityResult(int request, int result, Intent data) { + protected void onActivityResult(int request, int result, + @Nullable Intent data) { super.onActivityResult(request, result, data); if (request == REQUEST_INTRODUCTION && result == RESULT_OK) { @@ -725,19 +727,19 @@ public class ConversationActivity extends BriarActivity if (show == null || !show) return; // show onboarding only after the enter transition has ended // otherwise the tap target animation won't play - textInputView.postDelayed(() -> { + observeOnce(canShowOnboarding, this, canShow -> { // remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS ((TextAttachmentController) sendController) .showImageOnboarding(this, () -> viewModel.onImageOnboardingSeen()); - }, ONBOARDING_DELAY); + }); } 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(() -> { + observeOnce(canShowOnboarding, this, canShow -> { // find view of overflow icon View target = null; for (int i = 0; i < toolbar.getChildCount(); i++) { @@ -767,7 +769,7 @@ public class ConversationActivity extends BriarActivity ContextCompat.getColor(this, R.color.briar_primary)) .setPromptStateChangeListener(listener) .show(); - }, ONBOARDING_DELAY); + }); } @Override @@ -919,4 +921,39 @@ public class ConversationActivity extends BriarActivity return attachments; } + @Override + @RequiresApi(api = 21) + public void setSceneTransitionAnimation( + @Nullable Transition enterTransition, + @Nullable Transition exitTransition, + @Nullable Transition returnTransition) { + super.setSceneTransitionAnimation(enterTransition, exitTransition, + returnTransition); + // workaround for MaterialTapTargetPrompt bug: + // https://github.com/sjwall/MaterialTapTargetPrompt/issues/147 + getWindow().getEnterTransition().addListener( + new Transition.TransitionListener() { + @Override + public void onTransitionStart(Transition transition) { + } + + @Override + public void onTransitionEnd(Transition transition) { + canShowOnboarding.setValue(true); + } + + @Override + public void onTransitionCancel(Transition transition) { + } + + @Override + public void onTransitionPause(Transition transition) { + } + + @Override + public void onTransitionResume(Transition transition) { + } + }); + } + } From 20c51c1aa4893c402aaa109559811f524ca5bf3c Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 14 Jan 2019 14:25:32 +0000 Subject: [PATCH 6/9] Group together fields with the same access restrictions. --- .../conversation/ConversationActivity.java | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 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 13ba6bffb..5ccdd5f58 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 @@ -152,21 +152,8 @@ public class ConversationActivity extends BriarActivity @Inject @CryptoExecutor Executor cryptoExecutor; - - private final Map textCache = new ConcurrentHashMap<>(); - private AttachmentController attachmentController; - - private ConversationViewModel viewModel; - private ConversationVisitor visitor; - private ConversationAdapter adapter; - private Toolbar toolbar; - private CircleImageView toolbarAvatar; - private ImageView toolbarStatus; - private TextView toolbarTitle; - private BriarRecyclerView list; - private LinearLayoutManager layoutManager; - private TextInputView textInputView; - private TextSendController sendController; + @Inject + ViewModelProvider.Factory viewModelFactory; // Fields that are accessed from background threads must be volatile @Inject @@ -189,20 +176,32 @@ public class ConversationActivity extends BriarActivity volatile BlogSharingManager blogSharingManager; @Inject volatile GroupInvitationManager groupInvitationManager; - @Inject - ViewModelProvider.Factory viewModelFactory; - private volatile ContactId contactId; - @Nullable - private Parcelable layoutManagerState; + private final Map textCache = new ConcurrentHashMap<>(); private final MutableLiveData canShowOnboarding = new MutableLiveData<>(); - private final Observer contactNameObserver = name -> { requireNonNull(name); loadMessages(); }; + private AttachmentController attachmentController; + private ConversationViewModel viewModel; + private ConversationVisitor visitor; + private ConversationAdapter adapter; + private Toolbar toolbar; + private CircleImageView toolbarAvatar; + private ImageView toolbarStatus; + private TextView toolbarTitle; + private BriarRecyclerView list; + private LinearLayoutManager layoutManager; + private TextInputView textInputView; + private TextSendController sendController; + @Nullable + private Parcelable layoutManagerState; + + private volatile ContactId contactId; + @Override public void onCreate(@Nullable Bundle state) { if (SDK_INT >= 21) { From ab07dfb32ce3a16a0bba1fd3fd8bc15716c27433 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 14 Jan 2019 14:26:09 +0000 Subject: [PATCH 7/9] Use expression lambda. --- .../briar/android/conversation/ConversationActivity.java | 5 ++--- 1 file changed, 2 insertions(+), 3 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 5ccdd5f58..932a0a2c1 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 @@ -365,9 +365,8 @@ public class ConversationActivity extends BriarActivity } }); // enable alias action if available - observeOnce(viewModel.getContact(), this, contact -> { - menu.findItem(R.id.action_set_alias).setEnabled(true); - }); + observeOnce(viewModel.getContact(), this, contact -> + menu.findItem(R.id.action_set_alias).setEnabled(true)); return super.onCreateOptionsMenu(menu); } From 226ed3dd7335026be3688cc1db8659dc28eb9f11 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 14 Jan 2019 14:31:31 +0000 Subject: [PATCH 8/9] Wrap long line, remove redundant variable. --- .../briar/android/view/TextAttachmentController.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java b/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java index 79fc6eb7a..6cb7868de 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java @@ -219,18 +219,20 @@ public class TextAttachmentController extends TextSendController reset(); } - public void showImageOnboarding(Activity activity, Runnable onOnboardingSeen) { + public void showImageOnboarding(Activity activity, + Runnable onOnboardingSeen) { PromptStateChangeListener listener = (prompt, state) -> { if (state == STATE_DISMISSED || state == STATE_FINISHED) { onOnboardingSeen.run(); } }; int color = resolveColorAttribute(activity, R.attr.colorControlNormal); - MaterialTapTargetPrompt p = new MaterialTapTargetPrompt.Builder(activity, + new MaterialTapTargetPrompt.Builder(activity, R.style.OnboardingDialogTheme).setTarget(imageButton) .setPrimaryText(R.string.dialog_title_image_support) .setSecondaryText(R.string.dialog_message_image_support) - .setBackgroundColour(getColor(activity, R.color.briar_primary)) + .setBackgroundColour( + getColor(activity, R.color.briar_primary)) .setIcon(R.drawable.ic_image) .setIconDrawableColourFilter(color) .setPromptStateChangeListener(listener) From 4b62c51fbf8ab997469a19372639aa86d275b592 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 14 Jan 2019 17:36:07 +0000 Subject: [PATCH 9/9] Revert to using a fixed delay for the onboarding. --- .../conversation/ConversationActivity.java | 144 ++++++++---------- .../view/TextAttachmentController.java | 3 +- 2 files changed, 64 insertions(+), 83 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 932a0a2c1..a25460cd4 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 @@ -1,7 +1,6 @@ package org.briarproject.briar.android.conversation; import android.annotation.SuppressLint; -import android.arch.lifecycle.MutableLiveData; import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProvider; import android.arch.lifecycle.ViewModelProviders; @@ -11,7 +10,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; import android.support.annotation.UiThread; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; @@ -145,6 +143,9 @@ public class ConversationActivity extends BriarActivity private static final Logger LOG = Logger.getLogger(ConversationActivity.class.getName()); + private static final int TRANSITION_DURATION_MS = 500; + private static final int ONBOARDING_DELAY_MS = 250; + @Inject AndroidNotificationManager notificationManager; @Inject @@ -178,8 +179,6 @@ public class ConversationActivity extends BriarActivity volatile GroupInvitationManager groupInvitationManager; private final Map textCache = new ConcurrentHashMap<>(); - private final MutableLiveData canShowOnboarding = - new MutableLiveData<>(); private final Observer contactNameObserver = name -> { requireNonNull(name); loadMessages(); @@ -208,6 +207,7 @@ public class ConversationActivity extends BriarActivity // Spurious lint warning - using END causes a crash @SuppressLint("RtlHardcoded") Transition slide = new Slide(RIGHT); + slide.setDuration(TRANSITION_DURATION_MS); setSceneTransitionAnimation(slide, null, slide); } super.onCreate(state); @@ -242,8 +242,8 @@ public class ConversationActivity extends BriarActivity requireNonNull(deleted); if (deleted) finish(); }); - viewModel.getAddedPrivateMessage() - .observe(this, this::onAddedPrivateMessage); + viewModel.getAddedPrivateMessage().observe(this, + this::onAddedPrivateMessage); setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId)); setTransitionName(toolbarStatus, getBulbTransitionName(contactId)); @@ -723,51 +723,68 @@ public class ConversationActivity extends BriarActivity private void showImageOnboarding(@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 - observeOnce(canShowOnboarding, this, canShow -> { - // remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS - ((TextAttachmentController) sendController) - .showImageOnboarding(this, () -> - viewModel.onImageOnboardingSeen()); - }); + if (SDK_INT >= 21) { + // show onboarding only after the enter transition has ended + // otherwise the tap target animation won't play + textInputView.postDelayed(this::showImageOnboarding, + TRANSITION_DURATION_MS + ONBOARDING_DELAY_MS); + } else { + showImageOnboarding(); + } + } + + private void showImageOnboarding() { + // remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS + ((TextAttachmentController) sendController) + .showImageOnboarding(this, () -> + viewModel.onImageOnboardingSeen()); } 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 - observeOnce(canShowOnboarding, this, canShow -> { - // find view of overflow icon - View target = null; - for (int i = 0; i < toolbar.getChildCount(); i++) { - if (toolbar.getChildAt(i) instanceof ActionMenuView) { - ActionMenuView menu = - (ActionMenuView) toolbar.getChildAt(i); - target = menu.getChildAt(menu.getChildCount() - 1); - break; - } - } - if (target == null) { - LOG.warning("No Overflow Icon found!"); - return; - } + if (SDK_INT >= 21) { + // show onboarding only after the enter transition has ended + // otherwise the tap target animation won't play + textInputView.postDelayed(this::showIntroductionOnboarding, + TRANSITION_DURATION_MS + ONBOARDING_DELAY_MS); + } else { + showIntroductionOnboarding(); + } + } - PromptStateChangeListener listener = (prompt, state) -> { - if (state == STATE_DISMISSED || state == STATE_FINISHED) { - viewModel.onIntroductionOnboardingSeen(); - } - }; - new MaterialTapTargetPrompt.Builder(ConversationActivity.this, - R.style.OnboardingDialogTheme).setTarget(target) - .setPrimaryText(R.string.introduction_onboarding_title) - .setSecondaryText(R.string.introduction_onboarding_text) - .setIcon(R.drawable.ic_more_vert_accent) - .setBackgroundColour( - ContextCompat.getColor(this, R.color.briar_primary)) - .setPromptStateChangeListener(listener) - .show(); - }); + private void showIntroductionOnboarding() { + // find view of overflow icon + View target = null; + for (int i = 0; i < toolbar.getChildCount(); i++) { + if (toolbar.getChildAt(i) instanceof ActionMenuView) { + ActionMenuView menu = (ActionMenuView) toolbar.getChildAt(i); + // The overflow icon should be the last child of the menu + target = menu.getChildAt(menu.getChildCount() - 1); + // If the menu hasn't been populated yet, use the menu itself + // as the target + if (target == null) target = menu; + break; + } + } + if (target == null) { + LOG.warning("No Overflow Icon found!"); + return; + } + + PromptStateChangeListener listener = (prompt, state) -> { + if (state == STATE_DISMISSED || state == STATE_FINISHED) { + viewModel.onIntroductionOnboardingSeen(); + } + }; + new MaterialTapTargetPrompt.Builder(ConversationActivity.this, + R.style.OnboardingDialogTheme).setTarget(target) + .setPrimaryText(R.string.introduction_onboarding_title) + .setSecondaryText(R.string.introduction_onboarding_text) + .setIcon(R.drawable.ic_more_vert_accent) + .setBackgroundColour( + ContextCompat.getColor(this, R.color.briar_primary)) + .setPromptStateChangeListener(listener) + .show(); } @Override @@ -919,39 +936,4 @@ public class ConversationActivity extends BriarActivity return attachments; } - @Override - @RequiresApi(api = 21) - public void setSceneTransitionAnimation( - @Nullable Transition enterTransition, - @Nullable Transition exitTransition, - @Nullable Transition returnTransition) { - super.setSceneTransitionAnimation(enterTransition, exitTransition, - returnTransition); - // workaround for MaterialTapTargetPrompt bug: - // https://github.com/sjwall/MaterialTapTargetPrompt/issues/147 - getWindow().getEnterTransition().addListener( - new Transition.TransitionListener() { - @Override - public void onTransitionStart(Transition transition) { - } - - @Override - public void onTransitionEnd(Transition transition) { - canShowOnboarding.setValue(true); - } - - @Override - public void onTransitionCancel(Transition transition) { - } - - @Override - public void onTransitionPause(Transition transition) { - } - - @Override - public void onTransitionResume(Transition transition) { - } - }); - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java b/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java index 6cb7868de..6248b6669 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java @@ -231,8 +231,7 @@ public class TextAttachmentController extends TextSendController R.style.OnboardingDialogTheme).setTarget(imageButton) .setPrimaryText(R.string.dialog_title_image_support) .setSecondaryText(R.string.dialog_message_image_support) - .setBackgroundColour( - getColor(activity, R.color.briar_primary)) + .setBackgroundColour(getColor(activity, R.color.briar_primary)) .setIcon(R.drawable.ic_image) .setIconDrawableColourFilter(color) .setPromptStateChangeListener(listener)