diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 109236ef94..65680bacc9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -443,7 +443,7 @@ android:theme="@style/TextSecure.FullScreenMedia" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> - diff --git a/app/src/main/java/org/thoughtcrime/securesms/BlockedContactsActivity.java b/app/src/main/java/org/thoughtcrime/securesms/BlockedContactsActivity.java deleted file mode 100644 index 9468f208fa..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/BlockedContactsActivity.java +++ /dev/null @@ -1,139 +0,0 @@ -package org.thoughtcrime.securesms; - -import android.content.Context; -import android.database.Cursor; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ListView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.cursoradapter.widget.CursorAdapter; -import androidx.fragment.app.ListFragment; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; - -import org.thoughtcrime.securesms.database.RecipientDatabase; -import org.thoughtcrime.securesms.database.loaders.BlockedContactsLoader; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.preferences.BlockedContactListItem; -import org.thoughtcrime.securesms.recipients.LiveRecipient; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.recipients.RecipientUtil; -import org.thoughtcrime.securesms.util.DynamicTheme; - -public class BlockedContactsActivity extends PassphraseRequiredActivity { - - private final DynamicTheme dynamicTheme = new DynamicTheme(); - - @Override - public void onPreCreate() { - dynamicTheme.onCreate(this); - } - - @Override - public void onCreate(Bundle bundle, boolean ready) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(R.string.BlockedContactsActivity_blocked_contacts); - initFragment(android.R.id.content, new BlockedContactsFragment()); - } - - @Override - public void onResume() { - super.onResume(); - dynamicTheme.onResume(this); - } - - @Override - public boolean onSupportNavigateUp() { - onBackPressed(); - return true; - } - - public static class BlockedContactsFragment - extends ListFragment - implements LoaderManager.LoaderCallbacks, ListView.OnItemClickListener - { - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { - return inflater.inflate(R.layout.blocked_contacts_fragment, container, false); - } - - @Override - public void onCreate(Bundle bundle) { - super.onCreate(bundle); - setListAdapter(new BlockedContactAdapter(requireActivity(), GlideApp.with(this), null)); - LoaderManager.getInstance(this).initLoader(0, null, this); - } - - @Override - public void onStart() { - super.onStart(); - LoaderManager.getInstance(this).restartLoader(0, null, this); - } - - @Override - public void onActivityCreated(Bundle bundle) { - super.onActivityCreated(bundle); - getListView().setOnItemClickListener(this); - } - - @Override - public @NonNull Loader onCreateLoader(int id, Bundle args) { - return new BlockedContactsLoader(getActivity()); - } - - @Override - public void onLoadFinished(@NonNull Loader loader, Cursor data) { - if (getListAdapter() != null) { - ((CursorAdapter) getListAdapter()).changeCursor(data); - } - } - - @Override - public void onLoaderReset(@NonNull Loader loader) { - if (getListAdapter() != null) { - ((CursorAdapter) getListAdapter()).changeCursor(null); - } - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - Recipient recipient = ((BlockedContactListItem)view).getRecipient(); - BlockUnblockDialog.showUnblockFor(requireContext(), getLifecycle(), recipient, () -> { - RecipientUtil.unblock(requireContext(), recipient); - LoaderManager.getInstance(this).restartLoader(0, null, this); - }); - } - - private static class BlockedContactAdapter extends CursorAdapter { - - private final GlideRequests glideRequests; - - BlockedContactAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, @Nullable Cursor c) { - super(context, c); - this.glideRequests = glideRequests; - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return LayoutInflater.from(context) - .inflate(R.layout.blocked_contact_list_item, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RecipientDatabase.ID))); - LiveRecipient recipient = Recipient.live(recipientId); - - ((BlockedContactListItem) view).set(glideRequests, recipient); - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersActivity.java b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersActivity.java new file mode 100644 index 0000000000..5d80d79c3e --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersActivity.java @@ -0,0 +1,167 @@ +package org.thoughtcrime.securesms.blocked; + +import android.app.AlertDialog; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.view.View; +import android.widget.ViewSwitcher; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProviders; + +import com.google.android.material.snackbar.Snackbar; + +import org.thoughtcrime.securesms.ContactSelectionListFragment; +import org.thoughtcrime.securesms.PassphraseRequiredActivity; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.components.ContactFilterToolbar; +import org.thoughtcrime.securesms.contacts.ContactsCursorLoader; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; +import org.thoughtcrime.securesms.util.DynamicTheme; +import org.whispersystems.libsignal.util.guava.Optional; + +public class BlockedUsersActivity extends PassphraseRequiredActivity implements BlockedUsersFragment.Listener, ContactSelectionListFragment.OnContactSelectedListener { + + private static final String CONTACT_SELECTION_FRAGMENT = "Contact.Selection.Fragment"; + + private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); + + private BlockedUsersViewModel viewModel; + + @Override + protected void onCreate(Bundle savedInstanceState, boolean ready) { + super.onCreate(savedInstanceState, ready); + + dynamicTheme.onCreate(this); + + setContentView(R.layout.blocked_users_activity); + + BlockedUsersRepository repository = new BlockedUsersRepository(this); + BlockedUsersViewModel.Factory factory = new BlockedUsersViewModel.Factory(repository); + + viewModel = ViewModelProviders.of(this, factory).get(BlockedUsersViewModel.class); + + ViewSwitcher viewSwitcher = findViewById(R.id.toolbar_switcher); + Toolbar toolbar = findViewById(R.id.toolbar); + ContactFilterToolbar contactFilterToolbar = findViewById(R.id.filter_toolbar); + View container = findViewById(R.id.fragment_container); + + toolbar.setNavigationOnClickListener(unused -> onBackPressed()); + contactFilterToolbar.setNavigationOnClickListener(unused -> onBackPressed()); + contactFilterToolbar.setOnFilterChangedListener(query -> { + Fragment fragment = getSupportFragmentManager().findFragmentByTag(CONTACT_SELECTION_FRAGMENT); + if (fragment != null) { + ((ContactSelectionListFragment) fragment).setQueryFilter(query); + } + }); + contactFilterToolbar.setHint(R.string.BlockedUsersActivity__add_blocked_user); + + //noinspection CodeBlock2Expr + getSupportFragmentManager().addOnBackStackChangedListener(() -> { + viewSwitcher.setDisplayedChild(getSupportFragmentManager().getBackStackEntryCount()); + }); + + getSupportFragmentManager().beginTransaction() + .add(R.id.fragment_container, new BlockedUsersFragment()) + .commit(); + + viewModel.getEvents().observe(this, event -> handleEvent(container, event)); + } + + @Override + protected void onResume() { + super.onResume(); + + dynamicTheme.onResume(this); + } + + @Override + public boolean onBeforeContactSelected(Optional recipientId, String number) { + final String displayName = recipientId.transform(id -> Recipient.resolved(id).getDisplayName(this)).or(number); + + AlertDialog confirmationDialog = new AlertDialog.Builder(BlockedUsersActivity.this) + .setTitle(R.string.BlockedUsersActivity__block_user) + .setMessage(getString(R.string.BlockedUserActivity__s_will_not_be_able_to, displayName)) + .setPositiveButton(R.string.BlockedUsersActivity__block, (dialog, which) -> { + if (recipientId.isPresent()) { + viewModel.block(recipientId.get()); + } else { + viewModel.createAndBlock(number); + } + dialog.dismiss(); + onBackPressed(); + }) + .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()) + .setCancelable(true) + .create(); + + confirmationDialog.setOnShowListener(dialog -> { + confirmationDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.RED); + }); + + confirmationDialog.show(); + + return false; + } + + @Override + public void onContactDeselected(Optional recipientId, String number) { + + } + + @Override + public void handleAddUserToBlockedList() { + ContactSelectionListFragment fragment = new ContactSelectionListFragment(); + Intent intent = getIntent(); + + fragment.setOnContactSelectedListener(this); + + intent.putExtra(ContactSelectionListFragment.REFRESHABLE, false); + intent.putExtra(ContactSelectionListFragment.SELECTION_LIMITS, 1); + intent.putExtra(ContactSelectionListFragment.HIDE_COUNT, true); + intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, + ContactsCursorLoader.DisplayMode.FLAG_PUSH | + ContactsCursorLoader.DisplayMode.FLAG_SMS | + ContactsCursorLoader.DisplayMode.FLAG_ACTIVE_GROUPS | + ContactsCursorLoader.DisplayMode.FLAG_INACTIVE_GROUPS | + ContactsCursorLoader.DisplayMode.FLAG_BLOCK); + + getSupportFragmentManager().beginTransaction() + .replace(R.id.fragment_container, fragment, CONTACT_SELECTION_FRAGMENT) + .addToBackStack(null) + .commit(); + } + + private void handleEvent(@NonNull View view, @NonNull BlockedUsersViewModel.Event event) { + final String displayName; + + if (event.getRecipient() == null) { + displayName = event.getNumber(); + } else { + displayName = event.getRecipient().getDisplayName(this); + } + + final @StringRes int messageResId; + switch (event.getEventType()) { + case BLOCK_SUCCEEDED: + messageResId = R.string.BlockedUsersActivity__s_has_been_blocked; + break; + case BLOCK_FAILED: + messageResId = R.string.BlockedUsersActivity__failed_to_block_s; + break; + case UNBLOCK_SUCCEEDED: + messageResId = R.string.BlockedUsersActivity__s_has_been_unblocked; + break; + default: + throw new IllegalArgumentException("Unsupported event type " + event); + } + + Snackbar.make(view, getString(messageResId, displayName), Snackbar.LENGTH_SHORT).show(); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersAdapter.java new file mode 100644 index 0000000000..f5a2dd091f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersAdapter.java @@ -0,0 +1,96 @@ +package org.thoughtcrime.securesms.blocked; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.util.Consumer; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; +import androidx.recyclerview.widget.RecyclerView; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.components.AvatarImageView; +import org.thoughtcrime.securesms.recipients.Recipient; + +import java.util.Objects; + +final class BlockedUsersAdapter extends ListAdapter { + + private final RecipientClickedListener recipientClickedListener; + + BlockedUsersAdapter(@NonNull RecipientClickedListener recipientClickedListener) { + super(new RecipientDiffCallback()); + + this.recipientClickedListener = recipientClickedListener; + } + + @Override + public @NonNull ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.blocked_users_adapter_item, parent, false), + position -> recipientClickedListener.onRecipientClicked(Objects.requireNonNull(getItem(position)))); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(Objects.requireNonNull(getItem(position))); + } + + static final class ViewHolder extends RecyclerView.ViewHolder { + + private final AvatarImageView avatar; + private final TextView displayName; + private final TextView numberOrUsername; + + public ViewHolder(@NonNull View itemView, Consumer clickConsumer) { + super(itemView); + + this.avatar = itemView.findViewById(R.id.avatar); + this.displayName = itemView.findViewById(R.id.display_name); + this.numberOrUsername = itemView.findViewById(R.id.number_or_username); + + itemView.setOnClickListener(unused -> { + if (getAdapterPosition() != RecyclerView.NO_POSITION) { + clickConsumer.accept(getAdapterPosition()); + } + }); + } + + public void bind(@NonNull Recipient recipient) { + avatar.setAvatar(recipient); + displayName.setText(recipient.getDisplayName(itemView.getContext())); + + if (recipient.hasAUserSetDisplayName(itemView.getContext())) { + String identifier = recipient.getE164().or(recipient.getUsername()).orNull(); + + if (identifier != null) { + numberOrUsername.setText(identifier); + numberOrUsername.setVisibility(View.VISIBLE); + } else { + numberOrUsername.setVisibility(View.GONE); + } + } else { + numberOrUsername.setVisibility(View.GONE); + } + } + } + + private static final class RecipientDiffCallback extends DiffUtil.ItemCallback { + + @Override + public boolean areItemsTheSame(@NonNull Recipient oldItem, @NonNull Recipient newItem) { + return oldItem.equals(newItem); + } + + @Override + public boolean areContentsTheSame(@NonNull Recipient oldItem, @NonNull Recipient newItem) { + return oldItem.equals(newItem); + } + } + + interface RecipientClickedListener { + void onRecipientClicked(@NonNull Recipient recipient); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersFragment.java b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersFragment.java new file mode 100644 index 0000000000..eb7f7e7c22 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersFragment.java @@ -0,0 +1,100 @@ +package org.thoughtcrime.securesms.blocked; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Color; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.RecyclerView; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.recipients.Recipient; + +public class BlockedUsersFragment extends Fragment { + + private BlockedUsersViewModel viewModel; + private Listener listener; + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + + if (context instanceof Listener) { + listener = (Listener) context; + } else { + throw new ClassCastException("Expected context to implement Listener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + + listener = null; + } + + @Override + public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.blocked_users_fragment, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + View addUser = view.findViewById(R.id.add_blocked_user_touch_target); + RecyclerView recycler = view.findViewById(R.id.blocked_users_recycler); + View empty = view.findViewById(R.id.no_blocked_users); + BlockedUsersAdapter adapter = new BlockedUsersAdapter(this::handleRecipientClicked); + + recycler.setAdapter(adapter); + + addUser.setOnClickListener(unused -> { + if (listener != null) { + listener.handleAddUserToBlockedList(); + } + }); + + viewModel = ViewModelProviders.of(requireActivity()).get(BlockedUsersViewModel.class); + viewModel.getRecipients().observe(getViewLifecycleOwner(), list -> { + if (list.isEmpty()) { + empty.setVisibility(View.VISIBLE); + } else { + empty.setVisibility(View.GONE); + } + + adapter.submitList(list); + }); + } + + private void handleRecipientClicked(@NonNull Recipient recipient) { + AlertDialog confirmationDialog = new AlertDialog.Builder(requireContext()) + .setTitle(R.string.BlockedUsersActivity__unblock_user) + .setMessage(getString(R.string.BlockedUsersActivity__do_you_want_to_unblock_s, recipient.getDisplayName(requireContext()))) + .setPositiveButton(R.string.BlockedUsersActivity__unblock, (dialog, which) -> { + viewModel.unblock(recipient.getId()); + dialog.dismiss(); + }) + .setNegativeButton(android.R.string.cancel, (dialog, which) -> { + dialog.dismiss(); + }) + .setCancelable(true) + .create(); + + confirmationDialog.setOnShowListener(dialog -> { + confirmationDialog.getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(Color.RED); + }); + + confirmationDialog.show(); + } + + interface Listener { + void handleAddUserToBlockedList(); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersRepository.java b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersRepository.java new file mode 100644 index 0000000000..4c387a94f3 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersRepository.java @@ -0,0 +1,76 @@ +package org.thoughtcrime.securesms.blocked; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.core.util.Consumer; + +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.RecipientDatabase; +import org.thoughtcrime.securesms.groups.GroupChangeBusyException; +import org.thoughtcrime.securesms.groups.GroupChangeFailedException; +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.recipients.RecipientUtil; +import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +class BlockedUsersRepository { + + private static final String TAG = Log.tag(BlockedUsersRepository.class); + + private final Context context; + + BlockedUsersRepository(@NonNull Context context) { + this.context = context; + } + + void getBlocked(@NonNull Consumer> blockedUsers) { + SignalExecutors.BOUNDED.execute(() -> { + RecipientDatabase db = DatabaseFactory.getRecipientDatabase(context); + try (RecipientDatabase.RecipientReader reader = db.readerForBlocked(db.getBlocked())) { + int count = reader.getCount(); + if (count == 0) { + blockedUsers.accept(Collections.emptyList()); + } else { + List recipients = new ArrayList<>(); + while (reader.getNext() != null) { + recipients.add(reader.getCurrent()); + } + blockedUsers.accept(recipients); + } + } + }); + } + + void block(@NonNull RecipientId recipientId, @NonNull Runnable success, @NonNull Runnable failure) { + SignalExecutors.BOUNDED.execute(() -> { + try { + RecipientUtil.block(context, Recipient.resolved(recipientId)); + success.run(); + } catch (IOException | GroupChangeFailedException | GroupChangeBusyException e) { + Log.w(TAG, "block: failed to block recipient: ", e); + failure.run(); + } + }); + } + + void createAndBlock(@NonNull String number, @NonNull Runnable success) { + SignalExecutors.BOUNDED.execute(() -> { + RecipientUtil.blockNonGroup(context, Recipient.external(context, number)); + success.run(); + }); + } + + void unblock(@NonNull RecipientId recipientId, @NonNull Runnable success) { + SignalExecutors.BOUNDED.execute(() -> { + RecipientUtil.unblock(context, Recipient.resolved(recipientId)); + success.run(); + }); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersViewModel.java new file mode 100644 index 0000000000..f2d6ef0e2f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersViewModel.java @@ -0,0 +1,115 @@ +package org.thoughtcrime.securesms.blocked; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.util.SingleLiveEvent; + +import java.util.List; +import java.util.Objects; + +public class BlockedUsersViewModel extends ViewModel { + + private final BlockedUsersRepository repository; + private final MutableLiveData> recipients; + private final SingleLiveEvent events = new SingleLiveEvent<>(); + + private BlockedUsersViewModel(@NonNull BlockedUsersRepository repository) { + this.repository = repository; + this.recipients = new MutableLiveData<>(); + + loadRecipients(); + } + + public LiveData> getRecipients() { + return recipients; + } + + public LiveData getEvents() { + return events; + } + + void block(@NonNull RecipientId recipientId) { + repository.block(recipientId, + () -> { + loadRecipients(); + events.postValue(new Event(EventType.BLOCK_SUCCEEDED, Recipient.resolved(recipientId))); + }, + () -> events.postValue(new Event(EventType.BLOCK_FAILED, Recipient.resolved(recipientId)))); + } + + void createAndBlock(@NonNull String number) { + repository.createAndBlock(number, () -> { + loadRecipients(); + events.postValue(new Event(EventType.BLOCK_SUCCEEDED, number)); + }); + } + + void unblock(@NonNull RecipientId recipientId) { + repository.unblock(recipientId, () -> { + loadRecipients(); + events.postValue(new Event(EventType.UNBLOCK_SUCCEEDED, Recipient.resolved(recipientId))); + }); + } + + private void loadRecipients() { + repository.getBlocked(recipients::postValue); + } + + enum EventType { + BLOCK_SUCCEEDED, + BLOCK_FAILED, + UNBLOCK_SUCCEEDED + } + + public static final class Event { + + private final EventType eventType; + private final Recipient recipient; + private final String number; + + private Event(@NonNull EventType eventType, @NonNull Recipient recipient) { + this.eventType = eventType; + this.recipient = recipient; + this.number = null; + } + + private Event(@NonNull EventType eventType, @NonNull String number) { + this.eventType = eventType; + this.recipient = null; + this.number = number; + } + + public @Nullable Recipient getRecipient() { + return recipient; + } + + public @Nullable String getNumber() { + return number; + } + + public @NonNull EventType getEventType() { + return eventType; + } + } + + public static class Factory implements ViewModelProvider.Factory { + + private final BlockedUsersRepository repository; + + public Factory(@NonNull BlockedUsersRepository repository) { + this.repository = repository; + } + + @Override + public @NonNull T create(@NonNull Class modelClass) { + return Objects.requireNonNull(modelClass.cast(new BlockedUsersViewModel(repository))); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java index 7b4ed13f12..294aa1a3a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java @@ -60,6 +60,7 @@ public class ContactsCursorLoader extends CursorLoader { public static final int FLAG_ACTIVE_GROUPS = 1 << 2; public static final int FLAG_INACTIVE_GROUPS = 1 << 3; public static final int FLAG_SELF = 1 << 4; + public static final int FLAG_BLOCK = 1 << 5; public static final int FLAG_ALL = FLAG_PUSH | FLAG_SMS | FLAG_ACTIVE_GROUPS | FLAG_INACTIVE_GROUPS | FLAG_SELF; } @@ -342,8 +343,13 @@ public class ContactsCursorLoader extends CursorLoader { } private String getUnknownContactTitle() { - return getContext().getString(newConversation(mode) ? R.string.contact_selection_list__unknown_contact - : R.string.contact_selection_list__unknown_contact_add_to_group); + if (blockUser(mode)) { + return getContext().getString(R.string.contact_selection_list__unknown_contact_block); + } else if (newConversation(mode)) { + return getContext().getString(R.string.contact_selection_list__unknown_contact); + } else { + return getContext().getString(R.string.contact_selection_list__unknown_contact_add_to_group); + } } private @NonNull Cursor filterNonPushContacts(@NonNull Cursor cursor) { @@ -382,6 +388,10 @@ public class ContactsCursorLoader extends CursorLoader { return flagSet(mode, DisplayMode.FLAG_SELF); } + private static boolean blockUser(int mode) { + return flagSet(mode, DisplayMode.FLAG_BLOCK); + } + private static boolean newConversation(int mode) { return groupsEnabled(mode); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 15cd5ec055..0b5bb0e414 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -3018,6 +3018,11 @@ public class RecipientDatabase extends Database { return getCurrent(); } + public int getCount() { + if (cursor != null) return cursor.getCount(); + else return 0; + } + public void close() { cursor.close(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/BlockedContactsLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/BlockedContactsLoader.java deleted file mode 100644 index 0707a6d5f1..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/BlockedContactsLoader.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.thoughtcrime.securesms.database.loaders; - -import android.content.Context; -import android.database.Cursor; - -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.util.AbstractCursorLoader; - -public class BlockedContactsLoader extends AbstractCursorLoader { - - public BlockedContactsLoader(Context context) { - super(context); - } - - @Override - public Cursor getCursor() { - return DatabaseFactory.getRecipientDatabase(getContext()).getBlocked(); - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java index 7dd5a0f7a3..1029bedda1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java @@ -30,9 +30,9 @@ import com.google.android.material.snackbar.Snackbar; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationPreferencesActivity; -import org.thoughtcrime.securesms.BlockedContactsActivity; import org.thoughtcrime.securesms.PassphraseChangeActivity; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.blocked.BlockedUsersActivity; import org.thoughtcrime.securesms.components.SwitchPreferenceCompat; import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; @@ -280,7 +280,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment private class BlockedContactsClickListener implements Preference.OnPreferenceClickListener { @Override public boolean onPreferenceClick(Preference preference) { - Intent intent = new Intent(getActivity(), BlockedContactsActivity.class); + Intent intent = new Intent(getActivity(), BlockedUsersActivity.class); startActivity(intent); return true; } diff --git a/app/src/main/res/layout/blocked_contact_list_item.xml b/app/src/main/res/layout/blocked_contact_list_item.xml deleted file mode 100644 index 2a83568a18..0000000000 --- a/app/src/main/res/layout/blocked_contact_list_item.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/layout/blocked_contacts_fragment.xml b/app/src/main/res/layout/blocked_contacts_fragment.xml deleted file mode 100644 index 49ccc82aa6..0000000000 --- a/app/src/main/res/layout/blocked_contacts_fragment.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/blocked_users_activity.xml b/app/src/main/res/layout/blocked_users_activity.xml new file mode 100644 index 0000000000..6b4923ebaa --- /dev/null +++ b/app/src/main/res/layout/blocked_users_activity.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/blocked_users_adapter_item.xml b/app/src/main/res/layout/blocked_users_adapter_item.xml new file mode 100644 index 0000000000..bf842af77d --- /dev/null +++ b/app/src/main/res/layout/blocked_users_adapter_item.xml @@ -0,0 +1,41 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/blocked_users_fragment.xml b/app/src/main/res/layout/blocked_users_fragment.xml new file mode 100644 index 0000000000..bd959d5856 --- /dev/null +++ b/app/src/main/res/layout/blocked_users_fragment.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 02513b0141..c23b1feed2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -97,8 +97,18 @@ Error playing audio! - - Blocked contacts + + Blocked users + Add blocked user + Blocked users will not be able to call you or send you messages. + No blocked users + Block user? + \"%1$s\" will not be able to call you or send you messages. + Block + Unblock user? + Do you want to unblock \"%1$s\"? + Unblock + Block and leave %1$s? @@ -2253,7 +2263,7 @@ Typing indicators If typing indicators are disabled, you won\'t be able to see typing indicators from others. Request keyboard to disable personalized learning - Blocked contacts + Blocked users When using mobile data When using Wi-Fi When roaming @@ -2351,6 +2361,7 @@ New message to… + Block user Add to group @@ -2824,6 +2835,9 @@ %1$s/%2$s + \"%1$s\" has been blocked. + Failed to block \"%1$s\" + \"%1$s\" has been unblocked. Review Members diff --git a/app/src/main/res/xml/preferences_app_protection.xml b/app/src/main/res/xml/preferences_app_protection.xml index 730c14d931..545cc630d5 100644 --- a/app/src/main/res/xml/preferences_app_protection.xml +++ b/app/src/main/res/xml/preferences_app_protection.xml @@ -95,7 +95,7 @@ + android:title="@string/preferences_app_protection__blocked_users" />