mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 09:20:19 +01: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.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()
|
||||
}
|
||||
|
||||
@@ -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,43 +219,25 @@ 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())) {
|
||||
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();
|
||||
if (hasContactsPermissions(requireContext()) && !TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) {
|
||||
handleContactPermissionGranted();
|
||||
} else {
|
||||
requireActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||
contactSearchMediator.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
|
||||
|
||||
emptyText = view.findViewById(android.R.id.empty);
|
||||
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);
|
||||
emptyText = view.findViewById(android.R.id.empty);
|
||||
recyclerView = view.findViewById(R.id.recycler_view);
|
||||
swipeRefresh = view.findViewById(R.id.swipe_refresh);
|
||||
fastScroller = view.findViewById(R.id.fast_scroller);
|
||||
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());
|
||||
|
||||
@@ -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());
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user