mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 08:39:22 +01:00
Refresh contact search views.
This commit is contained in:
committed by
Greyson Parrelli
parent
a4d458f969
commit
a157c1ae1d
@@ -20,13 +20,13 @@ import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
||||
import org.thoughtcrime.securesms.components.ContactFilterView;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
@@ -56,7 +56,8 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
||||
|
||||
protected ContactSelectionListFragment contactsFragment;
|
||||
|
||||
private ContactFilterToolbar toolbar;
|
||||
private Toolbar toolbar;
|
||||
private ContactFilterView contactFilterView;
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
@@ -73,6 +74,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
||||
|
||||
setContentView(getIntent().getIntExtra(EXTRA_LAYOUT_RES_ID, R.layout.contact_selection_activity));
|
||||
|
||||
initializeContactFilterView();
|
||||
initializeToolbar();
|
||||
initializeResources();
|
||||
initializeSearch();
|
||||
@@ -84,16 +86,23 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
||||
dynamicTheme.onResume(this);
|
||||
}
|
||||
|
||||
protected ContactFilterToolbar getToolbar() {
|
||||
protected Toolbar getToolbar() {
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
protected ContactFilterView getContactFilterView() {
|
||||
return contactFilterView;
|
||||
}
|
||||
|
||||
private void initializeContactFilterView() {
|
||||
this.contactFilterView = findViewById(R.id.contact_filter_edit_text);
|
||||
}
|
||||
|
||||
private void initializeToolbar() {
|
||||
this.toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
getSupportActionBar().setIcon(null);
|
||||
getSupportActionBar().setLogo(null);
|
||||
}
|
||||
@@ -104,7 +113,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
||||
}
|
||||
|
||||
private void initializeSearch() {
|
||||
toolbar.setOnFilterChangedListener(filter -> contactsFragment.setQueryFilter(filter));
|
||||
contactFilterView.setOnFilterChangedListener(filter -> contactsFragment.setQueryFilter(filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -155,7 +164,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
||||
ContactSelectionActivity activity = this.activity.get();
|
||||
|
||||
if (activity != null && !activity.isFinishing()) {
|
||||
activity.toolbar.clear();
|
||||
activity.contactFilterView.clear();
|
||||
activity.contactsFragment.resetQueryFilter();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,13 +57,14 @@ import com.pnikosis.materialishprogress.ProgressWheel;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
|
||||
import org.thoughtcrime.securesms.components.emoji.WarningTextView;
|
||||
import org.thoughtcrime.securesms.components.recyclerview.ToolbarShadowAnimationHelper;
|
||||
import org.thoughtcrime.securesms.contacts.AbstractContactsCursorLoader;
|
||||
import org.thoughtcrime.securesms.contacts.ContactChip;
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||
import org.thoughtcrime.securesms.contacts.LetterHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
@@ -74,7 +75,6 @@ import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
@@ -131,9 +131,10 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
private ContactSelectionListAdapter cursorRecyclerViewAdapter;
|
||||
private ChipGroup chipGroup;
|
||||
private HorizontalScrollView chipGroupScrollContainer;
|
||||
private WarningTextView groupLimit;
|
||||
private OnSelectionLimitReachedListener onSelectionLimitReachedListener;
|
||||
private AbstractContactsCursorLoaderFactoryProvider cursorFactoryProvider;
|
||||
private View shadowView;
|
||||
private ToolbarShadowAnimationHelper toolbarShadowAnimationHelper;
|
||||
|
||||
|
||||
@Nullable private FixedViewsAdapter headerAdapter;
|
||||
@@ -233,9 +234,12 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
showContactsProgress = view.findViewById(R.id.progress);
|
||||
chipGroup = view.findViewById(R.id.chipGroup);
|
||||
chipGroupScrollContainer = view.findViewById(R.id.chipGroupScrollContainer);
|
||||
groupLimit = view.findViewById(R.id.group_limit);
|
||||
constraintLayout = view.findViewById(R.id.container);
|
||||
shadowView = view.findViewById(R.id.toolbar_shadow);
|
||||
|
||||
toolbarShadowAnimationHelper = new ToolbarShadowAnimationHelper(shadowView);
|
||||
|
||||
recyclerView.addOnScrollListener(toolbarShadowAnimationHelper);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
recyclerView.setItemAnimator(new DefaultItemAnimator() {
|
||||
@Override
|
||||
@@ -272,8 +276,6 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
|
||||
currentSelection = getCurrentSelection();
|
||||
|
||||
updateGroupLimit(getChipCount());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -281,13 +283,6 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
return getArguments() != null ? getArguments() : new Bundle();
|
||||
}
|
||||
|
||||
private void updateGroupLimit(int chipCount) {
|
||||
int members = currentSelection.size() + chipCount;
|
||||
groupLimit.setText(getResources().getQuantityString(R.plurals.ContactSelectionListFragment_d_members, members, members));
|
||||
groupLimit.setVisibility(isMulti && !hideCount ? View.VISIBLE : View.GONE);
|
||||
groupLimit.setWarning(selectionWarningLimitExceeded());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
@@ -309,6 +304,14 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
return cursorRecyclerViewAdapter.getSelectedContactsCount();
|
||||
}
|
||||
|
||||
public int getTotalMemberCount() {
|
||||
if (cursorRecyclerViewAdapter == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return cursorRecyclerViewAdapter.getSelectedContactsCount() + cursorRecyclerViewAdapter.getCurrentContactsCount();
|
||||
}
|
||||
|
||||
private Set<RecipientId> getCurrentSelection() {
|
||||
List<RecipientId> currentSelection = safeArguments().getParcelableArrayList(CURRENT_SELECTION);
|
||||
if (currentSelection == null) {
|
||||
@@ -349,8 +352,8 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
concatenateAdapter.addAdapter(footerAdapter);
|
||||
}
|
||||
|
||||
recyclerView.addItemDecoration(new LetterHeaderDecoration(requireContext(), this::hideLetterHeaders));
|
||||
recyclerView.setAdapter(concatenateAdapter);
|
||||
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true, 0));
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
@@ -361,6 +364,14 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (onContactSelectedListener != null) {
|
||||
onContactSelectedListener.onSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hideLetterHeaders() {
|
||||
return hasQueryFilter() || shouldDisplayRecents();
|
||||
}
|
||||
|
||||
private View createInviteActionView(@NonNull ListCallback listCallback) {
|
||||
@@ -429,7 +440,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
FragmentActivity activity = requireActivity();
|
||||
int displayMode = safeArguments().getInt(DISPLAY_MODE, activity.getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL));
|
||||
boolean displayRecents = safeArguments().getBoolean(RECENTS, activity.getIntent().getBooleanExtra(RECENTS, false));
|
||||
boolean displayRecents = shouldDisplayRecents();
|
||||
|
||||
if (cursorFactoryProvider != null) {
|
||||
return cursorFactoryProvider.get().create();
|
||||
@@ -475,6 +486,10 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
fastScroller.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private boolean shouldDisplayRecents() {
|
||||
return safeArguments().getBoolean(RECENTS, requireActivity().getIntent().getBooleanExtra(RECENTS, false));
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private void handleContactPermissionGranted() {
|
||||
final Context context = requireContext();
|
||||
@@ -606,12 +621,19 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
if (isMulti) {
|
||||
addChipForSelectedContact(selectedContact);
|
||||
}
|
||||
if (onContactSelectedListener != null) {
|
||||
onContactSelectedListener.onSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void markContactUnselected(@NonNull SelectedContact selectedContact) {
|
||||
cursorRecyclerViewAdapter.removeFromSelectedContacts(selectedContact);
|
||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
removeChipForContact(selectedContact);
|
||||
|
||||
if (onContactSelectedListener != null) {
|
||||
onContactSelectedListener.onSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeChipForContact(@NonNull SelectedContact contact) {
|
||||
@@ -622,8 +644,6 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
}
|
||||
}
|
||||
|
||||
updateGroupLimit(getChipCount());
|
||||
|
||||
if (getChipCount() == 0) {
|
||||
setChipGroupVisibility(ConstraintSet.GONE);
|
||||
}
|
||||
@@ -673,7 +693,6 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
|
||||
private void addChip(@NonNull ContactChip chip) {
|
||||
chipGroup.addView(chip);
|
||||
updateGroupLimit(getChipCount());
|
||||
if (selectionWarningLimitReachedExactly()) {
|
||||
if (onSelectionLimitReachedListener != null) {
|
||||
onSelectionLimitReachedListener.onSuggestedLimitReached(selectionLimit.getRecommendedLimit());
|
||||
@@ -726,6 +745,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
/** @return True if the contact is allowed to be selected, otherwise false. */
|
||||
boolean onBeforeContactSelected(Optional<RecipientId> recipientId, String number);
|
||||
void onContactDeselected(Optional<RecipientId> recipientId, String number);
|
||||
void onSelectionChanged();
|
||||
}
|
||||
|
||||
public interface OnSelectionLimitReachedListener {
|
||||
|
||||
@@ -7,8 +7,6 @@ import android.graphics.PorterDuff;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
@@ -24,8 +22,8 @@ import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
|
||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
|
||||
import org.thoughtcrime.securesms.components.ContactFilterView;
|
||||
import org.thoughtcrime.securesms.components.ContactFilterView.OnFilterChangedListener;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@@ -100,7 +98,8 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
||||
View shareButton = findViewById(R.id.share_button);
|
||||
Button smsButton = findViewById(R.id.sms_button);
|
||||
Button smsCancelButton = findViewById(R.id.cancel_sms_button);
|
||||
ContactFilterToolbar contactFilter = findViewById(R.id.contact_filter);
|
||||
Toolbar smsToolbar = findViewById(R.id.sms_send_frame_toolbar);
|
||||
ContactFilterView contactFilter = findViewById(R.id.contact_filter_edit_text);
|
||||
|
||||
inviteText = findViewById(R.id.invite_text);
|
||||
smsSendFrame = findViewById(R.id.sms_send_frame);
|
||||
@@ -121,7 +120,7 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
||||
smsCancelButton.setOnClickListener(new SmsCancelClickListener());
|
||||
smsSendButton.setOnClickListener(new SmsSendClickListener());
|
||||
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
|
||||
contactFilter.setNavigationIcon(R.drawable.ic_search_conversation_24);
|
||||
smsToolbar.setNavigationIcon(R.drawable.ic_search_conversation_24);
|
||||
|
||||
if (Util.isDefaultSmsProvider(this)) {
|
||||
shareButton.setOnClickListener(new ShareClickListener());
|
||||
@@ -150,6 +149,10 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
||||
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged() {
|
||||
}
|
||||
|
||||
private void sendSmsInvites() {
|
||||
new SendSmsInvitesAsyncTask(this, inviteText.getText().toString())
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
||||
|
||||
@@ -56,6 +56,7 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||
super.onCreate(bundle, ready);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setTitle(R.string.NewConversationActivity__new_message);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,6 +97,10 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged() {
|
||||
}
|
||||
|
||||
private void launch(Recipient recipient) {
|
||||
long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient.getId());
|
||||
Intent intent = ConversationIntents.createBuilder(this, recipient.getId(), existingThread)
|
||||
|
||||
@@ -65,4 +65,8 @@ public class PushContactSelectionActivity extends ContactSelectionActivity {
|
||||
setResult(RESULT_OK, resultIntent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.ViewSwitcher;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
@@ -18,7 +17,7 @@ import com.google.android.material.snackbar.Snackbar;
|
||||
import org.thoughtcrime.securesms.ContactSelectionListFragment;
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
||||
import org.thoughtcrime.securesms.components.ContactFilterView;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
@@ -47,27 +46,26 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
||||
|
||||
viewModel = ViewModelProviders.of(this, factory).get(BlockedUsersViewModel.class);
|
||||
|
||||
ViewSwitcher viewSwitcher = findViewById(R.id.toolbar_switcher);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
ContactFilterToolbar contactFilterToolbar = findViewById(R.id.filter_toolbar);
|
||||
View container = findViewById(R.id.fragment_container);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
ContactFilterView contactFilterView = findViewById(R.id.contact_filter_edit_text);
|
||||
View container = findViewById(R.id.fragment_container);
|
||||
|
||||
toolbar.setNavigationOnClickListener(unused -> onBackPressed());
|
||||
contactFilterToolbar.setNavigationOnClickListener(unused -> onBackPressed());
|
||||
contactFilterToolbar.setOnFilterChangedListener(query -> {
|
||||
contactFilterView.setOnFilterChangedListener(query -> {
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(CONTACT_SELECTION_FRAGMENT);
|
||||
if (fragment != null) {
|
||||
((ContactSelectionListFragment) fragment).setQueryFilter(query);
|
||||
}
|
||||
});
|
||||
contactFilterToolbar.setHint(R.string.BlockedUsersActivity__add_blocked_user);
|
||||
contactFilterView.setHint(R.string.BlockedUsersActivity__add_blocked_user);
|
||||
|
||||
//noinspection CodeBlock2Expr
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(() -> {
|
||||
viewSwitcher.setDisplayedChild(getSupportFragmentManager().getBackStackEntryCount());
|
||||
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
|
||||
contactFilterToolbar.focusAndShowKeyboard();
|
||||
contactFilterView.setVisibility(View.VISIBLE);
|
||||
contactFilterView.focusAndShowKeyboard();
|
||||
} else {
|
||||
contactFilterView.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -119,6 +117,10 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleAddUserToBlockedList() {
|
||||
ContactSelectionListFragment fragment = new ContactSelectionListFragment();
|
||||
@@ -164,6 +166,6 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
||||
throw new IllegalArgumentException("Unsupported event type " + event);
|
||||
}
|
||||
|
||||
Snackbar.make(view, getString(messageResId, displayName), Snackbar.LENGTH_SHORT).show();
|
||||
Snackbar.make(view, getString(messageResId, displayName), Snackbar.LENGTH_SHORT).setTextColor(Color.WHITE).show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.util.AttributeSet;
|
||||
import android.view.TouchDelegate;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
@@ -20,9 +21,8 @@ import androidx.core.widget.TextViewCompat;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.DarkOverflowToolbar;
|
||||
|
||||
public final class ContactFilterToolbar extends DarkOverflowToolbar {
|
||||
public final class ContactFilterView extends FrameLayout {
|
||||
private OnFilterChangedListener listener;
|
||||
|
||||
private final EditText searchText;
|
||||
@@ -32,17 +32,17 @@ public final class ContactFilterToolbar extends DarkOverflowToolbar {
|
||||
private final ImageView clearToggle;
|
||||
private final LinearLayout toggleContainer;
|
||||
|
||||
public ContactFilterToolbar(Context context) {
|
||||
public ContactFilterView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ContactFilterToolbar(Context context, AttributeSet attrs) {
|
||||
public ContactFilterView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.toolbarStyle);
|
||||
}
|
||||
|
||||
public ContactFilterToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
public ContactFilterView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
inflate(context, R.layout.contact_filter_toolbar, this);
|
||||
inflate(context, R.layout.contact_filter_view, this);
|
||||
|
||||
this.searchText = findViewById(R.id.search_view);
|
||||
this.toggle = findViewById(R.id.button_toggle);
|
||||
@@ -99,8 +99,6 @@ public final class ContactFilterToolbar extends DarkOverflowToolbar {
|
||||
}
|
||||
});
|
||||
|
||||
setLogo(null);
|
||||
setContentInsetStartWithNavigation(0);
|
||||
expandTapArea(toggleContainer, dialpadToggle);
|
||||
applyAttributes(searchText, context, attrs, defStyleAttr);
|
||||
searchText.requestFocus();
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.thoughtcrime.securesms.components.recyclerview
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
/**
|
||||
* Allows implementor to trigger an animation when the attached recyclerview is
|
||||
* scrolled.
|
||||
*/
|
||||
abstract class OnScrollAnimationHelper : RecyclerView.OnScrollListener() {
|
||||
private var lastAnimationState = AnimationState.NONE
|
||||
|
||||
protected open val duration: Long = 250L
|
||||
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
val newAnimationState = getAnimationState(recyclerView)
|
||||
|
||||
if (newAnimationState == lastAnimationState) {
|
||||
return
|
||||
}
|
||||
|
||||
if (lastAnimationState == AnimationState.NONE) {
|
||||
setImmediateState(recyclerView)
|
||||
return
|
||||
}
|
||||
|
||||
when (newAnimationState) {
|
||||
AnimationState.NONE -> throw AssertionError()
|
||||
AnimationState.HIDE -> hide(duration)
|
||||
AnimationState.SHOW -> show(duration)
|
||||
}
|
||||
|
||||
lastAnimationState = newAnimationState
|
||||
}
|
||||
|
||||
fun setImmediateState(recyclerView: RecyclerView) {
|
||||
val newAnimationState = getAnimationState(recyclerView)
|
||||
|
||||
when (newAnimationState) {
|
||||
AnimationState.NONE -> throw AssertionError()
|
||||
AnimationState.HIDE -> hide(0L)
|
||||
AnimationState.SHOW -> show(0L)
|
||||
}
|
||||
|
||||
lastAnimationState = newAnimationState
|
||||
}
|
||||
|
||||
protected open fun getAnimationState(recyclerView: RecyclerView): AnimationState {
|
||||
return if (recyclerView.canScrollVertically(-1)) AnimationState.SHOW else AnimationState.HIDE
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the RecyclerView is able to be scrolled up
|
||||
*/
|
||||
protected abstract fun show(duration: Long)
|
||||
|
||||
/**
|
||||
* Fired when the RecyclerView is not able to be scrolled up
|
||||
*/
|
||||
protected abstract fun hide(duration: Long)
|
||||
|
||||
enum class AnimationState {
|
||||
NONE,
|
||||
HIDE,
|
||||
SHOW
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.thoughtcrime.securesms.components.recyclerview
|
||||
|
||||
import android.view.View
|
||||
|
||||
/**
|
||||
* Animates in and out a given view. This is intended to be used to show and hide a toolbar shadow,
|
||||
* but makes no restrictions in this manner.
|
||||
*/
|
||||
open class ToolbarShadowAnimationHelper(private val toolbarShadow: View) : OnScrollAnimationHelper() {
|
||||
|
||||
override fun show(duration: Long) {
|
||||
toolbarShadow.animate()
|
||||
.setDuration(duration)
|
||||
.alpha(1f)
|
||||
}
|
||||
|
||||
override fun hide(duration: Long) {
|
||||
toolbarShadow.animate()
|
||||
.setDuration(duration)
|
||||
.alpha(0f)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.recyclerview.OnScrollAnimationHelper
|
||||
import org.thoughtcrime.securesms.components.recyclerview.ToolbarShadowAnimationHelper
|
||||
|
||||
abstract class DSLSettingsFragment(
|
||||
@StringRes private val titleId: Int = -1,
|
||||
@@ -66,72 +68,4 @@ abstract class DSLSettingsFragment(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class OnScrollAnimationHelper : RecyclerView.OnScrollListener() {
|
||||
private var lastAnimationState = AnimationState.NONE
|
||||
|
||||
protected open val duration: Long = 250L
|
||||
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
val newAnimationState = getAnimationState(recyclerView)
|
||||
|
||||
if (newAnimationState == lastAnimationState) {
|
||||
return
|
||||
}
|
||||
|
||||
if (lastAnimationState == AnimationState.NONE) {
|
||||
setImmediateState(recyclerView)
|
||||
return
|
||||
}
|
||||
|
||||
when (newAnimationState) {
|
||||
AnimationState.NONE -> throw AssertionError()
|
||||
AnimationState.HIDE -> hide(duration)
|
||||
AnimationState.SHOW -> show(duration)
|
||||
}
|
||||
|
||||
lastAnimationState = newAnimationState
|
||||
}
|
||||
|
||||
fun setImmediateState(recyclerView: RecyclerView) {
|
||||
val newAnimationState = getAnimationState(recyclerView)
|
||||
|
||||
when (newAnimationState) {
|
||||
AnimationState.NONE -> throw AssertionError()
|
||||
AnimationState.HIDE -> hide(0L)
|
||||
AnimationState.SHOW -> show(0L)
|
||||
}
|
||||
|
||||
lastAnimationState = newAnimationState
|
||||
}
|
||||
|
||||
protected open fun getAnimationState(recyclerView: RecyclerView): AnimationState {
|
||||
return if (recyclerView.canScrollVertically(-1)) AnimationState.SHOW else AnimationState.HIDE
|
||||
}
|
||||
|
||||
protected abstract fun show(duration: Long)
|
||||
|
||||
protected abstract fun hide(duration: Long)
|
||||
|
||||
enum class AnimationState {
|
||||
NONE,
|
||||
HIDE,
|
||||
SHOW
|
||||
}
|
||||
}
|
||||
|
||||
open class ToolbarShadowAnimationHelper(private val toolbarShadow: View) : OnScrollAnimationHelper() {
|
||||
|
||||
override fun show(duration: Long) {
|
||||
toolbarShadow.animate()
|
||||
.setDuration(duration)
|
||||
.alpha(1f)
|
||||
}
|
||||
|
||||
override fun hide(duration: Long) {
|
||||
toolbarShadow.animate()
|
||||
.setDuration(duration)
|
||||
.alpha(0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import org.thoughtcrime.securesms.PushContactSelectionActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.VerifyIdentityActivity
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||
import org.thoughtcrime.securesms.components.recyclerview.OnScrollAnimationHelper
|
||||
import org.thoughtcrime.securesms.components.recyclerview.ToolbarShadowAnimationHelper
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
|
||||
@@ -108,15 +108,31 @@ public class ContactRepository {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public Cursor querySignalContacts(@NonNull String query) {
|
||||
public @NonNull Cursor querySignalContacts(@NonNull String query) {
|
||||
return querySignalContacts(query, true);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public Cursor querySignalContacts(@NonNull String query, boolean includeSelf) {
|
||||
Cursor cursor = TextUtils.isEmpty(query) ? recipientDatabase.getSignalContacts(includeSelf)
|
||||
: recipientDatabase.querySignalContacts(query, includeSelf);
|
||||
public @NonNull Cursor querySignalContacts(@NonNull String query, boolean includeSelf) {
|
||||
Cursor cursor = TextUtils.isEmpty(query) ? recipientDatabase.getSignalContacts(includeSelf)
|
||||
: recipientDatabase.querySignalContacts(query, includeSelf);
|
||||
|
||||
cursor = handleNoteToSelfQuery(query, includeSelf, cursor);
|
||||
|
||||
return new SearchCursorWrapper(cursor, SEARCH_CURSOR_MAPPERS);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public @NonNull Cursor queryNonGroupContacts(@NonNull String query, boolean includeSelf) {
|
||||
Cursor cursor = TextUtils.isEmpty(query) ? recipientDatabase.getNonGroupContacts(includeSelf)
|
||||
: recipientDatabase.queryNonGroupContacts(query, includeSelf);
|
||||
|
||||
cursor = handleNoteToSelfQuery(query, includeSelf, cursor);
|
||||
|
||||
return new SearchCursorWrapper(cursor, SEARCH_CURSOR_MAPPERS);
|
||||
}
|
||||
|
||||
private @NonNull Cursor handleNoteToSelfQuery(@NonNull String query, boolean includeSelf, Cursor cursor) {
|
||||
if (includeSelf && noteToSelfTitle.toLowerCase().contains(query.toLowerCase())) {
|
||||
Recipient self = Recipient.self();
|
||||
boolean nameMatch = self.getDisplayName(context).toLowerCase().contains(query.toLowerCase());
|
||||
@@ -130,8 +146,7 @@ public class ContactRepository {
|
||||
cursor = cursor == null ? selfCursor : new MergeCursor(new Cursor[]{ cursor, selfCursor });
|
||||
}
|
||||
}
|
||||
|
||||
return new SearchCursorWrapper(cursor, SEARCH_CURSOR_MAPPERS);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -21,6 +21,7 @@ import android.database.Cursor;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -29,7 +30,6 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
@@ -40,12 +40,15 @@ import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.ViewHolde
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.CharacterIterable;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration.StickyHeaderAdapter;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -54,8 +57,8 @@ import java.util.Set;
|
||||
* @author Jake McGinty
|
||||
*/
|
||||
public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewHolder>
|
||||
implements FastScrollAdapter,
|
||||
StickyHeaderAdapter<HeaderViewHolder>
|
||||
implements FastScrollAdapter,
|
||||
StickyHeaderAdapter<HeaderViewHolder>
|
||||
{
|
||||
@SuppressWarnings("unused")
|
||||
private final static String TAG = Log.tag(ContactSelectionListAdapter.class);
|
||||
@@ -98,14 +101,28 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public abstract void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkboxVisible);
|
||||
public abstract void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, boolean checkboxVisible);
|
||||
|
||||
public abstract void unbind(@NonNull GlideRequests glideRequests);
|
||||
|
||||
public abstract void setChecked(boolean checked);
|
||||
|
||||
public void animateChecked(boolean checked) {
|
||||
// Intentionally empty.
|
||||
}
|
||||
|
||||
public abstract void setEnabled(boolean enabled);
|
||||
|
||||
public void setLetterHeaderCharacter(@Nullable String letterHeaderCharacter) {
|
||||
// Intentionally empty.
|
||||
}
|
||||
}
|
||||
|
||||
public static class ContactViewHolder extends ViewHolder {
|
||||
ContactViewHolder(@NonNull final View itemView,
|
||||
public static class ContactViewHolder extends ViewHolder implements LetterHeaderDecoration.LetterHeaderItem {
|
||||
|
||||
private String letterHeader;
|
||||
|
||||
ContactViewHolder(@NonNull final View itemView,
|
||||
@Nullable final ItemClickListener clickListener)
|
||||
{
|
||||
super(itemView);
|
||||
@@ -118,8 +135,8 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
return (ContactSelectionListItem) itemView;
|
||||
}
|
||||
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkBoxVisible) {
|
||||
getView().set(glideRequests, recipientId, type, name, number, label, about, color, checkBoxVisible);
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, boolean checkBoxVisible) {
|
||||
getView().set(glideRequests, recipientId, type, name, number, label, about, checkBoxVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,13 +146,28 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean checked) {
|
||||
getView().setChecked(checked);
|
||||
getView().setChecked(checked, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animateChecked(boolean checked) {
|
||||
getView().setChecked(checked, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
getView().setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getHeaderLetter() {
|
||||
return letterHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLetterHeaderCharacter(@Nullable String letterHeaderCharacter) {
|
||||
this.letterHeader = letterHeaderCharacter;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DividerViewHolder extends ViewHolder {
|
||||
@@ -148,7 +180,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkboxVisible) {
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, boolean checkboxVisible) {
|
||||
this.label.setText(name);
|
||||
}
|
||||
|
||||
@@ -168,15 +200,15 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
}
|
||||
}
|
||||
|
||||
public ContactSelectionListAdapter(@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
public ContactSelectionListAdapter(@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable Cursor cursor,
|
||||
@Nullable ItemClickListener clickListener,
|
||||
boolean multiSelect,
|
||||
@NonNull Set<RecipientId> currentContacts)
|
||||
{
|
||||
super(context, cursor);
|
||||
this.layoutInflater = LayoutInflater.from(context);
|
||||
this.layoutInflater = LayoutInflater.from(context);
|
||||
this.glideRequests = glideRequests;
|
||||
this.multiSelect = multiSelect;
|
||||
this.clickListener = clickListener;
|
||||
@@ -186,7 +218,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
@Override
|
||||
public long getHeaderId(int i) {
|
||||
if (!isActiveCursor()) return -1;
|
||||
else if (i == -1) return -1;
|
||||
else if (i == -1) return -1;
|
||||
|
||||
int contactType = getContactType(i);
|
||||
|
||||
@@ -215,15 +247,10 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
String label = CursorUtil.requireString(cursor, ContactRepository.LABEL_COLUMN);
|
||||
String labelText = ContactsContract.CommonDataKinds.Phone.getTypeLabel(getContext().getResources(),
|
||||
numberType, label).toString();
|
||||
boolean isPush = (contactType & ContactRepository.PUSH_TYPE) > 0;
|
||||
|
||||
int color = isPush ? ContextCompat.getColor(getContext(), R.color.signal_text_primary)
|
||||
: ContextCompat.getColor(getContext(), R.color.signal_inverse_transparent_60);
|
||||
|
||||
boolean currentContact = currentContacts.contains(id);
|
||||
|
||||
viewHolder.unbind(glideRequests);
|
||||
viewHolder.bind(glideRequests, id, contactType, name, number, labelText, about, color, multiSelect || currentContact);
|
||||
viewHolder.bind(glideRequests, id, contactType, name, number, labelText, about, multiSelect || currentContact);
|
||||
viewHolder.setEnabled(true);
|
||||
|
||||
if (currentContact) {
|
||||
@@ -234,6 +261,54 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
} else {
|
||||
viewHolder.setChecked(selectedContacts.contains(SelectedContact.forPhone(id, number)));
|
||||
}
|
||||
|
||||
if (isContactRow(contactType)) {
|
||||
int position = cursor.getPosition();
|
||||
if (position == 0) {
|
||||
viewHolder.setLetterHeaderCharacter(getHeaderLetterForDisplayName(cursor));
|
||||
} else {
|
||||
cursor.moveToPrevious();
|
||||
|
||||
int previousRowContactType = CursorUtil.requireInt(cursor, ContactRepository.CONTACT_TYPE_COLUMN);
|
||||
|
||||
if (!isContactRow(previousRowContactType)) {
|
||||
cursor.moveToNext();
|
||||
viewHolder.setLetterHeaderCharacter(getHeaderLetterForDisplayName(cursor));
|
||||
} else {
|
||||
String previousHeaderLetter = getHeaderLetterForDisplayName(cursor);
|
||||
cursor.moveToNext();
|
||||
String newHeaderLetter = getHeaderLetterForDisplayName(cursor);
|
||||
|
||||
if (Objects.equals(previousHeaderLetter, newHeaderLetter)) {
|
||||
viewHolder.setLetterHeaderCharacter(null);
|
||||
} else {
|
||||
viewHolder.setLetterHeaderCharacter(newHeaderLetter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isContactRow(int contactType) {
|
||||
return (contactType & (ContactRepository.NEW_PHONE_TYPE | ContactRepository.NEW_USERNAME_TYPE | ContactRepository.DIVIDER_TYPE)) == 0;
|
||||
}
|
||||
|
||||
private @Nullable String getHeaderLetterForDisplayName(@NonNull Cursor cursor) {
|
||||
String name = CursorUtil.requireString(cursor, ContactRepository.NAME_COLUMN);
|
||||
Iterator<String> characterIterator = new CharacterIterable(name).iterator();
|
||||
|
||||
if (!TextUtils.isEmpty(name) && characterIterator.hasNext()) {
|
||||
String next = characterIterator.next();
|
||||
|
||||
if (Character.isLetter(next.codePointAt(0))) {
|
||||
return next.toUpperCase();
|
||||
} else {
|
||||
return "#";
|
||||
}
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -250,12 +325,12 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
viewHolder.setEnabled(true);
|
||||
|
||||
if (currentContacts.contains(id)) {
|
||||
viewHolder.setChecked(true);
|
||||
viewHolder.animateChecked(true);
|
||||
viewHolder.setEnabled(false);
|
||||
} else if (numberType == ContactRepository.NEW_USERNAME_TYPE) {
|
||||
viewHolder.setChecked(selectedContacts.contains(SelectedContact.forUsername(id, number)));
|
||||
viewHolder.animateChecked(selectedContacts.contains(SelectedContact.forUsername(id, number)));
|
||||
} else {
|
||||
viewHolder.setChecked(selectedContacts.contains(SelectedContact.forPhone(id, number)));
|
||||
viewHolder.animateChecked(selectedContacts.contains(SelectedContact.forPhone(id, number)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +350,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
|
||||
@Override
|
||||
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position, int type) {
|
||||
((TextView)viewHolder.itemView).setText(getSpannedHeaderString(position));
|
||||
((TextView) viewHolder.itemView).setText(getSpannedHeaderString(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -301,6 +376,10 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
return selectedContacts.size();
|
||||
}
|
||||
|
||||
public int getCurrentContactsCount() {
|
||||
return currentContacts.size();
|
||||
}
|
||||
|
||||
private CharSequence getSpannedHeaderString(int position) {
|
||||
final String headerString = getHeaderString(position);
|
||||
if (isPush(position)) {
|
||||
|
||||
@@ -34,6 +34,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
private FromTextView nameView;
|
||||
private TextView labelView;
|
||||
private CheckBox checkBox;
|
||||
private View smsTag;
|
||||
|
||||
private String number;
|
||||
private String chipName;
|
||||
@@ -61,6 +62,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
this.labelView = findViewById(R.id.label);
|
||||
this.nameView = findViewById(R.id.name);
|
||||
this.checkBox = findViewById(R.id.check_box);
|
||||
this.smsTag = findViewById(R.id.sms_tag);
|
||||
|
||||
ViewUtil.setTextViewGravityStart(this.nameView, getContext());
|
||||
}
|
||||
@@ -72,7 +74,6 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
String number,
|
||||
String label,
|
||||
String about,
|
||||
int color,
|
||||
boolean checkboxVisible)
|
||||
{
|
||||
this.glideRequests = glideRequests;
|
||||
@@ -92,10 +93,15 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
name = this.recipient.get().getDisplayName(getContext());
|
||||
}
|
||||
|
||||
boolean isPush = (contactType & ContactRepository.PUSH_TYPE) > 0;
|
||||
if (isPush) {
|
||||
smsTag.setVisibility(GONE);
|
||||
} else {
|
||||
smsTag.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
Recipient recipientSnapshot = recipient != null ? recipient.get() : null;
|
||||
|
||||
this.nameView.setTextColor(color);
|
||||
this.numberView.setTextColor(color);
|
||||
if (recipientSnapshot == null || recipientSnapshot.isResolving()) {
|
||||
this.contactPhotoImage.setAvatar(glideRequests, null, false);
|
||||
setText(null, type, name, number, label, about);
|
||||
@@ -107,8 +113,20 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
this.checkBox.setVisibility(checkboxVisible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
public void setChecked(boolean selected) {
|
||||
this.checkBox.setChecked(selected);
|
||||
public void setChecked(boolean selected, boolean animate) {
|
||||
boolean wasSelected = checkBox.isChecked();
|
||||
|
||||
if (wasSelected != selected) {
|
||||
checkBox.setChecked(selected);
|
||||
|
||||
float alpha = selected ? 1f : 0f;
|
||||
if (animate) {
|
||||
checkBox.animate().setDuration(250L).alpha(alpha);
|
||||
} else {
|
||||
checkBox.animate().cancel();
|
||||
checkBox.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -146,7 +164,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
} else {
|
||||
this.numberView.setText(!Util.isEmpty(about) ? about : number);
|
||||
this.nameView.setEnabled(true);
|
||||
this.labelView.setText(label != null && !label.equals("null") ? label : "");
|
||||
this.labelView.setText(label != null && !label.equals("null") ? getResources().getString(R.string.ContactSelectionListItem__dot_s, label) : "");
|
||||
this.labelView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.contacts;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
@@ -30,7 +29,6 @@ import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
@@ -126,7 +124,9 @@ public class ContactsCursorLoader extends AbstractContactsCursorLoader {
|
||||
List<Cursor> contacts = getContactsCursors();
|
||||
|
||||
if (!isCursorListEmpty(contacts)) {
|
||||
cursorList.add(ContactsCursorRows.forContactsHeader(getContext()));
|
||||
if (!getFilter().isEmpty() || recents) {
|
||||
cursorList.add(ContactsCursorRows.forContactsHeader(getContext()));
|
||||
}
|
||||
cursorList.addAll(contacts);
|
||||
}
|
||||
}
|
||||
@@ -195,19 +195,14 @@ public class ContactsCursorLoader extends AbstractContactsCursorLoader {
|
||||
private List<Cursor> getContactsCursors() {
|
||||
List<Cursor> cursorList = new ArrayList<>(2);
|
||||
|
||||
if (!Permissions.hasAny(getContext(), Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) {
|
||||
return cursorList;
|
||||
}
|
||||
|
||||
if (pushEnabled(mode)) {
|
||||
cursorList.add(contactRepository.querySignalContacts(getFilter(), selfEnabled(mode)));
|
||||
}
|
||||
|
||||
if (pushEnabled(mode) && smsEnabled(mode)) {
|
||||
cursorList.add(contactRepository.queryNonSignalContacts(getFilter()));
|
||||
cursorList.add(contactRepository.queryNonGroupContacts(getFilter(), selfEnabled(mode)));
|
||||
} else if (pushEnabled(mode)) {
|
||||
cursorList.add(contactRepository.querySignalContacts(getFilter(), selfEnabled(mode)));
|
||||
} else if (smsEnabled(mode)) {
|
||||
cursorList.add(filterNonPushContacts(contactRepository.queryNonSignalContacts(getFilter())));
|
||||
cursorList.add(contactRepository.queryNonSignalContacts(getFilter()));
|
||||
}
|
||||
|
||||
return cursorList;
|
||||
}
|
||||
|
||||
@@ -240,25 +235,6 @@ public class ContactsCursorLoader extends AbstractContactsCursorLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull Cursor filterNonPushContacts(@NonNull Cursor cursor) {
|
||||
try {
|
||||
final long startMillis = System.currentTimeMillis();
|
||||
final MatrixCursor matrix = ContactsCursorRows.createMatrixCursor();
|
||||
while (cursor.moveToNext()) {
|
||||
final RecipientId id = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN)));
|
||||
final Recipient recipient = Recipient.resolved(id);
|
||||
|
||||
if (recipient.resolve().getRegistered() != RecipientDatabase.RegisteredState.REGISTERED) {
|
||||
matrix.addRow(ContactsCursorRows.forNonPushContact(cursor));
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "filterNonPushContacts() -> " + (System.currentTimeMillis() - startMillis) + "ms");
|
||||
return matrix;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCursorListEmpty(List<Cursor> list) {
|
||||
int sum = 0;
|
||||
for (Cursor cursor : list) {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.thoughtcrime.securesms.contacts
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Typeface
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
|
||||
/**
|
||||
* ItemDecoration which paints a letter header at the appropriate location above a LetterHeaderItem.
|
||||
*/
|
||||
class LetterHeaderDecoration(private val context: Context, private val hideDecoration: () -> Boolean) : RecyclerView.ItemDecoration() {
|
||||
|
||||
private val textBounds = Rect()
|
||||
private val bounds = Rect()
|
||||
private val padTop = ViewUtil.dpToPx(16)
|
||||
private val padStart = context.resources.getDimensionPixelSize(R.dimen.dsl_settings_gutter)
|
||||
|
||||
private var dividerHeight = -1
|
||||
|
||||
private val textPaint = Paint().apply {
|
||||
color = ContextCompat.getColor(context, R.color.signal_text_primary)
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.FILL
|
||||
typeface = Typeface.create("sans-serif-medium", Typeface.BOLD)
|
||||
textAlign = Paint.Align.LEFT
|
||||
textSize = ViewUtil.spToPx(16f).toFloat()
|
||||
}
|
||||
|
||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||
val viewHolder = parent.getChildViewHolder(view)
|
||||
if (hideDecoration() || viewHolder !is LetterHeaderItem || viewHolder.getHeaderLetter() == null) {
|
||||
outRect.set(0, 0, 0, 0)
|
||||
return
|
||||
}
|
||||
|
||||
if (dividerHeight == -1) {
|
||||
val v = LayoutInflater.from(context).inflate(R.layout.dsl_section_header, parent, false)
|
||||
v.measure(0, 0)
|
||||
dividerHeight = v.measuredHeight
|
||||
}
|
||||
outRect.set(0, dividerHeight, 0, 0)
|
||||
}
|
||||
|
||||
override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||
if (hideDecoration()) {
|
||||
return
|
||||
}
|
||||
|
||||
val childCount = parent.childCount
|
||||
val isRtl = parent.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||
|
||||
for (i in 0 until childCount) {
|
||||
val child = parent.getChildAt(i)
|
||||
val holder = parent.getChildViewHolder(child)
|
||||
val headerLetter = if (holder is LetterHeaderItem) holder.getHeaderLetter() else null
|
||||
|
||||
if (headerLetter != null) {
|
||||
parent.getDecoratedBoundsWithMargins(child, bounds)
|
||||
|
||||
textPaint.getTextBounds(headerLetter, 0, headerLetter.length, textBounds)
|
||||
|
||||
val x = if (isRtl) getLayoutBoundsRTL() else getLayoutBoundsLTR()
|
||||
val y = bounds.top + padTop - textBounds.top
|
||||
|
||||
canvas.save()
|
||||
canvas.drawText(headerLetter, x.toFloat(), y.toFloat(), textPaint)
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLayoutBoundsLTR() = bounds.left + padStart
|
||||
|
||||
private fun getLayoutBoundsRTL() = bounds.right - padStart - textBounds.width()
|
||||
|
||||
interface LetterHeaderItem {
|
||||
fun getHeaderLetter(): String?
|
||||
}
|
||||
}
|
||||
@@ -186,7 +186,7 @@ public class RecipientDatabase extends Database {
|
||||
};
|
||||
|
||||
private static final String[] ID_PROJECTION = new String[]{ID};
|
||||
private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_JOINED_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, EXTRAS, GROUPS_IN_COMMON, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_JOINED_NAME) + ", " + nullIfEmpty(SYSTEM_GIVEN_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME};
|
||||
private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_JOINED_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, EXTRAS, GROUPS_IN_COMMON, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "LOWER(COALESCE(" + nullIfEmpty(SYSTEM_JOINED_NAME) + ", " + nullIfEmpty(SYSTEM_GIVEN_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ")) AS " + SORT_NAME};
|
||||
public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_JOINED_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, EXTRAS, GROUPS_IN_COMMON, SEARCH_PROFILE_NAME, SORT_NAME};
|
||||
private static final String[] TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
|
||||
.map(columnName -> TABLE_NAME + "." + columnName)
|
||||
@@ -2392,6 +2392,50 @@ public class RecipientDatabase extends Database {
|
||||
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy);
|
||||
}
|
||||
|
||||
public @Nullable Cursor getNonGroupContacts(boolean includeSelf) {
|
||||
String selection = BLOCKED + " = ? AND " +
|
||||
GROUP_ID + " IS NULL AND " +
|
||||
"(" + SYSTEM_JOINED_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " +
|
||||
"(" + SORT_NAME + " NOT NULL OR " + USERNAME + " NOT NULL)";
|
||||
String[] args;
|
||||
|
||||
if (includeSelf) {
|
||||
args = SqlUtil.buildArgs("0", "1");
|
||||
} else {
|
||||
selection += " AND " + ID + " != ?";
|
||||
args = SqlUtil.buildArgs("0", "1", Recipient.self().getId().serialize());
|
||||
}
|
||||
|
||||
String orderBy = SORT_NAME + ", " + SYSTEM_JOINED_NAME + ", " + SEARCH_PROFILE_NAME + ", " + USERNAME + ", " + PHONE;
|
||||
|
||||
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy);
|
||||
}
|
||||
|
||||
public @Nullable Cursor queryNonGroupContacts(@NonNull String query, boolean includeSelf) {
|
||||
query = buildCaseInsensitiveGlobPattern(query);
|
||||
|
||||
String selection = BLOCKED + " = ? AND " +
|
||||
GROUP_ID + " IS NULL AND " +
|
||||
"(" + SYSTEM_JOINED_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " +
|
||||
"(" +
|
||||
PHONE + " GLOB ? OR " +
|
||||
SORT_NAME + " GLOB ? OR " +
|
||||
USERNAME + " GLOB ?" +
|
||||
")";
|
||||
String[] args;
|
||||
|
||||
if (includeSelf) {
|
||||
args = SqlUtil.buildArgs("0", "1", query, query, query);
|
||||
} else {
|
||||
selection += " AND " + ID + " != ?";
|
||||
args = SqlUtil.buildArgs("0", "1", query, query, query, Recipient.self().getId().toLong());
|
||||
}
|
||||
|
||||
String orderBy = SORT_NAME + ", " + SYSTEM_JOINED_NAME + ", " + SEARCH_PROFILE_NAME + ", " + PHONE;
|
||||
|
||||
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy);
|
||||
}
|
||||
|
||||
public @Nullable Cursor queryAllContacts(@NonNull String query) {
|
||||
query = buildCaseInsensitiveGlobPattern(query);
|
||||
|
||||
@@ -2402,7 +2446,7 @@ public class RecipientDatabase extends Database {
|
||||
PHONE + " GLOB ? OR " +
|
||||
EMAIL + " GLOB ?" +
|
||||
")";
|
||||
String[] args = new String[] { "0", query, query, query, query };
|
||||
String[] args = SqlUtil.buildArgs("0", query, query, query, query);
|
||||
|
||||
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, null);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.ContactSelectionActivity;
|
||||
import org.thoughtcrime.securesms.ContactSelectionListFragment;
|
||||
import org.thoughtcrime.securesms.PushContactSelectionActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -83,7 +82,7 @@ public class AddMembersActivity extends PushContactSelectionActivity {
|
||||
}
|
||||
|
||||
if (contactsFragment.hasQueryFilter()) {
|
||||
getToolbar().clear();
|
||||
getContactFilterView().clear();
|
||||
}
|
||||
|
||||
enableDone();
|
||||
@@ -94,7 +93,7 @@ public class AddMembersActivity extends PushContactSelectionActivity {
|
||||
@Override
|
||||
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {
|
||||
if (contactsFragment.hasQueryFilter()) {
|
||||
getToolbar().clear();
|
||||
getContactFilterView().clear();
|
||||
}
|
||||
|
||||
if (contactsFragment.getSelectedContactsCount() < 1) {
|
||||
@@ -102,6 +101,16 @@ public class AddMembersActivity extends PushContactSelectionActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged() {
|
||||
int selectedContactsCount = contactsFragment.getTotalMemberCount() + 1;
|
||||
if (selectedContactsCount == 0) {
|
||||
getToolbar().setTitle(getString(R.string.AddMembersActivity__add_members));
|
||||
} else {
|
||||
getToolbar().setTitle(getResources().getQuantityString(R.plurals.CreateGroupActivity__d_members, selectedContactsCount, selectedContactsCount));
|
||||
}
|
||||
}
|
||||
|
||||
private void enableDone() {
|
||||
done.setEnabled(true);
|
||||
done.animate().alpha(1f);
|
||||
|
||||
@@ -64,7 +64,7 @@ public final class AddToGroupsActivity extends ContactSelectionActivity {
|
||||
|
||||
next = findViewById(R.id.next);
|
||||
|
||||
getToolbar().setHint(contactsFragment.isMulti() ? R.string.AddToGroupActivity_add_to_groups : R.string.AddToGroupActivity_add_to_group);
|
||||
getContactFilterView().setHint(contactsFragment.isMulti() ? R.string.AddToGroupActivity_add_to_groups : R.string.AddToGroupActivity_add_to_group);
|
||||
|
||||
next.setVisibility(contactsFragment.isMulti() ? View.VISIBLE : View.GONE);
|
||||
|
||||
@@ -134,7 +134,7 @@ public final class AddToGroupsActivity extends ContactSelectionActivity {
|
||||
@Override
|
||||
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {
|
||||
if (contactsFragment.hasQueryFilter()) {
|
||||
getToolbar().clear();
|
||||
getContactFilterView().clear();
|
||||
}
|
||||
|
||||
if (contactsFragment.getSelectedContactsCount() < MINIMUM_GROUP_SELECT_SIZE) {
|
||||
@@ -142,6 +142,10 @@ public final class AddToGroupsActivity extends ContactSelectionActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged() {
|
||||
}
|
||||
|
||||
private void enableNext() {
|
||||
next.setEnabled(true);
|
||||
next.animate().alpha(1f);
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupsV2CapabilityChecker;
|
||||
import org.thoughtcrime.securesms.groups.ui.creategroup.details.AddGroupDetailsActivity;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
@@ -100,7 +99,7 @@ public class CreateGroupActivity extends ContactSelectionActivity {
|
||||
@Override
|
||||
public boolean onBeforeContactSelected(Optional<RecipientId> recipientId, String number) {
|
||||
if (contactsFragment.hasQueryFilter()) {
|
||||
getToolbar().clear();
|
||||
getContactFilterView().clear();
|
||||
}
|
||||
|
||||
shrinkSkip();
|
||||
@@ -111,7 +110,7 @@ public class CreateGroupActivity extends ContactSelectionActivity {
|
||||
@Override
|
||||
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {
|
||||
if (contactsFragment.hasQueryFilter()) {
|
||||
getToolbar().clear();
|
||||
getContactFilterView().clear();
|
||||
}
|
||||
|
||||
if (contactsFragment.getSelectedContactsCount() == 0) {
|
||||
@@ -119,6 +118,16 @@ public class CreateGroupActivity extends ContactSelectionActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged() {
|
||||
int selectedContactsCount = contactsFragment.getTotalMemberCount();
|
||||
if (selectedContactsCount == 0) {
|
||||
getToolbar().setTitle(getString(R.string.CreateGroupActivity__select_members));
|
||||
} else {
|
||||
getToolbar().setTitle(getResources().getQuantityString(R.plurals.CreateGroupActivity__d_members, selectedContactsCount, selectedContactsCount));
|
||||
}
|
||||
}
|
||||
|
||||
private void extendSkip() {
|
||||
next.setIconGravity(MaterialButton.ICON_GRAVITY_END);
|
||||
next.extend();
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.navigation.Navigation;
|
||||
@@ -12,7 +13,7 @@ import androidx.navigation.Navigation;
|
||||
import org.thoughtcrime.securesms.ContactSelectionListFragment;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
||||
import org.thoughtcrime.securesms.components.ContactFilterView;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@@ -27,7 +28,8 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
public class PaymentRecipientSelectionFragment extends LoggingFragment implements ContactSelectionListFragment.OnContactSelectedListener, ContactSelectionListFragment.ScrollCallback {
|
||||
|
||||
private ContactFilterToolbar toolbar;
|
||||
private Toolbar toolbar;
|
||||
private ContactFilterView contactFilterView;
|
||||
private ContactSelectionListFragment contactsFragment;
|
||||
|
||||
public PaymentRecipientSelectionFragment() {
|
||||
@@ -39,6 +41,8 @@ public class PaymentRecipientSelectionFragment extends LoggingFragment implement
|
||||
toolbar = view.findViewById(R.id.payment_recipient_selection_fragment_toolbar);
|
||||
toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(v).popBackStack());
|
||||
|
||||
contactFilterView = view.findViewById(R.id.contact_filter_edit_text);
|
||||
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putBoolean(ContactSelectionListFragment.REFRESHABLE, false);
|
||||
arguments.putInt(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_PUSH | DisplayMode.FLAG_HIDE_NEW);
|
||||
@@ -59,7 +63,7 @@ public class PaymentRecipientSelectionFragment extends LoggingFragment implement
|
||||
}
|
||||
|
||||
private void initializeSearch() {
|
||||
toolbar.setOnFilterChangedListener(filter -> contactsFragment.setQueryFilter(filter));
|
||||
contactFilterView.setOnFilterChangedListener(filter -> contactsFragment.setQueryFilter(filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,6 +80,10 @@ public class PaymentRecipientSelectionFragment extends LoggingFragment implement
|
||||
@Override
|
||||
public void onContactDeselected(@NonNull Optional<RecipientId> recipientId, @Nullable String number) { }
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeginScroll() {
|
||||
hideKeyboard();
|
||||
|
||||
@@ -190,6 +190,10 @@ public class ShareActivity extends PassphraseRequiredActivity
|
||||
viewModel.onContactDeselected(new ShareContact(recipientId, number));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged() {
|
||||
}
|
||||
|
||||
private void animateInSelection() {
|
||||
contactsRecyclerDivider.animate()
|
||||
.alpha(1f)
|
||||
|
||||
@@ -59,6 +59,13 @@ public class RecyclerViewConcatenateAdapter extends RecyclerView.Adapter<Recycle
|
||||
mergeAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
|
||||
int subAdapterOffset = mergeAdapter.getSubAdapterFirstGlobalPosition(adapter);
|
||||
|
||||
mergeAdapter.notifyItemRangeChanged(subAdapterOffset + positionStart, itemCount, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount) {
|
||||
int subAdapterOffset = mergeAdapter.getSubAdapterFirstGlobalPosition(adapter);
|
||||
@@ -248,6 +255,14 @@ public class RecyclerViewConcatenateAdapter extends RecyclerView.Adapter<Recycle
|
||||
throw new AssertionError("Adapter not found in list of adapters");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
|
||||
ChildAdapterPositionPair childAdapterPositionPair = getLocalPosition(position);
|
||||
RecyclerView.Adapter adapter = childAdapterPositionPair.getAdapter();
|
||||
//noinspection unchecked
|
||||
adapter.onBindViewHolder(holder, childAdapterPositionPair.localPosition, payloads);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
|
||||
ChildAdapterPositionPair childAdapterPositionPair = getLocalPosition(position);
|
||||
|
||||
Reference in New Issue
Block a user