From 1c3636eedd8d675fd7e76c35ab719ac95afbd8c2 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Mon, 20 Mar 2023 13:16:56 -0300 Subject: [PATCH] Add undo-ability to call tab deletion. --- .../securesms/ContactSelectionActivity.java | 2 +- .../ContactSelectionListFragment.java | 11 ++- .../securesms/InviteActivity.java | 2 +- .../securesms/NewConversationActivity.java | 2 +- .../blocked/BlockedUsersActivity.java | 2 +- .../securesms/calls/log/CallLogAdapter.kt | 18 +++-- .../securesms/calls/log/CallLogFragment.kt | 65 +++++++++++------- .../securesms/calls/log/CallLogRepository.kt | 10 +-- .../calls/log/CallLogStagedDeletion.kt | 42 ++++++++++++ .../securesms/calls/log/CallLogViewModel.kt | 68 +++++++++++++------ .../securesms/calls/new/NewCallActivity.kt | 65 ++++++++++++++++++ .../profiles/SelectRecipientsFragment.kt | 2 +- .../ui/addmembers/AddMembersActivity.java | 2 +- .../ui/addtogroup/AddToGroupsActivity.java | 2 +- .../ui/creategroup/CreateGroupActivity.java | 2 +- .../PaymentRecipientSelectionFragment.java | 2 +- .../BaseStoryRecipientSelectionFragment.kt | 2 +- app/src/main/res/layout/call_log_fragment.xml | 24 +++++++ app/src/main/res/values/strings.xml | 6 ++ 19 files changed, 262 insertions(+), 67 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogStagedDeletion.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionActivity.java b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionActivity.java index 0c9fe3da23..63e76eb580 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionActivity.java @@ -125,7 +125,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit } @Override - public void onBeforeContactSelected(@NonNull Optional recipientId, String number, @NonNull Consumer callback) { + public void onBeforeContactSelected(boolean isFromUnknownSearchKey, @NonNull Optional recipientId, String number, @NonNull Consumer callback) { callback.accept(true); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java index b35a6bff0b..ebe9a01794 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java @@ -638,6 +638,7 @@ public final class ContactSelectionListFragment extends LoggingFragment { private class ListClickListener { public void onItemClick(ContactSearchKey contact) { + boolean isUnknown = contact instanceof ContactSearchKey.UnknownRecipientKey; SelectedContact selectedContact = contact.requireSelectedContact(); if (!canSelectSelf && !selectedContact.hasUsername() && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) { @@ -668,7 +669,7 @@ public final class ContactSelectionListFragment extends LoggingFragment { SelectedContact selected = SelectedContact.forUsername(recipient.getId(), username); if (onContactSelectedListener != null) { - onContactSelectedListener.onBeforeContactSelected(Optional.of(recipient.getId()), null, allowed -> { + onContactSelectedListener.onBeforeContactSelected(true, Optional.of(recipient.getId()), null, allowed -> { if (allowed) { markContactSelected(selected); } @@ -686,7 +687,11 @@ public final class ContactSelectionListFragment extends LoggingFragment { }); } else { if (onContactSelectedListener != null) { - onContactSelectedListener.onBeforeContactSelected(Optional.ofNullable(selectedContact.getRecipientId()), selectedContact.getNumber(), allowed -> { + onContactSelectedListener.onBeforeContactSelected( + isUnknown, + Optional.ofNullable(selectedContact.getRecipientId()), + selectedContact.getNumber(), + allowed -> { if (allowed) { markContactSelected(selectedContact); } @@ -955,7 +960,7 @@ public final class ContactSelectionListFragment extends LoggingFragment { /** * Provides an opportunity to disallow selecting an item. Call the callback with false to disallow, or true to allow it. */ - void onBeforeContactSelected(@NonNull Optional recipientId, @Nullable String number, @NonNull Consumer callback); + void onBeforeContactSelected(boolean isFromUnknownSearchKey, @NonNull Optional recipientId, @Nullable String number, @NonNull Consumer callback); void onContactDeselected(@NonNull Optional recipientId, @Nullable String number); diff --git a/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java b/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java index 5b48afe4c6..28f3fb541d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java @@ -136,7 +136,7 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac } @Override - public void onBeforeContactSelected(@NonNull Optional recipientId, String number, @NonNull Consumer callback) { + public void onBeforeContactSelected(boolean isFromUnknownSearchKey, @NonNull Optional recipientId, String number, @NonNull Consumer callback) { updateSmsButtonText(contactsFragment.getSelectedContacts().size() + 1); callback.accept(true); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java index ae0f022bae..3ce549f823 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java @@ -102,7 +102,7 @@ public class NewConversationActivity extends ContactSelectionActivity } @Override - public void onBeforeContactSelected(@NonNull Optional recipientId, String number, @NonNull Consumer callback) { + public void onBeforeContactSelected(boolean isFromUnknownSearchKey, @NonNull Optional recipientId, String number, @NonNull Consumer callback) { boolean smsSupported = SignalStore.misc().getSmsExportPhase().allowSmsFeatures(); if (recipientId.isPresent()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersActivity.java b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersActivity.java index 049faf8e98..52c32e2e2a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersActivity.java @@ -97,7 +97,7 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements } @Override - public void onBeforeContactSelected(@NonNull Optional recipientId, String number, @NonNull Consumer callback) { + public void onBeforeContactSelected(boolean isFromUnknownSearchKey, @NonNull Optional recipientId, String number, @NonNull Consumer callback) { final String displayName = recipientId.map(id -> Recipient.resolved(id).getDisplayName(this)).orElse(number); AlertDialog confirmationDialog = new MaterialAlertDialogBuilder(this) diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogAdapter.kt index 5b8d04ecfe..7eb8cd77f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogAdapter.kt @@ -53,15 +53,24 @@ class CallLogAdapter( ) } - fun submitCallRows(rows: List, selectionState: CallLogSelectionState) { - submitList( - rows.filterNotNull().map { + fun submitCallRows( + rows: List, + selectionState: CallLogSelectionState, + stagedDeletion: CallLogStagedDeletion? + ): Int { + val filteredRows = rows + .filterNotNull() + .filterNot { stagedDeletion?.isStagedForDeletion(it.id) == true } + .map { when (it) { is CallLogRow.Call -> CallModel(it, selectionState, itemCount) is CallLogRow.ClearFilter -> ClearFilterModel() } } - ) + + submitList(filteredRows) + + return filteredRows.size } private class CallModel( @@ -172,6 +181,7 @@ class CallLogAdapter( binding.callType.setImageResource(R.drawable.symbol_phone_24) binding.callType.setOnClickListener { onStartAudioCallClicked(peer) } } + CallTable.Type.VIDEO_CALL -> { binding.callType.setImageResource(R.drawable.symbol_video_24) binding.callType.setOnClickListener { onStartVideoCallClicked(peer) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogFragment.kt index 2afa5ceaa2..6be8bdd38f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogFragment.kt @@ -47,6 +47,7 @@ import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.fragments.requireListener +import org.thoughtcrime.securesms.util.visible import java.util.Objects /** @@ -99,18 +100,19 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal disposables.bindTo(viewLifecycleOwner) adapter.setPagingController(viewModel.controller) - disposables += Flowables.combineLatest(viewModel.data, viewModel.selected) + disposables += Flowables.combineLatest(viewModel.data, viewModel.selectedAndStagedDeletion) .observeOn(AndroidSchedulers.mainThread()) .subscribe { (data, selected) -> - adapter.submitCallRows(data, selected) + val filteredCount = adapter.submitCallRows(data, selected.first, selected.second) + binding.emptyState.visible = filteredCount == 0 } - disposables += Flowables.combineLatest(viewModel.selected, viewModel.totalCount) + disposables += Flowables.combineLatest(viewModel.selectedAndStagedDeletion, viewModel.totalCount) .distinctUntilChanged() .observeOn(AndroidSchedulers.mainThread()) .subscribe { (selected, totalCount) -> - if (selected.isNotEmpty(totalCount)) { - callLogActionMode.setCount(selected.count(totalCount)) + if (selected.first.isNotEmpty(totalCount)) { + callLogActionMode.setCount(selected.first.count(totalCount)) } else { callLogActionMode.end() } @@ -181,19 +183,23 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal } private fun handleDeleteSelectedRows() { + val count = callLogActionMode.getCount() MaterialAlertDialogBuilder(requireContext()) - .setTitle(resources.getQuantityString(R.plurals.CallLogFragment__delete_d_calls, callLogActionMode.getCount(), callLogActionMode.getCount())) + .setTitle(resources.getQuantityString(R.plurals.CallLogFragment__delete_d_calls, count, count)) .setPositiveButton(R.string.CallLogFragment__delete_for_me) { _, _ -> - disposables += viewModel.deleteSelection() - .observeOn(AndroidSchedulers.mainThread()) - .subscribeBy(onSuccess = { - callLogActionMode.end() - Snackbar.make( - binding.root, - resources.getQuantityString(R.plurals.CallLogFragment__d_calls_deleted, it, it), - Snackbar.LENGTH_SHORT - ).show() - }) + viewModel.stageSelectionDeletion() + callLogActionMode.end() + Snackbar + .make( + binding.root, + resources.getQuantityString(R.plurals.CallLogFragment__d_calls_deleted, count, count), + Snackbar.LENGTH_SHORT + ) + .addCallback(SnackbarDeletionCallback()) + .setAction(R.string.CallLogFragment__undo) { + viewModel.cancelStagedDeletion() + } + .show() } .setNegativeButton(android.R.string.cancel) { _, _ -> } .show() @@ -296,15 +302,18 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal MaterialAlertDialogBuilder(requireContext()) .setTitle(resources.getQuantityString(R.plurals.CallLogFragment__delete_d_calls, 1, 1)) .setPositiveButton(R.string.CallLogFragment__delete_for_me) { _, _ -> - disposables += viewModel.deleteCall(call) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeBy(onSuccess = { - Snackbar.make( - binding.root, - resources.getQuantityString(R.plurals.CallLogFragment__d_calls_deleted, it, it), - Snackbar.LENGTH_SHORT - ).show() - }) + viewModel.stageCallDeletion(call) + Snackbar + .make( + binding.root, + resources.getQuantityString(R.plurals.CallLogFragment__d_calls_deleted, 1, 1), + Snackbar.LENGTH_SHORT + ) + .addCallback(SnackbarDeletionCallback()) + .setAction(R.string.CallLogFragment__undo) { + viewModel.cancelStagedDeletion() + } + .show() } .setNegativeButton(android.R.string.cancel) { _, _ -> } .show() @@ -356,6 +365,12 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal } } + private inner class SnackbarDeletionCallback : Snackbar.Callback() { + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + viewModel.commitStagedDeletion() + } + } + interface Callback { fun onMultiSelectStarted() fun onMultiSelectFinished() diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRepository.kt index c440c9bf9b..8020ea9513 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRepository.kt @@ -1,7 +1,7 @@ package org.thoughtcrime.securesms.calls.log +import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers import org.thoughtcrime.securesms.database.DatabaseObserver import org.thoughtcrime.securesms.database.SignalDatabase @@ -42,16 +42,16 @@ class CallLogRepository : CallLogPagedDataSource.CallRepository { fun deleteSelectedCallLogs( selectedMessageIds: Set - ): Single { - return Single.fromCallable { + ): Completable { + return Completable.fromAction { SignalDatabase.messages.deleteCallUpdates(selectedMessageIds) }.observeOn(Schedulers.io()) } fun deleteAllCallLogsExcept( selectedMessageIds: Set - ): Single { - return Single.fromCallable { + ): Completable { + return Completable.fromAction { SignalDatabase.messages.deleteAllCallUpdatesExcept(selectedMessageIds) }.observeOn(Schedulers.io()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogStagedDeletion.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogStagedDeletion.kt new file mode 100644 index 0000000000..e9ea12200d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogStagedDeletion.kt @@ -0,0 +1,42 @@ +package org.thoughtcrime.securesms.calls.log + +import androidx.annotation.MainThread + +/** + * Encapsulates a single deletion action + */ +class CallLogStagedDeletion( + private val stateSnapshot: CallLogSelectionState, + private val repository: CallLogRepository +) { + + private var isCommitted = false + + fun isStagedForDeletion(id: CallLogRow.Id): Boolean { + return stateSnapshot.contains(id) + } + + @MainThread + fun cancel() { + isCommitted = true + } + + @MainThread + fun commit() { + if (isCommitted) { + return + } + + isCommitted = true + val messageIds = stateSnapshot.selected() + .filterIsInstance() + .map { it.messageId } + .toSet() + + if (stateSnapshot.isExclusionary()) { + repository.deleteAllCallLogsExcept(messageIds).subscribe() + } else { + repository.deleteSelectedCallLogs(messageIds).subscribe() + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogViewModel.kt index 16f398d5e9..1d91e31245 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogViewModel.kt @@ -1,9 +1,9 @@ package org.thoughtcrime.securesms.calls.log +import androidx.annotation.MainThread import androidx.lifecycle.ViewModel import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.processors.BehaviorProcessor @@ -31,9 +31,9 @@ class CallLogViewModel( val controller = ProxyPagingController() val data: Flowable> = pagedData.switchMap { it.data.toFlowable(BackpressureStrategy.LATEST) } - val selected: Flowable = callLogStore + val selectedAndStagedDeletion: Flowable> = callLogStore .stateFlowable - .map { it.selectionState } + .map { it.selectionState to it.stagedDeletion } val totalCount: Flowable = Flowable.combineLatest(distinctQueryFilterPairs, data) { a, _ -> a } .map { (query, filter) -> callLogRepository.getCallsCount(query, filter) } @@ -73,6 +73,7 @@ class CallLogViewModel( } override fun onCleared() { + commitStagedDeletion() disposables.dispose() } @@ -90,8 +91,48 @@ class CallLogViewModel( } } - fun deleteCall(call: CallLogRow.Call): Single { - return callLogRepository.deleteSelectedCallLogs(setOf(call.call.messageId)) + @MainThread + fun stageCallDeletion(call: CallLogRow.Call) { + callLogStore.state.stagedDeletion?.commit() + callLogStore.update { + it.copy( + stagedDeletion = CallLogStagedDeletion( + CallLogSelectionState.empty().toggle(call.id), + callLogRepository + ) + ) + } + } + + @MainThread + fun stageSelectionDeletion() { + callLogStore.state.stagedDeletion?.commit() + callLogStore.update { + it.copy( + stagedDeletion = CallLogStagedDeletion( + it.selectionState, + callLogRepository + ) + ) + } + } + + fun commitStagedDeletion() { + callLogStore.state.stagedDeletion?.commit() + callLogStore.update { + it.copy( + stagedDeletion = null + ) + } + } + + fun cancelStagedDeletion() { + callLogStore.state.stagedDeletion?.cancel() + callLogStore.update { + it.copy( + stagedDeletion = null + ) + } } fun clearSelected() { @@ -108,23 +149,10 @@ class CallLogViewModel( callLogStore.update { it.copy(filter = filter) } } - fun deleteSelection(): Single { - val stateSnapshot = callLogStore.state - val messageIds: Set = stateSnapshot.selectionState.selected() - .filterIsInstance() - .map { it.messageId } - .toSet() - - return if (stateSnapshot.selectionState.isExclusionary()) { - callLogRepository.deleteAllCallLogsExcept(messageIds) - } else { - callLogRepository.deleteSelectedCallLogs(messageIds) - } - } - private data class CallLogState( val query: String? = null, val filter: CallLogFilter = CallLogFilter.ALL, - val selectionState: CallLogSelectionState = CallLogSelectionState.empty() + val selectionState: CallLogSelectionState = CallLogSelectionState.empty(), + val stagedDeletion: CallLogStagedDeletion? = null ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/new/NewCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/new/NewCallActivity.kt index f72d8f2a05..eb45c32a9e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/new/NewCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/new/NewCallActivity.kt @@ -8,11 +8,23 @@ import android.view.MenuInflater import android.view.MenuItem import androidx.core.app.ActivityCompat import androidx.core.view.MenuProvider +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.signal.core.util.concurrent.SimpleTask +import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.ContactSelectionActivity import org.thoughtcrime.securesms.ContactSelectionListFragment import org.thoughtcrime.securesms.InviteActivity import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.contacts.ContactSelectionDisplayMode +import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery.refresh +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.util.CommunicationActions +import org.thoughtcrime.securesms.util.views.SimpleProgressDialog +import java.io.IOException +import java.util.Optional +import java.util.function.Consumer class NewCallActivity : ContactSelectionActivity(), ContactSelectionListFragment.NewCallCallback { @@ -26,7 +38,60 @@ class NewCallActivity : ContactSelectionActivity(), ContactSelectionListFragment override fun onSelectionChanged() = Unit + override fun onBeforeContactSelected(isFromUnknownSearchKey: Boolean, recipientId: Optional, number: String?, callback: Consumer) { + if (isFromUnknownSearchKey) { + Log.i(TAG, "[onContactSelected] Maybe creating a new recipient.") + if (SignalStore.account().isRegistered) { + Log.i(TAG, "[onContactSelected] Doing contact refresh.") + val progress = SimpleProgressDialog.show(this) + SimpleTask.run(lifecycle, { + var resolved = Recipient.external(this, number!!) + if (!resolved.isRegistered || !resolved.hasServiceId()) { + Log.i(TAG, "[onContactSelected] Not registered or no UUID. Doing a directory refresh.") + resolved = try { + refresh(this, resolved, false) + Recipient.resolved(resolved.id) + } catch (e: IOException) { + Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact.") + return@run null + } + } + resolved + }) { resolved: Recipient? -> + progress.dismiss() + if (resolved != null) { + if (resolved.isRegistered && resolved.hasServiceId()) { + launch(resolved) + } else { + MaterialAlertDialogBuilder(this) + .setMessage(getString(R.string.NewConversationActivity__s_is_not_a_signal_user, resolved.getDisplayName(this))) + .setPositiveButton(android.R.string.ok, null) + .show() + } + } else { + MaterialAlertDialogBuilder(this) + .setMessage(R.string.NetworkFailure__network_error_check_your_connection_and_try_again) + .setPositiveButton(android.R.string.ok, null) + .show() + } + } + } + } + callback.accept(true) + } + + private fun launch(recipient: Recipient) { + if (recipient.isGroup) { + CommunicationActions.startVideoCall(this, recipient) + } else { + CommunicationActions.startVoiceCall(this, recipient) + } + } + companion object { + + private val TAG = Log.tag(NewCallActivity::class.java) + fun createIntent(context: Context): Intent { return Intent(context, NewCallActivity::class.java) .putExtra( diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/SelectRecipientsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/SelectRecipientsFragment.kt index be881b6c34..577082be57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/SelectRecipientsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/SelectRecipientsFragment.kt @@ -113,7 +113,7 @@ class SelectRecipientsFragment : LoggingFragment(), ContactSelectionListFragment return mode or ContactSelectionDisplayMode.FLAG_HIDE_GROUPS_V1 } - override fun onBeforeContactSelected(recipientId: Optional, number: String?, callback: Consumer) { + override fun onBeforeContactSelected(isFromUnknownSearchKey: Boolean, recipientId: Optional, number: String?, callback: Consumer) { if (recipientId.isPresent) { viewModel.select(recipientId.get()) callback.accept(true) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.java index 85bf306c35..dc78fc69e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.java @@ -79,7 +79,7 @@ public class AddMembersActivity extends PushContactSelectionActivity { } @Override - public void onBeforeContactSelected(@NonNull Optional recipientId, String number, @NonNull Consumer callback) { + public void onBeforeContactSelected(boolean isFromUnknownSearchKey, @NonNull Optional recipientId, String number, @NonNull Consumer callback) { if (getGroupId().isV1() && recipientId.isPresent() && !Recipient.resolved(recipientId.get()).hasE164()) { Toast.makeText(this, R.string.AddMembersActivity__this_person_cant_be_added_to_legacy_groups, Toast.LENGTH_SHORT).show(); callback.accept(false); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java index f0011718e3..ca199c681d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java @@ -112,7 +112,7 @@ public final class AddToGroupsActivity extends ContactSelectionActivity { } @Override - public void onBeforeContactSelected(@NonNull Optional recipientId, String number, @NonNull Consumer callback) { + public void onBeforeContactSelected(boolean isFromUnknownSearchKey, @NonNull Optional recipientId, String number, @NonNull Consumer callback) { if (contactsFragment.isMulti()) { throw new UnsupportedOperationException("Not yet built to handle multi-select."); // if (contactsFragment.hasQueryFilter()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java index 599c7adbbc..617d5f88e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java @@ -94,7 +94,7 @@ public class CreateGroupActivity extends ContactSelectionActivity { } @Override - public void onBeforeContactSelected(@NonNull Optional recipientId, String number, @NonNull Consumer callback) { + public void onBeforeContactSelected(boolean isFromUnknownSearchKey, @NonNull Optional recipientId, String number, @NonNull Consumer callback) { if (contactsFragment.hasQueryFilter()) { getContactFilterView().clear(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentRecipientSelectionFragment.java b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentRecipientSelectionFragment.java index d8e6fb47d9..fdd11261a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentRecipientSelectionFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentRecipientSelectionFragment.java @@ -71,7 +71,7 @@ public class PaymentRecipientSelectionFragment extends LoggingFragment implement } @Override - public void onBeforeContactSelected(@NonNull Optional recipientId, @Nullable String number, @NonNull Consumer callback) { + public void onBeforeContactSelected(boolean isFromUnknownSearchKey, @NonNull Optional recipientId, @Nullable String number, @NonNull Consumer callback) { if (recipientId.isPresent()) { SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> Recipient.resolved(recipientId.get()), diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/select/BaseStoryRecipientSelectionFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/select/BaseStoryRecipientSelectionFragment.kt index 0f39f3f6db..634d89003f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/select/BaseStoryRecipientSelectionFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/select/BaseStoryRecipientSelectionFragment.kt @@ -117,7 +117,7 @@ abstract class BaseStoryRecipientSelectionFragment : Fragment(R.layout.stories_b } } - override fun onBeforeContactSelected(recipientId: Optional, number: String?, callback: Consumer) { + override fun onBeforeContactSelected(isFromUnknownSearchKey: Boolean, recipientId: Optional, number: String?, callback: Consumer) { viewModel.addRecipient(recipientId.get()) searchField.setText("") callback.accept(true) diff --git a/app/src/main/res/layout/call_log_fragment.xml b/app/src/main/res/layout/call_log_fragment.xml index de9cf8bc08..6fca1ff24c 100644 --- a/app/src/main/res/layout/call_log_fragment.xml +++ b/app/src/main/res/layout/call_log_fragment.xml @@ -42,6 +42,30 @@ app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:listitem="@layout/conversation_list_item_view" /> + + + + + + + + %1$d call deleted %1$d calls deleted + + Undo + + No calls. + + Get started by calling a friend.