Update contacts permission UI.

This commit is contained in:
mtang-signal
2024-05-14 09:22:24 -07:00
committed by Nicholas Tinsley
parent 13bd4a9c74
commit 0465fdea62
8 changed files with 292 additions and 135 deletions

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms
import android.content.Context
import android.view.View
import android.widget.TextView
import com.google.android.material.button.MaterialButton
import org.thoughtcrime.securesms.contacts.paged.ContactSearchAdapter
import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration
import org.thoughtcrime.securesms.contacts.paged.ContactSearchData
@@ -24,6 +25,8 @@ class ContactSelectionListAdapter(
init {
registerFactory(NewGroupModel::class.java, LayoutFactory({ NewGroupViewHolder(it, onClickCallbacks::onNewGroupClicked) }, R.layout.contact_selection_new_group_item))
registerFactory(InviteToSignalModel::class.java, LayoutFactory({ InviteToSignalViewHolder(it, onClickCallbacks::onInviteToSignalClicked) }, R.layout.contact_selection_invite_action_item))
registerFactory(FindContactsModel::class.java, LayoutFactory({ FindContactsViewHolder(it, onClickCallbacks::onFindContactsClicked) }, R.layout.contact_selection_find_contacts_item))
registerFactory(FindContactsBannerModel::class.java, LayoutFactory({ FindContactsBannerViewHolder(it, onClickCallbacks::onDismissFindContactsBannerClicked, onClickCallbacks::onFindContactsClicked) }, R.layout.contact_selection_find_contacts_banner_item))
registerFactory(RefreshContactsModel::class.java, LayoutFactory({ RefreshContactsViewHolder(it, onClickCallbacks::onRefreshContactsClicked) }, R.layout.contact_selection_refresh_action_item))
registerFactory(MoreHeaderModel::class.java, LayoutFactory({ MoreHeaderViewHolder(it) }, R.layout.contact_search_section_header))
registerFactory(EmptyModel::class.java, LayoutFactory({ EmptyViewHolder(it) }, R.layout.contact_selection_empty_state))
@@ -46,6 +49,16 @@ class ContactSelectionListAdapter(
override fun areContentsTheSame(newItem: RefreshContactsModel): Boolean = true
}
class FindContactsModel : MappingModel<FindContactsModel> {
override fun areItemsTheSame(newItem: FindContactsModel): Boolean = true
override fun areContentsTheSame(newItem: FindContactsModel): Boolean = true
}
class FindContactsBannerModel : MappingModel<FindContactsBannerModel> {
override fun areItemsTheSame(newItem: FindContactsBannerModel): Boolean = true
override fun areContentsTheSame(newItem: FindContactsBannerModel): Boolean = true
}
class FindByUsernameModel : MappingModel<FindByUsernameModel> {
override fun areItemsTheSame(newItem: FindByUsernameModel): Boolean = true
override fun areContentsTheSame(newItem: FindByUsernameModel): Boolean = true
@@ -86,6 +99,23 @@ class ContactSelectionListAdapter(
override fun bind(model: RefreshContactsModel) = Unit
}
private class FindContactsViewHolder(itemView: View, onClickListener: () -> Unit) : MappingViewHolder<FindContactsModel>(itemView) {
init {
itemView.setOnClickListener { onClickListener() }
}
override fun bind(model: FindContactsModel) = Unit
}
private class FindContactsBannerViewHolder(itemView: View, onDismissListener: () -> Unit, onClickListener: () -> Unit) : MappingViewHolder<FindContactsBannerModel>(itemView) {
init {
itemView.findViewById<MaterialButton>(R.id.no_thanks_button).setOnClickListener { onDismissListener() }
itemView.findViewById<MaterialButton>(R.id.allow_contacts_button).setOnClickListener { onClickListener() }
}
override fun bind(model: FindContactsBannerModel) = Unit
}
private class MoreHeaderViewHolder(itemView: View) : MappingViewHolder<MoreHeaderModel>(itemView) {
private val headerTextView: TextView = itemView.findViewById(R.id.section_header)
@@ -129,6 +159,8 @@ class ContactSelectionListAdapter(
INVITE_TO_SIGNAL("invite-to-signal"),
MORE_HEADING("more-heading"),
REFRESH_CONTACTS("refresh-contacts"),
FIND_CONTACTS("find-contacts"),
FIND_CONTACTS_BANNER("find-contacts-banner"),
FIND_BY_USERNAME("find-by-username"),
FIND_BY_PHONE_NUMBER("find-by-phone-number");
@@ -152,6 +184,8 @@ class ContactSelectionListAdapter(
ArbitraryRow.INVITE_TO_SIGNAL -> InviteToSignalModel()
ArbitraryRow.MORE_HEADING -> MoreHeaderModel()
ArbitraryRow.REFRESH_CONTACTS -> RefreshContactsModel()
ArbitraryRow.FIND_CONTACTS -> FindContactsModel()
ArbitraryRow.FIND_CONTACTS_BANNER -> FindContactsBannerModel()
ArbitraryRow.FIND_BY_PHONE_NUMBER -> FindByPhoneNumberModel()
ArbitraryRow.FIND_BY_USERNAME -> FindByUsernameModel()
}
@@ -162,6 +196,8 @@ class ContactSelectionListAdapter(
fun onNewGroupClicked()
fun onInviteToSignalClicked()
fun onRefreshContactsClicked()
fun onFindContactsClicked()
fun onDismissFindContactsBannerClicked()
fun onFindByPhoneNumberClicked()
fun onFindByUsernameClicked()
}

View File

@@ -70,13 +70,13 @@ import org.thoughtcrime.securesms.contacts.paged.ContactSearchState;
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.groups.SelectionLimits;
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository;
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository.UsernameAciFetchResult;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.UsernameUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -125,10 +125,6 @@ public final class ContactSelectionListFragment extends LoggingFragment {
private TextView emptyText;
private OnContactSelectedListener onContactSelectedListener;
private SwipeRefreshLayout swipeRefresh;
private View showContactsLayout;
private Button showContactsButton;
private TextView showContactsDescription;
private ProgressWheel showContactsProgress;
private String cursorFilter;
private RecyclerView recyclerView;
private RecyclerViewFastScroller fastScroller;
@@ -223,26 +219,12 @@ public final class ContactSelectionListFragment extends LoggingFragment {
public void onStart() {
super.onStart();
Permissions.with(this)
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
.ifNecessary()
.onAllGranted(() -> {
if (!TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) {
if (hasContactsPermissions(requireContext()) && !TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) {
handleContactPermissionGranted();
} else {
contactSearchMediator.refresh();
}
})
.onAnyDenied(() -> {
requireActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
if (safeArguments().getBoolean(RECENTS, requireActivity().getIntent().getBooleanExtra(RECENTS, false))) {
contactSearchMediator.refresh();
} else {
initializeNoContactsPermission();
}
})
.execute();
}
@Override
@@ -253,10 +235,6 @@ public final class ContactSelectionListFragment extends LoggingFragment {
recyclerView = view.findViewById(R.id.recycler_view);
swipeRefresh = view.findViewById(R.id.swipe_refresh);
fastScroller = view.findViewById(R.id.fast_scroller);
showContactsLayout = view.findViewById(R.id.show_contacts_container);
showContactsButton = view.findViewById(R.id.show_contacts_button);
showContactsDescription = view.findViewById(R.id.show_contacts_description);
showContactsProgress = view.findViewById(R.id.progress);
chipRecycler = view.findViewById(R.id.chipRecycler);
constraintLayout = view.findViewById(R.id.container);
headerActionView = view.findViewById(R.id.header_action);
@@ -269,6 +247,11 @@ public final class ContactSelectionListFragment extends LoggingFragment {
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
return true;
}
@Override
public void onAnimationFinished(@NonNull RecyclerView.ViewHolder viewHolder) {
recyclerView.setAlpha(1f);
}
});
contactChipViewModel = new ViewModelProvider(this).get(ContactChipViewModel.class);
@@ -372,6 +355,19 @@ public final class ContactSelectionListFragment extends LoggingFragment {
fixedContacts,
displayOptions,
new ContactSelectionListAdapter.OnContactSelectionClick() {
@Override
public void onDismissFindContactsBannerClicked() {
SignalStore.uiHints().markDismissedContactsPermissionBanner();
if (onRefreshListener != null) {
onRefreshListener.onRefresh();
}
}
@Override
public void onFindContactsClicked() {
requestContactPermissions();
}
@Override
public void onRefreshContactsClicked() {
if (onRefreshListener != null) {
@@ -498,6 +494,27 @@ public final class ContactSelectionListFragment extends LoggingFragment {
return isMulti;
}
private void requestContactPermissions() {
Permissions.with(this)
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
.ifNecessary()
.onAllGranted(() -> {
recyclerView.setAlpha(0.5f);
if (!TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) {
handleContactPermissionGranted();
} else {
contactSearchMediator.refresh();
if (onRefreshListener != null) {
swipeRefresh.setRefreshing(true);
onRefreshListener.onRefresh();
}
}
})
.onAnyDenied(() -> contactSearchMediator.refresh())
.withPermanentDenialDialog(getString(R.string.ContactSelectionListFragment_signal_requires_the_contacts_permission_in_order_to_display_your_contacts), null, R.string.ContactSelectionListFragment_allow_access_contacts, R.string.ContactSelectionListFragment_to_find_people, getParentFragmentManager())
.execute();
}
private void initializeCursor() {
recyclerView.addItemDecoration(new LetterHeaderDecoration(requireContext(), this::hideLetterHeaders));
recyclerView.setAdapter(contactSearchMediator.getAdapter());
@@ -521,28 +538,6 @@ public final class ContactSelectionListFragment extends LoggingFragment {
return hasQueryFilter() || shouldDisplayRecents();
}
private void initializeNoContactsPermission() {
swipeRefresh.setVisibility(View.GONE);
showContactsLayout.setVisibility(View.VISIBLE);
showContactsProgress.setVisibility(View.INVISIBLE);
showContactsDescription.setText(R.string.contact_selection_list_fragment__signal_needs_access_to_your_contacts_in_order_to_display_them);
showContactsButton.setVisibility(View.VISIBLE);
showContactsButton.setOnClickListener(v -> {
Permissions.with(this)
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
.ifNecessary()
.withPermanentDenialDialog(getString(R.string.ContactSelectionListFragment_signal_requires_the_contacts_permission_in_order_to_display_your_contacts))
.onSomeGranted(permissions -> {
if (permissions.contains(Manifest.permission.WRITE_CONTACTS)) {
handleContactPermissionGranted();
}
})
.execute();
});
}
public void setQueryFilter(String filter) {
if (Objects.equals(filter, this.cursorFilter)) {
return;
@@ -583,7 +578,6 @@ public final class ContactSelectionListFragment extends LoggingFragment {
}
swipeRefresh.setVisibility(View.VISIBLE);
showContactsLayout.setVisibility(View.GONE);
emptyText.setText(R.string.contact_selection_group_activity__no_contacts);
boolean useFastScroller = count > 20;
@@ -614,12 +608,10 @@ public final class ContactSelectionListFragment extends LoggingFragment {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected void onPreExecute() {
swipeRefresh.setVisibility(View.GONE);
showContactsLayout.setVisibility(View.VISIBLE);
showContactsButton.setVisibility(View.INVISIBLE);
showContactsDescription.setText(R.string.ConversationListFragment_loading);
showContactsProgress.setVisibility(View.VISIBLE);
showContactsProgress.spin();
if (onRefreshListener != null) {
setRefreshing(true);
onRefreshListener.onRefresh();
}
}
@Override
@@ -636,14 +628,11 @@ public final class ContactSelectionListFragment extends LoggingFragment {
@Override
protected void onPostExecute(Boolean result) {
if (result) {
showContactsLayout.setVisibility(View.GONE);
swipeRefresh.setVisibility(View.VISIBLE);
reset();
} else {
Context context = getContext();
if (context != null) {
Toast.makeText(getContext(), R.string.ContactSelectionListFragment_error_retrieving_contacts_check_your_network_connection, Toast.LENGTH_LONG).show();
initializeNoContactsPermission();
}
}
}
@@ -890,6 +879,13 @@ public final class ContactSelectionListFragment extends LoggingFragment {
return ContactSearchConfiguration.build(builder -> {
builder.setQuery(contactSearchState.getQuery());
if (newConversationCallback != null &&
!hasContactsPermissions(requireContext()) &&
!SignalStore.uiHints().getDismissedContactsPermissionBanner() &&
!hasQuery) {
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.FIND_CONTACTS_BANNER.getCode());
}
if (newConversationCallback != null && !hasQuery) {
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.NEW_GROUP.getCode());
}
@@ -946,7 +942,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
builder.username(newRowMode);
}
if ((newCallCallback != null || newConversationCallback != null) && !hasQuery) {
if ((newCallCallback != null || newConversationCallback != null)) {
addMoreSection(builder);
builder.withEmptyState(emptyBuilder -> {
emptyBuilder.addSection(ContactSearchConfiguration.Section.Empty.INSTANCE);
@@ -959,9 +955,17 @@ public final class ContactSelectionListFragment extends LoggingFragment {
});
}
private boolean hasContactsPermissions(@NonNull Context context) {
return Permissions.hasAll(context, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS);
}
private void addMoreSection(@NonNull ContactSearchConfiguration.Builder builder) {
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.MORE_HEADING.getCode());
if (hasContactsPermissions(requireContext())) {
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.REFRESH_CONTACTS.getCode());
} else if (SignalStore.uiHints().getDismissedContactsPermissionBanner()) {
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.FIND_CONTACTS.getCode());
}
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.INVITE_TO_SIGNAL.getCode());
}

View File

@@ -24,6 +24,7 @@ public class UiHints extends SignalStoreValues {
private static final String LAST_CRASH_PROMPT = "uihints.last_crash_prompt";
private static final String HAS_COMPLETED_USERNAME_ONBOARDING = "uihints.has_completed_username_onboarding";
private static final String HAS_SEEN_DOUBLE_TAP_EDIT_EDUCATION_SHEET = "uihints.has_seen_double_tap_edit_education_sheet";
private static final String DISMISSED_CONTACTS_PERMISSION_BANNER = "uihints.dismissed_contacts_permission_banner";
UiHints(@NonNull KeyValueStore store) {
super(store);
@@ -167,4 +168,12 @@ public class UiHints extends SignalStoreValues {
public boolean getHasSeenDoubleTapEditEducationSheet() {
return getBoolean(HAS_SEEN_DOUBLE_TAP_EDIT_EDUCATION_SHEET, false);
}
public void markDismissedContactsPermissionBanner() {
putBoolean(DISMISSED_CONTACTS_PERMISSION_BANNER, true);
}
public boolean getDismissedContactsPermissionBanner() {
return getBoolean(DISMISSED_CONTACTS_PERMISSION_BANNER, false);
}
}

View File

@@ -0,0 +1,31 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="72"
android:viewportHeight="72">
<path
android:pathData="M12,12C12,8.686 14.686,6 18,6V54.75H12V12Z"
android:fillColor="#A0AFCF"/>
<path
android:pathData="M18,6H60V54H18V6Z"
android:fillColor="#C3CFE9"/>
<path
android:pathData="M18,54H60V63C60,64.657 58.657,66 57,66H18C14.686,66 12,63.314 12,60C12,56.686 14.686,54 18,54Z"
android:fillColor="#F5F8FF"/>
<path
android:pathData="M18,4.5C13.858,4.5 10.5,7.858 10.5,12V60C10.5,64.142 13.858,67.5 18,67.5H57C59.485,67.5 61.5,65.485 61.5,63V9C61.5,6.515 59.485,4.5 57,4.5H18ZM58.5,9C58.5,8.172 57.828,7.5 57,7.5H18C15.515,7.5 13.5,9.515 13.5,12V54C14.753,53.058 16.312,52.5 18,52.5H57C57.828,52.5 58.5,51.828 58.5,51V9ZM58.5,55.244C58.031,55.41 57.526,55.5 57,55.5H18C15.515,55.5 13.5,57.515 13.5,60C13.5,62.485 15.515,64.5 18,64.5H57C57.828,64.5 58.5,63.828 58.5,63V55.244Z"
android:fillColor="#6C7B9D"
android:fillType="evenOdd"/>
<path
android:pathData="M39.219,23.935C39.219,21.45 40.873,19.375 43.125,19.375C45.377,19.375 47.031,21.45 47.031,23.935C47.031,25.189 46.622,26.373 45.939,27.254C45.257,28.136 44.268,28.75 43.125,28.75C41.982,28.75 40.993,28.136 40.311,27.254C39.628,26.373 39.219,25.189 39.219,23.935Z"
android:fillColor="#4C5876"/>
<path
android:pathData="M33.125,38.438C33.125,35.928 34.198,33.766 35.879,32.183C34.689,31.587 33.319,31.25 31.875,31.25C27.469,31.25 23.75,34.392 23.75,38.438C23.75,38.98 24.198,39.375 24.696,39.375H33.176C33.142,39.068 33.125,38.756 33.125,38.438Z"
android:fillColor="#4C5876"/>
<path
android:pathData="M31.875,19.375C29.623,19.375 27.969,21.45 27.969,23.935C27.969,25.189 28.378,26.373 29.061,27.254C29.743,28.136 30.732,28.75 31.875,28.75C33.018,28.75 34.007,28.136 34.689,27.254C35.372,26.373 35.781,25.189 35.781,23.935C35.781,21.45 34.127,19.375 31.875,19.375Z"
android:fillColor="#4C5876"/>
<path
android:pathData="M35,38.438C35,34.392 38.719,31.25 43.125,31.25C47.531,31.25 51.25,34.392 51.25,38.438C51.25,38.98 50.802,39.375 50.304,39.375H35.946C35.448,39.375 35,38.98 35,38.438Z"
android:fillColor="#4C5876"/>
</vector>

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/rounded_outline"
android:padding="15dp"
tools:viewBindingIgnore="true">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image"
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="centerInside"
android:importantForAccessibility="no"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/permissions_contact_book" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="20dp"
android:text="@string/contact_selection_activity__find_people_you_know"
android:textAppearance="@style/Signal.Text.BodyLarge"
app:layout_constraintTop_toBottomOf="@id/image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="@string/ContactSelectionListFragment__allow_access_to_your_contacts_encrypted"
android:textAppearance="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />
<com.google.android.material.button.MaterialButton
android:id="@+id/no_thanks_button"
style="@style/Signal.Widget.Button.Medium.Tonal"
android:layout_width="140dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="@id/description"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/allow_contacts_button"
android:text="@string/RatingManager_no_thanks" />
<com.google.android.material.button.MaterialButton
android:id="@+id/allow_contacts_button"
style="@style/Signal.Widget.Button.Medium.Tonal"
android:layout_width="140dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
app:layout_constraintStart_toEndOf="@id/no_thanks_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/description"
android:text="@string/ContactSelectionListFragment__allow_access" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/rounded_inset_ripple_background"
android:minHeight="@dimen/contact_selection_item_height"
android:paddingStart="@dimen/dsl_settings_gutter"
android:paddingEnd="@dimen/dsl_settings_gutter"
tools:viewBindingIgnore="true">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@color/signal_colorSurfaceVariant"
android:importantForAccessibility="no"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.Circle"
app:srcCompat="@drawable/symbol_person_circle_24"
app:tint="@color/signal_colorOnSurface" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:ellipsize="marquee"
android:labelFor="@id/action_icon"
android:singleLine="true"
android:text="@string/contact_selection_activity__find_people_you_know"
android:textAppearance="@style/Signal.Text.BodyLarge"
app:layout_constraintBottom_toTopOf="@id/description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/image"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:ellipsize="marquee"
android:labelFor="@id/action_icon"
android:maxLines="2"
android:text="@string/contact_selection_activity__allow_access_to_contacts"
android:textAppearance="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/image"
app:layout_constraintTop_toBottomOf="@id/title" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -48,71 +48,6 @@
app:layout_constraintTop_toBottomOf="@+id/chipRecycler"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/show_contacts_container"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="center"
android:orientation="vertical"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chipRecycler"
tools:visibility="visible">
<com.google.android.material.button.MaterialButton
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"
android:layout_marginTop="20dp"
android:background="@color/core_ultramarine"
android:padding="10dp"
android:text="@string/contact_selection_list_fragment__show_contacts"
android:textColor="@color/white" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.pnikosis.materialishprogress.ProgressWheel
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"
app:matProg_barColor="@color/core_ultramarine_dark"
app:matProg_barWidth="6dp"
app:matProg_circleRadius="145dp"
app:matProg_progressIndeterminate="true"
app:matProg_rimColor="@color/core_ultramarine"
tools:visibility="visible" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:importantForAccessibility="no"
android:src="@drawable/no_contacts" />
</FrameLayout>
<TextView
android:id="@+id/show_contacts_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="50dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="50dp"
android:gravity="center"
android:lineSpacingMultiplier="1.3"
android:text="@string/contact_selection_list_fragment__signal_needs_access_to_your_contacts_in_order_to_display_them"
android:textSize="15sp" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/header_action"
style="@style/Widget.Signal.Button.Small"

View File

@@ -2714,6 +2714,10 @@
<string name="contact_selection_activity__refresh_contacts">Refresh contacts</string>
<!-- Row item description for refreshing contacts -->
<string name="contact_selection_activity__missing_someone">Missing someone? Try refreshing</string>
<!-- Row item title for finding people on Signal via your contacts -->
<string name="contact_selection_activity__find_people_you_know">Find people you know on Signal</string>
<!-- Row item description asking users for access to their contacts to find people they know -->
<string name="contact_selection_activity__allow_access_to_contacts">Allow access to your contacts</string>
<!-- Row header title for more section -->
<string name="contact_selection_activity__more">More</string>
@@ -2747,6 +2751,14 @@
<string name="ContactSelectionListFragment__find_by_phone_number">Find by phone number</string>
<!-- Text on row item to find user by username -->
<string name="ContactSelectionListFragment__find_by_username">Find by username</string>
<!-- Dialog title asking users for contact permission -->
<string name="ContactSelectionListFragment_allow_access_contacts">Allow access to contacts</string>
<!-- Dialog description that will explain the steps needed to give contact permissions -->
<string name="ContactSelectionListFragment_to_find_people">To find people you know on Signal:</string>
<!-- Text on button prompting users to give Signal contact permissions -->
<string name="ContactSelectionListFragment__allow_access">Allow access</string>
<!-- Text asking for user's contact permission -->
<string name="ContactSelectionListFragment__allow_access_to_your_contacts_encrypted">Allow access to your contacts. Your contacts are encrypted and not visible to the Signal service.</string>
<!-- contact_selection_list_fragment -->
<string name="contact_selection_list_fragment__signal_needs_access_to_your_contacts_in_order_to_display_them">Signal needs access to your contacts in order to display them.</string>