mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-21 23:29:52 +01:00
Merge branch '1628-multi-select' into 'master'
Multi-select conversion messages (to delete) Closes #1628 See merge request briar/briar!1179
This commit is contained in:
@@ -97,6 +97,7 @@ dependencies {
|
|||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
|
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'com.google.android.material:material:1.1.0-beta01'
|
implementation 'com.google.android.material:material:1.1.0-beta01'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview-selection:1.0.0'
|
||||||
|
|
||||||
implementation 'ch.acra:acra:4.11'
|
implementation 'ch.acra:acra:4.11'
|
||||||
implementation 'info.guardianproject.panic:panic:1.0'
|
implementation 'info.guardianproject.panic:panic:1.0'
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.os.Parcelable;
|
|||||||
import android.transition.Slide;
|
import android.transition.Slide;
|
||||||
import android.transition.Transition;
|
import android.transition.Transition;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
import android.view.ActionMode;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -98,7 +99,13 @@ import androidx.core.content.ContextCompat;
|
|||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
|
import androidx.recyclerview.selection.Selection;
|
||||||
|
import androidx.recyclerview.selection.SelectionPredicates;
|
||||||
|
import androidx.recyclerview.selection.SelectionTracker;
|
||||||
|
import androidx.recyclerview.selection.SelectionTracker.SelectionObserver;
|
||||||
|
import androidx.recyclerview.selection.StorageStrategy;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import de.hdodenhof.circleimageview.CircleImageView;
|
import de.hdodenhof.circleimageview.CircleImageView;
|
||||||
import im.delight.android.identicons.IdenticonDrawable;
|
import im.delight.android.identicons.IdenticonDrawable;
|
||||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
||||||
@@ -120,6 +127,7 @@ import static java.util.logging.Logger.getLogger;
|
|||||||
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.bramble.util.StringUtils.fromHexString;
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ATTACH_IMAGE;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ATTACH_IMAGE;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
|
||||||
@@ -137,7 +145,7 @@ import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVAT
|
|||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class ConversationActivity extends BriarActivity
|
public class ConversationActivity extends BriarActivity
|
||||||
implements EventListener, ConversationListener, TextCache,
|
implements EventListener, ConversationListener, TextCache,
|
||||||
AttachmentCache, AttachmentListener {
|
AttachmentCache, AttachmentListener, ActionMode.Callback {
|
||||||
|
|
||||||
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
||||||
|
|
||||||
@@ -194,8 +202,11 @@ public class ConversationActivity extends BriarActivity
|
|||||||
private LinearLayoutManager layoutManager;
|
private LinearLayoutManager layoutManager;
|
||||||
private TextInputView textInputView;
|
private TextInputView textInputView;
|
||||||
private TextSendController sendController;
|
private TextSendController sendController;
|
||||||
|
private SelectionTracker<String> tracker;
|
||||||
@Nullable
|
@Nullable
|
||||||
private Parcelable layoutManagerState;
|
private Parcelable layoutManagerState;
|
||||||
|
@Nullable
|
||||||
|
private ActionMode actionMode;
|
||||||
|
|
||||||
private volatile ContactId contactId;
|
private volatile ContactId contactId;
|
||||||
|
|
||||||
@@ -257,6 +268,9 @@ public class ConversationActivity extends BriarActivity
|
|||||||
ConversationScrollListener scrollListener =
|
ConversationScrollListener scrollListener =
|
||||||
new ConversationScrollListener(adapter, viewModel);
|
new ConversationScrollListener(adapter, viewModel);
|
||||||
list.getRecyclerView().addOnScrollListener(scrollListener);
|
list.getRecyclerView().addOnScrollListener(scrollListener);
|
||||||
|
if (featureFlags.shouldEnablePrivateMessageDeletion()) {
|
||||||
|
addSelectionTracker();
|
||||||
|
}
|
||||||
|
|
||||||
textInputView = findViewById(R.id.text_input_container);
|
textInputView = findViewById(R.id.text_input_container);
|
||||||
if (featureFlags.shouldEnableImageAttachments()) {
|
if (featureFlags.shouldEnableImageAttachments()) {
|
||||||
@@ -346,12 +360,14 @@ public class ConversationActivity extends BriarActivity
|
|||||||
layoutManagerState = layoutManager.onSaveInstanceState();
|
layoutManagerState = layoutManager.onSaveInstanceState();
|
||||||
outState.putParcelable("layoutManager", layoutManagerState);
|
outState.putParcelable("layoutManager", layoutManagerState);
|
||||||
}
|
}
|
||||||
|
if (tracker != null) tracker.onSaveInstanceState(outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||||
super.onRestoreInstanceState(savedInstanceState);
|
super.onRestoreInstanceState(savedInstanceState);
|
||||||
layoutManagerState = savedInstanceState.getParcelable("layoutManager");
|
layoutManagerState = savedInstanceState.getParcelable("layoutManager");
|
||||||
|
if (tracker != null) tracker.onRestoreInstanceState(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -409,6 +425,83 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||||
|
MenuInflater inflater = mode.getMenuInflater();
|
||||||
|
inflater.inflate(R.menu.conversation_message_actions, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||||
|
return false; // no update needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.action_delete) {
|
||||||
|
deleteSelectedMessages();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyActionMode(ActionMode mode) {
|
||||||
|
tracker.clearSelection();
|
||||||
|
actionMode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSelectionTracker() {
|
||||||
|
RecyclerView recyclerView = list.getRecyclerView();
|
||||||
|
if (recyclerView.getAdapter() != adapter)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
|
||||||
|
tracker = new SelectionTracker.Builder<>(
|
||||||
|
"conversationSelection",
|
||||||
|
recyclerView,
|
||||||
|
new ConversationItemKeyProvider(adapter),
|
||||||
|
new ConversationItemDetailsLookup(recyclerView),
|
||||||
|
StorageStrategy.createStringStorage()
|
||||||
|
).withSelectionPredicate(
|
||||||
|
SelectionPredicates.createSelectAnything()
|
||||||
|
).build();
|
||||||
|
|
||||||
|
SelectionObserver<String> observer = new SelectionObserver<String>() {
|
||||||
|
@Override
|
||||||
|
public void onItemStateChanged(String key, boolean selected) {
|
||||||
|
if (selected && actionMode == null) {
|
||||||
|
actionMode = startActionMode(ConversationActivity.this);
|
||||||
|
updateActionModeTitle();
|
||||||
|
} else if (actionMode != null) {
|
||||||
|
if (selected || tracker.hasSelection()) {
|
||||||
|
updateActionModeTitle();
|
||||||
|
} else {
|
||||||
|
actionMode.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tracker.addObserver(observer);
|
||||||
|
adapter.setSelectionTracker(tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateActionModeTitle() {
|
||||||
|
if (actionMode == null) throw new IllegalStateException();
|
||||||
|
String title = String.valueOf(tracker.getSelection().size());
|
||||||
|
actionMode.setTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<MessageId> getSelection() {
|
||||||
|
Selection<String> selection = tracker.getSelection();
|
||||||
|
List<MessageId> messages = new ArrayList<>(selection.size());
|
||||||
|
for (String str : selection) {
|
||||||
|
MessageId id = new MessageId(fromHexString(str));
|
||||||
|
messages.add(id);
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void displayContactOnlineStatus() {
|
private void displayContactOnlineStatus() {
|
||||||
if (connectionRegistry.isConnected(contactId)) {
|
if (connectionRegistry.isConnected(contactId)) {
|
||||||
@@ -766,7 +859,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
try {
|
try {
|
||||||
boolean allDeleted =
|
boolean allDeleted =
|
||||||
conversationManager.deleteAllMessages(contactId);
|
conversationManager.deleteAllMessages(contactId);
|
||||||
reloadConversationAfterDeletingAllMessages(allDeleted);
|
reloadConversationAfterDeletingMessages(allDeleted);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
runOnUiThreadUnlessDestroyed(() -> list.showData());
|
runOnUiThreadUnlessDestroyed(() -> list.showData());
|
||||||
@@ -774,10 +867,28 @@ public class ConversationActivity extends BriarActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reloadConversationAfterDeletingAllMessages(
|
private void deleteSelectedMessages() {
|
||||||
|
list.showProgressBar();
|
||||||
|
Collection<MessageId> selected = getSelection();
|
||||||
|
// close action mode only after getting the selection
|
||||||
|
if (actionMode != null) actionMode.finish();
|
||||||
|
runOnDbThread(() -> {
|
||||||
|
try {
|
||||||
|
boolean allDeleted =
|
||||||
|
conversationManager.deleteMessages(contactId, selected);
|
||||||
|
reloadConversationAfterDeletingMessages(allDeleted);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
runOnUiThreadUnlessDestroyed(() -> list.showData());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadConversationAfterDeletingMessages(
|
||||||
boolean allDeleted) {
|
boolean allDeleted) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
adapter.clear();
|
adapter.clear();
|
||||||
|
list.showProgressBar(); // otherwise clearing shows empty state
|
||||||
loadMessages();
|
loadMessages();
|
||||||
if (!allDeleted) showNotAllDeletedDialog();
|
if (!allDeleted) showNotAllDeletedDialog();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ import org.briarproject.briar.R;
|
|||||||
import org.briarproject.briar.android.util.BriarAdapter;
|
import org.briarproject.briar.android.util.BriarAdapter;
|
||||||
import org.briarproject.briar.android.util.ItemReturningAdapter;
|
import org.briarproject.briar.android.util.ItemReturningAdapter;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import androidx.annotation.LayoutRes;
|
import androidx.annotation.LayoutRes;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.selection.SelectionTracker;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool;
|
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool;
|
||||||
|
|
||||||
|
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ConversationAdapter
|
class ConversationAdapter
|
||||||
extends BriarAdapter<ConversationItem, ConversationItemViewHolder>
|
extends BriarAdapter<ConversationItem, ConversationItemViewHolder>
|
||||||
@@ -27,6 +29,8 @@ class ConversationAdapter
|
|||||||
private ConversationListener listener;
|
private ConversationListener listener;
|
||||||
private final RecycledViewPool imageViewPool;
|
private final RecycledViewPool imageViewPool;
|
||||||
private final ImageItemDecoration imageItemDecoration;
|
private final ImageItemDecoration imageItemDecoration;
|
||||||
|
@Nullable
|
||||||
|
private SelectionTracker<String> tracker = null;
|
||||||
|
|
||||||
ConversationAdapter(Context ctx,
|
ConversationAdapter(Context ctx,
|
||||||
ConversationListener conversationListener) {
|
ConversationListener conversationListener) {
|
||||||
@@ -45,6 +49,17 @@ class ConversationAdapter
|
|||||||
return item.getLayout();
|
return item.getLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getItemKey(int position) {
|
||||||
|
return items.get(position).getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getPositionOfKey(String key) {
|
||||||
|
for (int i = 0; i < items.size(); i++) {
|
||||||
|
if (key.equals(items.get(i).getKey())) return i;
|
||||||
|
}
|
||||||
|
return NO_POSITION;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConversationItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
|
public ConversationItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
|
||||||
@LayoutRes int type) {
|
@LayoutRes int type) {
|
||||||
@@ -71,7 +86,8 @@ class ConversationAdapter
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(ConversationItemViewHolder ui, int position) {
|
public void onBindViewHolder(ConversationItemViewHolder ui, int position) {
|
||||||
ConversationItem item = items.get(position);
|
ConversationItem item = items.get(position);
|
||||||
ui.bind(item);
|
boolean selected = tracker != null && tracker.isSelected(item.getKey());
|
||||||
|
ui.bind(item, selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -91,6 +107,10 @@ class ConversationAdapter
|
|||||||
return c1.equals(c2);
|
return c1.equals(c2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setSelectionTracker(SelectionTracker<String> tracker) {
|
||||||
|
this.tracker = tracker;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
ConversationItem getLastItem() {
|
ConversationItem getLastItem() {
|
||||||
if (items.size() > 0) {
|
if (items.size() > 0) {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import javax.annotation.concurrent.NotThreadSafe;
|
|||||||
|
|
||||||
import androidx.annotation.LayoutRes;
|
import androidx.annotation.LayoutRes;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.toHexString;
|
||||||
|
|
||||||
@NotThreadSafe
|
@NotThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
abstract class ConversationItem {
|
abstract class ConversationItem {
|
||||||
@@ -45,6 +47,10 @@ abstract class ConversationItem {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getKey() {
|
||||||
|
return toHexString(id.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
GroupId getGroupId() {
|
GroupId getGroupId() {
|
||||||
return groupId;
|
return groupId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.selection.ItemDetailsLookup;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ConversationItemDetailsLookup extends ItemDetailsLookup<String > {
|
||||||
|
|
||||||
|
private final RecyclerView recyclerView;
|
||||||
|
|
||||||
|
ConversationItemDetailsLookup(RecyclerView recyclerView) {
|
||||||
|
this.recyclerView = recyclerView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public ItemDetails<String> getItemDetails(MotionEvent e) {
|
||||||
|
// find view that corresponds to MotionEvent
|
||||||
|
View view = recyclerView.findChildViewUnder(e.getX(), e.getY());
|
||||||
|
if (view == null) return null;
|
||||||
|
|
||||||
|
// get position
|
||||||
|
int pos = recyclerView.getChildAdapterPosition(view);
|
||||||
|
if (pos == NO_POSITION) return null;
|
||||||
|
|
||||||
|
// get key ID
|
||||||
|
ConversationItemViewHolder holder =
|
||||||
|
(ConversationItemViewHolder) recyclerView.getChildViewHolder(view);
|
||||||
|
String id = holder.getItemKey();
|
||||||
|
if (id == null) return null;
|
||||||
|
|
||||||
|
return new ItemDetails<String>() {
|
||||||
|
@Override
|
||||||
|
public int getPosition() {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSelectionKey() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.selection.ItemKeyProvider;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ConversationItemKeyProvider extends ItemKeyProvider<String> {
|
||||||
|
|
||||||
|
private final ConversationAdapter adapter;
|
||||||
|
|
||||||
|
protected ConversationItemKeyProvider(ConversationAdapter adapter) {
|
||||||
|
super(SCOPE_MAPPED);
|
||||||
|
this.adapter = adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getKey(int position) {
|
||||||
|
return adapter.getItemKey(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPosition(String key) {
|
||||||
|
return adapter.getPositionOfKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -20,24 +20,31 @@ import static org.briarproject.briar.android.util.UiUtils.formatDate;
|
|||||||
abstract class ConversationItemViewHolder extends ViewHolder {
|
abstract class ConversationItemViewHolder extends ViewHolder {
|
||||||
|
|
||||||
protected final ConversationListener listener;
|
protected final ConversationListener listener;
|
||||||
|
private final View root;
|
||||||
protected final ConstraintLayout layout;
|
protected final ConstraintLayout layout;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final OutItemViewHolder outViewHolder;
|
private final OutItemViewHolder outViewHolder;
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
protected final TextView time;
|
protected final TextView time;
|
||||||
|
@Nullable
|
||||||
|
private String itemKey = null;
|
||||||
|
|
||||||
ConversationItemViewHolder(View v, ConversationListener listener,
|
ConversationItemViewHolder(View v, ConversationListener listener,
|
||||||
boolean isIncoming) {
|
boolean isIncoming) {
|
||||||
super(v);
|
super(v);
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
this.outViewHolder = isIncoming ? null : new OutItemViewHolder(v);
|
this.outViewHolder = isIncoming ? null : new OutItemViewHolder(v);
|
||||||
|
root = v;
|
||||||
layout = v.findViewById(R.id.layout);
|
layout = v.findViewById(R.id.layout);
|
||||||
text = v.findViewById(R.id.text);
|
text = v.findViewById(R.id.text);
|
||||||
time = v.findViewById(R.id.time);
|
time = v.findViewById(R.id.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
void bind(ConversationItem item) {
|
void bind(ConversationItem item, boolean selected) {
|
||||||
|
itemKey = item.getKey();
|
||||||
|
root.setActivated(selected);
|
||||||
|
|
||||||
if (item.getText() != null) {
|
if (item.getText() != null) {
|
||||||
text.setText(trim(item.getText()));
|
text.setText(trim(item.getText()));
|
||||||
}
|
}
|
||||||
@@ -52,4 +59,9 @@ abstract class ConversationItemViewHolder extends ViewHolder {
|
|||||||
return outViewHolder == null;
|
return outViewHolder == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getItemKey() {
|
||||||
|
return itemKey;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
|||||||
timeColorBubble = getColor(v.getContext(), R.color.briar_white);
|
timeColorBubble = getColor(v.getContext(), R.color.briar_white);
|
||||||
|
|
||||||
// clone constraint sets from layout files
|
// clone constraint sets from layout files
|
||||||
textConstraints
|
textConstraints.clone(v.getContext(),
|
||||||
.clone(v.getContext(), R.layout.list_item_conversation_msg_in);
|
R.layout.list_item_conversation_msg_in_content);
|
||||||
imageConstraints.clone(v.getContext(),
|
imageConstraints.clone(v.getContext(),
|
||||||
R.layout.list_item_conversation_msg_image);
|
R.layout.list_item_conversation_msg_image);
|
||||||
imageTextConstraints.clone(v.getContext(),
|
imageTextConstraints.clone(v.getContext(),
|
||||||
@@ -61,8 +61,8 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void bind(ConversationItem conversationItem) {
|
void bind(ConversationItem conversationItem, boolean selected) {
|
||||||
super.bind(conversationItem);
|
super.bind(conversationItem, selected);
|
||||||
ConversationMessageItem item =
|
ConversationMessageItem item =
|
||||||
(ConversationMessageItem) conversationItem;
|
(ConversationMessageItem) conversationItem;
|
||||||
if (item.getAttachments().isEmpty()) {
|
if (item.getAttachments().isEmpty()) {
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ class ConversationNoticeViewHolder extends ConversationItemViewHolder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CallSuper
|
@CallSuper
|
||||||
void bind(ConversationItem item) {
|
void bind(ConversationItem item, boolean selected) {
|
||||||
ConversationNoticeItem notice = (ConversationNoticeItem) item;
|
ConversationNoticeItem notice = (ConversationNoticeItem) item;
|
||||||
super.bind(notice);
|
super.bind(notice, selected);
|
||||||
|
|
||||||
String text = notice.getMsgText();
|
String text = notice.getMsgText();
|
||||||
if (isNullOrEmpty(text)) {
|
if (isNullOrEmpty(text)) {
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ class ConversationRequestViewHolder extends ConversationNoticeViewHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void bind(ConversationItem item) {
|
void bind(ConversationItem item, boolean selected) {
|
||||||
ConversationRequestItem request = (ConversationRequestItem) item;
|
ConversationRequestItem request = (ConversationRequestItem) item;
|
||||||
super.bind(request);
|
super.bind(request, selected);
|
||||||
|
|
||||||
if (request.wasAnswered() && request.canBeOpened()) {
|
if (request.wasAnswered() && request.canBeOpened()) {
|
||||||
acceptButton.setVisibility(VISIBLE);
|
acceptButton.setVisibility(VISIBLE);
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@color/msg_selected_background" android:state_activated="true" />
|
||||||
|
</selector>
|
||||||
@@ -1,73 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
android:layout_width="match_parent"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/layout"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/message_bubble_margin_tail"
|
android:background="@drawable/list_item_background_selectable">
|
||||||
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
|
|
||||||
android:layout_marginTop="@dimen/message_bubble_margin"
|
|
||||||
android:layout_marginEnd="@dimen/message_bubble_margin_non_tail"
|
|
||||||
android:layout_marginRight="@dimen/message_bubble_margin_non_tail"
|
|
||||||
android:layout_marginBottom="@dimen/message_bubble_margin"
|
|
||||||
android:background="@drawable/msg_in"
|
|
||||||
android:elevation="@dimen/message_bubble_elevation">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<!--
|
||||||
android:id="@+id/imageList"
|
We need to wrap the actual layout, because
|
||||||
android:layout_width="@dimen/message_bubble_image_default"
|
* we want to clone the ConstraintLayout's constraints in the ViewHolder
|
||||||
android:layout_height="@dimen/message_bubble_image_default"
|
* we want to have a selectable frame around the message bubble
|
||||||
android:orientation="vertical"
|
-->
|
||||||
android:visibility="gone"
|
<include layout="@layout/list_item_conversation_msg_in_content" />
|
||||||
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/text"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:spanCount="2"
|
|
||||||
tools:ignore="ContentDescription"
|
|
||||||
tools:listitem="@layout/list_item_image" />
|
|
||||||
|
|
||||||
<com.vanniktech.emoji.EmojiTextView
|
</FrameLayout>
|
||||||
android:id="@+id/text"
|
|
||||||
style="@style/TextMessage"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
|
|
||||||
android:layout_marginTop="@dimen/message_bubble_padding_top_inner"
|
|
||||||
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
|
||||||
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
app:layout_constrainedWidth="true"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/imageList"
|
|
||||||
tools:text="The text of a message which can sometimes be a bit longer as well" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/statusLayout"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
|
|
||||||
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
|
||||||
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="1.0"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/text">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/time"
|
|
||||||
style="@style/TextMessage.Timestamp"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
tools:text="Dec 24, 13:37" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/layout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/message_bubble_margin_tail"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_margin"
|
||||||
|
android:layout_marginEnd="@dimen/message_bubble_margin_non_tail"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_margin_non_tail"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_margin"
|
||||||
|
android:background="@drawable/msg_in"
|
||||||
|
android:elevation="@dimen/message_bubble_elevation">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/imageList"
|
||||||
|
android:layout_width="@dimen/message_bubble_image_default"
|
||||||
|
android:layout_height="@dimen/message_bubble_image_default"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:spanCount="2"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:listitem="@layout/list_item_image" />
|
||||||
|
|
||||||
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
style="@style/TextMessage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_padding_top_inner"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/imageList"
|
||||||
|
tools:text="The text of a message which can sometimes be a bit longer as well" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/statusLayout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="1.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/text">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/time"
|
||||||
|
style="@style/TextMessage.Timestamp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="Dec 24, 13:37" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!-- This is needed to right-align message bubble in RecyclerView -->
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/list_item_background_selectable">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/layout"
|
android:id="@+id/layout"
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/message_bubble_margin"
|
android:background="@drawable/list_item_background_selectable"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="@dimen/message_bubble_margin">
|
||||||
|
|
||||||
<com.vanniktech.emoji.EmojiTextView
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
android:id="@+id/msgText"
|
android:id="@+id/msgText"
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/message_bubble_margin"
|
android:background="@drawable/list_item_background_selectable"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="@dimen/message_bubble_margin">
|
||||||
|
|
||||||
<com.vanniktech.emoji.EmojiTextView
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
android:id="@+id/msgText"
|
android:id="@+id/msgText"
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/message_bubble_margin"
|
android:background="@drawable/list_item_background_selectable"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="@dimen/message_bubble_margin">
|
||||||
|
|
||||||
<com.vanniktech.emoji.EmojiTextView
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
android:id="@+id/msgText"
|
android:id="@+id/msgText"
|
||||||
@@ -19,7 +20,8 @@
|
|||||||
android:background="@drawable/msg_in_top"
|
android:background="@drawable/msg_in_top"
|
||||||
android:elevation="@dimen/message_bubble_elevation"
|
android:elevation="@dimen/message_bubble_elevation"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
tools:text="Short message" />
|
tools:text="Short message"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/layout"
|
android:id="@+id/layout"
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_delete"
|
||||||
|
android:icon="@drawable/action_delete_white"
|
||||||
|
android:title="@string/delete"
|
||||||
|
app:showAsAction="always"/>
|
||||||
|
|
||||||
|
</menu>
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
<color name="msg_stroke_dark">#333333</color>
|
<color name="msg_stroke_dark">#333333</color>
|
||||||
<color name="msg_stroke">@color/msg_stroke_light</color>
|
<color name="msg_stroke">@color/msg_stroke_light</color>
|
||||||
<color name="msg_status_bubble_background">#66000000</color>
|
<color name="msg_status_bubble_background">#66000000</color>
|
||||||
|
<color name="msg_selected_background">@color/briar_accent</color>
|
||||||
|
|
||||||
<!-- text colors -->
|
<!-- text colors -->
|
||||||
<color name="briar_text_link">@color/briar_blue_light</color>
|
<color name="briar_text_link">@color/briar_blue_light</color>
|
||||||
|
|||||||
@@ -139,7 +139,7 @@
|
|||||||
<string name="dialog_title_delete_all_messages">Confirm Message Deletion</string>
|
<string name="dialog_title_delete_all_messages">Confirm Message Deletion</string>
|
||||||
<string name="dialog_message_delete_all_messages">Are you sure that you want to delete all messages?</string>
|
<string name="dialog_message_delete_all_messages">Are you sure that you want to delete all messages?</string>
|
||||||
<string name="dialog_title_not_all_messages_deleted">Could not delete all messages</string>
|
<string name="dialog_title_not_all_messages_deleted">Could not delete all messages</string>
|
||||||
<string name="dialog_message_not_all_messages_deleted">Messages related to ongoing introductions or invitations cannot be deleted until they conclude.</string>
|
<string name="dialog_message_not_all_messages_deleted">Messages related to\n\n• ongoing introductions\n• ongoing (blog/forum/group) invitations\n• partly downloaded messages\n\ncannot be deleted until they conclude.</string>
|
||||||
<string name="delete_contact">Delete contact</string>
|
<string name="delete_contact">Delete contact</string>
|
||||||
<string name="dialog_title_delete_contact">Confirm Contact Deletion</string>
|
<string name="dialog_title_delete_contact">Confirm Contact Deletion</string>
|
||||||
<string name="dialog_message_delete_contact">Are you sure that you want to remove this contact and all messages exchanged with this contact?</string>
|
<string name="dialog_message_delete_contact">Are you sure that you want to remove this contact and all messages exchanged with this contact?</string>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<item name="android:textColorLink">@color/briar_text_link</item>
|
<item name="android:textColorLink">@color/briar_text_link</item>
|
||||||
<item name="android:windowBackground">@color/window_background</item>
|
<item name="android:windowBackground">@color/window_background</item>
|
||||||
<item name="android:windowAnimationStyle">@style/ActivityAnimation</item>
|
<item name="android:windowAnimationStyle">@style/ActivityAnimation</item>
|
||||||
|
<item name="windowActionModeOverlay">true</item>
|
||||||
<item name="alertDialogTheme">@style/BriarDialogTheme.Neutral</item>
|
<item name="alertDialogTheme">@style/BriarDialogTheme.Neutral</item>
|
||||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
|
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ dependencyVerification {
|
|||||||
'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0:localbroadcastmanager-1.0.0.aar:e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8',
|
'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0:localbroadcastmanager-1.0.0.aar:e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8',
|
||||||
'androidx.preference:preference:1.1.0:preference-1.1.0.aar:6cf1a099b03b3254041b04701841865b2708c0b546b9036c8b0dada0aa59de57',
|
'androidx.preference:preference:1.1.0:preference-1.1.0.aar:6cf1a099b03b3254041b04701841865b2708c0b546b9036c8b0dada0aa59de57',
|
||||||
'androidx.print:print:1.0.0:print-1.0.0.aar:1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd',
|
'androidx.print:print:1.0.0:print-1.0.0.aar:1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd',
|
||||||
|
'androidx.recyclerview:recyclerview-selection:1.0.0:recyclerview-selection-1.0.0.aar:db3db72af8cfcd701ab6ed7a080327a2e993e3a429f5efb8f0c108bff38c4922',
|
||||||
'androidx.recyclerview:recyclerview:1.1.0-beta04:recyclerview-1.1.0-beta04.aar:c3c8310eb058a660a845cf814a54f56e6f448b7855f9ccea2a5ad18189f57f69',
|
'androidx.recyclerview:recyclerview:1.1.0-beta04:recyclerview-1.1.0-beta04.aar:c3c8310eb058a660a845cf814a54f56e6f448b7855f9ccea2a5ad18189f57f69',
|
||||||
'androidx.savedstate:savedstate:1.0.0:savedstate-1.0.0.aar:2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83',
|
'androidx.savedstate:savedstate:1.0.0:savedstate-1.0.0.aar:2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83',
|
||||||
'androidx.test.espresso:espresso-contrib:3.2.0:espresso-contrib-3.2.0.aar:9e43811e5845e84f2964f0032fd50cd11dca3dc8d3b0703626dd12d50433bb35',
|
'androidx.test.espresso:espresso-contrib:3.2.0:espresso-contrib-3.2.0.aar:9e43811e5845e84f2964f0032fd50cd11dca3dc8d3b0703626dd12d50433bb35',
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ 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.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -371,15 +372,20 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
public Set<MessageId> getMessageIds(Transaction txn, ContactId c)
|
public Set<MessageId> getMessageIds(Transaction txn, ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
GroupId g = getContactGroup(db.getContact(txn, c)).getId();
|
GroupId g = getContactGroup(db.getContact(txn, c)).getId();
|
||||||
BdfDictionary query = BdfDictionary.of(
|
Set<MessageId> result = new HashSet<>();
|
||||||
new BdfEntry(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE));
|
|
||||||
try {
|
try {
|
||||||
Map<MessageId, BdfDictionary> results =
|
Map<MessageId, BdfDictionary> messages =
|
||||||
clientHelper.getMessageMetadataAsDictionary(txn, g, query);
|
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||||
return results.keySet();
|
for (Map.Entry<MessageId, BdfDictionary> entry : messages
|
||||||
|
.entrySet()) {
|
||||||
|
Long type = entry.getValue().getOptionalLong(MSG_KEY_MSG_TYPE);
|
||||||
|
if (type == null || type == PRIVATE_MESSAGE)
|
||||||
|
result.add(entry.getKey());
|
||||||
|
}
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -450,7 +456,8 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
Long messageType = meta.getOptionalLong(MSG_KEY_MSG_TYPE);
|
Long messageType = meta.getOptionalLong(MSG_KEY_MSG_TYPE);
|
||||||
if (messageType != null && messageType != PRIVATE_MESSAGE)
|
if (messageType != null && messageType != PRIVATE_MESSAGE)
|
||||||
throw new AssertionError("not supported");
|
throw new AssertionError("not supported");
|
||||||
headers = parseAttachmentHeaders(meta);
|
headers = messageType == null ? emptyList() :
|
||||||
|
parseAttachmentHeaders(meta);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,6 +208,32 @@ public class MessagingManagerIntegrationTest
|
|||||||
assertGroupCounts(c0, 0, 0);
|
assertGroupCounts(c0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteLegacySubset() throws Exception {
|
||||||
|
// send legacy message
|
||||||
|
GroupId g = c0.getMessagingManager().getConversationId(contactId);
|
||||||
|
PrivateMessage m0 = messageFactory.createLegacyPrivateMessage(g,
|
||||||
|
clock.currentTimeMillis(), getRandomString(42));
|
||||||
|
c0.getMessagingManager().addLocalMessage(m0);
|
||||||
|
syncMessage(c0, c1, contactId, 1, true);
|
||||||
|
|
||||||
|
// message arrived on both sides
|
||||||
|
assertEquals(1, getMessages(c0).size());
|
||||||
|
assertEquals(1, getMessages(c1).size());
|
||||||
|
|
||||||
|
// delete message on both sides (deletes all, because returns true)
|
||||||
|
Set<MessageId> toDelete = new HashSet<>();
|
||||||
|
toDelete.add(m0.getMessage().getId());
|
||||||
|
assertTrue(c0.getConversationManager()
|
||||||
|
.deleteMessages(contactId, toDelete));
|
||||||
|
assertTrue(c1.getConversationManager()
|
||||||
|
.deleteMessages(contactId, toDelete));
|
||||||
|
|
||||||
|
// message was deleted
|
||||||
|
assertEquals(0, getMessages(c0).size());
|
||||||
|
assertEquals(0, getMessages(c1).size());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteAttachment() throws Exception {
|
public void testDeleteAttachment() throws Exception {
|
||||||
// send one message with attachment
|
// send one message with attachment
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.briarproject.briar.api.blog.BlogFactory;
|
|||||||
import org.briarproject.briar.api.blog.BlogManager;
|
import org.briarproject.briar.api.blog.BlogManager;
|
||||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||||
import org.briarproject.briar.api.client.MessageTracker;
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.forum.ForumManager;
|
import org.briarproject.briar.api.forum.ForumManager;
|
||||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||||
@@ -89,6 +90,8 @@ public interface BriarIntegrationTestComponent
|
|||||||
|
|
||||||
ContactManager getContactManager();
|
ContactManager getContactManager();
|
||||||
|
|
||||||
|
ConversationManager getConversationManager();
|
||||||
|
|
||||||
DatabaseComponent getDatabaseComponent();
|
DatabaseComponent getDatabaseComponent();
|
||||||
|
|
||||||
BlogManager getBlogManager();
|
BlogManager getBlogManager();
|
||||||
|
|||||||
Reference in New Issue
Block a user