mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-09 11:08:33 +00:00
Add support for mark as unread.
This commit is contained in:
parent
6b2e000e61
commit
d70c33d20f
@ -10,8 +10,8 @@ import java.util.Set;
|
|||||||
|
|
||||||
public interface BindableConversationListItem extends Unbindable {
|
public interface BindableConversationListItem extends Unbindable {
|
||||||
|
|
||||||
public void bind(@NonNull ThreadRecord thread,
|
void bind(@NonNull ThreadRecord thread,
|
||||||
@NonNull GlideRequests glideRequests, @NonNull Locale locale,
|
@NonNull GlideRequests glideRequests, @NonNull Locale locale,
|
||||||
@NonNull Set<Long> typingThreads,
|
@NonNull Set<Long> typingThreads,
|
||||||
@NonNull Set<Long> selectedThreads, boolean batchMode);
|
@NonNull Set<Long> selectedThreads, boolean batchMode);
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,9 @@ import android.widget.LinearLayout;
|
|||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.views.DarkOverflowToolbar;
|
||||||
|
|
||||||
public class ContactFilterToolbar extends Toolbar {
|
public class ContactFilterToolbar extends DarkOverflowToolbar {
|
||||||
private OnFilterChangedListener listener;
|
private OnFilterChangedListener listener;
|
||||||
|
|
||||||
private EditText searchText;
|
private EditText searchText;
|
||||||
|
@ -25,6 +25,9 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.annimon.stream.Collectors;
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.BindableConversationListItem;
|
import org.thoughtcrime.securesms.BindableConversationListItem;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||||
@ -37,8 +40,10 @@ import org.thoughtcrime.securesms.util.Conversions;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,9 +64,9 @@ class ConversationListAdapter extends CursorRecyclerViewAdapter<ConversationList
|
|||||||
private final @Nullable ItemClickListener clickListener;
|
private final @Nullable ItemClickListener clickListener;
|
||||||
private final @NonNull MessageDigest digest;
|
private final @NonNull MessageDigest digest;
|
||||||
|
|
||||||
private final Set<Long> batchSet = Collections.synchronizedSet(new HashSet<Long>());
|
private final Map<Long, ThreadRecord> batchSet = Collections.synchronizedMap(new HashMap<>());
|
||||||
private boolean batchMode = false;
|
private boolean batchMode = false;
|
||||||
private final Set<Long> typingSet = new HashSet<>();
|
private final Set<Long> typingSet = new HashSet<>();
|
||||||
|
|
||||||
protected static class ViewHolder extends RecyclerView.ViewHolder {
|
protected static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
public <V extends View & BindableConversationListItem> ViewHolder(final @NonNull V itemView)
|
public <V extends View & BindableConversationListItem> ViewHolder(final @NonNull V itemView)
|
||||||
@ -143,7 +148,7 @@ class ConversationListAdapter extends CursorRecyclerViewAdapter<ConversationList
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
|
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
|
||||||
viewHolder.getItem().bind(getThreadRecord(cursor), glideRequests, locale, typingSet, batchSet, batchMode);
|
viewHolder.getItem().bind(getThreadRecord(cursor), glideRequests, locale, typingSet, batchSet.keySet(), batchMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -169,16 +174,20 @@ class ConversationListAdapter extends CursorRecyclerViewAdapter<ConversationList
|
|||||||
return threadDatabase.readerFor(cursor).getCurrent();
|
return threadDatabase.readerFor(cursor).getCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleThreadInBatchSet(long threadId) {
|
void toggleThreadInBatchSet(@NonNull ThreadRecord thread) {
|
||||||
if (batchSet.contains(threadId)) {
|
if (batchSet.containsKey(thread.getThreadId())) {
|
||||||
batchSet.remove(threadId);
|
batchSet.remove(thread.getThreadId());
|
||||||
} else if (threadId != -1) {
|
} else if (thread.getThreadId() != -1) {
|
||||||
batchSet.add(threadId);
|
batchSet.put(thread.getThreadId(), thread);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<Long> getBatchSelections() {
|
@NonNull Set<Long> getBatchSelectionIds() {
|
||||||
return batchSet;
|
return batchSet.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull Set<ThreadRecord> getBatchSelection() {
|
||||||
|
return new HashSet<>(batchSet.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
void initializeBatchMode(boolean toggle) {
|
void initializeBatchMode(boolean toggle) {
|
||||||
@ -193,8 +202,10 @@ class ConversationListAdapter extends CursorRecyclerViewAdapter<ConversationList
|
|||||||
|
|
||||||
void selectAllThreads() {
|
void selectAllThreads() {
|
||||||
for (int i = 0; i < getItemCount(); i++) {
|
for (int i = 0; i < getItemCount(); i++) {
|
||||||
long threadId = getThreadRecord(getCursorAtPositionOrThrow(i)).getThreadId();
|
ThreadRecord record = getThreadRecord(getCursorAtPositionOrThrow(i));
|
||||||
if (threadId != -1) batchSet.add(threadId);
|
if (record.getThreadId() != -1) {
|
||||||
|
batchSet.put(record.getThreadId(), record);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.notifyDataSetChanged();
|
this.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import android.graphics.BitmapFactory;
|
|||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -65,6 +66,7 @@ import androidx.recyclerview.widget.ItemTouchHelper;
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
@ -90,6 +92,7 @@ import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder;
|
|||||||
import org.thoughtcrime.securesms.components.reminder.ShareReminder;
|
import org.thoughtcrime.securesms.components.reminder.ShareReminder;
|
||||||
import org.thoughtcrime.securesms.components.reminder.SystemSmsImportReminder;
|
import org.thoughtcrime.securesms.components.reminder.SystemSmsImportReminder;
|
||||||
import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder;
|
import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder;
|
||||||
|
import org.thoughtcrime.securesms.conversation.ConversationAdapter;
|
||||||
import org.thoughtcrime.securesms.conversationlist.ConversationListAdapter.ItemClickListener;
|
import org.thoughtcrime.securesms.conversationlist.ConversationListAdapter.ItemClickListener;
|
||||||
import org.thoughtcrime.securesms.conversationlist.model.MessageResult;
|
import org.thoughtcrime.securesms.conversationlist.model.MessageResult;
|
||||||
import org.thoughtcrime.securesms.conversationlist.model.SearchResult;
|
import org.thoughtcrime.securesms.conversationlist.model.SearchResult;
|
||||||
@ -116,10 +119,12 @@ import org.thoughtcrime.securesms.permissions.Permissions;
|
|||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
@ -593,6 +598,41 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleMarkSelectedAsRead() {
|
||||||
|
Context context = requireContext();
|
||||||
|
Set<Long> selectedConversations = new HashSet<>(defaultAdapter.getBatchSelectionIds());
|
||||||
|
|
||||||
|
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
|
||||||
|
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(selectedConversations, false);
|
||||||
|
|
||||||
|
ApplicationDependencies.getMessageNotifier().updateNotification(context);
|
||||||
|
MarkReadReceiver.process(context, messageIds);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, none -> {
|
||||||
|
if (actionMode != null) {
|
||||||
|
actionMode.finish();
|
||||||
|
actionMode = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMarkSelectedAsUnread() {
|
||||||
|
Context context = requireContext();
|
||||||
|
Set<Long> selectedConversations = new HashSet<>(defaultAdapter.getBatchSelectionIds());
|
||||||
|
|
||||||
|
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
|
||||||
|
DatabaseFactory.getThreadDatabase(context).setForcedUnread(selectedConversations);
|
||||||
|
StorageSyncHelper.scheduleSyncForDataChange();
|
||||||
|
return null;
|
||||||
|
}, none -> {
|
||||||
|
if (actionMode != null) {
|
||||||
|
actionMode.finish();
|
||||||
|
actionMode = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void handleInvite() {
|
private void handleInvite() {
|
||||||
getNavigator().goToInvite();
|
getNavigator().goToInvite();
|
||||||
}
|
}
|
||||||
@ -603,7 +643,7 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private void handleArchiveAllSelected() {
|
private void handleArchiveAllSelected() {
|
||||||
Set<Long> selectedConversations = new HashSet<>(defaultAdapter.getBatchSelections());
|
Set<Long> selectedConversations = new HashSet<>(defaultAdapter.getBatchSelectionIds());
|
||||||
int count = selectedConversations.size();
|
int count = selectedConversations.size();
|
||||||
String snackBarTitle = getResources().getQuantityString(getArchivedSnackbarTitleRes(), count, count);
|
String snackBarTitle = getResources().getQuantityString(getArchivedSnackbarTitleRes(), count, count);
|
||||||
|
|
||||||
@ -642,7 +682,7 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private void handleDeleteAllSelected() {
|
private void handleDeleteAllSelected() {
|
||||||
int conversationsCount = defaultAdapter.getBatchSelections().size();
|
int conversationsCount = defaultAdapter.getBatchSelectionIds().size();
|
||||||
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
||||||
alert.setIconAttribute(R.attr.dialog_alert_icon);
|
alert.setIconAttribute(R.attr.dialog_alert_icon);
|
||||||
alert.setTitle(getActivity().getResources().getQuantityString(R.plurals.ConversationListFragment_delete_selected_conversations,
|
alert.setTitle(getActivity().getResources().getQuantityString(R.plurals.ConversationListFragment_delete_selected_conversations,
|
||||||
@ -652,7 +692,7 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
alert.setCancelable(true);
|
alert.setCancelable(true);
|
||||||
|
|
||||||
alert.setPositiveButton(R.string.delete, (dialog, which) -> {
|
alert.setPositiveButton(R.string.delete, (dialog, which) -> {
|
||||||
final Set<Long> selectedConversations = defaultAdapter.getBatchSelections();
|
final Set<Long> selectedConversations = defaultAdapter.getBatchSelectionIds();
|
||||||
|
|
||||||
if (!selectedConversations.isEmpty()) {
|
if (!selectedConversations.isEmpty()) {
|
||||||
new AsyncTask<Void, Void, Void>() {
|
new AsyncTask<Void, Void, Void>() {
|
||||||
@ -691,7 +731,7 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
|
|
||||||
private void handleSelectAllThreads() {
|
private void handleSelectAllThreads() {
|
||||||
defaultAdapter.selectAllThreads();
|
defaultAdapter.selectAllThreads();
|
||||||
actionMode.setTitle(String.valueOf(defaultAdapter.getBatchSelections().size()));
|
actionMode.setTitle(String.valueOf(defaultAdapter.getBatchSelectionIds().size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCreateConversation(long threadId, Recipient recipient, int distributionType) {
|
private void handleCreateConversation(long threadId, Recipient recipient, int distributionType) {
|
||||||
@ -732,12 +772,13 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
handleCreateConversation(item.getThreadId(), item.getRecipient(), item.getDistributionType());
|
handleCreateConversation(item.getThreadId(), item.getRecipient(), item.getDistributionType());
|
||||||
} else {
|
} else {
|
||||||
ConversationListAdapter adapter = (ConversationListAdapter)list.getAdapter();
|
ConversationListAdapter adapter = (ConversationListAdapter)list.getAdapter();
|
||||||
adapter.toggleThreadInBatchSet(item.getThreadId());
|
adapter.toggleThreadInBatchSet(item.getThread());
|
||||||
|
|
||||||
if (adapter.getBatchSelections().size() == 0) {
|
if (adapter.getBatchSelectionIds().size() == 0) {
|
||||||
actionMode.finish();
|
actionMode.finish();
|
||||||
} else {
|
} else {
|
||||||
actionMode.setTitle(String.valueOf(defaultAdapter.getBatchSelections().size()));
|
actionMode.setTitle(String.valueOf(defaultAdapter.getBatchSelectionIds().size()));
|
||||||
|
setCorrectMenuVisibility(actionMode.getMenu());
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
@ -749,7 +790,7 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(ConversationListFragment.this);
|
actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(ConversationListFragment.this);
|
||||||
|
|
||||||
defaultAdapter.initializeBatchMode(true);
|
defaultAdapter.initializeBatchMode(true);
|
||||||
defaultAdapter.toggleThreadInBatchSet(item.getThreadId());
|
defaultAdapter.toggleThreadInBatchSet(item.getThread());
|
||||||
defaultAdapter.notifyDataSetChanged();
|
defaultAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -781,15 +822,18 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||||
|
setCorrectMenuVisibility(menu);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_select_all: handleSelectAllThreads(); return true;
|
case R.id.menu_select_all: handleSelectAllThreads(); return true;
|
||||||
case R.id.menu_delete_selected: handleDeleteAllSelected(); return true;
|
case R.id.menu_delete_selected: handleDeleteAllSelected(); return true;
|
||||||
case R.id.menu_archive_selected: handleArchiveAllSelected(); return true;
|
case R.id.menu_archive_selected: handleArchiveAllSelected(); return true;
|
||||||
|
case R.id.menu_mark_as_read: handleMarkSelectedAsRead(); return true;
|
||||||
|
case R.id.menu_mark_as_unread: handleMarkSelectedAsUnread(); return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -830,6 +874,18 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
closeSearchIfOpen();
|
closeSearchIfOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setCorrectMenuVisibility(@NonNull Menu menu) {
|
||||||
|
boolean hasUnread = Stream.of(defaultAdapter.getBatchSelection()).anyMatch(thread -> !thread.isRead());
|
||||||
|
|
||||||
|
if (hasUnread) {
|
||||||
|
menu.findItem(R.id.menu_mark_as_unread).setVisible(false);
|
||||||
|
menu.findItem(R.id.menu_mark_as_read).setVisible(true);
|
||||||
|
} else {
|
||||||
|
menu.findItem(R.id.menu_mark_as_unread).setVisible(true);
|
||||||
|
menu.findItem(R.id.menu_mark_as_read).setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected @IdRes int getToolbarRes() {
|
protected @IdRes int getToolbarRes() {
|
||||||
return R.id.toolbar;
|
return R.id.toolbar;
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@ public class ConversationListItem extends RelativeLayout
|
|||||||
|
|
||||||
this.fromView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), name, highlightSubstring));
|
this.fromView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), name, highlightSubstring));
|
||||||
} else {
|
} else {
|
||||||
this.fromView.setText(recipient.get(), unreadCount == 0);
|
this.fromView.setText(recipient.get(), thread.isRead());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typingThreads.contains(threadId)) {
|
if (typingThreads.contains(threadId)) {
|
||||||
@ -190,17 +190,17 @@ public class ConversationListItem extends RelativeLayout
|
|||||||
groupAddedBy.observeForever(groupAddedByObserver);
|
groupAddedBy.observeForever(groupAddedByObserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.subjectView.setTypeface(unreadCount == 0 ? LIGHT_TYPEFACE : BOLD_TYPEFACE);
|
this.subjectView.setTypeface(thread.isRead() ? LIGHT_TYPEFACE : BOLD_TYPEFACE);
|
||||||
this.subjectView.setTextColor(unreadCount == 0 ? ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_subject_color)
|
this.subjectView.setTextColor(thread.isRead() ? ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_subject_color)
|
||||||
: ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_unread_color));
|
: ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_unread_color));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thread.getDate() > 0) {
|
if (thread.getDate() > 0) {
|
||||||
CharSequence date = DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, thread.getDate());
|
CharSequence date = DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, thread.getDate());
|
||||||
dateView.setText(date);
|
dateView.setText(date);
|
||||||
dateView.setTypeface(unreadCount == 0 ? LIGHT_TYPEFACE : BOLD_TYPEFACE);
|
dateView.setTypeface(thread.isRead() ? LIGHT_TYPEFACE : BOLD_TYPEFACE);
|
||||||
dateView.setTextColor(unreadCount == 0 ? ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_date_color)
|
dateView.setTextColor(thread.isRead() ? ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_date_color)
|
||||||
: ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_unread_color));
|
: ThemeUtil.getThemedColor(getContext(), R.attr.conversation_list_item_unread_color));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thread.isArchived()) {
|
if (thread.isArchived()) {
|
||||||
@ -292,7 +292,7 @@ public class ConversationListItem extends RelativeLayout
|
|||||||
|
|
||||||
private void setBatchMode(boolean batchMode) {
|
private void setBatchMode(boolean batchMode) {
|
||||||
this.batchMode = batchMode;
|
this.batchMode = batchMode;
|
||||||
setSelected(batchMode && selectedThreads.contains(threadId));
|
setSelected(batchMode && selectedThreads.contains(thread.getThreadId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Recipient getRecipient() {
|
public Recipient getRecipient() {
|
||||||
@ -303,6 +303,10 @@ public class ConversationListItem extends RelativeLayout
|
|||||||
return threadId;
|
return threadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull ThreadRecord getThread() {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
public int getUnreadCount() {
|
public int getUnreadCount() {
|
||||||
return unreadCount;
|
return unreadCount;
|
||||||
}
|
}
|
||||||
@ -368,12 +372,12 @@ public class ConversationListItem extends RelativeLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setUnreadIndicator(ThreadRecord thread) {
|
private void setUnreadIndicator(ThreadRecord thread) {
|
||||||
if (thread.isOutgoing() || thread.getUnreadCount() == 0) {
|
if ((thread.isOutgoing() && !thread.isForcedUnread()) || thread.isRead()) {
|
||||||
unreadIndicator.setVisibility(View.GONE);
|
unreadIndicator.setVisibility(View.GONE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
unreadIndicator.setText(String.valueOf(unreadCount));
|
unreadIndicator.setText(unreadCount > 0 ? String.valueOf(unreadCount) : " ");
|
||||||
unreadIndicator.setVisibility(View.VISIBLE);
|
unreadIndicator.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,12 +56,14 @@ import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
|||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -92,17 +94,27 @@ public class ThreadDatabase extends Database {
|
|||||||
public static final String LAST_SEEN = "last_seen";
|
public static final String LAST_SEEN = "last_seen";
|
||||||
public static final String HAS_SENT = "has_sent";
|
public static final String HAS_SENT = "has_sent";
|
||||||
|
|
||||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" +
|
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
||||||
ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " +
|
DATE + " INTEGER DEFAULT 0, " +
|
||||||
MESSAGE_COUNT + " INTEGER DEFAULT 0, " + RECIPIENT_ID + " INTEGER, " + SNIPPET + " TEXT, " +
|
MESSAGE_COUNT + " INTEGER DEFAULT 0, " +
|
||||||
SNIPPET_CHARSET + " INTEGER DEFAULT 0, " + READ + " INTEGER DEFAULT 1, " +
|
RECIPIENT_ID + " INTEGER, " +
|
||||||
TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " +
|
SNIPPET + " TEXT, " +
|
||||||
SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " +
|
SNIPPET_CHARSET + " INTEGER DEFAULT 0, " +
|
||||||
SNIPPET_CONTENT_TYPE + " TEXT DEFAULT NULL, " + SNIPPET_EXTRAS + " TEXT DEFAULT NULL, " +
|
READ + " INTEGER DEFAULT " + ReadStatus.READ.serialize() + ", " +
|
||||||
ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " +
|
TYPE + " INTEGER DEFAULT 0, " +
|
||||||
DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " +
|
ERROR + " INTEGER DEFAULT 0, " +
|
||||||
LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0, " +
|
SNIPPET_TYPE + " INTEGER DEFAULT 0, " +
|
||||||
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + UNREAD_COUNT + " INTEGER DEFAULT 0);";
|
SNIPPET_URI + " TEXT DEFAULT NULL, " +
|
||||||
|
SNIPPET_CONTENT_TYPE + " TEXT DEFAULT NULL, " +
|
||||||
|
SNIPPET_EXTRAS + " TEXT DEFAULT NULL, " +
|
||||||
|
ARCHIVED + " INTEGER DEFAULT 0, " +
|
||||||
|
STATUS + " INTEGER DEFAULT 0, " +
|
||||||
|
DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0, " +
|
||||||
|
EXPIRES_IN + " INTEGER DEFAULT 0, " +
|
||||||
|
LAST_SEEN + " INTEGER DEFAULT 0, " +
|
||||||
|
HAS_SENT + " INTEGER DEFAULT 0, " +
|
||||||
|
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " +
|
||||||
|
UNREAD_COUNT + " INTEGER DEFAULT 0);";
|
||||||
|
|
||||||
public static final String[] CREATE_INDEXS = {
|
public static final String[] CREATE_INDEXS = {
|
||||||
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_ID + ");",
|
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_ID + ");",
|
||||||
@ -280,7 +292,7 @@ public class ThreadDatabase extends Database {
|
|||||||
public List<MarkedMessageInfo> setAllThreadsRead() {
|
public List<MarkedMessageInfo> setAllThreadsRead() {
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
ContentValues contentValues = new ContentValues(1);
|
ContentValues contentValues = new ContentValues(1);
|
||||||
contentValues.put(READ, 1);
|
contentValues.put(READ, ReadStatus.READ.serialize());
|
||||||
contentValues.put(UNREAD_COUNT, 0);
|
contentValues.put(UNREAD_COUNT, 0);
|
||||||
|
|
||||||
db.update(TABLE_NAME, contentValues, null, null);
|
db.update(TABLE_NAME, contentValues, null, null);
|
||||||
@ -310,32 +322,69 @@ public class ThreadDatabase extends Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<MarkedMessageInfo> setRead(long threadId, boolean lastSeen) {
|
public List<MarkedMessageInfo> setRead(long threadId, boolean lastSeen) {
|
||||||
ContentValues contentValues = new ContentValues(1);
|
return setRead(Collections.singletonList(threadId), lastSeen);
|
||||||
contentValues.put(READ, 1);
|
}
|
||||||
contentValues.put(UNREAD_COUNT, 0);
|
|
||||||
|
|
||||||
if (lastSeen) {
|
|
||||||
contentValues.put(LAST_SEEN, System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public List<MarkedMessageInfo> setRead(Collection<Long> threadIds, boolean lastSeen) {
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
|
|
||||||
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId+""});
|
List<MarkedMessageInfo> smsRecords = new LinkedList<>();
|
||||||
|
List<MarkedMessageInfo> mmsRecords = new LinkedList<>();
|
||||||
|
|
||||||
final List<MarkedMessageInfo> smsRecords = DatabaseFactory.getSmsDatabase(context).setMessagesRead(threadId);
|
db.beginTransaction();
|
||||||
final List<MarkedMessageInfo> mmsRecords = DatabaseFactory.getMmsDatabase(context).setMessagesRead(threadId);
|
|
||||||
|
|
||||||
DatabaseFactory.getSmsDatabase(context).setReactionsSeen(threadId);
|
try {
|
||||||
DatabaseFactory.getMmsDatabase(context).setReactionsSeen(threadId);
|
ContentValues contentValues = new ContentValues(2);
|
||||||
|
contentValues.put(READ, ReadStatus.READ.serialize());
|
||||||
|
contentValues.put(UNREAD_COUNT, 0);
|
||||||
|
|
||||||
|
if (lastSeen) {
|
||||||
|
contentValues.put(LAST_SEEN, System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (long threadId : threadIds) {
|
||||||
|
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[]{threadId + ""});
|
||||||
|
|
||||||
|
smsRecords.addAll(DatabaseFactory.getSmsDatabase(context).setMessagesRead(threadId));
|
||||||
|
mmsRecords.addAll(DatabaseFactory.getMmsDatabase(context).setMessagesRead(threadId));
|
||||||
|
|
||||||
|
DatabaseFactory.getSmsDatabase(context).setReactionsSeen(threadId);
|
||||||
|
DatabaseFactory.getMmsDatabase(context).setReactionsSeen(threadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
} finally {
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
notifyConversationListListeners();
|
notifyConversationListListeners();
|
||||||
|
|
||||||
return Util.concatenatedList(smsRecords, mmsRecords);
|
return Util.concatenatedList(smsRecords, mmsRecords);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setForcedUnread(@NonNull Collection<Long> threadIds) {
|
||||||
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
|
|
||||||
|
db.beginTransaction();
|
||||||
|
try {
|
||||||
|
ContentValues contentValues = new ContentValues();
|
||||||
|
contentValues.put(READ, ReadStatus.FORCED_UNREAD.serialize());
|
||||||
|
|
||||||
|
for (long threadId : threadIds) {
|
||||||
|
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] { String.valueOf(threadId) });
|
||||||
|
}
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
} finally {
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyConversationListListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void incrementUnread(long threadId, int amount) {
|
public void incrementUnread(long threadId, int amount) {
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " +
|
db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = " + ReadStatus.UNREAD.serialize() + ", " +
|
||||||
UNREAD_COUNT + " = " + UNREAD_COUNT + " + ? WHERE " + ID + " = ?",
|
UNREAD_COUNT + " = " + UNREAD_COUNT + " + ? WHERE " + ID + " = ?",
|
||||||
new String[] {String.valueOf(amount),
|
new String[] {String.valueOf(amount),
|
||||||
String.valueOf(threadId)});
|
String.valueOf(threadId)});
|
||||||
@ -882,6 +931,7 @@ public class ThreadDatabase extends Database {
|
|||||||
.setContentType(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_CONTENT_TYPE)))
|
.setContentType(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_CONTENT_TYPE)))
|
||||||
.setCount(cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)))
|
.setCount(cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)))
|
||||||
.setUnreadCount(cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_COUNT)))
|
.setUnreadCount(cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_COUNT)))
|
||||||
|
.setForcedUnread(cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.READ)) == ReadStatus.FORCED_UNREAD.serialize())
|
||||||
.setExtra(extra)
|
.setExtra(extra)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
@ -990,4 +1040,27 @@ public class ThreadDatabase extends Database {
|
|||||||
return groupAddedBy;
|
return groupAddedBy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum ReadStatus {
|
||||||
|
READ(1), UNREAD(0), FORCED_UNREAD(2);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
ReadStatus(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReadStatus deserialize(int value) {
|
||||||
|
for (ReadStatus status : ReadStatus.values()) {
|
||||||
|
if (status.value == value) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("No matching status for value " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int serialize() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,11 +24,14 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase.Extra;
|
import org.thoughtcrime.securesms.database.ThreadDatabase.Extra;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.whispersystems.libsignal.util.guava.Preconditions;
|
import org.whispersystems.libsignal.util.guava.Preconditions;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an entry in the {@link org.thoughtcrime.securesms.database.ThreadDatabase}.
|
* Represents an entry in the {@link org.thoughtcrime.securesms.database.ThreadDatabase}.
|
||||||
*/
|
*/
|
||||||
@ -47,6 +50,7 @@ public final class ThreadRecord {
|
|||||||
private final Extra extra;
|
private final Extra extra;
|
||||||
private final long count;
|
private final long count;
|
||||||
private final int unreadCount;
|
private final int unreadCount;
|
||||||
|
private final boolean forcedUnread;
|
||||||
private final int distributionType;
|
private final int distributionType;
|
||||||
private final boolean archived;
|
private final boolean archived;
|
||||||
private final long expiresIn;
|
private final long expiresIn;
|
||||||
@ -66,6 +70,7 @@ public final class ThreadRecord {
|
|||||||
this.extra = builder.extra;
|
this.extra = builder.extra;
|
||||||
this.count = builder.count;
|
this.count = builder.count;
|
||||||
this.unreadCount = builder.unreadCount;
|
this.unreadCount = builder.unreadCount;
|
||||||
|
this.forcedUnread = builder.forcedUnread;
|
||||||
this.distributionType = builder.distributionType;
|
this.distributionType = builder.distributionType;
|
||||||
this.archived = builder.archived;
|
this.archived = builder.archived;
|
||||||
this.expiresIn = builder.expiresIn;
|
this.expiresIn = builder.expiresIn;
|
||||||
@ -104,6 +109,14 @@ public final class ThreadRecord {
|
|||||||
return unreadCount;
|
return unreadCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isForcedUnread() {
|
||||||
|
return forcedUnread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRead() {
|
||||||
|
return unreadCount == 0 && !forcedUnread;
|
||||||
|
}
|
||||||
|
|
||||||
public long getDate() {
|
public long getDate() {
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
@ -188,6 +201,7 @@ public final class ThreadRecord {
|
|||||||
private Extra extra;
|
private Extra extra;
|
||||||
private long count;
|
private long count;
|
||||||
private int unreadCount;
|
private int unreadCount;
|
||||||
|
private boolean forcedUnread;
|
||||||
private int distributionType;
|
private int distributionType;
|
||||||
private boolean archived;
|
private boolean archived;
|
||||||
private long expiresIn;
|
private long expiresIn;
|
||||||
@ -262,6 +276,11 @@ public final class ThreadRecord {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder setForcedUnread(boolean forcedUnread) {
|
||||||
|
this.forcedUnread = forcedUnread;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setDistributionType(int distributionType) {
|
public Builder setDistributionType(int distributionType) {
|
||||||
this.distributionType = distributionType;
|
this.distributionType = distributionType;
|
||||||
return this;
|
return this;
|
||||||
@ -283,10 +302,12 @@ public final class ThreadRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ThreadRecord build() {
|
public ThreadRecord build() {
|
||||||
Preconditions.checkArgument(threadId > 0);
|
if (distributionType == ThreadDatabase.DistributionTypes.CONVERSATION) {
|
||||||
Preconditions.checkArgument(date > 0);
|
Preconditions.checkArgument(threadId > 0);
|
||||||
Preconditions.checkNotNull(body);
|
Preconditions.checkArgument(date > 0);
|
||||||
Preconditions.checkNotNull(recipient);
|
Preconditions.checkNotNull(body);
|
||||||
|
Preconditions.checkNotNull(recipient);
|
||||||
|
}
|
||||||
return new ThreadRecord(this);
|
return new ThreadRecord(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import androidx.annotation.WorkerThread;
|
|||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
package org.thoughtcrime.securesms.util.views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It seems to be impossible to tint the overflow icon in an ActionMode independently from the
|
||||||
|
* default toolbar overflow icon. So we default the overflow icon to white, then we can use this
|
||||||
|
* subclass to make it the correct themed color for most use cases.
|
||||||
|
*/
|
||||||
|
public class DarkOverflowToolbar extends Toolbar {
|
||||||
|
public DarkOverflowToolbar(Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DarkOverflowToolbar(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DarkOverflowToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
if (getOverflowIcon() != null) {
|
||||||
|
getOverflowIcon().setColorFilter(ThemeUtil.getThemedColor(getContext(), R.attr.icon_tint), PorterDuff.Mode.SRC_ATOP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?android:windowBackground">
|
android:background="?android:windowBackground">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<org.thoughtcrime.securesms.util.views.DarkOverflowToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
@ -66,7 +66,7 @@
|
|||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</androidx.appcompat.widget.Toolbar>
|
</org.thoughtcrime.securesms.util.views.DarkOverflowToolbar>
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/toolbar_basic"
|
android:id="@+id/toolbar_basic"
|
||||||
|
@ -1,15 +1,27 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item android:title="@string/conversation_list_batch__menu_delete_selected"
|
<item
|
||||||
android:id="@+id/menu_delete_selected"
|
android:id="@+id/menu_delete_selected"
|
||||||
android:icon="?menu_trash_icon"
|
android:icon="?menu_trash_icon"
|
||||||
app:showAsAction="always" />
|
android:title="@string/conversation_list_batch__menu_delete_selected"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
|
||||||
<item android:title="@string/conversation_list_batch__menu_select_all"
|
<item
|
||||||
android:id="@+id/menu_select_all"
|
android:id="@+id/menu_mark_as_read"
|
||||||
android:icon="?menu_selectall_icon"
|
android:title="@string/conversation_list_batch__menu_mark_as_read"
|
||||||
app:showAsAction="always"/>
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_mark_as_unread"
|
||||||
|
android:title="@string/conversation_list_batch__menu_mark_as_unread"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_select_all"
|
||||||
|
android:title="@string/conversation_list_batch__menu_select_all"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
@ -1906,6 +1906,9 @@
|
|||||||
<string name="conversation_list_batch__menu_select_all">Select all</string>
|
<string name="conversation_list_batch__menu_select_all">Select all</string>
|
||||||
<string name="conversation_list_batch_archive__menu_archive_selected">Archive selected</string>
|
<string name="conversation_list_batch_archive__menu_archive_selected">Archive selected</string>
|
||||||
<string name="conversation_list_batch_unarchive__menu_unarchive_selected">Unarchive selected</string>
|
<string name="conversation_list_batch_unarchive__menu_unarchive_selected">Unarchive selected</string>
|
||||||
|
<string name="conversation_list_batch__menu_unarchive_selected">Unarchive selected</string>
|
||||||
|
<string name="conversation_list_batch__menu_mark_as_read">Mark as read</string>
|
||||||
|
<string name="conversation_list_batch__menu_mark_as_unread">Mark as unread</string>
|
||||||
|
|
||||||
<!-- conversation_list -->
|
<!-- conversation_list -->
|
||||||
<string name="conversation_list_settings_shortcut">Settings shortcut</string>
|
<string name="conversation_list_settings_shortcut">Settings shortcut</string>
|
||||||
|
@ -326,7 +326,7 @@
|
|||||||
<style name="Signal.Toolbar.Overflow" parent="Widget.AppCompat.ActionButton.Overflow">
|
<style name="Signal.Toolbar.Overflow" parent="Widget.AppCompat.ActionButton.Overflow">
|
||||||
<item name="srcCompat">@drawable/ic_more_vert_24</item>
|
<item name="srcCompat">@drawable/ic_more_vert_24</item>
|
||||||
<item name="android:src">@null</item>
|
<item name="android:src">@null</item>
|
||||||
<item name="android:tint">?icon_tint</item>
|
<item name="android:tint">@color/core_white</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Signal.Toolbar.Overflow.Conversation">
|
<style name="Signal.Toolbar.Overflow.Conversation">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user