Remove rxjava from deletestatus API (#3041)
* Remove rxjava from API calls used by AccountListFragment * Remove rxjava from API calls used by AccountViewModel::changeRelationship() The affected API functions are also called from - ReportViewModel.kt - SearchViewModel.kt - AccountListFragment.kt - SFragment.java - TimelineCases.kt so they have also been updated. This change requires bridging from Java code to Kotlin `suspend` functions, by creating wrappers for the `mute` and `block` functions that can be called from Java and create a coroutine scope. I've deliberately made this fairly ugly so that it sticks out and can be removed later. * Use "Throwable" type and name * Delete 46.json Not sure where this came from. * Remove rxjava from the deleteStatus call path * Emit log messages with the correct tag * Add another log tag, and lint * Use TAG in log messages now it's present * Lint * Move viewModelScope.launch in to changeRelationshop() * Use onSuccess/onFailure pair instead of fold * Return Deferred when deleting statuses
This commit is contained in:
parent
c650ca9362
commit
561eda8482
5 changed files with 45 additions and 50 deletions
|
@ -20,6 +20,7 @@ import androidx.lifecycle.viewModelScope
|
||||||
import androidx.paging.Pager
|
import androidx.paging.Pager
|
||||||
import androidx.paging.PagingConfig
|
import androidx.paging.PagingConfig
|
||||||
import androidx.paging.cachedIn
|
import androidx.paging.cachedIn
|
||||||
|
import at.connyduck.calladapter.networkresult.NetworkResult
|
||||||
import com.keylesspalace.tusky.components.search.adapter.SearchPagingSourceFactory
|
import com.keylesspalace.tusky.components.search.adapter.SearchPagingSourceFactory
|
||||||
import com.keylesspalace.tusky.db.AccountEntity
|
import com.keylesspalace.tusky.db.AccountEntity
|
||||||
import com.keylesspalace.tusky.db.AccountManager
|
import com.keylesspalace.tusky.db.AccountManager
|
||||||
|
@ -31,7 +32,8 @@ import com.keylesspalace.tusky.util.RxAwareViewModel
|
||||||
import com.keylesspalace.tusky.util.toViewData
|
import com.keylesspalace.tusky.util.toViewData
|
||||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.core.Single
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -99,17 +101,13 @@ class SearchViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeItem(statusViewData: StatusViewData.Concrete) {
|
fun removeItem(statusViewData: StatusViewData.Concrete) {
|
||||||
timelineCases.delete(statusViewData.id)
|
viewModelScope.launch {
|
||||||
.subscribe(
|
if (timelineCases.delete(statusViewData.id).isSuccess) {
|
||||||
{
|
if (loadedStatuses.remove(statusViewData)) {
|
||||||
if (loadedStatuses.remove(statusViewData))
|
statusesPagingSourceFactory.invalidate()
|
||||||
statusesPagingSourceFactory.invalidate()
|
|
||||||
},
|
|
||||||
{ err ->
|
|
||||||
Log.d(TAG, "Failed to delete status", err)
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
.autoDispose()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun expandedChange(statusViewData: StatusViewData.Concrete, expanded: Boolean) {
|
fun expandedChange(statusViewData: StatusViewData.Concrete, expanded: Boolean) {
|
||||||
|
@ -185,8 +183,10 @@ class SearchViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteStatus(id: String): Single<DeletedStatus> {
|
fun deleteStatusAsync(id: String): Deferred<NetworkResult<DeletedStatus>> {
|
||||||
return timelineCases.delete(id)
|
return viewModelScope.async {
|
||||||
|
timelineCases.delete(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun muteConversation(statusViewData: StatusViewData.Concrete, mute: Boolean) {
|
fun muteConversation(statusViewData: StatusViewData.Concrete, mute: Boolean) {
|
||||||
|
|
|
@ -32,7 +32,6 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import androidx.paging.PagingDataAdapter
|
import androidx.paging.PagingDataAdapter
|
||||||
|
@ -40,8 +39,6 @@ import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import at.connyduck.calladapter.networkresult.fold
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from
|
|
||||||
import autodispose2.autoDispose
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.keylesspalace.tusky.BaseActivity
|
import com.keylesspalace.tusky.BaseActivity
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
|
@ -63,7 +60,6 @@ import com.keylesspalace.tusky.util.openLink
|
||||||
import com.keylesspalace.tusky.view.showMuteAccountDialog
|
import com.keylesspalace.tusky.view.showMuteAccountDialog
|
||||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -445,7 +441,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
|
||||||
AlertDialog.Builder(it)
|
AlertDialog.Builder(it)
|
||||||
.setMessage(R.string.dialog_delete_post_warning)
|
.setMessage(R.string.dialog_delete_post_warning)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
viewModel.deleteStatus(id)
|
viewModel.deleteStatusAsync(id)
|
||||||
removeItem(position)
|
removeItem(position)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
@ -458,10 +454,8 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
|
||||||
AlertDialog.Builder(it)
|
AlertDialog.Builder(it)
|
||||||
.setMessage(R.string.dialog_redraft_post_warning)
|
.setMessage(R.string.dialog_redraft_post_warning)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
viewModel.deleteStatus(id)
|
lifecycleScope.launch {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
viewModel.deleteStatusAsync(id).await().fold(
|
||||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
|
||||||
.subscribe(
|
|
||||||
{ deletedStatus ->
|
{ deletedStatus ->
|
||||||
removeItem(position)
|
removeItem(position)
|
||||||
|
|
||||||
|
@ -492,6 +486,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
|
||||||
Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
|
|
|
@ -364,18 +364,19 @@ abstract class SFragment : Fragment(), Injectable {
|
||||||
AlertDialog.Builder(requireActivity())
|
AlertDialog.Builder(requireActivity())
|
||||||
.setMessage(R.string.dialog_delete_post_warning)
|
.setMessage(R.string.dialog_delete_post_warning)
|
||||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||||
timelineCases.delete(id)
|
lifecycleScope.launch {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
val result = timelineCases.delete(id).exceptionOrNull()
|
||||||
.to(
|
if (result != null) {
|
||||||
AutoDispose.autoDisposable(
|
Log.w("SFragment", "error deleting status", result)
|
||||||
AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.subscribe({ }) { error: Throwable? ->
|
|
||||||
Log.w("SFragment", "error deleting status", error)
|
|
||||||
Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
removeItem(position)
|
// XXX: Removes the item even if there was an error. This is probably not
|
||||||
|
// correct (see similar code in showConfirmEditDialog() which only
|
||||||
|
// removes the item if the timelineCases.delete() call succeeded.
|
||||||
|
//
|
||||||
|
// Either way, this logic should be in the view model.
|
||||||
|
removeItem(position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
|
@ -388,14 +389,8 @@ abstract class SFragment : Fragment(), Injectable {
|
||||||
AlertDialog.Builder(requireActivity())
|
AlertDialog.Builder(requireActivity())
|
||||||
.setMessage(R.string.dialog_redraft_post_warning)
|
.setMessage(R.string.dialog_redraft_post_warning)
|
||||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||||
timelineCases.delete(id)
|
lifecycleScope.launch {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
timelineCases.delete(id).fold(
|
||||||
.to(
|
|
||||||
AutoDispose.autoDisposable(
|
|
||||||
AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.subscribe(
|
|
||||||
{ deletedStatus ->
|
{ deletedStatus ->
|
||||||
removeItem(position)
|
removeItem(position)
|
||||||
val sourceStatus = if (deletedStatus.isEmpty()) {
|
val sourceStatus = if (deletedStatus.isEmpty()) {
|
||||||
|
@ -416,11 +411,14 @@ abstract class SFragment : Fragment(), Injectable {
|
||||||
kind = ComposeActivity.ComposeKind.NEW
|
kind = ComposeActivity.ComposeKind.NEW
|
||||||
)
|
)
|
||||||
startActivity(startIntent(requireContext(), composeOptions))
|
startActivity(startIntent(requireContext(), composeOptions))
|
||||||
|
},
|
||||||
|
{ error: Throwable? ->
|
||||||
|
Log.w("SFragment", "error deleting status", error)
|
||||||
|
Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
) { error: Throwable? ->
|
)
|
||||||
Log.w("SFragment", "error deleting status", error)
|
}
|
||||||
Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
|
|
|
@ -213,9 +213,9 @@ interface MastodonApi {
|
||||||
): Response<List<TimelineAccount>>
|
): Response<List<TimelineAccount>>
|
||||||
|
|
||||||
@DELETE("api/v1/statuses/{id}")
|
@DELETE("api/v1/statuses/{id}")
|
||||||
fun deleteStatus(
|
suspend fun deleteStatus(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Single<DeletedStatus>
|
): NetworkResult<DeletedStatus>
|
||||||
|
|
||||||
@POST("api/v1/statuses/{id}/reblog")
|
@POST("api/v1/statuses/{id}/reblog")
|
||||||
fun reblogStatus(
|
fun reblogStatus(
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
package com.keylesspalace.tusky.usecase
|
package com.keylesspalace.tusky.usecase
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import at.connyduck.calladapter.networkresult.NetworkResult
|
||||||
|
import at.connyduck.calladapter.networkresult.onFailure
|
||||||
|
import at.connyduck.calladapter.networkresult.onSuccess
|
||||||
import com.keylesspalace.tusky.appstore.BlockEvent
|
import com.keylesspalace.tusky.appstore.BlockEvent
|
||||||
import com.keylesspalace.tusky.appstore.BookmarkEvent
|
import com.keylesspalace.tusky.appstore.BookmarkEvent
|
||||||
import com.keylesspalace.tusky.appstore.EventHub
|
import com.keylesspalace.tusky.appstore.EventHub
|
||||||
|
@ -112,11 +115,10 @@ class TimelineCases @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(statusId: String): Single<DeletedStatus> {
|
suspend fun delete(statusId: String): NetworkResult<DeletedStatus> {
|
||||||
return mastodonApi.deleteStatus(statusId)
|
return mastodonApi.deleteStatus(statusId)
|
||||||
.doAfterSuccess {
|
.onSuccess { eventHub.dispatch(StatusDeletedEvent(statusId)) }
|
||||||
eventHub.dispatch(StatusDeletedEvent(statusId))
|
.onFailure { Log.w(TAG, "Failed to delete status", it) }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pin(statusId: String, pin: Boolean): Single<Status> {
|
fun pin(statusId: String, pin: Boolean): Single<Status> {
|
||||||
|
|
Loading…
Reference in a new issue