mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 05:09:53 +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 'info.guardianproject.trustedintents:trustedintents:0.2'
|
||||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||||
implementation 'com.google.zxing:core:3.3.3'
|
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'
|
implementation 'com.vanniktech:emoji-google:0.5.1'
|
||||||
def glideVersion = '4.8.0'
|
def glideVersion = '4.8.0'
|
||||||
implementation("com.github.bumptech.glide:glide:$glideVersion") {
|
implementation("com.github.bumptech.glide:glide:$glideVersion") {
|
||||||
@@ -134,7 +134,7 @@ dependencies {
|
|||||||
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
|
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
|
||||||
testImplementation 'org.robolectric:robolectric:4.0.1'
|
testImplementation 'org.robolectric:robolectric:4.0.1'
|
||||||
testImplementation 'org.robolectric:shadows-support-v4:3.3.2'
|
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 'junit:junit:4.12'
|
||||||
testImplementation "org.jmock:jmock:2.8.2"
|
testImplementation "org.jmock:jmock:2.8.2"
|
||||||
testImplementation "org.jmock:jmock-junit4: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.ConnectionRegistry;
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
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.settings.SettingsManager;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
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.ATTACHMENT_POSITION;
|
||||||
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
|
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
|
||||||
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
|
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.getAvatarTransitionName;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||||
@@ -144,8 +142,9 @@ public class ConversationActivity extends BriarActivity
|
|||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(ConversationActivity.class.getName());
|
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
|
@Inject
|
||||||
AndroidNotificationManager notificationManager;
|
AndroidNotificationManager notificationManager;
|
||||||
@@ -154,21 +153,8 @@ public class ConversationActivity extends BriarActivity
|
|||||||
@Inject
|
@Inject
|
||||||
@CryptoExecutor
|
@CryptoExecutor
|
||||||
Executor cryptoExecutor;
|
Executor cryptoExecutor;
|
||||||
|
@Inject
|
||||||
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
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;
|
|
||||||
|
|
||||||
// Fields that are accessed from background threads must be volatile
|
// Fields that are accessed from background threads must be volatile
|
||||||
@Inject
|
@Inject
|
||||||
@@ -191,24 +177,37 @@ public class ConversationActivity extends BriarActivity
|
|||||||
volatile BlogSharingManager blogSharingManager;
|
volatile BlogSharingManager blogSharingManager;
|
||||||
@Inject
|
@Inject
|
||||||
volatile GroupInvitationManager groupInvitationManager;
|
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 -> {
|
private final Observer<String> contactNameObserver = name -> {
|
||||||
requireNonNull(name);
|
requireNonNull(name);
|
||||||
loadMessages();
|
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
|
@Override
|
||||||
public void onCreate(@Nullable Bundle state) {
|
public void onCreate(@Nullable Bundle state) {
|
||||||
if (SDK_INT >= 21) {
|
if (SDK_INT >= 21) {
|
||||||
// Spurious lint warning - using END causes a crash
|
// Spurious lint warning - using END causes a crash
|
||||||
@SuppressLint("RtlHardcoded")
|
@SuppressLint("RtlHardcoded")
|
||||||
Transition slide = new Slide(RIGHT);
|
Transition slide = new Slide(RIGHT);
|
||||||
|
slide.setDuration(TRANSITION_DURATION_MS);
|
||||||
setSceneTransitionAnimation(slide, null, slide);
|
setSceneTransitionAnimation(slide, null, slide);
|
||||||
}
|
}
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
@@ -243,8 +242,8 @@ public class ConversationActivity extends BriarActivity
|
|||||||
requireNonNull(deleted);
|
requireNonNull(deleted);
|
||||||
if (deleted) finish();
|
if (deleted) finish();
|
||||||
});
|
});
|
||||||
viewModel.getAddedPrivateMessage()
|
viewModel.getAddedPrivateMessage().observe(this,
|
||||||
.observe(this, this::onAddedPrivateMessage);
|
this::onAddedPrivateMessage);
|
||||||
|
|
||||||
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
||||||
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
|
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
|
||||||
@@ -263,6 +262,13 @@ public class ConversationActivity extends BriarActivity
|
|||||||
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
||||||
sendController = new TextAttachmentController(textInputView,
|
sendController = new TextAttachmentController(textInputView,
|
||||||
imagePreview, this, this);
|
imagePreview, this, this);
|
||||||
|
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
|
||||||
|
if (hasSupport != null && hasSupport) {
|
||||||
|
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
||||||
|
((TextAttachmentController) sendController)
|
||||||
|
.setImagesSupported();
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
sendController = new TextSendController(textInputView, this, false);
|
sendController = new TextSendController(textInputView, this, false);
|
||||||
}
|
}
|
||||||
@@ -283,7 +289,8 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
super.onActivityResult(request, result, data);
|
||||||
|
|
||||||
if (request == REQUEST_INTRODUCTION && result == RESULT_OK) {
|
if (request == REQUEST_INTRODUCTION && result == RESULT_OK) {
|
||||||
@@ -348,10 +355,18 @@ public class ConversationActivity extends BriarActivity
|
|||||||
MenuInflater inflater = getMenuInflater();
|
MenuInflater inflater = getMenuInflater();
|
||||||
inflater.inflate(R.menu.conversation_actions, menu);
|
inflater.inflate(R.menu.conversation_actions, menu);
|
||||||
|
|
||||||
enableIntroductionActionIfAvailable(
|
// enable introduction action if available
|
||||||
menu.findItem(R.id.action_introduction));
|
observeOnce(viewModel.showIntroductionAction(), this, enable -> {
|
||||||
enableAliasActionIfAvailable(
|
if (enable != null && enable) {
|
||||||
menu.findItem(R.id.action_set_alias));
|
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);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
@@ -461,6 +476,10 @@ public class ConversationActivity extends BriarActivity
|
|||||||
if (revision == adapter.getRevision()) {
|
if (revision == adapter.getRevision()) {
|
||||||
adapter.incrementRevision();
|
adapter.incrementRevision();
|
||||||
textInputView.setEnabled(true);
|
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);
|
List<ConversationItem> items = createItems(headers);
|
||||||
adapter.addAll(items);
|
adapter.addAll(items);
|
||||||
list.showData();
|
list.showData();
|
||||||
@@ -702,74 +721,70 @@ public class ConversationActivity extends BriarActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableIntroductionActionIfAvailable(MenuItem item) {
|
private void showImageOnboarding(@Nullable Boolean show) {
|
||||||
runOnDbThread(() -> {
|
if (show == null || !show) return;
|
||||||
try {
|
if (SDK_INT >= 21) {
|
||||||
if (contactManager.getActiveContacts().size() > 1) {
|
// show onboarding only after the enter transition has ended
|
||||||
enableIntroductionAction(item);
|
// otherwise the tap target animation won't play
|
||||||
Settings settings =
|
textInputView.postDelayed(this::showImageOnboarding,
|
||||||
settingsManager.getSettings(SETTINGS_NAMESPACE);
|
TRANSITION_DURATION_MS + ONBOARDING_DELAY_MS);
|
||||||
if (settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION,
|
} else {
|
||||||
true)) {
|
showImageOnboarding();
|
||||||
showIntroductionOnboarding();
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableAliasActionIfAvailable(MenuItem item) {
|
private void showImageOnboarding() {
|
||||||
observeOnce(viewModel.getContact(), this, c -> item.setEnabled(true));
|
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
||||||
|
((TextAttachmentController) sendController)
|
||||||
|
.showImageOnboarding(this, () ->
|
||||||
|
viewModel.onImageOnboardingSeen());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableIntroductionAction(MenuItem item) {
|
private void showIntroductionOnboarding(@Nullable Boolean show) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> item.setEnabled(true));
|
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() {
|
private void showIntroductionOnboarding() {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
// find view of overflow icon
|
||||||
// find view of overflow icon
|
View target = null;
|
||||||
View target = null;
|
for (int i = 0; i < toolbar.getChildCount(); i++) {
|
||||||
for (int i = 0; i < toolbar.getChildCount(); i++) {
|
if (toolbar.getChildAt(i) instanceof ActionMenuView) {
|
||||||
if (toolbar.getChildAt(i) instanceof ActionMenuView) {
|
ActionMenuView menu = (ActionMenuView) toolbar.getChildAt(i);
|
||||||
ActionMenuView menu =
|
// The overflow icon should be the last child of the menu
|
||||||
(ActionMenuView) toolbar.getChildAt(i);
|
target = menu.getChildAt(menu.getChildCount() - 1);
|
||||||
target = menu.getChildAt(menu.getChildCount() - 1);
|
// If the menu hasn't been populated yet, use the menu itself
|
||||||
break;
|
// as the target
|
||||||
}
|
if (target == null) target = menu;
|
||||||
}
|
break;
|
||||||
if (target == null) {
|
|
||||||
LOG.warning("No Overflow Icon found!");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (target == null) {
|
||||||
|
LOG.warning("No Overflow Icon found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PromptStateChangeListener listener = (prompt, state) -> {
|
PromptStateChangeListener listener = (prompt, state) -> {
|
||||||
if (state == STATE_DISMISSED || state == STATE_FINISHED) {
|
if (state == STATE_DISMISSED || state == STATE_FINISHED) {
|
||||||
introductionOnboardingSeen();
|
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)
|
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
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
|
@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.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
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.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
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.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
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.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
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.android.util.UiUtils.observeForeverOnce;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -54,13 +59,20 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
private static Logger LOG =
|
private static Logger LOG =
|
||||||
getLogger(ConversationViewModel.class.getName());
|
getLogger(ConversationViewModel.class.getName());
|
||||||
|
private static final String SHOW_ONBOARDING_IMAGE =
|
||||||
|
"showOnboardingImage";
|
||||||
|
private static final String SHOW_ONBOARDING_INTRODUCTION =
|
||||||
|
"showOnboardingIntroduction";
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
@CryptoExecutor
|
@CryptoExecutor
|
||||||
private final Executor cryptoExecutor;
|
private final Executor cryptoExecutor;
|
||||||
|
// TODO replace with TransactionManager once it exists
|
||||||
|
private final DatabaseComponent db;
|
||||||
private final MessagingManager messagingManager;
|
private final MessagingManager messagingManager;
|
||||||
private final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
|
private final SettingsManager settingsManager;
|
||||||
private final PrivateMessageFactory privateMessageFactory;
|
private final PrivateMessageFactory privateMessageFactory;
|
||||||
private final AttachmentController attachmentController;
|
private final AttachmentController attachmentController;
|
||||||
|
|
||||||
@@ -71,6 +83,14 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
Transformations.map(contact, c -> c.getAuthor().getId());
|
Transformations.map(contact, c -> c.getAuthor().getId());
|
||||||
private final LiveData<String> contactName =
|
private final LiveData<String> contactName =
|
||||||
Transformations.map(contact, UiUtils::getContactDisplayName);
|
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 =
|
private final MutableLiveData<Boolean> contactDeleted =
|
||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
private final MutableLiveData<GroupId> messagingGroupId =
|
private final MutableLiveData<GroupId> messagingGroupId =
|
||||||
@@ -81,38 +101,46 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
@Inject
|
@Inject
|
||||||
ConversationViewModel(Application application,
|
ConversationViewModel(Application application,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
@CryptoExecutor Executor cryptoExecutor,
|
@CryptoExecutor Executor cryptoExecutor, DatabaseComponent db,
|
||||||
MessagingManager messagingManager,
|
MessagingManager messagingManager, ContactManager contactManager,
|
||||||
ContactManager contactManager,
|
SettingsManager settingsManager,
|
||||||
PrivateMessageFactory privateMessageFactory) {
|
PrivateMessageFactory privateMessageFactory) {
|
||||||
super(application);
|
super(application);
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.cryptoExecutor = cryptoExecutor;
|
this.cryptoExecutor = cryptoExecutor;
|
||||||
|
this.db = db;
|
||||||
this.messagingManager = messagingManager;
|
this.messagingManager = messagingManager;
|
||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
|
this.settingsManager = settingsManager;
|
||||||
this.privateMessageFactory = privateMessageFactory;
|
this.privateMessageFactory = privateMessageFactory;
|
||||||
this.attachmentController = new AttachmentController(messagingManager,
|
this.attachmentController = new AttachmentController(messagingManager,
|
||||||
application.getResources());
|
application.getResources());
|
||||||
contactDeleted.setValue(false);
|
contactDeleted.setValue(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting the {@link ContactId} automatically triggers loading of other
|
||||||
|
* data.
|
||||||
|
*/
|
||||||
void setContactId(ContactId contactId) {
|
void setContactId(ContactId contactId) {
|
||||||
if (this.contactId == null) {
|
if (this.contactId == null) {
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
loadContact();
|
loadContact(contactId);
|
||||||
} else if (!contactId.equals(this.contactId)) {
|
} else if (!contactId.equals(this.contactId)) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadContact() {
|
private void loadContact(ContactId contactId) {
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
long start = now();
|
long start = now();
|
||||||
Contact c =
|
Contact c = contactManager.getContact(contactId);
|
||||||
contactManager.getContact(requireNonNull(contactId));
|
|
||||||
contact.postValue(c);
|
contact.postValue(c);
|
||||||
logDuration(LOG, "Loading contact", start);
|
logDuration(LOG, "Loading contact", start);
|
||||||
|
start = now();
|
||||||
|
checkFeaturesAndOnboarding(contactId);
|
||||||
|
logDuration(LOG, "Checking for image support", start);
|
||||||
} catch (NoSuchContactException e) {
|
} catch (NoSuchContactException e) {
|
||||||
contactDeleted.postValue(true);
|
contactDeleted.postValue(true);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
@@ -126,7 +154,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
try {
|
try {
|
||||||
contactManager.setContactAlias(requireNonNull(contactId),
|
contactManager.setContactAlias(requireNonNull(contactId),
|
||||||
alias.isEmpty() ? null : alias);
|
alias.isEmpty() ? null : alias);
|
||||||
loadContact();
|
loadContact(contactId);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, 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,
|
private void storeAttachments(GroupId groupId, @Nullable String text,
|
||||||
List<Uri> uris, long timestamp) {
|
List<Uri> uris, long timestamp) {
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
@@ -262,6 +343,22 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
return contactName;
|
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() {
|
LiveData<Boolean> isContactDeleted() {
|
||||||
return contactDeleted;
|
return contactDeleted;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.briarproject.briar.android.view;
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
@@ -8,6 +10,7 @@ import android.os.Parcelable;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.v4.view.AbsSavedState;
|
import android.support.v4.view.AbsSavedState;
|
||||||
|
import android.support.v7.app.AlertDialog.Builder;
|
||||||
import android.support.v7.widget.AppCompatImageButton;
|
import android.support.v7.widget.AppCompatImageButton;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -18,11 +21,15 @@ import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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_GET_CONTENT;
|
||||||
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
|
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
|
||||||
import static android.content.Intent.CATEGORY_OPENABLE;
|
import static android.content.Intent.CATEGORY_OPENABLE;
|
||||||
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
|
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
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.support.v4.view.AbsSavedState.EMPTY_STATE;
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
import static android.view.View.INVISIBLE;
|
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 android.widget.Toast.LENGTH_LONG;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Objects.requireNonNull;
|
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
|
@UiThread
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -42,6 +52,7 @@ public class TextAttachmentController extends TextSendController
|
|||||||
private final AttachImageListener imageListener;
|
private final AttachImageListener imageListener;
|
||||||
|
|
||||||
private CharSequence textHint;
|
private CharSequence textHint;
|
||||||
|
private boolean hasImageSupport = false;
|
||||||
private List<Uri> imageUris = emptyList();
|
private List<Uri> imageUris = emptyList();
|
||||||
|
|
||||||
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
|
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
|
||||||
@@ -78,7 +89,28 @@ public class TextAttachmentController extends TextSendController
|
|||||||
return !imageUris.isEmpty();
|
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() {
|
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 ?
|
Intent intent = new Intent(SDK_INT >= 19 ?
|
||||||
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
|
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
|
||||||
intent.addCategory(CATEGORY_OPENABLE);
|
intent.addCategory(CATEGORY_OPENABLE);
|
||||||
@@ -187,6 +219,25 @@ public class TextAttachmentController extends TextSendController
|
|||||||
reset();
|
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 {
|
private static class SavedState extends AbsSavedState {
|
||||||
|
|
||||||
@Nullable
|
@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:focusable="true"
|
||||||
android:padding="4dp"
|
android:padding="4dp"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
android:src="@drawable/ic_image"
|
android:src="@drawable/ic_image_off"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
app:tint="?attr/colorControlNormal"/>
|
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="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_success">Image was saved</string>
|
||||||
<string name="save_image_error">Could not save image</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 -->
|
<!-- Adding Contacts -->
|
||||||
<string name="add_contact_title">Add a Contact</string>
|
<string name="add_contact_title">Add a Contact</string>
|
||||||
|
|||||||
@@ -134,8 +134,8 @@ dependencyVerification {
|
|||||||
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
||||||
'nekohtml:nekohtml:1.9.6.2:nekohtml-1.9.6.2.jar:fdff6cfa9ed9cc911c842a5d2395f209ec621ef1239d46810e9e495809d3ae09',
|
'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',
|
'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-agent:1.8.10:byte-buddy-agent-1.8.10.jar:f7403b1126137eb68a5cc3beaf543d965bafca87fad7d7d30082617748c19e05',
|
||||||
'net.bytebuddy:byte-buddy:1.7.9:byte-buddy-1.7.9.jar:2ea2ada12b790d16ac7f6e6c065cb55cbcdb6ba519355f5958851159cad3b16a',
|
'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.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',
|
'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',
|
'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-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
|
||||||
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
|
'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.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.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-analysis:6.0:asm-analysis-6.0.jar:2f1a6387219c3a6cc4856481f221b03bd9f2408a326d416af09af5d6f608c1f4',
|
||||||
'org.ow2.asm:asm-commons:6.0:asm-commons-6.0.jar:f1bce5c648a96a017bdcd01fe5d59af9845297fd7b79b81c015a6fbbd9719abf',
|
'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: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',
|
'org.robolectric:utils:4.0.1:utils-4.0.1.jar:ee923ed66847271009ebeb246286b7206b160c2b6d1347fe820c00be06c280cb',
|
||||||
'tools.fastlane:screengrab:1.2.0:screengrab-1.2.0.aar:af4ee23bb06f94404d3ab18e2ea69db8265539fc8da29f9ee45b7e472684ba83',
|
'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.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
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.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.ClientId;
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
@@ -60,4 +61,13 @@ public interface MessagingManager extends ConversationClient {
|
|||||||
*/
|
*/
|
||||||
Attachment getAttachment(MessageId m) throws DbException;
|
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));
|
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