mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 04:28:35 +00:00
Add "You can pull to filter" tip.
This commit is contained in:
committed by
Greyson Parrelli
parent
43fe789807
commit
296a113c65
@@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.megaphone.Megaphones
|
||||
import org.thoughtcrime.securesms.payments.DataExportUtil
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import java.util.Optional
|
||||
@@ -576,6 +577,17 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
||||
viewModel.resetPnpInitializedState()
|
||||
}
|
||||
)
|
||||
|
||||
if (FeatureFlags.chatFilters()) {
|
||||
dividerPref()
|
||||
sectionHeaderPref(DSLSettingsText.from("Chat Filters"))
|
||||
clickPref(
|
||||
title = DSLSettingsText.from("Reset pull to refresh tip count"),
|
||||
onClick = {
|
||||
SignalStore.uiHints().resetNeverDisplayPullToRefreshCount()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.thoughtcrime.securesms.conversationlist;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationReader;
|
||||
|
||||
class ClearFilterViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final View tip;
|
||||
|
||||
ClearFilterViewHolder(@NonNull View itemView, OnClearFilterClickListener listener) {
|
||||
super(itemView);
|
||||
tip = itemView.findViewById(R.id.clear_filter_tip);
|
||||
itemView.findViewById(R.id.clear_filter).setOnClickListener(v -> {
|
||||
listener.onClearFilterClick();
|
||||
});
|
||||
}
|
||||
|
||||
void bind(@NonNull Conversation conversation) {
|
||||
if (conversation.getThreadRecord().getType() == ConversationReader.TYPE_SHOW_TIP) {
|
||||
tip.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
tip.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
interface OnClearFilterClickListener {
|
||||
void onClearFilterClick();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import org.signal.paging.PagingController;
|
||||
import org.thoughtcrime.securesms.BindableConversationListItem;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationReader;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationSet;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||
@@ -45,9 +46,9 @@ class ConversationListAdapter extends ListAdapter<Conversation, RecyclerView.Vie
|
||||
|
||||
private final LifecycleOwner lifecycleOwner;
|
||||
private final GlideRequests glideRequests;
|
||||
private final OnConversationClickListener onConversationClickListener;
|
||||
private final OnClearFilterClickListener onClearFilterClicked;
|
||||
private ConversationSet selectedConversations = new ConversationSet();
|
||||
private final OnConversationClickListener onConversationClickListener;
|
||||
private final ClearFilterViewHolder.OnClearFilterClickListener onClearFilterClicked;
|
||||
private ConversationSet selectedConversations = new ConversationSet();
|
||||
private final Set<Long> typingSet = new HashSet<>();
|
||||
|
||||
private PagingController pagingController;
|
||||
@@ -55,7 +56,7 @@ class ConversationListAdapter extends ListAdapter<Conversation, RecyclerView.Vie
|
||||
protected ConversationListAdapter(@NonNull LifecycleOwner lifecycleOwner,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull OnConversationClickListener onConversationClickListener,
|
||||
@NonNull OnClearFilterClickListener onClearFilterClicked)
|
||||
@NonNull ClearFilterViewHolder.OnClearFilterClickListener onClearFilterClicked)
|
||||
{
|
||||
super(new ConversationDiffCallback());
|
||||
|
||||
@@ -165,6 +166,11 @@ class ConversationListAdapter extends ListAdapter<Conversation, RecyclerView.Vie
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
} else if (holder.getItemViewType() == TYPE_CLEAR_FILTER_FOOTER || holder.getItemViewType() == TYPE_CLEAR_FILTER_EMPTY) {
|
||||
ClearFilterViewHolder casted = (ClearFilterViewHolder) holder;
|
||||
Conversation conversation = Objects.requireNonNull(getItem(position));
|
||||
|
||||
casted.bind(conversation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,22 +274,9 @@ class ConversationListAdapter extends ListAdapter<Conversation, RecyclerView.Vie
|
||||
}
|
||||
}
|
||||
|
||||
static class ClearFilterViewHolder extends RecyclerView.ViewHolder {
|
||||
ClearFilterViewHolder(@NonNull View itemView, OnClearFilterClickListener listener) {
|
||||
super(itemView);
|
||||
itemView.findViewById(R.id.clear_filter).setOnClickListener(v -> {
|
||||
listener.onClearFilterClick();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface OnConversationClickListener {
|
||||
void onConversationClick(@NonNull Conversation conversation);
|
||||
boolean onConversationLongClick(@NonNull Conversation conversation, @NonNull View view);
|
||||
void onShowArchiveClick();
|
||||
}
|
||||
|
||||
interface OnClearFilterClickListener {
|
||||
void onClearFilterClick();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,15 +38,17 @@ abstract class ConversationListDataSource implements PagedDataSource<Long, Conve
|
||||
|
||||
protected final ThreadTable threadTable;
|
||||
protected final ConversationFilter conversationFilter;
|
||||
protected final boolean showConversationFooterTip;
|
||||
|
||||
protected ConversationListDataSource(@NonNull ConversationFilter conversationFilter) {
|
||||
this.threadTable = SignalDatabase.threads();
|
||||
this.conversationFilter = conversationFilter;
|
||||
protected ConversationListDataSource(@NonNull ConversationFilter conversationFilter, boolean showConversationFooterTip) {
|
||||
this.threadTable = SignalDatabase.threads();
|
||||
this.conversationFilter = conversationFilter;
|
||||
this.showConversationFooterTip = showConversationFooterTip;
|
||||
}
|
||||
|
||||
public static ConversationListDataSource create(@NonNull ConversationFilter conversationFilter, boolean isArchived) {
|
||||
if (!isArchived) return new UnarchivedConversationListDataSource(conversationFilter);
|
||||
else return new ArchivedConversationListDataSource(conversationFilter);
|
||||
public static ConversationListDataSource create(@NonNull ConversationFilter conversationFilter, boolean isArchived, boolean showConversationFooterTip) {
|
||||
if (!isArchived) return new UnarchivedConversationListDataSource(conversationFilter, showConversationFooterTip);
|
||||
else return new ArchivedConversationListDataSource(conversationFilter, showConversationFooterTip);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,9 +100,11 @@ abstract class ConversationListDataSource implements PagedDataSource<Long, Conve
|
||||
|
||||
if (conversations.isEmpty() && start == 0 && length == 1) {
|
||||
if (conversationFilter == ConversationFilter.OFF) {
|
||||
return Collections.singletonList(new Conversation(ConversationReader.buildThreadRecordForType(Conversation.Type.EMPTY, 0)));
|
||||
return Collections.singletonList(new Conversation(ConversationReader.buildThreadRecordForType(Conversation.Type.EMPTY, 0, false)));
|
||||
} else {
|
||||
return Collections.singletonList(new Conversation(ConversationReader.buildThreadRecordForType(Conversation.Type.CONVERSATION_FILTER_EMPTY, 0)));
|
||||
return Collections.singletonList(new Conversation(ConversationReader.buildThreadRecordForType(Conversation.Type.CONVERSATION_FILTER_EMPTY,
|
||||
0,
|
||||
showConversationFooterTip)));
|
||||
}
|
||||
} else {
|
||||
return conversations;
|
||||
@@ -124,8 +128,8 @@ abstract class ConversationListDataSource implements PagedDataSource<Long, Conve
|
||||
|
||||
private int totalCount;
|
||||
|
||||
ArchivedConversationListDataSource(@NonNull ConversationFilter conversationFilter) {
|
||||
super(conversationFilter);
|
||||
ArchivedConversationListDataSource(@NonNull ConversationFilter conversationFilter, boolean showConversationFooterTip) {
|
||||
super(conversationFilter, showConversationFooterTip);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,8 +145,8 @@ abstract class ConversationListDataSource implements PagedDataSource<Long, Conve
|
||||
|
||||
cursors.add(cursor);
|
||||
if (offset + limit >= totalCount && totalCount > 0 && conversationFilter != ConversationFilter.OFF) {
|
||||
MatrixCursor conversationFilterFooter = new MatrixCursor(ConversationReader.HEADER_COLUMN);
|
||||
conversationFilterFooter.addRow(ConversationReader.CONVERSATION_FILTER_FOOTER);
|
||||
MatrixCursor conversationFilterFooter = new MatrixCursor(ConversationReader.FILTER_FOOTER_COLUMNS);
|
||||
conversationFilterFooter.addRow(ConversationReader.createConversationFilterFooterRow(showConversationFooterTip));
|
||||
cursors.add(conversationFilterFooter);
|
||||
}
|
||||
|
||||
@@ -158,8 +162,8 @@ abstract class ConversationListDataSource implements PagedDataSource<Long, Conve
|
||||
private int archivedCount;
|
||||
private int unpinnedCount;
|
||||
|
||||
UnarchivedConversationListDataSource(@NonNull ConversationFilter conversationFilter) {
|
||||
super(conversationFilter);
|
||||
UnarchivedConversationListDataSource(@NonNull ConversationFilter conversationFilter, boolean showConversationFooterTip) {
|
||||
super(conversationFilter, showConversationFooterTip);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -222,8 +226,8 @@ abstract class ConversationListDataSource implements PagedDataSource<Long, Conve
|
||||
}
|
||||
|
||||
if (shouldInsertConversationFilterFooter) {
|
||||
MatrixCursor conversationFilterFooter = new MatrixCursor(ConversationReader.HEADER_COLUMN);
|
||||
conversationFilterFooter.addRow(ConversationReader.CONVERSATION_FILTER_FOOTER);
|
||||
MatrixCursor conversationFilterFooter = new MatrixCursor(ConversationReader.FILTER_FOOTER_COLUMNS);
|
||||
conversationFilterFooter.addRow(ConversationReader.createConversationFilterFooterRow(showConversationFooterTip));
|
||||
cursors.add(conversationFilterFooter);
|
||||
}
|
||||
|
||||
@@ -251,7 +255,7 @@ abstract class ConversationListDataSource implements PagedDataSource<Long, Conve
|
||||
}
|
||||
|
||||
boolean hasConversationFilterFooter() {
|
||||
return totalCount > 1 && conversationFilter != ConversationFilter.OFF;
|
||||
return totalCount >= 1 && conversationFilter != ConversationFilter.OFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@ import org.thoughtcrime.securesms.components.voice.VoiceNotePlayerView;
|
||||
import org.thoughtcrime.securesms.contacts.sync.CdsPermanentErrorBottomSheet;
|
||||
import org.thoughtcrime.securesms.contacts.sync.CdsTemporaryErrorBottomSheet;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationFragment;
|
||||
import org.thoughtcrime.securesms.conversationlist.chatfilter.ConversationFilterSource;
|
||||
import org.thoughtcrime.securesms.conversationlist.chatfilter.ConversationListFilterPullView;
|
||||
import org.thoughtcrime.securesms.conversationlist.chatfilter.FilterLerp;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
|
||||
@@ -195,7 +196,7 @@ import static android.app.Activity.RESULT_OK;
|
||||
public class ConversationListFragment extends MainFragment implements ActionMode.Callback,
|
||||
ConversationListAdapter.OnConversationClickListener,
|
||||
ConversationListSearchAdapter.EventListener,
|
||||
MegaphoneActionController, ConversationListAdapter.OnClearFilterClickListener
|
||||
MegaphoneActionController, ClearFilterViewHolder.OnClearFilterClickListener
|
||||
{
|
||||
public static final short MESSAGE_REQUESTS_REQUEST_CODE_CREATE_NAME = 32562;
|
||||
public static final short SMS_ROLE_REQUEST_CODE = 32563;
|
||||
@@ -283,16 +284,19 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
CollapsingToolbarLayout collapsingToolbarLayout = view.findViewById(R.id.collapsing_toolbar);
|
||||
int openHeight = (int) DimensionUnit.DP.toPixels(FilterLerp.FILTER_OPEN_HEIGHT);
|
||||
|
||||
pullView.setOnFilterStateChanged(state -> {
|
||||
pullView.setOnFilterStateChanged((state, source) -> {
|
||||
switch (state) {
|
||||
case CLOSING:
|
||||
viewModel.setFiltered(false);
|
||||
viewModel.setFiltered(false, source);
|
||||
break;
|
||||
case OPENING:
|
||||
viewModel.setFiltered(true);
|
||||
viewModel.setFiltered(true, source);
|
||||
break;
|
||||
case OPEN_APEX:
|
||||
ViewUtil.setMinimumHeight(collapsingToolbarLayout, openHeight);
|
||||
if (source == ConversationFilterSource.DRAG) {
|
||||
SignalStore.uiHints().incrementNeverDisplayPullToFilterTip();
|
||||
}
|
||||
break;
|
||||
case CLOSE_APEX:
|
||||
ViewUtil.setMinimumHeight(collapsingToolbarLayout, 0);
|
||||
@@ -747,7 +751,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
private void initializeListAdapters() {
|
||||
defaultAdapter = new ConversationListAdapter(getViewLifecycleOwner(), GlideApp.with(this), this, this);
|
||||
searchAdapter = new ConversationListSearchAdapter(getViewLifecycleOwner(), GlideApp.with(this), this, Locale.getDefault());
|
||||
searchAdapter = new ConversationListSearchAdapter(getViewLifecycleOwner(), GlideApp.with(this), this, Locale.getDefault(), this);
|
||||
searchAdapterDecoration = new StickyHeaderDecoration(searchAdapter, false, false, 0);
|
||||
|
||||
setAdapter(defaultAdapter);
|
||||
|
||||
@@ -10,7 +10,9 @@ import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationSet;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
@@ -25,30 +27,35 @@ import java.util.Locale;
|
||||
class ConversationListSearchAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
||||
implements StickyHeaderDecoration.StickyHeaderAdapter<ConversationListSearchAdapter.HeaderViewHolder>
|
||||
{
|
||||
private static final int VIEW_TYPE_EMPTY = 0;
|
||||
private static final int VIEW_TYPE_NON_EMPTY = 1;
|
||||
private static final int VIEW_TYPE_EMPTY = 0;
|
||||
private static final int VIEW_TYPE_NON_EMPTY = 1;
|
||||
private static final int VIEW_TYPE_CHAT_FILTER = 2;
|
||||
private static final int VIEW_TYPE_CHAT_FILTER_EMPTY = 3;
|
||||
|
||||
private static final int TYPE_CONVERSATIONS = 1;
|
||||
private static final int TYPE_CONTACTS = 2;
|
||||
private static final int TYPE_MESSAGES = 3;
|
||||
|
||||
private final LifecycleOwner lifecycleOwner;
|
||||
private final GlideRequests glideRequests;
|
||||
private final EventListener eventListener;
|
||||
private final Locale locale;
|
||||
private final LifecycleOwner lifecycleOwner;
|
||||
private final GlideRequests glideRequests;
|
||||
private final EventListener eventListener;
|
||||
private final Locale locale;
|
||||
private final ClearFilterViewHolder.OnClearFilterClickListener onClearFilterClicked;
|
||||
|
||||
@NonNull
|
||||
private SearchResult searchResult = SearchResult.EMPTY;
|
||||
|
||||
ConversationListSearchAdapter(@NonNull LifecycleOwner lifecycleOwner,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull EventListener eventListener,
|
||||
@NonNull Locale locale)
|
||||
ConversationListSearchAdapter(@NonNull LifecycleOwner lifecycleOwner,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull EventListener eventListener,
|
||||
@NonNull Locale locale,
|
||||
@NonNull ClearFilterViewHolder.OnClearFilterClickListener onClearFilterClicked)
|
||||
{
|
||||
this.lifecycleOwner = lifecycleOwner;
|
||||
this.glideRequests = glideRequests;
|
||||
this.eventListener = eventListener;
|
||||
this.locale = locale;
|
||||
this.lifecycleOwner = lifecycleOwner;
|
||||
this.glideRequests = glideRequests;
|
||||
this.eventListener = eventListener;
|
||||
this.locale = locale;
|
||||
this.onClearFilterClicked = onClearFilterClicked;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,6 +63,12 @@ class ConversationListSearchAdapter extends RecyclerView.Adapter<RecyclerView
|
||||
if (viewType == VIEW_TYPE_EMPTY) {
|
||||
return new EmptyViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.conversation_list_empty_search_state, parent, false));
|
||||
} else if (viewType == VIEW_TYPE_CHAT_FILTER) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.conversation_list_item_clear_filter, parent, false);
|
||||
return new ClearFilterViewHolder(v, onClearFilterClicked);
|
||||
} else if (viewType == VIEW_TYPE_CHAT_FILTER_EMPTY) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.conversation_list_item_clear_filter_empty, parent, false);
|
||||
return new ClearFilterViewHolder(v, onClearFilterClicked);
|
||||
} else {
|
||||
return new SearchResultViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.conversation_list_item_view, parent, false));
|
||||
@@ -93,8 +106,12 @@ class ConversationListSearchAdapter extends RecyclerView.Adapter<RecyclerView
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (searchResult.isEmpty()) {
|
||||
if (searchResult.isEmpty() && searchResult.getConversationFilter() == ConversationFilter.OFF) {
|
||||
return VIEW_TYPE_EMPTY;
|
||||
} else if (searchResult.isEmpty()) {
|
||||
return VIEW_TYPE_CHAT_FILTER_EMPTY;
|
||||
} else if (position == getChatFilterIndex()) {
|
||||
return VIEW_TYPE_CHAT_FILTER;
|
||||
} else {
|
||||
return VIEW_TYPE_NON_EMPTY;
|
||||
}
|
||||
@@ -109,14 +126,20 @@ class ConversationListSearchAdapter extends RecyclerView.Adapter<RecyclerView
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return searchResult.isEmpty() ? 1 : searchResult.size();
|
||||
if (searchResult.isEmpty()) {
|
||||
return 1;
|
||||
} if (searchResult.getConversationFilter() != ConversationFilter.OFF) {
|
||||
return searchResult.size() + 1;
|
||||
} else {
|
||||
return searchResult.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getHeaderId(int position) {
|
||||
if (position < 0 || searchResult.isEmpty()) {
|
||||
if (position < 0 || searchResult.isEmpty() || position == getChatFilterIndex()) {
|
||||
return StickyHeaderDecoration.StickyHeaderAdapter.NO_HEADER_ID;
|
||||
} else if (getConversationResult(position) != null) {
|
||||
} else if (getConversationResult(position) != null) {
|
||||
return TYPE_CONVERSATIONS;
|
||||
} else if (getContactResult(position) != null) {
|
||||
return TYPE_CONTACTS;
|
||||
@@ -173,6 +196,18 @@ class ConversationListSearchAdapter extends RecyclerView.Adapter<RecyclerView
|
||||
return getFirstContactIndex() + searchResult.getContacts().size();
|
||||
}
|
||||
|
||||
private int getChatFilterIndex() {
|
||||
if (searchResult.getConversationFilter() == ConversationFilter.OFF) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (searchResult.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return searchResult.size();
|
||||
}
|
||||
|
||||
public interface EventListener {
|
||||
void onConversationClicked(@NonNull ThreadRecord threadRecord);
|
||||
void onContactClicked(@NonNull Recipient contact);
|
||||
|
||||
@@ -16,6 +16,8 @@ import org.signal.paging.PagedData;
|
||||
import org.signal.paging.PagingConfig;
|
||||
import org.signal.paging.PagingController;
|
||||
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.NotificationProfilesRepository;
|
||||
import org.thoughtcrime.securesms.conversationlist.chatfilter.ConversationFilterRequest;
|
||||
import org.thoughtcrime.securesms.conversationlist.chatfilter.ConversationFilterSource;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationSet;
|
||||
@@ -24,6 +26,7 @@ import org.thoughtcrime.securesms.conversationlist.model.UnreadPaymentsLiveData;
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphone;
|
||||
import org.thoughtcrime.securesms.megaphone.MegaphoneRepository;
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||
@@ -63,7 +66,7 @@ class ConversationListViewModel extends ViewModel {
|
||||
private final MutableLiveData<Megaphone> megaphone;
|
||||
private final MutableLiveData<SearchResult> searchResult;
|
||||
private final MutableLiveData<ConversationSet> selectedConversations;
|
||||
private final MutableLiveData<ConversationFilter> conversationFilter;
|
||||
private final MutableLiveData<ConversationFilterRequest> conversationFilterRequest;
|
||||
private final LiveData<ConversationListDataSource> conversationListDataSource;
|
||||
private final Set<Conversation> internalSelection;
|
||||
private final LiveData<LivePagedData<Long, Conversation>> pagedData;
|
||||
@@ -99,9 +102,11 @@ class ConversationListViewModel extends ViewModel {
|
||||
this.activeSearchResult = SearchResult.EMPTY;
|
||||
this.invalidator = new Invalidator();
|
||||
this.disposables = new CompositeDisposable();
|
||||
this.conversationFilter = new MutableLiveData<>(ConversationFilter.OFF);
|
||||
this.conversationListDataSource = Transformations.map(Transformations.distinctUntilChanged(conversationFilter),
|
||||
filter -> ConversationListDataSource.create(filter, isArchived));
|
||||
this.conversationFilterRequest = new MutableLiveData<>(new ConversationFilterRequest(ConversationFilter.OFF, ConversationFilterSource.DRAG));
|
||||
this.conversationListDataSource = Transformations.map(Transformations.distinctUntilChanged(conversationFilterRequest),
|
||||
request -> ConversationListDataSource.create(request.getFilter(),
|
||||
isArchived,
|
||||
SignalStore.uiHints().canDisplayPullToFilterTip() && request.getSource() == ConversationFilterSource.OVERFLOW));
|
||||
this.pagedData = Transformations.map(conversationListDataSource, source -> PagedData.createForLiveData(source,
|
||||
new PagingConfig.Builder()
|
||||
.setPageSize(15)
|
||||
@@ -123,13 +128,13 @@ class ConversationListViewModel extends ViewModel {
|
||||
});
|
||||
};
|
||||
|
||||
this.hasNoConversations = LiveDataUtil.mapAsync(LiveDataUtil.combineLatest(conversationFilter, getConversationList(), Pair::new), filterAndData -> {
|
||||
this.hasNoConversations = LiveDataUtil.mapAsync(LiveDataUtil.combineLatest(conversationFilterRequest, getConversationList(), Pair::new), filterAndData -> {
|
||||
pinnedCount = SignalDatabase.threads().getPinnedConversationListCount(ConversationFilter.OFF);
|
||||
|
||||
if (filterAndData.getSecond().size() > 0) {
|
||||
return false;
|
||||
} else {
|
||||
return SignalDatabase.threads().getArchivedConversationListCount(filterAndData.getFirst()) == 0;
|
||||
return SignalDatabase.threads().getArchivedConversationListCount(filterAndData.getFirst().getFilter()) == 0;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -211,14 +216,14 @@ class ConversationListViewModel extends ViewModel {
|
||||
setSelection(newSelection);
|
||||
}
|
||||
|
||||
void setFiltered(boolean isFiltered) {
|
||||
void setFiltered(boolean isFiltered, @NonNull ConversationFilterSource conversationFilterSource) {
|
||||
if (isFiltered) {
|
||||
conversationFilter.setValue(ConversationFilter.UNREAD);
|
||||
conversationFilterRequest.setValue(new ConversationFilterRequest(ConversationFilter.UNREAD, conversationFilterSource));
|
||||
if (activeQuery != null) {
|
||||
onSearchQueryUpdated(activeQuery);
|
||||
}
|
||||
} else {
|
||||
conversationFilter.setValue(ConversationFilter.OFF);
|
||||
conversationFilterRequest.setValue(new ConversationFilterRequest(ConversationFilter.OFF, conversationFilterSource));
|
||||
if (activeQuery != null) {
|
||||
onSearchQueryUpdated(activeQuery);
|
||||
}
|
||||
@@ -266,14 +271,14 @@ class ConversationListViewModel extends ViewModel {
|
||||
void onSearchQueryUpdated(String query) {
|
||||
activeQuery = query;
|
||||
|
||||
ConversationFilter filter = conversationFilter.getValue();
|
||||
ConversationFilter filter = Objects.requireNonNull(conversationFilterRequest.getValue()).getFilter();
|
||||
if (filter != ConversationFilter.OFF) {
|
||||
contactSearchDebouncer.publish(() -> submitConversationSearch(query));
|
||||
contactSearchDebouncer.publish(() -> submitConversationSearch(query, filter));
|
||||
return;
|
||||
}
|
||||
|
||||
contactSearchDebouncer.publish(() -> {
|
||||
submitConversationSearch(query);
|
||||
submitConversationSearch(query, filter);
|
||||
|
||||
searchRepository.queryContacts(query, result -> {
|
||||
if (!result.getQuery().equals(activeQuery)) {
|
||||
@@ -305,8 +310,8 @@ class ConversationListViewModel extends ViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
private void submitConversationSearch(@NonNull String query) {
|
||||
searchRepository.queryThreads(query, result -> {
|
||||
private void submitConversationSearch(@NonNull String query, @NonNull ConversationFilter conversationFilter) {
|
||||
searchRepository.queryThreads(query, conversationFilter == ConversationFilter.UNREAD, result -> {
|
||||
if (!result.getQuery().equals(activeQuery)) {
|
||||
return;
|
||||
}
|
||||
@@ -315,7 +320,7 @@ class ConversationListViewModel extends ViewModel {
|
||||
activeSearchResult = SearchResult.EMPTY;
|
||||
}
|
||||
|
||||
activeSearchResult = activeSearchResult.merge(result);
|
||||
activeSearchResult = activeSearchResult.merge(result).merge(conversationFilter);
|
||||
searchResult.postValue(activeSearchResult);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.thoughtcrime.securesms.conversationlist.chatfilter
|
||||
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
|
||||
|
||||
data class ConversationFilterRequest(
|
||||
val filter: ConversationFilter,
|
||||
val source: ConversationFilterSource
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.thoughtcrime.securesms.conversationlist.chatfilter
|
||||
|
||||
/**
|
||||
* Describes where the chat filter was applied from.
|
||||
*/
|
||||
enum class ConversationFilterSource {
|
||||
/**
|
||||
* User pulled and released the pull view.
|
||||
*/
|
||||
DRAG,
|
||||
|
||||
/**
|
||||
* User utilized the menu item in the overflow.
|
||||
*/
|
||||
OVERFLOW
|
||||
}
|
||||
@@ -65,14 +65,14 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
||||
binding.filterCircle.progress = progress
|
||||
|
||||
if (state == FilterPullState.CLOSED && progress <= 0) {
|
||||
setState(FilterPullState.CLOSED)
|
||||
setState(FilterPullState.CLOSED, ConversationFilterSource.DRAG)
|
||||
} else if (state == FilterPullState.CLOSED && progress >= 1f) {
|
||||
setState(FilterPullState.OPEN_APEX)
|
||||
setState(FilterPullState.OPEN_APEX, ConversationFilterSource.DRAG)
|
||||
vibrate()
|
||||
resetHelpText()
|
||||
resetPillColor()
|
||||
} else if (state == FilterPullState.OPEN && progress >= 1f) {
|
||||
setState(FilterPullState.CLOSE_APEX)
|
||||
setState(FilterPullState.CLOSE_APEX, ConversationFilterSource.DRAG)
|
||||
vibrate()
|
||||
animatePillColor()
|
||||
}
|
||||
@@ -111,19 +111,19 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
||||
|
||||
fun onUserDragFinished() {
|
||||
if (state == FilterPullState.OPEN_APEX) {
|
||||
open()
|
||||
open(ConversationFilterSource.DRAG)
|
||||
} else if (state == FilterPullState.CLOSE_APEX) {
|
||||
close()
|
||||
close(ConversationFilterSource.DRAG)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggle() {
|
||||
if (state == FilterPullState.OPEN) {
|
||||
setState(FilterPullState.CLOSE_APEX)
|
||||
close()
|
||||
setState(FilterPullState.CLOSE_APEX, ConversationFilterSource.OVERFLOW)
|
||||
close(ConversationFilterSource.OVERFLOW)
|
||||
} else if (state == FilterPullState.CLOSED) {
|
||||
setState(FilterPullState.OPEN_APEX)
|
||||
open()
|
||||
setState(FilterPullState.OPEN_APEX, ConversationFilterSource.OVERFLOW)
|
||||
open(ConversationFilterSource.OVERFLOW)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,14 +131,14 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
||||
return state == FilterPullState.OPEN
|
||||
}
|
||||
|
||||
private fun open() {
|
||||
setState(FilterPullState.OPENING)
|
||||
animatePillIn()
|
||||
private fun open(source: ConversationFilterSource) {
|
||||
setState(FilterPullState.OPENING, source)
|
||||
animatePillIn(source)
|
||||
}
|
||||
|
||||
private fun close() {
|
||||
setState(FilterPullState.CLOSING)
|
||||
animatePillOut()
|
||||
private fun close(source: ConversationFilterSource) {
|
||||
setState(FilterPullState.CLOSING, source)
|
||||
animatePillOut(source)
|
||||
}
|
||||
|
||||
private fun resetHelpText() {
|
||||
@@ -152,7 +152,7 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
||||
})
|
||||
}
|
||||
|
||||
private fun animatePillIn() {
|
||||
private fun animatePillIn(source: ConversationFilterSource) {
|
||||
binding.filterText.visibility = VISIBLE
|
||||
binding.filterText.alpha = 0f
|
||||
binding.filterText.isEnabled = true
|
||||
@@ -162,20 +162,20 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
||||
startDelay = 300
|
||||
duration = 300
|
||||
doOnEnd {
|
||||
setState(FilterPullState.OPEN)
|
||||
setState(FilterPullState.OPEN, source)
|
||||
}
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
private fun animatePillOut() {
|
||||
private fun animatePillOut(source: ConversationFilterSource) {
|
||||
pillAnimator?.cancel()
|
||||
pillAnimator = ObjectAnimator.ofFloat(binding.filterText, ALPHA, 0f).apply {
|
||||
duration = 150
|
||||
doOnEnd {
|
||||
binding.filterText.visibility = GONE
|
||||
binding.filterText.isEnabled = false
|
||||
postDelayed({ setState(FilterPullState.CLOSED) }, 150)
|
||||
postDelayed({ setState(FilterPullState.CLOSED, source) }, 150)
|
||||
}
|
||||
start()
|
||||
}
|
||||
@@ -198,10 +198,10 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
||||
binding.filterText.chipBackgroundColor = ColorStateList.valueOf(pillDefaultBackgroundTint)
|
||||
}
|
||||
|
||||
private fun setState(state: FilterPullState) {
|
||||
private fun setState(state: FilterPullState, source: ConversationFilterSource) {
|
||||
this.state = state
|
||||
binding.filterCircle.state = state
|
||||
onFilterStateChanged?.newState(state)
|
||||
onFilterStateChanged?.newState(state, source)
|
||||
}
|
||||
|
||||
private fun vibrate() {
|
||||
@@ -211,7 +211,7 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
interface OnFilterStateChanged {
|
||||
fun newState(state: FilterPullState)
|
||||
fun newState(state: FilterPullState, source: ConversationFilterSource)
|
||||
}
|
||||
|
||||
interface OnCloseClicked {
|
||||
|
||||
@@ -14,9 +14,12 @@ public class ConversationReader extends ThreadTable.StaticReader {
|
||||
|
||||
public static final String[] HEADER_COLUMN = { "header" };
|
||||
public static final String[] ARCHIVED_COLUMNS = { "header", "count" };
|
||||
public static final String[] FILTER_FOOTER_COLUMNS = { "header", "show_tip" };
|
||||
public static final String[] PINNED_HEADER = { Conversation.Type.PINNED_HEADER.toString() };
|
||||
public static final String[] UNPINNED_HEADER = { Conversation.Type.UNPINNED_HEADER.toString() };
|
||||
public static final String[] CONVERSATION_FILTER_FOOTER = { Conversation.Type.CONVERSATION_FILTER_FOOTER.toString() };
|
||||
|
||||
public static final long TYPE_NONE = 0x0;
|
||||
public static final long TYPE_SHOW_TIP = 0x1;
|
||||
|
||||
private final Cursor cursor;
|
||||
|
||||
@@ -29,6 +32,10 @@ public class ConversationReader extends ThreadTable.StaticReader {
|
||||
return new String[]{Conversation.Type.ARCHIVED_FOOTER.toString(), String.valueOf(archivedCount)};
|
||||
}
|
||||
|
||||
public static String[] createConversationFilterFooterRow(boolean showTip) {
|
||||
return new String[]{Conversation.Type.CONVERSATION_FILTER_FOOTER.toString(), String.valueOf(showTip ? 1 : 0)};
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadRecord getCurrent() {
|
||||
if (cursor.getColumnIndex(HEADER_COLUMN[0]) == -1) {
|
||||
@@ -45,15 +52,21 @@ public class ConversationReader extends ThreadTable.StaticReader {
|
||||
count = CursorUtil.requireInt(cursor, ARCHIVED_COLUMNS[1]);
|
||||
}
|
||||
|
||||
return buildThreadRecordForType(type, count);
|
||||
boolean showTip = false;
|
||||
if (type == Conversation.Type.CONVERSATION_FILTER_FOOTER) {
|
||||
showTip = CursorUtil.requireBoolean(cursor, FILTER_FOOTER_COLUMNS[1]);
|
||||
}
|
||||
|
||||
return buildThreadRecordForType(type, count, showTip);
|
||||
}
|
||||
|
||||
public static ThreadRecord buildThreadRecordForType(@NonNull Conversation.Type type, int count) {
|
||||
public static ThreadRecord buildThreadRecordForType(@NonNull Conversation.Type type, int count, boolean showTip) {
|
||||
return new ThreadRecord.Builder(-(100 + type.ordinal()))
|
||||
.setBody(type.toString())
|
||||
.setDate(100)
|
||||
.setRecipient(Recipient.UNKNOWN)
|
||||
.setUnreadCount(count)
|
||||
.setType(showTip ? TYPE_SHOW_TIP : TYPE_NONE)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,7 +615,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
}
|
||||
}
|
||||
|
||||
fun getFilteredConversationList(filter: List<RecipientId>): Cursor? {
|
||||
fun getFilteredConversationList(filter: List<RecipientId>, unreadOnly: Boolean): Cursor? {
|
||||
if (filter.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
@@ -625,7 +625,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
val cursors: MutableList<Cursor> = LinkedList()
|
||||
|
||||
for (recipientIds in splitRecipientIds) {
|
||||
var selection = "$TABLE_NAME.$RECIPIENT_ID = ?"
|
||||
var selection = "($TABLE_NAME.$RECIPIENT_ID = ?"
|
||||
val selectionArgs = arrayOfNulls<String>(recipientIds.size)
|
||||
|
||||
for (i in 0 until recipientIds.size - 1) {
|
||||
@@ -638,6 +638,12 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
i++
|
||||
}
|
||||
|
||||
selection += if (unreadOnly) {
|
||||
") AND $TABLE_NAME.$READ != ${ReadStatus.READ.serialize()}"
|
||||
} else {
|
||||
")"
|
||||
}
|
||||
|
||||
val query = createQuery(selection, "$DATE DESC", 0, 0)
|
||||
cursors.add(db.rawQuery(query, selectionArgs))
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@ import java.util.List;
|
||||
|
||||
public class UiHints extends SignalStoreValues {
|
||||
|
||||
private static final int NEVER_DISPLAY_PULL_TO_FILTER_TIP_THRESHOLD = 3;
|
||||
|
||||
private static final String HAS_SEEN_GROUP_SETTINGS_MENU_TOAST = "uihints.has_seen_group_settings_menu_toast";
|
||||
private static final String HAS_CONFIRMED_DELETE_FOR_EVERYONE_ONCE = "uihints.has_confirmed_delete_for_everyone_once";
|
||||
private static final String HAS_SET_OR_SKIPPED_USERNAME_CREATION = "uihints.has_set_or_skipped_username_creation";
|
||||
private static final String NEVER_DISPLAY_PULL_TO_FILTER_TIP = "uihints.never_display_pull_to_filter_tip";
|
||||
|
||||
UiHints(@NonNull KeyValueStore store) {
|
||||
super(store);
|
||||
@@ -22,7 +25,7 @@ public class UiHints extends SignalStoreValues {
|
||||
|
||||
@Override
|
||||
@NonNull List<String> getKeysToIncludeInBackup() {
|
||||
return Collections.emptyList();
|
||||
return Collections.singletonList(NEVER_DISPLAY_PULL_TO_FILTER_TIP);
|
||||
}
|
||||
|
||||
public void markHasSeenGroupSettingsMenuToast() {
|
||||
@@ -48,4 +51,21 @@ public class UiHints extends SignalStoreValues {
|
||||
public void markHasSetOrSkippedUsernameCreation() {
|
||||
putBoolean(HAS_SET_OR_SKIPPED_USERNAME_CREATION, true);
|
||||
}
|
||||
|
||||
public void resetNeverDisplayPullToRefreshCount() {
|
||||
putInteger(NEVER_DISPLAY_PULL_TO_FILTER_TIP, 0);
|
||||
}
|
||||
|
||||
public boolean canDisplayPullToFilterTip() {
|
||||
return getNeverDisplayPullToFilterTip() < NEVER_DISPLAY_PULL_TO_FILTER_TIP_THRESHOLD;
|
||||
}
|
||||
|
||||
public void incrementNeverDisplayPullToFilterTip() {
|
||||
int inc = Math.min(NEVER_DISPLAY_PULL_TO_FILTER_TIP_THRESHOLD, getNeverDisplayPullToFilterTip() + 1);
|
||||
putInteger(NEVER_DISPLAY_PULL_TO_FILTER_TIP, inc);
|
||||
}
|
||||
|
||||
private int getNeverDisplayPullToFilterTip() {
|
||||
return getInteger(NEVER_DISPLAY_PULL_TO_FILTER_TIP, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,10 +80,10 @@ public class SearchRepository {
|
||||
this.serialExecutor = new SerialExecutor(SignalExecutors.BOUNDED);
|
||||
}
|
||||
|
||||
public void queryThreads(@NonNull String query, @NonNull Consumer<ThreadSearchResult> callback) {
|
||||
public void queryThreads(@NonNull String query, boolean unreadOnly, @NonNull Consumer<ThreadSearchResult> callback) {
|
||||
searchExecutor.execute(2, () -> {
|
||||
long start = System.currentTimeMillis();
|
||||
List<ThreadRecord> result = queryConversations(query);
|
||||
List<ThreadRecord> result = queryConversations(query, unreadOnly);
|
||||
|
||||
Log.d(TAG, "[threads] Search took " + (System.currentTimeMillis() - start) + " ms");
|
||||
|
||||
@@ -155,7 +155,7 @@ public class SearchRepository {
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull List<ThreadRecord> queryConversations(@NonNull String query) {
|
||||
private @NonNull List<ThreadRecord> queryConversations(@NonNull String query, boolean unreadOnly) {
|
||||
if (Util.isEmpty(query)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -192,15 +192,15 @@ public class SearchRepository {
|
||||
|
||||
List<ThreadRecord> output = new ArrayList<>(contactIds.size() + groupsByTitleIds.size() + groupsByMemberIds.size());
|
||||
|
||||
output.addAll(getMatchingThreads(contactIds));
|
||||
output.addAll(getMatchingThreads(groupsByTitleIds));
|
||||
output.addAll(getMatchingThreads(groupsByMemberIds));
|
||||
output.addAll(getMatchingThreads(contactIds, unreadOnly));
|
||||
output.addAll(getMatchingThreads(groupsByTitleIds, unreadOnly));
|
||||
output.addAll(getMatchingThreads(groupsByMemberIds, unreadOnly));
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private List<ThreadRecord> getMatchingThreads(@NonNull Collection<RecipientId> recipientIds) {
|
||||
try (Cursor cursor = threadTable.getFilteredConversationList(new ArrayList<>(recipientIds))) {
|
||||
private List<ThreadRecord> getMatchingThreads(@NonNull Collection<RecipientId> recipientIds, boolean unreadOnly) {
|
||||
try (Cursor cursor = threadTable.getFilteredConversationList(new ArrayList<>(recipientIds), unreadOnly)) {
|
||||
return readToList(cursor, new ThreadModelBuilder(threadTable));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.search
|
||||
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
@@ -11,7 +12,8 @@ data class SearchResult(
|
||||
val query: String,
|
||||
val contacts: List<Recipient>,
|
||||
val conversations: List<ThreadRecord>,
|
||||
val messages: List<MessageResult>
|
||||
val messages: List<MessageResult>,
|
||||
val conversationFilter: ConversationFilter
|
||||
) {
|
||||
fun size(): Int {
|
||||
return contacts.size + conversations.size + messages.size
|
||||
@@ -32,8 +34,12 @@ data class SearchResult(
|
||||
return this.copy(messages = result.results, query = result.query)
|
||||
}
|
||||
|
||||
fun merge(conversationFilter: ConversationFilter): SearchResult {
|
||||
return this.copy(conversationFilter = conversationFilter)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val EMPTY = SearchResult("", emptyList(), emptyList(), emptyList())
|
||||
val EMPTY = SearchResult("", emptyList(), emptyList(), emptyList(), ConversationFilter.OFF)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +114,7 @@ public final class FeatureFlags {
|
||||
@VisibleForTesting
|
||||
static final Set<String> REMOTE_CAPABLE = SetUtil.newHashSet(
|
||||
PAYMENTS_KILL_SWITCH,
|
||||
GROUPS_V2_RECOMMENDED_LIMIT,
|
||||
GROUPS_V2_HARD_LIMIT,
|
||||
GROUPS_V2_RECOMMENDED_LIMIT, GROUPS_V2_HARD_LIMIT,
|
||||
INTERNAL_USER,
|
||||
VERIFY_V2,
|
||||
CLIENT_EXPIRATION,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="88dp">
|
||||
android:gravity="center"
|
||||
android:minHeight="88dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/clear_filter"
|
||||
@@ -13,4 +15,14 @@
|
||||
android:text="@string/ConversationListFragment__clear_filter"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant" />
|
||||
|
||||
</FrameLayout>
|
||||
<TextView
|
||||
android:id="@+id/clear_filter_tip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/ChatFilter__tip_pull_down"
|
||||
android:textAppearance="@style/Signal.Text.BodySmall"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -21,4 +21,14 @@
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/ConversationListFragment__clear_filter"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/clear_filter_tip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/ChatFilter__tip_pull_down"
|
||||
android:textAppearance="@style/Signal.Text.BodySmall"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
@@ -5469,5 +5469,7 @@
|
||||
<string name="ChatFilter__filtered_by_unread">Filtered by unread</string>
|
||||
<!-- Displayed underneath the filter circle at the top of the chat list when the user pulls at a very low velocity -->
|
||||
<string name="ChatFilter__pull_to_filter">Pull to filter</string>
|
||||
<!-- Displayed in the "clear filter" item in the chat feed if the user opened the filter from the overflow menu -->
|
||||
<string name="ChatFilter__tip_pull_down">Tip: Pull down on the chat list to filter</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -54,7 +54,7 @@ public class UnarchivedConversationListDataSourceTest {
|
||||
when(SignalDatabase.threads()).thenReturn(threadTable);
|
||||
when(ApplicationDependencies.getDatabaseObserver()).thenReturn(mock(DatabaseObserver.class));
|
||||
|
||||
testSubject = new ConversationListDataSource.UnarchivedConversationListDataSource(ConversationFilter.OFF);
|
||||
testSubject = new ConversationListDataSource.UnarchivedConversationListDataSource(ConversationFilter.OFF, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -266,7 +266,7 @@ public class UnarchivedConversationListDataSourceTest {
|
||||
@Test
|
||||
public void givenHasNoArchivedAndIsFiltered_whenIGetCursor_thenIExpectConversationFilterFooter() {
|
||||
// GIVEN
|
||||
ConversationListDataSource.UnarchivedConversationListDataSource testSubject = new ConversationListDataSource.UnarchivedConversationListDataSource(ConversationFilter.UNREAD);
|
||||
ConversationListDataSource.UnarchivedConversationListDataSource testSubject = new ConversationListDataSource.UnarchivedConversationListDataSource(ConversationFilter.UNREAD, false);
|
||||
setupThreadDatabaseCursors(0, 3);
|
||||
when(threadTable.getPinnedConversationListCount(ConversationFilter.UNREAD)).thenReturn(0);
|
||||
when(threadTable.getUnarchivedConversationListCount(ConversationFilter.UNREAD)).thenReturn(3);
|
||||
|
||||
Reference in New Issue
Block a user