Reimplement contact chips with a recyclerview.
This commit is contained in:
parent
4215b0391d
commit
7bd34d2b99
6 changed files with 191 additions and 118 deletions
|
@ -18,7 +18,6 @@ package org.thoughtcrime.securesms;
|
|||
|
||||
|
||||
import android.Manifest;
|
||||
import android.animation.LayoutTransition;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
@ -32,7 +31,6 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -43,6 +41,7 @@ import androidx.appcompat.app.AlertDialog;
|
|||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.constraintlayout.widget.ConstraintSet;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
|
@ -54,7 +53,6 @@ import androidx.transition.TransitionManager;
|
|||
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.android.material.chip.ChipGroup;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.pnikosis.materialishprogress.ProgressWheel;
|
||||
|
||||
|
@ -62,7 +60,7 @@ import org.signal.core.util.concurrent.SimpleTask;
|
|||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
|
||||
import org.thoughtcrime.securesms.contacts.AbstractContactsCursorLoader;
|
||||
import org.thoughtcrime.securesms.contacts.ContactChip;
|
||||
import org.thoughtcrime.securesms.contacts.ContactChipViewModel;
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||
|
@ -70,6 +68,7 @@ import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
|||
import org.thoughtcrime.securesms.contacts.HeaderAction;
|
||||
import org.thoughtcrime.securesms.contacts.LetterHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||
import org.thoughtcrime.securesms.contacts.SelectedContacts;
|
||||
import org.thoughtcrime.securesms.contacts.selection.ContactSelectionArguments;
|
||||
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
|
@ -77,15 +76,17 @@ import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
|
|||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
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.sharing.ShareContact;
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter;
|
||||
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader;
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter;
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModelList;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -95,6 +96,10 @@ import java.util.Optional;
|
|||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import kotlin.Unit;
|
||||
|
||||
/**
|
||||
* Fragment for selecting a one or more contacts from a list.
|
||||
*
|
||||
|
@ -106,7 +111,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||
@SuppressWarnings("unused")
|
||||
private static final String TAG = Log.tag(ContactSelectionListFragment.class);
|
||||
|
||||
private static final int CHIP_GROUP_EMPTY_CHILD_COUNT = 1;
|
||||
private static final int CHIP_GROUP_EMPTY_CHILD_COUNT = 0;
|
||||
private static final int CHIP_GROUP_REVEAL_DURATION_MS = 150;
|
||||
|
||||
public static final int NO_LIMIT = Integer.MAX_VALUE;
|
||||
|
@ -134,10 +139,12 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||
private RecyclerView recyclerView;
|
||||
private RecyclerViewFastScroller fastScroller;
|
||||
private ContactSelectionListAdapter cursorRecyclerViewAdapter;
|
||||
private ChipGroup chipGroup;
|
||||
private HorizontalScrollView chipGroupScrollContainer;
|
||||
private RecyclerView chipRecycler;
|
||||
private OnSelectionLimitReachedListener onSelectionLimitReachedListener;
|
||||
private AbstractContactsCursorLoaderFactoryProvider cursorFactoryProvider;
|
||||
private MappingAdapter contactChipAdapter;
|
||||
private ContactChipViewModel contactChipViewModel;
|
||||
private LifecycleDisposable lifecycleDisposable;
|
||||
|
||||
private HeaderActionProvider headerActionProvider;
|
||||
private TextView headerActionView;
|
||||
|
@ -249,8 +256,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||
showContactsButton = view.findViewById(R.id.show_contacts_button);
|
||||
showContactsDescription = view.findViewById(R.id.show_contacts_description);
|
||||
showContactsProgress = view.findViewById(R.id.progress);
|
||||
chipGroup = view.findViewById(R.id.chipGroup);
|
||||
chipGroupScrollContainer = view.findViewById(R.id.chipGroupScrollContainer);
|
||||
chipRecycler = view.findViewById(R.id.chipRecycler);
|
||||
constraintLayout = view.findViewById(R.id.container);
|
||||
headerActionView = view.findViewById(R.id.header_action);
|
||||
|
||||
|
@ -264,6 +270,18 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||
}
|
||||
});
|
||||
|
||||
contactChipViewModel = new ViewModelProvider(this).get(ContactChipViewModel.class);
|
||||
contactChipAdapter = new MappingAdapter();
|
||||
lifecycleDisposable = new LifecycleDisposable();
|
||||
|
||||
lifecycleDisposable.bindTo(getViewLifecycleOwner());
|
||||
SelectedContacts.register(contactChipAdapter, this::onChipCloseIconClicked);
|
||||
chipRecycler.setAdapter(contactChipAdapter);
|
||||
|
||||
Disposable disposable = contactChipViewModel.getState().subscribe(this::handleSelectedContactsChanged);
|
||||
|
||||
lifecycleDisposable.add(disposable);
|
||||
|
||||
Intent intent = requireActivity().getIntent();
|
||||
Bundle arguments = safeArguments();
|
||||
|
||||
|
@ -730,75 +748,22 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||
private void markContactUnselected(@NonNull SelectedContact selectedContact) {
|
||||
cursorRecyclerViewAdapter.removeFromSelectedContacts(selectedContact);
|
||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
removeChipForContact(selectedContact);
|
||||
contactChipViewModel.remove(selectedContact);
|
||||
|
||||
if (onContactSelectedListener != null) {
|
||||
onContactSelectedListener.onSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeChipForContact(@NonNull SelectedContact contact) {
|
||||
for (int i = chipGroup.getChildCount() - 1; i >= 0; i--) {
|
||||
View v = chipGroup.getChildAt(i);
|
||||
if (v instanceof ContactChip && contact.matches(((ContactChip) v).getContact())) {
|
||||
chipGroup.removeView(v);
|
||||
}
|
||||
}
|
||||
private void handleSelectedContactsChanged(@NonNull List<SelectedContacts.Model> selectedContacts) {
|
||||
contactChipAdapter.submitList(new MappingModelList(selectedContacts), this::smoothScrollChipsToEnd);
|
||||
|
||||
if (getChipCount() == 0) {
|
||||
if (selectedContacts.isEmpty()) {
|
||||
setChipGroupVisibility(ConstraintSet.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void addChipForSelectedContact(@NonNull SelectedContact selectedContact) {
|
||||
SimpleTask.run(getViewLifecycleOwner().getLifecycle(),
|
||||
() -> Recipient.resolved(selectedContact.getOrCreateRecipientId(requireContext())),
|
||||
resolved -> addChipForRecipient(resolved, selectedContact));
|
||||
}
|
||||
|
||||
private void addChipForRecipient(@NonNull Recipient recipient, @NonNull SelectedContact selectedContact) {
|
||||
final ContactChip chip = new ContactChip(requireContext());
|
||||
|
||||
if (getChipCount() == 0) {
|
||||
} else {
|
||||
setChipGroupVisibility(ConstraintSet.VISIBLE);
|
||||
}
|
||||
|
||||
chip.setText(recipient.getShortDisplayName(requireContext()));
|
||||
chip.setContact(selectedContact);
|
||||
chip.setCloseIconVisible(true);
|
||||
chip.setOnCloseIconClickListener(view -> {
|
||||
markContactUnselected(selectedContact);
|
||||
|
||||
if (onContactSelectedListener != null) {
|
||||
onContactSelectedListener.onContactDeselected(Optional.of(recipient.getId()), recipient.getE164().orElse(null));
|
||||
}
|
||||
});
|
||||
|
||||
chipGroup.getLayoutTransition().addTransitionListener(new LayoutTransition.TransitionListener() {
|
||||
@Override
|
||||
public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
|
||||
if (getView() == null || !requireView().isAttachedToWindow()) {
|
||||
Log.w(TAG, "Fragment's view was detached before the animation completed.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (view == chip && transitionType == LayoutTransition.APPEARING) {
|
||||
chipGroup.getLayoutTransition().removeTransitionListener(this);
|
||||
registerChipRecipientObserver(chip, recipient.live());
|
||||
chipGroup.post(ContactSelectionListFragment.this::smoothScrollChipsToEnd);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chip.setAvatar(glideRequests, recipient, () -> addChip(chip));
|
||||
}
|
||||
|
||||
private void addChip(@NonNull ContactChip chip) {
|
||||
chipGroup.addView(chip);
|
||||
if (selectionWarningLimitReachedExactly()) {
|
||||
if (onSelectionLimitReachedListener != null) {
|
||||
onSelectionLimitReachedListener.onSuggestedLimitReached(selectionLimit.getRecommendedLimit());
|
||||
|
@ -808,21 +773,25 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||
}
|
||||
}
|
||||
|
||||
private int getChipCount() {
|
||||
int count = chipGroup.getChildCount() - CHIP_GROUP_EMPTY_CHILD_COUNT;
|
||||
if (count < 0) throw new AssertionError();
|
||||
return count;
|
||||
private void addChipForSelectedContact(@NonNull SelectedContact selectedContact) {
|
||||
SimpleTask.run(getViewLifecycleOwner().getLifecycle(),
|
||||
() -> Recipient.resolved(selectedContact.getOrCreateRecipientId(requireContext())),
|
||||
resolved -> contactChipViewModel.add(selectedContact));
|
||||
}
|
||||
|
||||
private void registerChipRecipientObserver(@NonNull ContactChip chip, @Nullable LiveRecipient recipient) {
|
||||
if (recipient != null) {
|
||||
recipient.observe(getViewLifecycleOwner(), resolved -> {
|
||||
if (chip.isAttachedToWindow()) {
|
||||
chip.setAvatar(glideRequests, resolved, null);
|
||||
chip.setText(resolved.getShortDisplayName(chip.getContext()));
|
||||
private Unit onChipCloseIconClicked(SelectedContacts.Model model) {
|
||||
markContactUnselected(model.getSelectedContact());
|
||||
if (onContactSelectedListener != null) {
|
||||
onContactSelectedListener.onContactDeselected(Optional.of(model.getRecipient().getId()), model.getRecipient().getE164().orElse(null));
|
||||
}
|
||||
});
|
||||
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
private int getChipCount() {
|
||||
int count = contactChipViewModel.getCount() - CHIP_GROUP_EMPTY_CHILD_COUNT;
|
||||
if (count < 0) throw new AssertionError();
|
||||
return count;
|
||||
}
|
||||
|
||||
private void setChipGroupVisibility(int visibility) {
|
||||
|
@ -838,7 +807,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
constraintSet.clone(constraintLayout);
|
||||
constraintSet.setVisibility(R.id.chipGroupScrollContainer, visibility);
|
||||
constraintSet.setVisibility(R.id.chipRecycler, visibility);
|
||||
constraintSet.applyTo(constraintLayout);
|
||||
}
|
||||
|
||||
|
@ -847,8 +816,8 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||
}
|
||||
|
||||
private void smoothScrollChipsToEnd() {
|
||||
int x = ViewUtil.isLtr(chipGroupScrollContainer) ? chipGroup.getWidth() : 0;
|
||||
chipGroupScrollContainer.smoothScrollTo(x, 0);
|
||||
int x = ViewUtil.isLtr(chipRecycler) ? chipRecycler.getWidth() : 0;
|
||||
chipRecycler.smoothScrollBy(x, 0);
|
||||
}
|
||||
|
||||
public interface OnContactSelectedListener {
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package org.thoughtcrime.securesms.contacts
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.BackpressureStrategy
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
|
||||
/**
|
||||
* ViewModel expressly for displaying the current state of the contact chips
|
||||
* in the contact selection fragment.
|
||||
*/
|
||||
class ContactChipViewModel : ViewModel() {
|
||||
|
||||
private val store = RxStore(emptyList<SelectedContacts.Model>())
|
||||
|
||||
val state: Flowable<List<SelectedContacts.Model>> = store.stateFlowable
|
||||
.distinctUntilChanged()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
val count = store.state.size
|
||||
|
||||
private val disposables = CompositeDisposable()
|
||||
private val disposableMap: MutableMap<RecipientId, Disposable> = mutableMapOf()
|
||||
|
||||
override fun onCleared() {
|
||||
disposables.clear()
|
||||
disposableMap.values.forEach { it.dispose() }
|
||||
disposableMap.clear()
|
||||
}
|
||||
|
||||
fun add(selectedContact: SelectedContact) {
|
||||
disposables += getOrCreateRecipientId(selectedContact).map { Recipient.resolved(it) }.observeOn(Schedulers.io()).subscribe { recipient ->
|
||||
store.update { it + SelectedContacts.Model(selectedContact, recipient) }
|
||||
disposableMap[recipient.id]?.dispose()
|
||||
disposableMap[recipient.id] = store.update(recipient.live().asObservable().toFlowable(BackpressureStrategy.LATEST)) { changedRecipient, state ->
|
||||
val index = state.indexOfFirst { it.selectedContact.matches(selectedContact) }
|
||||
when {
|
||||
index == 0 -> {
|
||||
listOf(SelectedContacts.Model(selectedContact, changedRecipient)) + state.drop(index + 1)
|
||||
}
|
||||
index > 0 -> {
|
||||
state.take(index) + SelectedContacts.Model(selectedContact, changedRecipient) + state.drop(index + 1)
|
||||
}
|
||||
else -> {
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(selectedContact: SelectedContact) {
|
||||
store.update { list ->
|
||||
list.filterNot { it.selectedContact.matches(selectedContact) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOrCreateRecipientId(selectedContact: SelectedContact): Single<RecipientId> {
|
||||
return Single.fromCallable {
|
||||
selectedContact.getOrCreateRecipientId(ApplicationDependencies.getApplication())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.thoughtcrime.securesms.contacts
|
||||
|
||||
import android.view.View
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
|
||||
|
||||
object SelectedContacts {
|
||||
@JvmStatic
|
||||
fun register(adapter: MappingAdapter, onCloseIconClicked: (Model) -> Unit) {
|
||||
adapter.registerFactory(Model::class.java, LayoutFactory({ ViewHolder(it, onCloseIconClicked) }, R.layout.contact_selection_list_chip))
|
||||
}
|
||||
|
||||
class Model(val selectedContact: SelectedContact, val recipient: Recipient) : MappingModel<Model> {
|
||||
override fun areItemsTheSame(newItem: Model): Boolean {
|
||||
return newItem.selectedContact.matches(selectedContact) && recipient == newItem.recipient
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(newItem: Model): Boolean {
|
||||
return areItemsTheSame(newItem) && recipient.hasSameContent(newItem.recipient)
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewHolder(itemView: View, private val onCloseIconClicked: (Model) -> Unit) : MappingViewHolder<Model>(itemView) {
|
||||
|
||||
private val chip: ContactChip = itemView.findViewById(R.id.contact_chip)
|
||||
|
||||
override fun bind(model: Model) {
|
||||
chip.text = model.recipient.getShortDisplayName(context)
|
||||
chip.setContact(model.selectedContact)
|
||||
chip.isCloseIconVisible = true
|
||||
chip.setOnCloseIconClickListener {
|
||||
onCloseIconClicked(model)
|
||||
}
|
||||
chip.setAvatar(GlideApp.with(itemView), model.recipient, null)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.util.rx
|
|||
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Scheduler
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.processors.BehaviorProcessor
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
|
@ -31,4 +32,10 @@ class RxStore<T : Any>(
|
|||
fun update(transformer: (T) -> T) {
|
||||
actionSubject.onNext(transformer)
|
||||
}
|
||||
|
||||
fun <U> update(flowable: Flowable<U>, transformer: (U, T) -> T): Disposable {
|
||||
return flowable.subscribe {
|
||||
actionSubject.onNext { t -> transformer(it, t) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
6
app/src/main/res/layout/contact_selection_list_chip.xml
Normal file
6
app/src/main/res/layout/contact_selection_list_chip.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.thoughtcrime.securesms.contacts.ContactChip xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/contact_chip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp" />
|
|
@ -14,7 +14,7 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chipGroupScrollContainer">
|
||||
app:layout_constraintTop_toBottomOf="@+id/chipRecycler">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
|
@ -44,7 +44,7 @@
|
|||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chipGroupScrollContainer"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chipRecycler"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
|
@ -58,12 +58,12 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chipGroupScrollContainer"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chipRecycler"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Signal.Widget.Button.Large.Primary"
|
||||
android:id="@+id/show_contacts_button"
|
||||
style="@style/Signal.Widget.Button.Large.Primary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
|
@ -112,46 +112,23 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/chipGroupScrollContainer"
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/chipRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:minHeight="48sp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/dsl_settings_gutter"
|
||||
android:paddingEnd="@dimen/dsl_settings_gutter"
|
||||
android:scrollbars="none"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/chipGroup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="96dp"
|
||||
android:animateLayoutChanges="true"
|
||||
app:singleLine="true">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
tools:text="Example"
|
||||
tools:listitem="@layout/contact_selection_list_chip"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
</FrameLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/header_action"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
Loading…
Add table
Reference in a new issue