mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-24 19:00:26 +01:00
Ensure owned call links are revoked on delete.
This commit is contained in:
committed by
Cody Henthorne
parent
03a212eee4
commit
290b0fe46f
@@ -75,12 +75,10 @@ class CallLogAdapter(
|
||||
fun submitCallRows(
|
||||
rows: List<CallLogRow?>,
|
||||
selectionState: CallLogSelectionState,
|
||||
stagedDeletion: CallLogStagedDeletion?,
|
||||
onCommit: () -> Unit
|
||||
): Int {
|
||||
val filteredRows = rows
|
||||
.filterNotNull()
|
||||
.filterNot { stagedDeletion?.isStagedForDeletion(it.id) == true }
|
||||
.map {
|
||||
when (it) {
|
||||
is CallLogRow.Call -> CallModel(it, selectionState, itemCount)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.log
|
||||
|
||||
sealed interface CallLogDeletionResult {
|
||||
object Success : CallLogDeletionResult
|
||||
|
||||
object Empty : CallLogDeletionResult
|
||||
data class FailedToRevoke(val failedRevocations: Int) : CallLogDeletionResult
|
||||
data class UnknownFailure(val reason: Throwable) : CallLogDeletionResult
|
||||
}
|
||||
@@ -7,7 +7,9 @@ import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
@@ -27,10 +29,13 @@ import io.reactivex.rxjava3.kotlin.Flowables
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.concurrent.addTo
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.calls.links.details.CallLinkDetailsActivity
|
||||
import org.thoughtcrime.securesms.calls.new.NewCallActivity
|
||||
import org.thoughtcrime.securesms.components.Material3SearchToolbar
|
||||
import org.thoughtcrime.securesms.components.ProgressCardDialogFragment
|
||||
import org.thoughtcrime.securesms.components.ScrollToPositionDelegate
|
||||
import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
import org.thoughtcrime.securesms.components.menu.ActionItem
|
||||
@@ -65,6 +70,10 @@ import java.util.concurrent.TimeUnit
|
||||
@SuppressLint("DiscouragedApi")
|
||||
class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Callbacks, CallLogContextMenu.Callbacks {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(CallLogFragment::class.java)
|
||||
}
|
||||
|
||||
private val viewModel: CallLogViewModel by viewModels()
|
||||
private val binding: CallLogFragmentBinding by ViewBinderDelegate(CallLogFragmentBinding::bind)
|
||||
private val disposables = LifecycleDisposable()
|
||||
@@ -114,24 +123,23 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
)
|
||||
|
||||
disposables += scrollToPositionDelegate
|
||||
disposables += Flowables.combineLatest(viewModel.data, viewModel.selectedAndStagedDeletion)
|
||||
disposables += Flowables.combineLatest(viewModel.data, viewModel.selected)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { (data, selected) ->
|
||||
val filteredCount = adapter.submitCallRows(
|
||||
data,
|
||||
selected.first,
|
||||
selected.second,
|
||||
selected,
|
||||
scrollToPositionDelegate::notifyListCommitted
|
||||
)
|
||||
binding.emptyState.visible = filteredCount == 0
|
||||
}
|
||||
|
||||
disposables += Flowables.combineLatest(viewModel.selectedAndStagedDeletion, viewModel.totalCount)
|
||||
disposables += Flowables.combineLatest(viewModel.selected, viewModel.totalCount)
|
||||
.distinctUntilChanged()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { (selected, totalCount) ->
|
||||
if (selected.first.isNotEmpty(totalCount)) {
|
||||
callLogActionMode.setCount(selected.first.count(totalCount))
|
||||
if (selected.isNotEmpty(totalCount)) {
|
||||
callLogActionMode.setCount(selected.count(totalCount))
|
||||
} else {
|
||||
callLogActionMode.end()
|
||||
}
|
||||
@@ -223,19 +231,8 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(resources.getQuantityString(R.plurals.CallLogFragment__delete_d_calls, count, count))
|
||||
.setPositiveButton(R.string.CallLogFragment__delete_for_me) { _, _ ->
|
||||
viewModel.stageSelectionDeletion()
|
||||
performDeletion(count, 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()
|
||||
@@ -272,6 +269,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
scrollToPositionDelegate.resetScrollPosition()
|
||||
}
|
||||
}
|
||||
|
||||
FilterPullState.OPENING -> {
|
||||
ViewUtil.setMinimumHeight(collapsingToolbarLayout, openHeight)
|
||||
viewModel.setFilter(CallLogFilter.MISSED)
|
||||
@@ -366,20 +364,8 @@ 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) { _, _ ->
|
||||
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()
|
||||
performDeletion(1, viewModel.stageCallDeletion(call))
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
@@ -394,18 +380,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
.setMessage(R.string.CallLogFragment__this_will_permanently_delete_all_call_history)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
callLogActionMode.end()
|
||||
viewModel.stageDeleteAll()
|
||||
Snackbar
|
||||
.make(
|
||||
binding.root,
|
||||
R.string.CallLogFragment__cleared_call_history,
|
||||
Snackbar.LENGTH_SHORT
|
||||
)
|
||||
.addCallback(SnackbarDeletionCallback())
|
||||
.setAction(R.string.CallLogFragment__undo) {
|
||||
viewModel.cancelStagedDeletion()
|
||||
}
|
||||
.show()
|
||||
performDeletion(-1, viewModel.stageDeleteAll())
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
@@ -426,7 +401,59 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
|
||||
private fun isSearchVisible(): Boolean {
|
||||
return requireListener<SearchBinder>().getSearchToolbar().resolved() &&
|
||||
requireListener<SearchBinder>().getSearchToolbar().get().getVisibility() == View.VISIBLE
|
||||
requireListener<SearchBinder>().getSearchToolbar().get().visibility == View.VISIBLE
|
||||
}
|
||||
|
||||
private fun performDeletion(count: Int, callLogStagedDeletion: CallLogStagedDeletion) {
|
||||
var progressDialog: ProgressCardDialogFragment? = null
|
||||
var errorDialog: AlertDialog? = null
|
||||
|
||||
fun cleanUp() {
|
||||
progressDialog?.dismissAllowingStateLoss()
|
||||
progressDialog = null
|
||||
errorDialog?.dismiss()
|
||||
errorDialog = null
|
||||
}
|
||||
|
||||
val snackbarMessage = if (count == -1) {
|
||||
getString(R.string.CallLogFragment__cleared_call_history)
|
||||
} else {
|
||||
resources.getQuantityString(R.plurals.CallLogFragment__d_calls_deleted, count, count)
|
||||
}
|
||||
|
||||
viewModel.delete(callLogStagedDeletion)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnSubscribe {
|
||||
progressDialog = ProgressCardDialogFragment.create(getString(R.string.CallLogFragment__deleting))
|
||||
progressDialog?.show(parentFragmentManager, null)
|
||||
}
|
||||
.doOnDispose { cleanUp() }
|
||||
.subscribeBy {
|
||||
cleanUp()
|
||||
when (it) {
|
||||
CallLogDeletionResult.Empty -> Unit
|
||||
is CallLogDeletionResult.FailedToRevoke -> {
|
||||
errorDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(resources.getQuantityString(R.plurals.CallLogFragment__cant_delete_call_link, it.failedRevocations))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
CallLogDeletionResult.Success -> {
|
||||
Snackbar
|
||||
.make(
|
||||
binding.root,
|
||||
snackbarMessage,
|
||||
Snackbar.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
is CallLogDeletionResult.UnknownFailure -> {
|
||||
Log.w(TAG, "Deletion failed.", it.reason)
|
||||
Toast.makeText(requireContext(), R.string.CallLogFragment__deletion_failed, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
.addTo(disposables)
|
||||
}
|
||||
|
||||
private inner class BottomActionBarControllerCallback : SignalBottomActionBarController.Callback {
|
||||
@@ -454,12 +481,6 @@ 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()
|
||||
|
||||
@@ -2,14 +2,20 @@ 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.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.calls.links.UpdateCallLinkRepository
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.CallLinkPeekJob
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.UpdateCallLinkResult
|
||||
|
||||
class CallLogRepository : CallLogPagedDataSource.CallRepository {
|
||||
class CallLogRepository(
|
||||
private val updateCallLinkRepository: UpdateCallLinkRepository = UpdateCallLinkRepository()
|
||||
) : CallLogPagedDataSource.CallRepository {
|
||||
override fun getCallsCount(query: String?, filter: CallLogFilter): Int {
|
||||
return SignalDatabase.calls.getCallsCount(query, filter)
|
||||
}
|
||||
@@ -61,7 +67,7 @@ class CallLogRepository : CallLogPagedDataSource.CallRepository {
|
||||
selectedCallRowIds: Set<Long>
|
||||
): Completable {
|
||||
return Completable.fromAction {
|
||||
SignalDatabase.calls.deleteCallEvents(selectedCallRowIds)
|
||||
SignalDatabase.calls.deleteNonAdHocCallEvents(selectedCallRowIds)
|
||||
}.observeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
@@ -70,7 +76,63 @@ class CallLogRepository : CallLogPagedDataSource.CallRepository {
|
||||
missedOnly: Boolean
|
||||
): Completable {
|
||||
return Completable.fromAction {
|
||||
SignalDatabase.calls.deleteAllCallEventsExcept(selectedCallRowIds, missedOnly)
|
||||
SignalDatabase.calls.deleteAllNonAdHocCallEventsExcept(selectedCallRowIds, missedOnly)
|
||||
}.observeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the selected call links. We DELETE those links we don't have admin keys for,
|
||||
* and revoke the ones we *do* have admin keys for. We then perform a cleanup step on
|
||||
* terminate to clean up call events.
|
||||
*/
|
||||
fun deleteSelectedCallLinks(
|
||||
selectedCallRowIds: Set<Long>,
|
||||
selectedRoomIds: Set<CallLinkRoomId>
|
||||
): Single<Int> {
|
||||
return Single.fromCallable {
|
||||
val allCallLinkIds = SignalDatabase.calls.getCallLinkRoomIdsFromCallRowIds(selectedCallRowIds) + selectedRoomIds
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinks(allCallLinkIds)
|
||||
SignalDatabase.callLinks.getAdminCallLinks(allCallLinkIds)
|
||||
}.flatMap { callLinksToRevoke ->
|
||||
Single.merge(
|
||||
callLinksToRevoke.map {
|
||||
updateCallLinkRepository.revokeCallLink(it.credentials!!)
|
||||
}
|
||||
).reduce(0) { acc, current ->
|
||||
acc + (if (current is UpdateCallLinkResult.Success) 0 else 1)
|
||||
}
|
||||
}.doOnTerminate {
|
||||
SignalDatabase.calls.updateAdHocCallEventDeletionTimestamps()
|
||||
}.doOnDispose {
|
||||
SignalDatabase.calls.updateAdHocCallEventDeletionTimestamps()
|
||||
}.observeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all but the selected call links. We DELETE those links we don't have admin keys for,
|
||||
* and revoke the ones we *do* have admin keys for. We then perform a cleanup step on
|
||||
* terminate to clean up call events.
|
||||
*/
|
||||
fun deleteAllCallLinksExcept(
|
||||
selectedCallRowIds: Set<Long>,
|
||||
selectedRoomIds: Set<CallLinkRoomId>
|
||||
): Single<Int> {
|
||||
return Single.fromCallable {
|
||||
val allCallLinkIds = SignalDatabase.calls.getCallLinkRoomIdsFromCallRowIds(selectedCallRowIds) + selectedRoomIds
|
||||
SignalDatabase.callLinks.deleteAllNonAdminCallLinksExcept(allCallLinkIds)
|
||||
SignalDatabase.callLinks.getAllAdminCallLinksExcept(allCallLinkIds)
|
||||
}.flatMap { callLinksToRevoke ->
|
||||
Single.merge(
|
||||
callLinksToRevoke.map {
|
||||
updateCallLinkRepository.revokeCallLink(it.credentials!!)
|
||||
}
|
||||
).reduce(0) { acc, current ->
|
||||
acc + (if (current is UpdateCallLinkResult.Success) 0 else 1)
|
||||
}
|
||||
}.doOnTerminate {
|
||||
SignalDatabase.calls.updateAdHocCallEventDeletionTimestamps()
|
||||
}.doOnDispose {
|
||||
SignalDatabase.calls.updateAdHocCallEventDeletionTimestamps()
|
||||
}.observeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.calls.log
|
||||
|
||||
import androidx.annotation.MainThread
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
|
||||
/**
|
||||
* Encapsulates a single deletion action
|
||||
@@ -13,19 +14,13 @@ class CallLogStagedDeletion(
|
||||
|
||||
private var isCommitted = false
|
||||
|
||||
fun isStagedForDeletion(id: CallLogRow.Id): Boolean {
|
||||
return stateSnapshot.contains(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Single<Int> which contains the number of failed call-link revocations.
|
||||
*/
|
||||
@MainThread
|
||||
fun cancel() {
|
||||
isCommitted = true
|
||||
}
|
||||
|
||||
@MainThread
|
||||
fun commit() {
|
||||
fun commit(): Single<Int> {
|
||||
if (isCommitted) {
|
||||
return
|
||||
return Single.just(0)
|
||||
}
|
||||
|
||||
isCommitted = true
|
||||
@@ -35,10 +30,19 @@ class CallLogStagedDeletion(
|
||||
.flatten()
|
||||
.toSet()
|
||||
|
||||
if (stateSnapshot.isExclusionary()) {
|
||||
repository.deleteAllCallLogsExcept(callRowIds, filter == CallLogFilter.MISSED).subscribe()
|
||||
val callLinkIds = stateSnapshot.selected()
|
||||
.filterIsInstance<CallLogRow.Id.CallLink>()
|
||||
.map { it.roomId }
|
||||
.toSet()
|
||||
|
||||
return if (stateSnapshot.isExclusionary()) {
|
||||
repository.deleteAllCallLogsExcept(callRowIds, filter == CallLogFilter.MISSED).andThen(
|
||||
repository.deleteAllCallLinksExcept(callRowIds, callLinkIds)
|
||||
)
|
||||
} else {
|
||||
repository.deleteSelectedCallLogs(callRowIds).subscribe()
|
||||
repository.deleteSelectedCallLogs(callRowIds).andThen(
|
||||
repository.deleteSelectedCallLinks(callRowIds, callLinkIds)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.thoughtcrime.securesms.calls.log
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
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.Maybe
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
@@ -36,9 +38,7 @@ class CallLogViewModel(
|
||||
|
||||
val controller = ProxyPagingController<CallLogRow.Id>()
|
||||
val data: Flowable<MutableList<CallLogRow?>> = pagedData.switchMap { it.data.toFlowable(BackpressureStrategy.LATEST) }
|
||||
val selectedAndStagedDeletion: Flowable<Pair<CallLogSelectionState, CallLogStagedDeletion?>> = callLogStore
|
||||
.stateFlowable
|
||||
.map { it.selectionState to it.stagedDeletion }
|
||||
val selected: Flowable<CallLogSelectionState> = callLogStore.stateFlowable.map { it.selectionState }
|
||||
|
||||
private val _isEmpty: BehaviorProcessor<Boolean> = BehaviorProcessor.createDefault(false)
|
||||
val isEmpty: Boolean get() = _isEmpty.value ?: false
|
||||
@@ -98,7 +98,6 @@ class CallLogViewModel(
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
commitStagedDeletion()
|
||||
disposables.dispose()
|
||||
}
|
||||
|
||||
@@ -121,63 +120,52 @@ class CallLogViewModel(
|
||||
}
|
||||
|
||||
@MainThread
|
||||
fun stageCallDeletion(call: CallLogRow) {
|
||||
callLogStore.state.stagedDeletion?.commit()
|
||||
callLogStore.update {
|
||||
it.copy(
|
||||
stagedDeletion = CallLogStagedDeletion(
|
||||
it.filter,
|
||||
CallLogSelectionState.empty().toggle(call.id),
|
||||
callLogRepository
|
||||
)
|
||||
)
|
||||
}
|
||||
fun stageCallDeletion(call: CallLogRow): CallLogStagedDeletion {
|
||||
return CallLogStagedDeletion(
|
||||
callLogStore.state.filter,
|
||||
CallLogSelectionState.empty().toggle(call.id),
|
||||
callLogRepository
|
||||
)
|
||||
}
|
||||
|
||||
@MainThread
|
||||
fun stageSelectionDeletion() {
|
||||
callLogStore.state.stagedDeletion?.commit()
|
||||
callLogStore.update {
|
||||
it.copy(
|
||||
stagedDeletion = CallLogStagedDeletion(
|
||||
it.filter,
|
||||
it.selectionState,
|
||||
callLogRepository
|
||||
)
|
||||
)
|
||||
}
|
||||
fun stageSelectionDeletion(): CallLogStagedDeletion {
|
||||
return CallLogStagedDeletion(
|
||||
callLogStore.state.filter,
|
||||
callLogStore.state.selectionState,
|
||||
callLogRepository
|
||||
)
|
||||
}
|
||||
|
||||
fun stageDeleteAll() {
|
||||
callLogStore.state.stagedDeletion?.cancel()
|
||||
fun stageDeleteAll(): CallLogStagedDeletion {
|
||||
callLogStore.update {
|
||||
it.copy(
|
||||
selectionState = CallLogSelectionState.empty(),
|
||||
stagedDeletion = CallLogStagedDeletion(
|
||||
it.filter,
|
||||
CallLogSelectionState.selectAll(),
|
||||
callLogRepository
|
||||
)
|
||||
selectionState = CallLogSelectionState.empty()
|
||||
)
|
||||
}
|
||||
|
||||
return CallLogStagedDeletion(
|
||||
callLogStore.state.filter,
|
||||
CallLogSelectionState.selectAll(),
|
||||
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
|
||||
)
|
||||
}
|
||||
@SuppressLint("CheckResult")
|
||||
fun delete(stagedDeletion: CallLogStagedDeletion): Maybe<CallLogDeletionResult> {
|
||||
return stagedDeletion.commit()
|
||||
.doOnSubscribe {
|
||||
clearSelected()
|
||||
}
|
||||
.map { failedRevocations ->
|
||||
if (failedRevocations == 0) {
|
||||
CallLogDeletionResult.Success
|
||||
} else {
|
||||
CallLogDeletionResult.FailedToRevoke(failedRevocations)
|
||||
}
|
||||
}
|
||||
.onErrorReturn { CallLogDeletionResult.UnknownFailure(it) }
|
||||
.toMaybe()
|
||||
}
|
||||
|
||||
fun clearSelected() {
|
||||
@@ -197,7 +185,6 @@ class CallLogViewModel(
|
||||
private data class CallLogState(
|
||||
val query: String? = null,
|
||||
val filter: CallLogFilter = CallLogFilter.ALL,
|
||||
val selectionState: CallLogSelectionState = CallLogSelectionState.empty(),
|
||||
val stagedDeletion: CallLogStagedDeletion? = null
|
||||
val selectionState: CallLogSelectionState = CallLogSelectionState.empty()
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user