diff --git a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionActivity.java b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionActivity.java index d2a8018c1..d250a9f91 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionActivity.java @@ -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(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java index 0ee0f3c51..60cef53c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java @@ -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 getCurrentSelection() { List 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 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, String number); void onContactDeselected(Optional recipientId, String number); + void onSelectionChanged(); } public interface OnSelectionLimitReachedListener { diff --git a/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java b/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java index 23da2180e..061ebf574 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java @@ -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, diff --git a/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java index 435c0e9dc..dec317095 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/PushContactSelectionActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PushContactSelectionActivity.java index 6703b232f..6d724cb00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/PushContactSelectionActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/PushContactSelectionActivity.java @@ -65,4 +65,8 @@ public class PushContactSelectionActivity extends ContactSelectionActivity { setResult(RESULT_OK, resultIntent); finish(); } + + @Override + public void onSelectionChanged() { + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersActivity.java b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersActivity.java index 9f8bd6e1c..1f97b5837 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersActivity.java @@ -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(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterToolbar.java b/app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterView.java similarity index 92% rename from app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterToolbar.java rename to app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterView.java index d68058ae8..92b22a9b6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterToolbar.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterView.java @@ -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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/recyclerview/OnScrollAnimationHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/components/recyclerview/OnScrollAnimationHelper.kt new file mode 100644 index 000000000..95cc06df4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/recyclerview/OnScrollAnimationHelper.kt @@ -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 + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/recyclerview/ToolbarShadowAnimationHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/components/recyclerview/ToolbarShadowAnimationHelper.kt new file mode 100644 index 000000000..0c3751486 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/recyclerview/ToolbarShadowAnimationHelper.kt @@ -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) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsFragment.kt index c3943c7ff..82d08b0ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsFragment.kt @@ -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) - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt index 83390906e..eaa96ae09 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactRepository.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactRepository.java index c623db212..792e998c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactRepository.java @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java index be38fdd9e..34672ebbc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java @@ -1,16 +1,16 @@ /** * Copyright (C) 2014 Open Whisper Systems - * + *

* 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. - * + *

* 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. - * + *

* You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -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 - implements FastScrollAdapter, - StickyHeaderAdapter + implements FastScrollAdapter, + StickyHeaderAdapter { @SuppressWarnings("unused") private final static String TAG = Log.tag(ContactSelectionListAdapter.class); @@ -98,14 +101,28 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter 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 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 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 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); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java index 6453ecad8..d847167dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java @@ -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 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 getContactsCursors() { List 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 list) { int sum = 0; for (Cursor cursor : list) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/LetterHeaderDecoration.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/LetterHeaderDecoration.kt new file mode 100644 index 000000000..16bf2fddc --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/LetterHeaderDecoration.kt @@ -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? + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 3ba759a34..86074e2ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -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); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.java index a2cd4df2c..de367b248 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.java @@ -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, 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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java index 7c899f03e..902d0f827 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java @@ -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, 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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java index def96f8e8..c07ecc278 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java @@ -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, 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, 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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentRecipientSelectionFragment.java b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentRecipientSelectionFragment.java index af3ef8361..b6ef335c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentRecipientSelectionFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentRecipientSelectionFragment.java @@ -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, @Nullable String number) { } + @Override + public void onSelectionChanged() { + } + @Override public void onBeginScroll() { hideKeyboard(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareActivity.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareActivity.java index ddbb56471..484751c91 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareActivity.java @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/adapter/RecyclerViewConcatenateAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/util/adapter/RecyclerViewConcatenateAdapter.java index 0471e77c9..c1c22a752 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/adapter/RecyclerViewConcatenateAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/adapter/RecyclerViewConcatenateAdapter.java @@ -59,6 +59,13 @@ public class RecyclerViewConcatenateAdapter extends RecyclerView.Adapter 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); diff --git a/app/src/main/res/drawable-night/contact_selection_checkbox.xml b/app/src/main/res/drawable-night/contact_selection_checkbox.xml index 2f2b7f548..9971813ea 100644 --- a/app/src/main/res/drawable-night/contact_selection_checkbox.xml +++ b/app/src/main/res/drawable-night/contact_selection_checkbox.xml @@ -5,7 +5,7 @@ - + @@ -16,7 +16,7 @@ - + diff --git a/app/src/main/res/drawable-night/ic_invite_circle.xml b/app/src/main/res/drawable-night/ic_invite_circle.xml index 7ad05907a..1e46e333d 100644 --- a/app/src/main/res/drawable-night/ic_invite_circle.xml +++ b/app/src/main/res/drawable-night/ic_invite_circle.xml @@ -7,7 +7,7 @@ diff --git a/app/src/main/res/drawable-night/ic_new_group_circle.xml b/app/src/main/res/drawable-night/ic_new_group_circle.xml index e15266121..8a5ff3c32 100644 --- a/app/src/main/res/drawable-night/ic_new_group_circle.xml +++ b/app/src/main/res/drawable-night/ic_new_group_circle.xml @@ -7,7 +7,7 @@ diff --git a/app/src/main/res/drawable/contact_selection_checkbox.xml b/app/src/main/res/drawable/contact_selection_checkbox.xml index 9a87f5d8f..0345d593d 100644 --- a/app/src/main/res/drawable/contact_selection_checkbox.xml +++ b/app/src/main/res/drawable/contact_selection_checkbox.xml @@ -5,7 +5,7 @@ - + @@ -16,7 +16,7 @@ - + diff --git a/app/src/main/res/drawable/contact_selection_scrollbar_thumb.xml b/app/src/main/res/drawable/contact_selection_scrollbar_thumb.xml new file mode 100644 index 000000000..3f3ed84a8 --- /dev/null +++ b/app/src/main/res/drawable/contact_selection_scrollbar_thumb.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_group_outline_ultramarine_28.xml b/app/src/main/res/drawable/ic_group_outline_new_28.xml similarity index 93% rename from app/src/main/res/drawable/ic_group_outline_ultramarine_28.xml rename to app/src/main/res/drawable/ic_group_outline_new_28.xml index 0b5cdec3d..999b04b1c 100644 --- a/app/src/main/res/drawable/ic_group_outline_ultramarine_28.xml +++ b/app/src/main/res/drawable/ic_group_outline_new_28.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/ic_invite_circle.xml b/app/src/main/res/drawable/ic_invite_circle.xml index d29426cf9..ff7c2446a 100644 --- a/app/src/main/res/drawable/ic_invite_circle.xml +++ b/app/src/main/res/drawable/ic_invite_circle.xml @@ -7,7 +7,7 @@ diff --git a/app/src/main/res/drawable/ic_invite_ultramarine_28.xml b/app/src/main/res/drawable/ic_invite_inverse_28.xml similarity index 82% rename from app/src/main/res/drawable/ic_invite_ultramarine_28.xml rename to app/src/main/res/drawable/ic_invite_inverse_28.xml index d37fce441..7bc4e11b7 100644 --- a/app/src/main/res/drawable/ic_invite_ultramarine_28.xml +++ b/app/src/main/res/drawable/ic_invite_inverse_28.xml @@ -4,19 +4,19 @@ android:viewportWidth="28" android:viewportHeight="28"> diff --git a/app/src/main/res/drawable/ic_new_group_circle.xml b/app/src/main/res/drawable/ic_new_group_circle.xml index a55c57956..3d603acb5 100644 --- a/app/src/main/res/drawable/ic_new_group_circle.xml +++ b/app/src/main/res/drawable/ic_new_group_circle.xml @@ -6,9 +6,9 @@ + android:bottom="12dp" + android:drawable="@drawable/ic_group_outline_new_28" + android:left="12dp" + android:right="12dp" + android:top="12dp" /> diff --git a/app/src/main/res/drawable/recycler_view_fast_scroller_bubble.xml b/app/src/main/res/drawable/recycler_view_fast_scroller_bubble.xml index f135c5c6d..b98063f05 100644 --- a/app/src/main/res/drawable/recycler_view_fast_scroller_bubble.xml +++ b/app/src/main/res/drawable/recycler_view_fast_scroller_bubble.xml @@ -4,6 +4,6 @@ android:topRightRadius="44dp" android:bottomLeftRadius="44dp" android:bottomRightRadius="0dp"/> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/recycler_view_fast_scroller_handle.xml b/app/src/main/res/drawable/recycler_view_fast_scroller_handle.xml index fa2ca8a58..6030d05d7 100644 --- a/app/src/main/res/drawable/recycler_view_fast_scroller_handle.xml +++ b/app/src/main/res/drawable/recycler_view_fast_scroller_handle.xml @@ -3,14 +3,14 @@ - + - + diff --git a/app/src/main/res/drawable/rounded_rectangle_secondary.xml b/app/src/main/res/drawable/rounded_rectangle_secondary.xml new file mode 100644 index 000000000..38a0cfeb9 --- /dev/null +++ b/app/src/main/res/drawable/rounded_rectangle_secondary.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/search_cursor.xml b/app/src/main/res/drawable/search_cursor.xml new file mode 100644 index 000000000..07527610f --- /dev/null +++ b/app/src/main/res/drawable/search_cursor.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/add_members_activity.xml b/app/src/main/res/layout/add_members_activity.xml index 1235d3554..59bdfa210 100644 --- a/app/src/main/res/layout/add_members_activity.xml +++ b/app/src/main/res/layout/add_members_activity.xml @@ -10,15 +10,27 @@ android:layout_gravity="center" android:orientation="vertical"> - + android:layout_height="56dp" + android:theme="?attr/settingsToolbarStyle" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:navigationIcon="@drawable/ic_arrow_left_24" + app:titleTextAppearance="@style/Signal.Text.Title" + app:title="@string/AddMembersActivity__add_members" /> + + - + app:title="@string/AddToGroupActivity_add_to_a_group" + app:titleTextAppearance="@style/Signal.Text.Title" /> + + + app:layout_constraintTop_toBottomOf="@id/contact_filter_edit_text" /> - + + - - - - - - + android:layout_marginLeft="@dimen/dsl_settings_gutter" + android:layout_marginTop="4dp" + android:layout_marginRight="@dimen/dsl_settings_gutter" + android:layout_marginBottom="12dp" + android:minHeight="44dp" + android:visibility="gone" + tools:visibility="visible" /> - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/contact_filter_view.xml b/app/src/main/res/layout/contact_filter_view.xml new file mode 100644 index 000000000..b92daec93 --- /dev/null +++ b/app/src/main/res/layout/contact_filter_view.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/contact_selection_activity.xml b/app/src/main/res/layout/contact_selection_activity.xml index ad2067bca..437a62782 100644 --- a/app/src/main/res/layout/contact_selection_activity.xml +++ b/app/src/main/res/layout/contact_selection_activity.xml @@ -1,24 +1,38 @@ - + - + - + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/contact_selection_invite_action_item.xml b/app/src/main/res/layout/contact_selection_invite_action_item.xml index ef57e20b5..df13359b0 100644 --- a/app/src/main/res/layout/contact_selection_invite_action_item.xml +++ b/app/src/main/res/layout/contact_selection_invite_action_item.xml @@ -1,18 +1,34 @@ - + android:layout_height="wrap_content" + android:minHeight="@dimen/contact_selection_item_height" + android:paddingStart="@dimen/dsl_settings_gutter" + android:paddingEnd="@dimen/dsl_settings_gutter"> + + + + + diff --git a/app/src/main/res/layout/contact_selection_list_divider.xml b/app/src/main/res/layout/contact_selection_list_divider.xml index d13e8e4db..58292f4d9 100644 --- a/app/src/main/res/layout/contact_selection_list_divider.xml +++ b/app/src/main/res/layout/contact_selection_list_divider.xml @@ -1,14 +1,14 @@ - + android:minHeight="48dp" + android:paddingStart="@dimen/dsl_settings_gutter" + android:paddingTop="16dp" + android:paddingEnd="@dimen/dsl_settings_gutter" + android:paddingBottom="12dp" + android:textAppearance="@style/TextAppearance.Signal.Body1.Bold" + android:textStyle="bold" + tools:text="@string/CameraContacts_recent_contacts" /> diff --git a/app/src/main/res/layout/contact_selection_list_fragment.xml b/app/src/main/res/layout/contact_selection_list_fragment.xml index 97a9bc4c5..4756a8965 100644 --- a/app/src/main/res/layout/contact_selection_list_fragment.xml +++ b/app/src/main/res/layout/contact_selection_list_fragment.xml @@ -21,6 +21,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" + android:scrollbarThumbVertical="@drawable/contact_selection_scrollbar_thumb" android:scrollbars="vertical" tools:listitem="@layout/contact_selection_list_item" /> @@ -110,53 +111,54 @@ - - - + android:layout_height="match_parent"> - + android:layout_gravity="center_vertical" + android:layout_marginEnd="96dp" + android:animateLayoutChanges="true" + app:singleLine="true"> - + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/contact_selection_list_item.xml b/app/src/main/res/layout/contact_selection_list_item.xml index 38ba67b76..e8445c655 100644 --- a/app/src/main/res/layout/contact_selection_list_item.xml +++ b/app/src/main/res/layout/contact_selection_list_item.xml @@ -1,93 +1,107 @@ + android:focusable="true" + android:gravity="center_vertical" + android:minHeight="@dimen/contact_selection_item_height" + android:orientation="horizontal" + android:paddingStart="@dimen/dsl_settings_gutter" + android:paddingEnd="@dimen/dsl_settings_gutter"> + android:layout_width="44dp" + android:layout_height="44dp" + android:layout_marginTop="2dp"> + android:cropToPadding="true" + android:foreground="@drawable/contact_photo_background" + tools:ignore="UnusedAttribute" + tools:src="@color/blue_600" /> + android:background="@drawable/contact_selection_checkbox" + android:button="@null" + android:alpha="0" + android:clickable="false" + android:focusable="false" /> - + + android:id="@+id/name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:checkMark="?android:attr/listChoiceIndicatorMultiple" + android:ellipsize="marquee" + android:singleLine="true" + android:textAppearance="@style/TextAppearance.Signal.Body1" + android:textColor="@color/signal_text_primary" + tools:text="@sample/contacts.json/data/name" /> - + + android:id="@+id/number" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="marquee" + android:singleLine="true" + android:textAppearance="@style/TextAppearance.Signal.Body2" + android:textColor="@color/signal_text_secondary" + android:textDirection="ltr" + app:emoji_forceCustom="true" + tools:text="@sample/contacts.json/data/number" /> - + + + diff --git a/app/src/main/res/layout/contact_selection_new_group_item.xml b/app/src/main/res/layout/contact_selection_new_group_item.xml index c1d67b6ef..23ca55869 100644 --- a/app/src/main/res/layout/contact_selection_new_group_item.xml +++ b/app/src/main/res/layout/contact_selection_new_group_item.xml @@ -1,18 +1,34 @@ - + android:layout_height="wrap_content" + android:minHeight="@dimen/contact_selection_item_height" + android:paddingStart="@dimen/dsl_settings_gutter" + android:paddingEnd="@dimen/dsl_settings_gutter"> + + + + + diff --git a/app/src/main/res/layout/create_group_activity.xml b/app/src/main/res/layout/create_group_activity.xml index 510900c5a..e077bc5b7 100644 --- a/app/src/main/res/layout/create_group_activity.xml +++ b/app/src/main/res/layout/create_group_activity.xml @@ -6,26 +6,38 @@ android:layout_gravity="center" android:orientation="vertical"> - + app:navigationIcon="@drawable/ic_arrow_left_24" + app:titleTextAppearance="@style/Signal.Text.Title" + app:title="@string/CreateGroupActivity__select_members" /> + + + app:layout_constraintTop_toBottomOf="@id/contact_filter_edit_text" /> - + android:layout_height="56dp" + android:theme="?attr/settingsToolbarStyle" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:navigationIcon="@drawable/ic_arrow_left_24" + app:title="@string/InviteActivity_invite_to_signal" + app:titleTextAppearance="@style/Signal.Text.Title" /> + + - + android:layout_height="56dp" + android:theme="?attr/settingsToolbarStyle" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:navigationIcon="@drawable/ic_arrow_left_24" + app:titleTextAppearance="@style/Signal.Text.Title" + app:title="@string/PaymentRecipientSelectionFragment__new_payment" /> + + + Some contacts cannot be in legacy groups. + Select members Profile @@ -513,6 +514,7 @@ Add to groups This person can\'t be added to legacy groups. Add + Add to a group Choose new admin @@ -668,6 +670,7 @@ Add %3$d members to \"%2$s\"? Add + Add members Name this group @@ -3240,6 +3243,10 @@ Skip + + %1$d member + %1$d members + Share @@ -3608,9 +3615,22 @@ 1x 2x + + New payment + + + New message + + + Search name or number + %1$s · %2$s + + SMS + · %1$s +