mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 04:28:35 +00:00
Update contacts permission UI.
This commit is contained in:
committed by
Nicholas Tinsley
parent
13bd4a9c74
commit
0465fdea62
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchAdapter
|
import org.thoughtcrime.securesms.contacts.paged.ContactSearchAdapter
|
||||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration
|
import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration
|
||||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchData
|
import org.thoughtcrime.securesms.contacts.paged.ContactSearchData
|
||||||
@@ -24,6 +25,8 @@ class ContactSelectionListAdapter(
|
|||||||
init {
|
init {
|
||||||
registerFactory(NewGroupModel::class.java, LayoutFactory({ NewGroupViewHolder(it, onClickCallbacks::onNewGroupClicked) }, R.layout.contact_selection_new_group_item))
|
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(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(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(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))
|
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
|
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> {
|
class FindByUsernameModel : MappingModel<FindByUsernameModel> {
|
||||||
override fun areItemsTheSame(newItem: FindByUsernameModel): Boolean = true
|
override fun areItemsTheSame(newItem: FindByUsernameModel): Boolean = true
|
||||||
override fun areContentsTheSame(newItem: FindByUsernameModel): Boolean = true
|
override fun areContentsTheSame(newItem: FindByUsernameModel): Boolean = true
|
||||||
@@ -86,6 +99,23 @@ class ContactSelectionListAdapter(
|
|||||||
override fun bind(model: RefreshContactsModel) = Unit
|
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 class MoreHeaderViewHolder(itemView: View) : MappingViewHolder<MoreHeaderModel>(itemView) {
|
||||||
|
|
||||||
private val headerTextView: TextView = itemView.findViewById(R.id.section_header)
|
private val headerTextView: TextView = itemView.findViewById(R.id.section_header)
|
||||||
@@ -129,6 +159,8 @@ class ContactSelectionListAdapter(
|
|||||||
INVITE_TO_SIGNAL("invite-to-signal"),
|
INVITE_TO_SIGNAL("invite-to-signal"),
|
||||||
MORE_HEADING("more-heading"),
|
MORE_HEADING("more-heading"),
|
||||||
REFRESH_CONTACTS("refresh-contacts"),
|
REFRESH_CONTACTS("refresh-contacts"),
|
||||||
|
FIND_CONTACTS("find-contacts"),
|
||||||
|
FIND_CONTACTS_BANNER("find-contacts-banner"),
|
||||||
FIND_BY_USERNAME("find-by-username"),
|
FIND_BY_USERNAME("find-by-username"),
|
||||||
FIND_BY_PHONE_NUMBER("find-by-phone-number");
|
FIND_BY_PHONE_NUMBER("find-by-phone-number");
|
||||||
|
|
||||||
@@ -152,6 +184,8 @@ class ContactSelectionListAdapter(
|
|||||||
ArbitraryRow.INVITE_TO_SIGNAL -> InviteToSignalModel()
|
ArbitraryRow.INVITE_TO_SIGNAL -> InviteToSignalModel()
|
||||||
ArbitraryRow.MORE_HEADING -> MoreHeaderModel()
|
ArbitraryRow.MORE_HEADING -> MoreHeaderModel()
|
||||||
ArbitraryRow.REFRESH_CONTACTS -> RefreshContactsModel()
|
ArbitraryRow.REFRESH_CONTACTS -> RefreshContactsModel()
|
||||||
|
ArbitraryRow.FIND_CONTACTS -> FindContactsModel()
|
||||||
|
ArbitraryRow.FIND_CONTACTS_BANNER -> FindContactsBannerModel()
|
||||||
ArbitraryRow.FIND_BY_PHONE_NUMBER -> FindByPhoneNumberModel()
|
ArbitraryRow.FIND_BY_PHONE_NUMBER -> FindByPhoneNumberModel()
|
||||||
ArbitraryRow.FIND_BY_USERNAME -> FindByUsernameModel()
|
ArbitraryRow.FIND_BY_USERNAME -> FindByUsernameModel()
|
||||||
}
|
}
|
||||||
@@ -162,6 +196,8 @@ class ContactSelectionListAdapter(
|
|||||||
fun onNewGroupClicked()
|
fun onNewGroupClicked()
|
||||||
fun onInviteToSignalClicked()
|
fun onInviteToSignalClicked()
|
||||||
fun onRefreshContactsClicked()
|
fun onRefreshContactsClicked()
|
||||||
|
fun onFindContactsClicked()
|
||||||
|
fun onDismissFindContactsBannerClicked()
|
||||||
fun onFindByPhoneNumberClicked()
|
fun onFindByPhoneNumberClicked()
|
||||||
fun onFindByUsernameClicked()
|
fun onFindByUsernameClicked()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,13 +70,13 @@ import org.thoughtcrime.securesms.contacts.paged.ContactSearchState;
|
|||||||
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
||||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||||
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
|
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository;
|
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository;
|
||||||
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository.UsernameAciFetchResult;
|
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository.UsernameAciFetchResult;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.UsernameUtil;
|
import org.thoughtcrime.securesms.util.UsernameUtil;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
@@ -125,10 +125,6 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
private TextView emptyText;
|
private TextView emptyText;
|
||||||
private OnContactSelectedListener onContactSelectedListener;
|
private OnContactSelectedListener onContactSelectedListener;
|
||||||
private SwipeRefreshLayout swipeRefresh;
|
private SwipeRefreshLayout swipeRefresh;
|
||||||
private View showContactsLayout;
|
|
||||||
private Button showContactsButton;
|
|
||||||
private TextView showContactsDescription;
|
|
||||||
private ProgressWheel showContactsProgress;
|
|
||||||
private String cursorFilter;
|
private String cursorFilter;
|
||||||
private RecyclerView recyclerView;
|
private RecyclerView recyclerView;
|
||||||
private RecyclerViewFastScroller fastScroller;
|
private RecyclerViewFastScroller fastScroller;
|
||||||
@@ -223,43 +219,25 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
Permissions.with(this)
|
if (hasContactsPermissions(requireContext()) && !TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) {
|
||||||
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
|
handleContactPermissionGranted();
|
||||||
.ifNecessary()
|
} else {
|
||||||
.onAllGranted(() -> {
|
requireActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||||
if (!TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) {
|
contactSearchMediator.refresh();
|
||||||
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
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
|
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
|
||||||
|
|
||||||
emptyText = view.findViewById(android.R.id.empty);
|
emptyText = view.findViewById(android.R.id.empty);
|
||||||
recyclerView = view.findViewById(R.id.recycler_view);
|
recyclerView = view.findViewById(R.id.recycler_view);
|
||||||
swipeRefresh = view.findViewById(R.id.swipe_refresh);
|
swipeRefresh = view.findViewById(R.id.swipe_refresh);
|
||||||
fastScroller = view.findViewById(R.id.fast_scroller);
|
fastScroller = view.findViewById(R.id.fast_scroller);
|
||||||
showContactsLayout = view.findViewById(R.id.show_contacts_container);
|
chipRecycler = view.findViewById(R.id.chipRecycler);
|
||||||
showContactsButton = view.findViewById(R.id.show_contacts_button);
|
constraintLayout = view.findViewById(R.id.container);
|
||||||
showContactsDescription = view.findViewById(R.id.show_contacts_description);
|
headerActionView = view.findViewById(R.id.header_action);
|
||||||
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);
|
|
||||||
|
|
||||||
final LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext());
|
final LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext());
|
||||||
|
|
||||||
@@ -269,6 +247,11 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
|
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationFinished(@NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
|
recyclerView.setAlpha(1f);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
contactChipViewModel = new ViewModelProvider(this).get(ContactChipViewModel.class);
|
contactChipViewModel = new ViewModelProvider(this).get(ContactChipViewModel.class);
|
||||||
@@ -372,6 +355,19 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
fixedContacts,
|
fixedContacts,
|
||||||
displayOptions,
|
displayOptions,
|
||||||
new ContactSelectionListAdapter.OnContactSelectionClick() {
|
new ContactSelectionListAdapter.OnContactSelectionClick() {
|
||||||
|
@Override
|
||||||
|
public void onDismissFindContactsBannerClicked() {
|
||||||
|
SignalStore.uiHints().markDismissedContactsPermissionBanner();
|
||||||
|
if (onRefreshListener != null) {
|
||||||
|
onRefreshListener.onRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFindContactsClicked() {
|
||||||
|
requestContactPermissions();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRefreshContactsClicked() {
|
public void onRefreshContactsClicked() {
|
||||||
if (onRefreshListener != null) {
|
if (onRefreshListener != null) {
|
||||||
@@ -498,6 +494,27 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
return isMulti;
|
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() {
|
private void initializeCursor() {
|
||||||
recyclerView.addItemDecoration(new LetterHeaderDecoration(requireContext(), this::hideLetterHeaders));
|
recyclerView.addItemDecoration(new LetterHeaderDecoration(requireContext(), this::hideLetterHeaders));
|
||||||
recyclerView.setAdapter(contactSearchMediator.getAdapter());
|
recyclerView.setAdapter(contactSearchMediator.getAdapter());
|
||||||
@@ -521,28 +538,6 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
return hasQueryFilter() || shouldDisplayRecents();
|
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) {
|
public void setQueryFilter(String filter) {
|
||||||
if (Objects.equals(filter, this.cursorFilter)) {
|
if (Objects.equals(filter, this.cursorFilter)) {
|
||||||
return;
|
return;
|
||||||
@@ -583,7 +578,6 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
swipeRefresh.setVisibility(View.VISIBLE);
|
swipeRefresh.setVisibility(View.VISIBLE);
|
||||||
showContactsLayout.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
emptyText.setText(R.string.contact_selection_group_activity__no_contacts);
|
emptyText.setText(R.string.contact_selection_group_activity__no_contacts);
|
||||||
boolean useFastScroller = count > 20;
|
boolean useFastScroller = count > 20;
|
||||||
@@ -614,12 +608,10 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
new AsyncTask<Void, Void, Boolean>() {
|
new AsyncTask<Void, Void, Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
swipeRefresh.setVisibility(View.GONE);
|
if (onRefreshListener != null) {
|
||||||
showContactsLayout.setVisibility(View.VISIBLE);
|
setRefreshing(true);
|
||||||
showContactsButton.setVisibility(View.INVISIBLE);
|
onRefreshListener.onRefresh();
|
||||||
showContactsDescription.setText(R.string.ConversationListFragment_loading);
|
}
|
||||||
showContactsProgress.setVisibility(View.VISIBLE);
|
|
||||||
showContactsProgress.spin();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -636,14 +628,11 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Boolean result) {
|
protected void onPostExecute(Boolean result) {
|
||||||
if (result) {
|
if (result) {
|
||||||
showContactsLayout.setVisibility(View.GONE);
|
|
||||||
swipeRefresh.setVisibility(View.VISIBLE);
|
|
||||||
reset();
|
reset();
|
||||||
} else {
|
} else {
|
||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
Toast.makeText(getContext(), R.string.ContactSelectionListFragment_error_retrieving_contacts_check_your_network_connection, Toast.LENGTH_LONG).show();
|
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 -> {
|
return ContactSearchConfiguration.build(builder -> {
|
||||||
builder.setQuery(contactSearchState.getQuery());
|
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) {
|
if (newConversationCallback != null && !hasQuery) {
|
||||||
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.NEW_GROUP.getCode());
|
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.NEW_GROUP.getCode());
|
||||||
}
|
}
|
||||||
@@ -946,7 +942,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
|||||||
builder.username(newRowMode);
|
builder.username(newRowMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((newCallCallback != null || newConversationCallback != null) && !hasQuery) {
|
if ((newCallCallback != null || newConversationCallback != null)) {
|
||||||
addMoreSection(builder);
|
addMoreSection(builder);
|
||||||
builder.withEmptyState(emptyBuilder -> {
|
builder.withEmptyState(emptyBuilder -> {
|
||||||
emptyBuilder.addSection(ContactSearchConfiguration.Section.Empty.INSTANCE);
|
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) {
|
private void addMoreSection(@NonNull ContactSearchConfiguration.Builder builder) {
|
||||||
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.MORE_HEADING.getCode());
|
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.MORE_HEADING.getCode());
|
||||||
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.REFRESH_CONTACTS.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());
|
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.INVITE_TO_SIGNAL.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public class UiHints extends SignalStoreValues {
|
|||||||
private static final String LAST_CRASH_PROMPT = "uihints.last_crash_prompt";
|
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_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 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) {
|
UiHints(@NonNull KeyValueStore store) {
|
||||||
super(store);
|
super(store);
|
||||||
@@ -167,4 +168,12 @@ public class UiHints extends SignalStoreValues {
|
|||||||
public boolean getHasSeenDoubleTapEditEducationSheet() {
|
public boolean getHasSeenDoubleTapEditEducationSheet() {
|
||||||
return getBoolean(HAS_SEEN_DOUBLE_TAP_EDIT_EDUCATION_SHEET, false);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
app/src/main/res/drawable/permissions_contact_book.xml
Normal file
31
app/src/main/res/drawable/permissions_contact_book.xml
Normal 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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -48,71 +48,6 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@+id/chipRecycler"
|
app:layout_constraintTop_toBottomOf="@+id/chipRecycler"
|
||||||
tools:visibility="visible" />
|
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
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/header_action"
|
android:id="@+id/header_action"
|
||||||
style="@style/Widget.Signal.Button.Small"
|
style="@style/Widget.Signal.Button.Small"
|
||||||
|
|||||||
@@ -2714,6 +2714,10 @@
|
|||||||
<string name="contact_selection_activity__refresh_contacts">Refresh contacts</string>
|
<string name="contact_selection_activity__refresh_contacts">Refresh contacts</string>
|
||||||
<!-- Row item description for refreshing contacts -->
|
<!-- Row item description for refreshing contacts -->
|
||||||
<string name="contact_selection_activity__missing_someone">Missing someone? Try refreshing</string>
|
<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 -->
|
<!-- Row header title for more section -->
|
||||||
<string name="contact_selection_activity__more">More</string>
|
<string name="contact_selection_activity__more">More</string>
|
||||||
|
|
||||||
@@ -2747,6 +2751,14 @@
|
|||||||
<string name="ContactSelectionListFragment__find_by_phone_number">Find by phone number</string>
|
<string name="ContactSelectionListFragment__find_by_phone_number">Find by phone number</string>
|
||||||
<!-- Text on row item to find user by username -->
|
<!-- Text on row item to find user by username -->
|
||||||
<string name="ContactSelectionListFragment__find_by_username">Find by username</string>
|
<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 -->
|
<!-- 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>
|
<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>
|
||||||
|
|||||||
Reference in New Issue
Block a user