[android] only enable image feature if contact supports it

Also show an onboarding the first time, the feature gets activiated
This commit is contained in:
Torsten Grote
2018-12-17 15:41:47 -02:00
committed by akwizgran
parent 8183b7b26a
commit 16c701a71a
8 changed files with 161 additions and 11 deletions

View File

@@ -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<ConversationItem> 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();
});

View File

@@ -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<String> contactName =
Transformations.map(contact, UiUtils::getContactDisplayName);
private final MutableLiveData<Boolean> imageSupport =
new MutableLiveData<>();
private final MutableLiveData<Boolean> showImageOnboarding =
new MutableLiveData<>();
private final MutableLiveData<Boolean> contactDeleted =
new MutableLiveData<>();
private final MutableLiveData<GroupId> 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<Uri> uris, long timestamp) {
dbExecutor.execute(() -> {
@@ -262,6 +314,14 @@ public class ConversationViewModel extends AndroidViewModel {
return contactName;
}
LiveData<Boolean> hasImageSupport() {
return imageSupport;
}
LiveData<Boolean> showImageOnboarding() {
return showImageOnboarding;
}
LiveData<Boolean> isContactDeleted() {
return contactDeleted;
}

View File

@@ -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<Uri> 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