mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Merge branch '1477-check-attachment-support' into 'master'
Find out if contacts support image attachments and enable them Closes #1477 See merge request briar/briar!1019
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
@@ -144,8 +142,9 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConversationActivity.class.getName());
|
||||
private static final String SHOW_ONBOARDING_INTRODUCTION =
|
||||
"showOnboardingIntroduction";
|
||||
|
||||
private static final int TRANSITION_DURATION_MS = 500;
|
||||
private static final int ONBOARDING_DELAY_MS = 250;
|
||||
|
||||
@Inject
|
||||
AndroidNotificationManager notificationManager;
|
||||
@@ -154,21 +153,8 @@ public class ConversationActivity extends BriarActivity
|
||||
@Inject
|
||||
@CryptoExecutor
|
||||
Executor cryptoExecutor;
|
||||
|
||||
private final Map<MessageId, String> 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
|
||||
@@ -191,24 +177,37 @@ 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<MessageId, String> textCache = new ConcurrentHashMap<>();
|
||||
private final Observer<String> 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) {
|
||||
// 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);
|
||||
@@ -243,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));
|
||||
@@ -263,6 +262,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);
|
||||
}
|
||||
@@ -283,7 +289,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) {
|
||||
@@ -348,10 +355,18 @@ 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);
|
||||
}
|
||||
@@ -461,6 +476,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();
|
||||
@@ -702,74 +721,70 @@ 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 showImageOnboarding(@Nullable Boolean show) {
|
||||
if (show == null || !show) 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::showImageOnboarding,
|
||||
TRANSITION_DURATION_MS + ONBOARDING_DELAY_MS);
|
||||
} else {
|
||||
showImageOnboarding();
|
||||
}
|
||||
}
|
||||
|
||||
private void enableAliasActionIfAvailable(MenuItem item) {
|
||||
observeOnce(viewModel.getContact(), this, c -> item.setEnabled(true));
|
||||
private void showImageOnboarding() {
|
||||
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
||||
((TextAttachmentController) sendController)
|
||||
.showImageOnboarding(this, () ->
|
||||
viewModel.onImageOnboardingSeen());
|
||||
}
|
||||
|
||||
private void enableIntroductionAction(MenuItem item) {
|
||||
runOnUiThreadUnlessDestroyed(() -> item.setEnabled(true));
|
||||
private void showIntroductionOnboarding(@Nullable Boolean show) {
|
||||
if (show == null || !show) 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();
|
||||
}
|
||||
}
|
||||
|
||||
private void showIntroductionOnboarding() {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
// 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;
|
||||
// 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) {
|
||||
introductionOnboardingSeen();
|
||||
}
|
||||
};
|
||||
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)
|
||||
.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);
|
||||
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
|
||||
|
||||
@@ -16,11 +16,14 @@ 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.briar.android.util.UiUtils;
|
||||
@@ -34,6 +37,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;
|
||||
@@ -47,6 +51,7 @@ 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;
|
||||
|
||||
@NotNullByDefault
|
||||
@@ -54,13 +59,20 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
|
||||
private static Logger LOG =
|
||||
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;
|
||||
@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 AttachmentController attachmentController;
|
||||
|
||||
@@ -71,6 +83,14 @@ 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> showIntroductionOnboarding =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> showIntroductionAction =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> contactDeleted =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<GroupId> messagingGroupId =
|
||||
@@ -81,38 +101,46 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
@Inject
|
||||
ConversationViewModel(Application application,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
MessagingManager messagingManager,
|
||||
ContactManager contactManager,
|
||||
@CryptoExecutor Executor cryptoExecutor, DatabaseComponent db,
|
||||
MessagingManager messagingManager, ContactManager contactManager,
|
||||
SettingsManager settingsManager,
|
||||
PrivateMessageFactory privateMessageFactory) {
|
||||
super(application);
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.cryptoExecutor = cryptoExecutor;
|
||||
this.db = db;
|
||||
this.messagingManager = messagingManager;
|
||||
this.contactManager = contactManager;
|
||||
this.settingsManager = settingsManager;
|
||||
this.privateMessageFactory = privateMessageFactory;
|
||||
this.attachmentController = new AttachmentController(messagingManager,
|
||||
application.getResources());
|
||||
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();
|
||||
checkFeaturesAndOnboarding(contactId);
|
||||
logDuration(LOG, "Checking for image support", start);
|
||||
} catch (NoSuchContactException e) {
|
||||
contactDeleted.postValue(true);
|
||||
} catch (DbException e) {
|
||||
@@ -126,7 +154,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);
|
||||
}
|
||||
@@ -154,6 +182,59 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void checkFeaturesAndOnboarding(ContactId c) throws DbException {
|
||||
// check if images are supported
|
||||
boolean imagesSupported = db.transactionWithResult(true, txn ->
|
||||
messagingManager.contactSupportsImages(txn, c));
|
||||
imageSupport.postValue(imagesSupported);
|
||||
|
||||
// check if introductions are supported
|
||||
Collection<Contact> 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 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(key, 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 +343,22 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
return contactName;
|
||||
}
|
||||
|
||||
LiveData<Boolean> hasImageSupport() {
|
||||
return imageSupport;
|
||||
}
|
||||
|
||||
LiveData<Boolean> showImageOnboarding() {
|
||||
return showImageOnboarding;
|
||||
}
|
||||
|
||||
LiveData<Boolean> showIntroductionOnboarding() {
|
||||
return showIntroductionOnboarding;
|
||||
}
|
||||
|
||||
LiveData<Boolean> showIntroductionAction() {
|
||||
return showIntroductionAction;
|
||||
}
|
||||
|
||||
LiveData<Boolean> isContactDeleted() {
|
||||
return contactDeleted;
|
||||
}
|
||||
|
||||
@@ -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,25 @@ 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);
|
||||
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
|
||||
|
||||
11
briar-android/src/main/res/drawable/ic_image_off.xml
Normal file
11
briar-android/src/main/res/drawable/ic_image_off.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:alpha="0.56"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M2.28,3L1,4.27L3,6.27V19A2,2 0 0,0 5,21H17.73L19.73,23L21,21.72L2.28,3M4.83,3L21,19.17V5C21,3.89 20.1,3 19,3H4.83M8.5,13.5L11,16.5L12,15.25L14.73,18H5L8.5,13.5Z"/>
|
||||
</vector>
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -144,6 +144,10 @@
|
||||
<string name="dialog_message_save_image">Saving this image will allow other apps to access it.\n\nAre you sure you want to save?</string>
|
||||
<string name="save_image_success">Image was saved</string>
|
||||
<string name="save_image_error">Could not save image</string>
|
||||
<string name="dialog_title_no_image_support">Images Unavailable</string>
|
||||
<string name="dialog_message_no_image_support">Your contact\'s Briar does not yet support image attachments. Once they upgrade you\'ll see a different icon.</string>
|
||||
<string name="dialog_title_image_support">You can now send images to this contact</string>
|
||||
<string name="dialog_message_image_support">Tap this icon to attach images.</string>
|
||||
|
||||
<!-- Adding Contacts -->
|
||||
<string name="add_contact_title">Add a Contact</string>
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user