Remove Rx from EventHub and TimelineCases (#3446)
* remove Rx from EventHub and TimelineCases * fix tests * fix AccountViewModel.unblockDomain * remove debug logging
This commit is contained in:
parent
66eadabd44
commit
321d17f5de
25 changed files with 264 additions and 330 deletions
|
@ -43,12 +43,10 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.core.view.MenuProvider
|
import androidx.core.view.MenuProvider
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.viewpager2.widget.MarginPageTransformer
|
import androidx.viewpager2.widget.MarginPageTransformer
|
||||||
import at.connyduck.calladapter.networkresult.fold
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
import autodispose2.androidx.lifecycle.autoDispose
|
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.RequestManager
|
import com.bumptech.glide.RequestManager
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||||
|
@ -61,7 +59,6 @@ import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import com.keylesspalace.tusky.appstore.AnnouncementReadEvent
|
import com.keylesspalace.tusky.appstore.AnnouncementReadEvent
|
||||||
import com.keylesspalace.tusky.appstore.CacheUpdater
|
import com.keylesspalace.tusky.appstore.CacheUpdater
|
||||||
import com.keylesspalace.tusky.appstore.Event
|
|
||||||
import com.keylesspalace.tusky.appstore.EventHub
|
import com.keylesspalace.tusky.appstore.EventHub
|
||||||
import com.keylesspalace.tusky.appstore.MainTabsChangedEvent
|
import com.keylesspalace.tusky.appstore.MainTabsChangedEvent
|
||||||
import com.keylesspalace.tusky.appstore.ProfileEditedEvent
|
import com.keylesspalace.tusky.appstore.ProfileEditedEvent
|
||||||
|
@ -133,7 +130,6 @@ import com.mikepenz.materialdrawer.widget.AccountHeaderView
|
||||||
import dagger.android.DispatchingAndroidInjector
|
import dagger.android.DispatchingAndroidInjector
|
||||||
import dagger.android.HasAndroidInjector
|
import dagger.android.HasAndroidInjector
|
||||||
import de.c1710.filemojicompat_ui.helpers.EMOJI_PREFERENCE
|
import de.c1710.filemojicompat_ui.helpers.EMOJI_PREFERENCE
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -287,10 +283,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
||||||
|
|
||||||
setupTabs(showNotificationTab)
|
setupTabs(showNotificationTab)
|
||||||
|
|
||||||
eventHub.events
|
lifecycleScope.launch {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
eventHub.events.collect { event ->
|
||||||
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
|
|
||||||
.subscribe { event: Event? ->
|
|
||||||
when (event) {
|
when (event) {
|
||||||
is ProfileEditedEvent -> onFetchUserInfoSuccess(event.newProfileData)
|
is ProfileEditedEvent -> onFetchUserInfoSuccess(event.newProfileData)
|
||||||
is MainTabsChangedEvent -> {
|
is MainTabsChangedEvent -> {
|
||||||
|
@ -308,6 +302,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Schedulers.io().scheduleDirect {
|
Schedulers.io().scheduleDirect {
|
||||||
// Flush old media that was cached for sharing
|
// Flush old media that was cached for sharing
|
||||||
|
|
|
@ -348,7 +348,9 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
if (tabsChanged) {
|
if (tabsChanged) {
|
||||||
eventHub.dispatch(MainTabsChangedEvent(currentTabs))
|
lifecycleScope.launch {
|
||||||
|
eventHub.dispatch(MainTabsChangedEvent(currentTabs))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.rx3.asFlow
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CacheUpdater @Inject constructor(
|
class CacheUpdater @Inject constructor(
|
||||||
|
@ -24,7 +23,7 @@ class CacheUpdater @Inject constructor(
|
||||||
val timelineDao = appDatabase.timelineDao()
|
val timelineDao = appDatabase.timelineDao()
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
eventHub.events.asFlow().collect { event ->
|
eventHub.events.collect { event ->
|
||||||
val accountId = accountManager.activeAccount?.id ?: return@collect
|
val accountId = accountManager.activeAccount?.id ?: return@collect
|
||||||
when (event) {
|
when (event) {
|
||||||
is FavoriteEvent ->
|
is FavoriteEvent ->
|
||||||
|
|
|
@ -5,21 +5,21 @@ import com.keylesspalace.tusky.entity.Account
|
||||||
import com.keylesspalace.tusky.entity.Poll
|
import com.keylesspalace.tusky.entity.Poll
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
|
|
||||||
data class FavoriteEvent(val statusId: String, val favourite: Boolean) : Dispatchable
|
data class FavoriteEvent(val statusId: String, val favourite: Boolean) : Event
|
||||||
data class ReblogEvent(val statusId: String, val reblog: Boolean) : Dispatchable
|
data class ReblogEvent(val statusId: String, val reblog: Boolean) : Event
|
||||||
data class BookmarkEvent(val statusId: String, val bookmark: Boolean) : Dispatchable
|
data class BookmarkEvent(val statusId: String, val bookmark: Boolean) : Event
|
||||||
data class MuteConversationEvent(val statusId: String, val mute: Boolean) : Dispatchable
|
data class MuteConversationEvent(val statusId: String, val mute: Boolean) : Event
|
||||||
data class UnfollowEvent(val accountId: String) : Dispatchable
|
data class UnfollowEvent(val accountId: String) : Event
|
||||||
data class BlockEvent(val accountId: String) : Dispatchable
|
data class BlockEvent(val accountId: String) : Event
|
||||||
data class MuteEvent(val accountId: String) : Dispatchable
|
data class MuteEvent(val accountId: String) : Event
|
||||||
data class StatusDeletedEvent(val statusId: String) : Dispatchable
|
data class StatusDeletedEvent(val statusId: String) : Event
|
||||||
data class StatusComposedEvent(val status: Status) : Dispatchable
|
data class StatusComposedEvent(val status: Status) : Event
|
||||||
data class StatusScheduledEvent(val status: Status) : Dispatchable
|
data class StatusScheduledEvent(val status: Status) : Event
|
||||||
data class StatusEditedEvent(val originalId: String, val status: Status) : Dispatchable
|
data class StatusEditedEvent(val originalId: String, val status: Status) : Event
|
||||||
data class ProfileEditedEvent(val newProfileData: Account) : Dispatchable
|
data class ProfileEditedEvent(val newProfileData: Account) : Event
|
||||||
data class PreferenceChangedEvent(val preferenceKey: String) : Dispatchable
|
data class PreferenceChangedEvent(val preferenceKey: String) : Event
|
||||||
data class MainTabsChangedEvent(val newTabs: List<TabData>) : Dispatchable
|
data class MainTabsChangedEvent(val newTabs: List<TabData>) : Event
|
||||||
data class PollVoteEvent(val statusId: String, val poll: Poll) : Dispatchable
|
data class PollVoteEvent(val statusId: String, val poll: Poll) : Event
|
||||||
data class DomainMuteEvent(val instance: String) : Dispatchable
|
data class DomainMuteEvent(val instance: String) : Event
|
||||||
data class AnnouncementReadEvent(val announcementId: String) : Dispatchable
|
data class AnnouncementReadEvent(val announcementId: String) : Event
|
||||||
data class PinEvent(val statusId: String, val pinned: Boolean) : Dispatchable
|
data class PinEvent(val statusId: String, val pinned: Boolean) : Event
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
package com.keylesspalace.tusky.appstore
|
package com.keylesspalace.tusky.appstore
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.Observable
|
import kotlinx.coroutines.flow.Flow
|
||||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
interface Event
|
interface Event
|
||||||
interface Dispatchable : Event
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class EventHub @Inject constructor() {
|
class EventHub @Inject constructor() {
|
||||||
|
|
||||||
private val eventsSubject = PublishSubject.create<Event>()
|
private val sharedEventFlow: MutableSharedFlow<Event> = MutableSharedFlow()
|
||||||
val events: Observable<Event> = eventsSubject
|
val events: Flow<Event> = sharedEventFlow
|
||||||
|
|
||||||
fun dispatch(event: Dispatchable) {
|
suspend fun dispatch(event: Event) {
|
||||||
eventsSubject.onNext(event)
|
sharedEventFlow.emit(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.keylesspalace.tusky.components.account
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
import com.keylesspalace.tusky.appstore.BlockEvent
|
import com.keylesspalace.tusky.appstore.BlockEvent
|
||||||
import com.keylesspalace.tusky.appstore.DomainMuteEvent
|
import com.keylesspalace.tusky.appstore.DomainMuteEvent
|
||||||
import com.keylesspalace.tusky.appstore.EventHub
|
import com.keylesspalace.tusky.appstore.EventHub
|
||||||
|
@ -21,9 +22,6 @@ import com.keylesspalace.tusky.util.Success
|
||||||
import io.reactivex.rxjava3.core.Single
|
import io.reactivex.rxjava3.core.Single
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.Callback
|
|
||||||
import retrofit2.Response
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -47,12 +45,13 @@ class AccountViewModel @Inject constructor(
|
||||||
private var noteDisposable: Disposable? = null
|
private var noteDisposable: Disposable? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
eventHub.events
|
viewModelScope.launch {
|
||||||
.subscribe { event ->
|
eventHub.events.collect { event ->
|
||||||
if (event is ProfileEditedEvent && event.newProfileData.id == accountData.value?.data?.id) {
|
if (event is ProfileEditedEvent && event.newProfileData.id == accountData.value?.data?.id) {
|
||||||
accountData.postValue(Success(event.newProfileData))
|
accountData.postValue(Success(event.newProfileData))
|
||||||
}
|
}
|
||||||
}.autoDispose()
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun obtainAccount(reload: Boolean = false) {
|
private fun obtainAccount(reload: Boolean = false) {
|
||||||
|
@ -133,42 +132,30 @@ class AccountViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun blockDomain(instance: String) {
|
fun blockDomain(instance: String) {
|
||||||
mastodonApi.blockDomain(instance).enqueue(object : Callback<Any> {
|
viewModelScope.launch {
|
||||||
override fun onResponse(call: Call<Any>, response: Response<Any>) {
|
mastodonApi.blockDomain(instance).fold({
|
||||||
if (response.isSuccessful) {
|
eventHub.dispatch(DomainMuteEvent(instance))
|
||||||
eventHub.dispatch(DomainMuteEvent(instance))
|
val relation = relationshipData.value?.data
|
||||||
val relation = relationshipData.value?.data
|
if (relation != null) {
|
||||||
if (relation != null) {
|
relationshipData.postValue(Success(relation.copy(blockingDomain = true)))
|
||||||
relationshipData.postValue(Success(relation.copy(blockingDomain = true)))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Error muting %s".format(instance))
|
|
||||||
}
|
}
|
||||||
}
|
}, { e ->
|
||||||
|
Log.e(TAG, "Error muting $instance", e)
|
||||||
override fun onFailure(call: Call<Any>, t: Throwable) {
|
})
|
||||||
Log.e(TAG, "Error muting %s".format(instance), t)
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unblockDomain(instance: String) {
|
fun unblockDomain(instance: String) {
|
||||||
mastodonApi.unblockDomain(instance).enqueue(object : Callback<Any> {
|
viewModelScope.launch {
|
||||||
override fun onResponse(call: Call<Any>, response: Response<Any>) {
|
mastodonApi.unblockDomain(instance).fold({
|
||||||
if (response.isSuccessful) {
|
val relation = relationshipData.value?.data
|
||||||
val relation = relationshipData.value?.data
|
if (relation != null) {
|
||||||
if (relation != null) {
|
relationshipData.postValue(Success(relation.copy(blockingDomain = false)))
|
||||||
relationshipData.postValue(Success(relation.copy(blockingDomain = false)))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Error unmuting %s".format(instance))
|
|
||||||
}
|
}
|
||||||
}
|
}, { e ->
|
||||||
|
Log.e(TAG, "Error unmuting $instance", e)
|
||||||
override fun onFailure(call: Call<Any>, t: Throwable) {
|
})
|
||||||
Log.e(TAG, "Error unmuting %s".format(instance), t)
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeShowReblogsState() {
|
fun changeShowReblogsState() {
|
||||||
|
|
|
@ -36,7 +36,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
import at.connyduck.sparkbutton.helpers.Utils
|
import at.connyduck.sparkbutton.helpers.Utils
|
||||||
import autodispose2.androidx.lifecycle.autoDispose
|
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.StatusListActivity
|
import com.keylesspalace.tusky.StatusListActivity
|
||||||
|
@ -62,7 +61,6 @@ import com.mikepenz.iconics.IconicsDrawable
|
||||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||||
import com.mikepenz.iconics.utils.colorInt
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
import com.mikepenz.iconics.utils.sizeDp
|
import com.mikepenz.iconics.utils.sizeDp
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -205,14 +203,13 @@ class ConversationsFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eventHub.events
|
lifecycleScope.launch {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
eventHub.events.collect { event ->
|
||||||
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
|
|
||||||
.subscribe { event ->
|
|
||||||
if (event is PreferenceChangedEvent) {
|
if (event is PreferenceChangedEvent) {
|
||||||
onPreferenceChanged(event.preferenceKey)
|
onPreferenceChanged(event.preferenceKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import androidx.paging.Pager
|
||||||
import androidx.paging.PagingConfig
|
import androidx.paging.PagingConfig
|
||||||
import androidx.paging.cachedIn
|
import androidx.paging.cachedIn
|
||||||
import androidx.paging.map
|
import androidx.paging.map
|
||||||
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
import com.keylesspalace.tusky.db.AccountManager
|
import com.keylesspalace.tusky.db.AccountManager
|
||||||
import com.keylesspalace.tusky.db.AppDatabase
|
import com.keylesspalace.tusky.db.AppDatabase
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
|
@ -30,7 +31,6 @@ import com.keylesspalace.tusky.usecase.TimelineCases
|
||||||
import com.keylesspalace.tusky.util.EmptyPagingSource
|
import com.keylesspalace.tusky.util.EmptyPagingSource
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.rx3.await
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ConversationsViewModel @Inject constructor(
|
class ConversationsViewModel @Inject constructor(
|
||||||
|
@ -61,51 +61,47 @@ class ConversationsViewModel @Inject constructor(
|
||||||
|
|
||||||
fun favourite(favourite: Boolean, conversation: ConversationViewData) {
|
fun favourite(favourite: Boolean, conversation: ConversationViewData) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
timelineCases.favourite(conversation.lastStatus.id, favourite).fold({
|
||||||
timelineCases.favourite(conversation.lastStatus.id, favourite).await()
|
|
||||||
|
|
||||||
val newConversation = conversation.toEntity(
|
val newConversation = conversation.toEntity(
|
||||||
accountId = accountManager.activeAccount!!.id,
|
accountId = accountManager.activeAccount!!.id,
|
||||||
favourited = favourite
|
favourited = favourite
|
||||||
)
|
)
|
||||||
|
|
||||||
saveConversationToDb(newConversation)
|
saveConversationToDb(newConversation)
|
||||||
} catch (e: Exception) {
|
}, { e ->
|
||||||
Log.w(TAG, "failed to favourite status", e)
|
Log.w(TAG, "failed to favourite status", e)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bookmark(bookmark: Boolean, conversation: ConversationViewData) {
|
fun bookmark(bookmark: Boolean, conversation: ConversationViewData) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
timelineCases.bookmark(conversation.lastStatus.id, bookmark).fold({
|
||||||
timelineCases.bookmark(conversation.lastStatus.id, bookmark).await()
|
|
||||||
|
|
||||||
val newConversation = conversation.toEntity(
|
val newConversation = conversation.toEntity(
|
||||||
accountId = accountManager.activeAccount!!.id,
|
accountId = accountManager.activeAccount!!.id,
|
||||||
bookmarked = bookmark
|
bookmarked = bookmark
|
||||||
)
|
)
|
||||||
|
|
||||||
saveConversationToDb(newConversation)
|
saveConversationToDb(newConversation)
|
||||||
} catch (e: Exception) {
|
}, { e ->
|
||||||
Log.w(TAG, "failed to bookmark status", e)
|
Log.w(TAG, "failed to bookmark status", e)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun voteInPoll(choices: List<Int>, conversation: ConversationViewData) {
|
fun voteInPoll(choices: List<Int>, conversation: ConversationViewData) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
timelineCases.voteInPoll(conversation.lastStatus.id, conversation.lastStatus.status.poll?.id!!, choices)
|
||||||
val poll = timelineCases.voteInPoll(conversation.lastStatus.id, conversation.lastStatus.status.poll?.id!!, choices).await()
|
.fold({ poll ->
|
||||||
val newConversation = conversation.toEntity(
|
val newConversation = conversation.toEntity(
|
||||||
accountId = accountManager.activeAccount!!.id,
|
accountId = accountManager.activeAccount!!.id,
|
||||||
poll = poll
|
poll = poll
|
||||||
)
|
)
|
||||||
|
|
||||||
saveConversationToDb(newConversation)
|
saveConversationToDb(newConversation)
|
||||||
} catch (e: Exception) {
|
}, { e ->
|
||||||
Log.w(TAG, "failed to vote in poll", e)
|
Log.w(TAG, "failed to vote in poll", e)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +156,7 @@ class ConversationsViewModel @Inject constructor(
|
||||||
timelineCases.muteConversation(
|
timelineCases.muteConversation(
|
||||||
conversation.lastStatus.id,
|
conversation.lastStatus.id,
|
||||||
!(conversation.lastStatus.status.muted ?: false)
|
!(conversation.lastStatus.status.muted ?: false)
|
||||||
).await()
|
)
|
||||||
|
|
||||||
val newConversation = conversation.toEntity(
|
val newConversation = conversation.toEntity(
|
||||||
accountId = accountManager.activeAccount!!.id,
|
accountId = accountManager.activeAccount!!.id,
|
||||||
|
|
|
@ -5,9 +5,11 @@ import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from
|
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from
|
||||||
import autodispose2.autoDispose
|
import autodispose2.autoDispose
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
@ -23,9 +25,7 @@ import com.keylesspalace.tusky.util.show
|
||||||
import com.keylesspalace.tusky.util.viewBinding
|
import com.keylesspalace.tusky.util.viewBinding
|
||||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener
|
import com.keylesspalace.tusky.view.EndlessOnScrollListener
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import retrofit2.Call
|
import kotlinx.coroutines.launch
|
||||||
import retrofit2.Callback
|
|
||||||
import retrofit2.Response
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -64,39 +64,25 @@ class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectab
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mute(mute: Boolean, instance: String, position: Int) {
|
override fun mute(mute: Boolean, instance: String, position: Int) {
|
||||||
if (mute) {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
api.blockDomain(instance).enqueue(object : Callback<Any> {
|
if (mute) {
|
||||||
override fun onFailure(call: Call<Any>, t: Throwable) {
|
api.blockDomain(instance).fold({
|
||||||
Log.e(TAG, "Error muting domain $instance")
|
adapter.addItem(instance)
|
||||||
}
|
}, { e ->
|
||||||
|
Log.e(TAG, "Error muting domain $instance", e)
|
||||||
override fun onResponse(call: Call<Any>, response: Response<Any>) {
|
})
|
||||||
if (response.isSuccessful) {
|
} else {
|
||||||
adapter.addItem(instance)
|
api.unblockDomain(instance).fold({
|
||||||
} else {
|
adapter.removeItem(position)
|
||||||
Log.e(TAG, "Error muting domain $instance")
|
Snackbar.make(binding.recyclerView, getString(R.string.confirmation_domain_unmuted, instance), Snackbar.LENGTH_LONG)
|
||||||
}
|
.setAction(R.string.action_undo) {
|
||||||
}
|
mute(true, instance, position)
|
||||||
})
|
}
|
||||||
} else {
|
.show()
|
||||||
api.unblockDomain(instance).enqueue(object : Callback<Any> {
|
}, { e ->
|
||||||
override fun onFailure(call: Call<Any>, t: Throwable) {
|
Log.e(TAG, "Error unmuting domain $instance", e)
|
||||||
Log.e(TAG, "Error unmuting domain $instance")
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call<Any>, response: Response<Any>) {
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
adapter.removeItem(position)
|
|
||||||
Snackbar.make(binding.recyclerView, getString(R.string.confirmation_domain_unmuted, instance), Snackbar.LENGTH_LONG)
|
|
||||||
.setAction(R.string.action_undo) {
|
|
||||||
mute(true, instance, position)
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Error unmuting domain $instance")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,6 @@ import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.rx3.asFlow
|
|
||||||
import kotlinx.coroutines.rx3.await
|
import kotlinx.coroutines.rx3.await
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -357,7 +356,7 @@ class NotificationsViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
eventHub.events.asFlow()
|
eventHub.events
|
||||||
.filterIsInstance<PreferenceChangedEvent>()
|
.filterIsInstance<PreferenceChangedEvent>()
|
||||||
.filter { StatusDisplayOptions.prefKeys.contains(it.preferenceKey) }
|
.filter { StatusDisplayOptions.prefKeys.contains(it.preferenceKey) }
|
||||||
.map {
|
.map {
|
||||||
|
@ -420,23 +419,23 @@ class NotificationsViewModel @Inject constructor(
|
||||||
timelineCases.bookmark(
|
timelineCases.bookmark(
|
||||||
action.statusViewData.actionableId,
|
action.statusViewData.actionableId,
|
||||||
action.state
|
action.state
|
||||||
).await()
|
)
|
||||||
is StatusAction.Favourite ->
|
is StatusAction.Favourite ->
|
||||||
timelineCases.favourite(
|
timelineCases.favourite(
|
||||||
action.statusViewData.actionableId,
|
action.statusViewData.actionableId,
|
||||||
action.state
|
action.state
|
||||||
).await()
|
)
|
||||||
is StatusAction.Reblog ->
|
is StatusAction.Reblog ->
|
||||||
timelineCases.reblog(
|
timelineCases.reblog(
|
||||||
action.statusViewData.actionableId,
|
action.statusViewData.actionableId,
|
||||||
action.state
|
action.state
|
||||||
).await()
|
)
|
||||||
is StatusAction.VoteInPoll ->
|
is StatusAction.VoteInPoll ->
|
||||||
timelineCases.voteInPoll(
|
timelineCases.voteInPoll(
|
||||||
action.statusViewData.actionableId,
|
action.statusViewData.actionableId,
|
||||||
action.poll.id,
|
action.poll.id,
|
||||||
action.choices
|
action.choices
|
||||||
).await()
|
)
|
||||||
}
|
}
|
||||||
uiSuccess.emit(StatusActionSuccess.from(action))
|
uiSuccess.emit(StatusActionSuccess.from(action))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -447,7 +446,7 @@ class NotificationsViewModel @Inject constructor(
|
||||||
|
|
||||||
// Handle events that should refresh the list
|
// Handle events that should refresh the list
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
eventHub.events.asFlow().collectLatest {
|
eventHub.events.collectLatest {
|
||||||
when (it) {
|
when (it) {
|
||||||
is BlockEvent -> uiSuccess.emit(UiSuccess.Block)
|
is BlockEvent -> uiSuccess.emit(UiSuccess.Block)
|
||||||
is MuteEvent -> uiSuccess.emit(UiSuccess.Mute)
|
is MuteEvent -> uiSuccess.emit(UiSuccess.Mute)
|
||||||
|
@ -504,7 +503,7 @@ class NotificationsViewModel @Inject constructor(
|
||||||
* @return Flow of relevant preferences that change the UI
|
* @return Flow of relevant preferences that change the UI
|
||||||
*/
|
*/
|
||||||
// TODO: Preferences should be in a repository
|
// TODO: Preferences should be in a repository
|
||||||
private fun getUiPrefs() = eventHub.events.asFlow()
|
private fun getUiPrefs() = eventHub.events
|
||||||
.filterIsInstance<PreferenceChangedEvent>()
|
.filterIsInstance<PreferenceChangedEvent>()
|
||||||
.filter { UiPrefs.prefKeys.contains(it.preferenceKey) }
|
.filter { UiPrefs.prefKeys.contains(it.preferenceKey) }
|
||||||
.map { toPrefs() }
|
.map { toPrefs() }
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
@ -57,6 +58,7 @@ import com.mikepenz.iconics.IconicsDrawable
|
||||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||||
import com.mikepenz.iconics.utils.colorInt
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
import com.mikepenz.iconics.utils.sizeRes
|
import com.mikepenz.iconics.utils.sizeRes
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
@ -198,7 +200,6 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
setIcon(getIconForVisibility(Status.Visibility.byString(newValue as String)))
|
setIcon(getIconForVisibility(Status.Visibility.byString(newValue as String)))
|
||||||
syncWithServer(visibility = newValue)
|
syncWithServer(visibility = newValue)
|
||||||
eventHub.dispatch(PreferenceChangedEvent(key))
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,7 +222,6 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
syncWithServer(language = (newValue as String))
|
syncWithServer(language = (newValue as String))
|
||||||
eventHub.dispatch(PreferenceChangedEvent(key))
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,7 +237,6 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
setIcon(getIconForSensitivity(newValue as Boolean))
|
setIcon(getIconForSensitivity(newValue as Boolean))
|
||||||
syncWithServer(sensitive = newValue)
|
syncWithServer(sensitive = newValue)
|
||||||
eventHub.dispatch(PreferenceChangedEvent(key))
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,7 +245,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
preferenceCategory(R.string.pref_title_timelines) {
|
preferenceCategory(R.string.pref_title_timelines) {
|
||||||
// TODO having no activeAccount in this fragment does not really make sense, enforce it?
|
// TODO having no activeAccount in this fragment does not really make sense, enforce it?
|
||||||
// All other locations here make it optional, however.
|
// All other locations here make it optional, however.
|
||||||
val accountPreferenceHandler = AccountPreferenceHandler(accountManager.activeAccount!!, accountManager, eventHub)
|
val accountPreferenceHandler = AccountPreferenceHandler(accountManager.activeAccount!!, accountManager, ::dispatchEvent)
|
||||||
|
|
||||||
switchPreference {
|
switchPreference {
|
||||||
key = PrefKeys.MEDIA_PREVIEW_ENABLED
|
key = PrefKeys.MEDIA_PREVIEW_ENABLED
|
||||||
|
@ -354,6 +353,12 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
activity?.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
|
activity?.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun dispatchEvent(event: PreferenceChangedEvent) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
eventHub.dispatch(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = AccountPreferencesFragment()
|
fun newInstance() = AccountPreferencesFragment()
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.util.Log
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
@ -38,6 +39,7 @@ import com.keylesspalace.tusky.util.getNonNullString
|
||||||
import com.keylesspalace.tusky.util.setAppNightMode
|
import com.keylesspalace.tusky.util.setAppNightMode
|
||||||
import dagger.android.DispatchingAndroidInjector
|
import dagger.android.DispatchingAndroidInjector
|
||||||
import dagger.android.HasAndroidInjector
|
import dagger.android.HasAndroidInjector
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class PreferencesActivity :
|
class PreferencesActivity :
|
||||||
|
@ -155,8 +157,9 @@ class PreferencesActivity :
|
||||||
restartActivitiesOnBackPressedCallback.isEnabled = true
|
restartActivitiesOnBackPressedCallback.isEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
eventHub.dispatch(PreferenceChangedEvent(key))
|
eventHub.dispatch(PreferenceChangedEvent(key))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restartCurrentActivity() {
|
private fun restartCurrentActivity() {
|
||||||
|
|
|
@ -28,7 +28,6 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import autodispose2.androidx.lifecycle.autoDispose
|
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.keylesspalace.tusky.BaseActivity
|
import com.keylesspalace.tusky.BaseActivity
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
|
@ -46,7 +45,6 @@ import com.mikepenz.iconics.IconicsDrawable
|
||||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||||
import com.mikepenz.iconics.utils.colorInt
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
import com.mikepenz.iconics.utils.sizeDp
|
import com.mikepenz.iconics.utils.sizeDp
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -119,14 +117,13 @@ class ScheduledStatusActivity :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eventHub.events
|
lifecycleScope.launch {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
eventHub.events.collect { event ->
|
||||||
.autoDispose(this)
|
|
||||||
.subscribe { event ->
|
|
||||||
if (event is StatusScheduledEvent) {
|
if (event is StatusScheduledEvent) {
|
||||||
adapter.refresh()
|
adapter.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
|
|
|
@ -16,11 +16,14 @@
|
||||||
package com.keylesspalace.tusky.components.search
|
package com.keylesspalace.tusky.components.search
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
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 at.connyduck.calladapter.networkresult.NetworkResult
|
||||||
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
|
import at.connyduck.calladapter.networkresult.onFailure
|
||||||
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
|
||||||
|
@ -28,10 +31,8 @@ import com.keylesspalace.tusky.entity.DeletedStatus
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.usecase.TimelineCases
|
import com.keylesspalace.tusky.usecase.TimelineCases
|
||||||
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 kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -41,7 +42,7 @@ class SearchViewModel @Inject constructor(
|
||||||
mastodonApi: MastodonApi,
|
mastodonApi: MastodonApi,
|
||||||
private val timelineCases: TimelineCases,
|
private val timelineCases: TimelineCases,
|
||||||
private val accountManager: AccountManager
|
private val accountManager: AccountManager
|
||||||
) : RxAwareViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
var currentQuery: String = ""
|
var currentQuery: String = ""
|
||||||
|
|
||||||
|
@ -115,22 +116,18 @@ class SearchViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reblog(statusViewData: StatusViewData.Concrete, reblog: Boolean) {
|
fun reblog(statusViewData: StatusViewData.Concrete, reblog: Boolean) {
|
||||||
timelineCases.reblog(statusViewData.id, reblog)
|
viewModelScope.launch {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
timelineCases.reblog(statusViewData.id, reblog).fold({
|
||||||
.subscribe(
|
updateStatus(
|
||||||
{ setRebloggedForStatus(statusViewData, reblog) },
|
statusViewData.status.copy(
|
||||||
{ t -> Log.d(TAG, "Failed to reblog status ${statusViewData.id}", t) }
|
reblogged = reblog,
|
||||||
)
|
reblog = statusViewData.status.reblog?.copy(reblogged = reblog)
|
||||||
.autoDispose()
|
)
|
||||||
}
|
)
|
||||||
|
}, { t ->
|
||||||
private fun setRebloggedForStatus(statusViewData: StatusViewData.Concrete, reblog: Boolean) {
|
Log.d(TAG, "Failed to reblog status ${statusViewData.id}", t)
|
||||||
updateStatus(
|
})
|
||||||
statusViewData.status.copy(
|
}
|
||||||
reblogged = reblog,
|
|
||||||
reblog = statusViewData.status.reblog?.copy(reblogged = reblog)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun contentHiddenChange(statusViewData: StatusViewData.Concrete, isShowing: Boolean) {
|
fun contentHiddenChange(statusViewData: StatusViewData.Concrete, isShowing: Boolean) {
|
||||||
|
@ -144,27 +141,24 @@ class SearchViewModel @Inject constructor(
|
||||||
fun voteInPoll(statusViewData: StatusViewData.Concrete, choices: MutableList<Int>) {
|
fun voteInPoll(statusViewData: StatusViewData.Concrete, choices: MutableList<Int>) {
|
||||||
val votedPoll = statusViewData.status.actionableStatus.poll!!.votedCopy(choices)
|
val votedPoll = statusViewData.status.actionableStatus.poll!!.votedCopy(choices)
|
||||||
updateStatus(statusViewData.status.copy(poll = votedPoll))
|
updateStatus(statusViewData.status.copy(poll = votedPoll))
|
||||||
timelineCases.voteInPoll(statusViewData.id, votedPoll.id, choices)
|
viewModelScope.launch {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
timelineCases.voteInPoll(statusViewData.id, votedPoll.id, choices)
|
||||||
.doOnError { t -> Log.d(TAG, "Failed to vote in poll: ${statusViewData.id}", t) }
|
.onFailure { t -> Log.d(TAG, "Failed to vote in poll: ${statusViewData.id}", t) }
|
||||||
.subscribe()
|
}
|
||||||
.autoDispose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun favorite(statusViewData: StatusViewData.Concrete, isFavorited: Boolean) {
|
fun favorite(statusViewData: StatusViewData.Concrete, isFavorited: Boolean) {
|
||||||
updateStatus(statusViewData.status.copy(favourited = isFavorited))
|
updateStatus(statusViewData.status.copy(favourited = isFavorited))
|
||||||
timelineCases.favourite(statusViewData.id, isFavorited)
|
viewModelScope.launch {
|
||||||
.onErrorReturnItem(statusViewData.status)
|
timelineCases.favourite(statusViewData.id, isFavorited)
|
||||||
.subscribe()
|
}
|
||||||
.autoDispose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bookmark(statusViewData: StatusViewData.Concrete, isBookmarked: Boolean) {
|
fun bookmark(statusViewData: StatusViewData.Concrete, isBookmarked: Boolean) {
|
||||||
updateStatus(statusViewData.status.copy(bookmarked = isBookmarked))
|
updateStatus(statusViewData.status.copy(bookmarked = isBookmarked))
|
||||||
timelineCases.bookmark(statusViewData.id, isBookmarked)
|
viewModelScope.launch {
|
||||||
.onErrorReturnItem(statusViewData.status)
|
timelineCases.bookmark(statusViewData.id, isBookmarked)
|
||||||
.subscribe()
|
}
|
||||||
.autoDispose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun muteAccount(accountId: String, notifications: Boolean, duration: Int?) {
|
fun muteAccount(accountId: String, notifications: Boolean, duration: Int?) {
|
||||||
|
@ -174,7 +168,9 @@ class SearchViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pinAccount(status: Status, isPin: Boolean) {
|
fun pinAccount(status: Status, isPin: Boolean) {
|
||||||
timelineCases.pin(status.id, isPin)
|
viewModelScope.launch {
|
||||||
|
timelineCases.pin(status.id, isPin)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun blockAccount(accountId: String) {
|
fun blockAccount(accountId: String) {
|
||||||
|
@ -191,10 +187,9 @@ class SearchViewModel @Inject constructor(
|
||||||
|
|
||||||
fun muteConversation(statusViewData: StatusViewData.Concrete, mute: Boolean) {
|
fun muteConversation(statusViewData: StatusViewData.Concrete, mute: Boolean) {
|
||||||
updateStatus(statusViewData.status.copy(muted = mute))
|
updateStatus(statusViewData.status.copy(muted = mute))
|
||||||
timelineCases.muteConversation(statusViewData.id, mute)
|
viewModelScope.launch {
|
||||||
.onErrorReturnItem(statusViewData.status)
|
timelineCases.muteConversation(statusViewData.id, mute)
|
||||||
.subscribe()
|
}
|
||||||
.autoDispose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateStatusViewData(newStatusViewData: StatusViewData.Concrete) {
|
private fun updateStatusViewData(newStatusViewData: StatusViewData.Concrete) {
|
||||||
|
|
|
@ -77,6 +77,7 @@ import com.mikepenz.iconics.utils.colorInt
|
||||||
import com.mikepenz.iconics.utils.sizeDp
|
import com.mikepenz.iconics.utils.sizeDp
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.core.Observable
|
import io.reactivex.rxjava3.core.Observable
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -299,10 +300,8 @@ class TimelineFragment :
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
eventHub.events
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
eventHub.events.collect { event ->
|
||||||
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
|
|
||||||
.subscribe { event ->
|
|
||||||
when (event) {
|
when (event) {
|
||||||
is PreferenceChangedEvent -> {
|
is PreferenceChangedEvent -> {
|
||||||
onPreferenceChanged(event.preferenceKey)
|
onPreferenceChanged(event.preferenceKey)
|
||||||
|
@ -316,6 +315,7 @@ class TimelineFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import androidx.lifecycle.viewModelScope
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import at.connyduck.calladapter.networkresult.fold
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
import at.connyduck.calladapter.networkresult.getOrElse
|
import at.connyduck.calladapter.networkresult.getOrElse
|
||||||
|
import at.connyduck.calladapter.networkresult.getOrThrow
|
||||||
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.DomainMuteEvent
|
import com.keylesspalace.tusky.appstore.DomainMuteEvent
|
||||||
|
@ -49,8 +50,6 @@ import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.rx3.asFlow
|
|
||||||
import kotlinx.coroutines.rx3.await
|
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
|
|
||||||
abstract class TimelineViewModel(
|
abstract class TimelineViewModel(
|
||||||
|
@ -101,7 +100,6 @@ abstract class TimelineViewModel(
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
eventHub.events
|
eventHub.events
|
||||||
.asFlow()
|
|
||||||
.collect { event -> handleEvent(event) }
|
.collect { event -> handleEvent(event) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +108,7 @@ abstract class TimelineViewModel(
|
||||||
|
|
||||||
fun reblog(reblog: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
fun reblog(reblog: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
timelineCases.reblog(status.actionableId, reblog).await()
|
timelineCases.reblog(status.actionableId, reblog).getOrThrow()
|
||||||
} catch (t: Exception) {
|
} catch (t: Exception) {
|
||||||
ifExpected(t) {
|
ifExpected(t) {
|
||||||
Log.d(TAG, "Failed to reblog status " + status.actionableId, t)
|
Log.d(TAG, "Failed to reblog status " + status.actionableId, t)
|
||||||
|
@ -120,7 +118,7 @@ abstract class TimelineViewModel(
|
||||||
|
|
||||||
fun favorite(favorite: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
fun favorite(favorite: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
timelineCases.favourite(status.actionableId, favorite).await()
|
timelineCases.favourite(status.actionableId, favorite).getOrThrow()
|
||||||
} catch (t: Exception) {
|
} catch (t: Exception) {
|
||||||
ifExpected(t) {
|
ifExpected(t) {
|
||||||
Log.d(TAG, "Failed to favourite status " + status.actionableId, t)
|
Log.d(TAG, "Failed to favourite status " + status.actionableId, t)
|
||||||
|
@ -130,7 +128,7 @@ abstract class TimelineViewModel(
|
||||||
|
|
||||||
fun bookmark(bookmark: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
fun bookmark(bookmark: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
timelineCases.bookmark(status.actionableId, bookmark).await()
|
timelineCases.bookmark(status.actionableId, bookmark).getOrThrow()
|
||||||
} catch (t: Exception) {
|
} catch (t: Exception) {
|
||||||
ifExpected(t) {
|
ifExpected(t) {
|
||||||
Log.d(TAG, "Failed to bookmark status " + status.actionableId, t)
|
Log.d(TAG, "Failed to bookmark status " + status.actionableId, t)
|
||||||
|
@ -148,7 +146,7 @@ abstract class TimelineViewModel(
|
||||||
updatePoll(votedPoll, status)
|
updatePoll(votedPoll, status)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
timelineCases.voteInPoll(status.actionableId, poll.id, choices).await()
|
timelineCases.voteInPoll(status.actionableId, poll.id, choices).getOrThrow()
|
||||||
} catch (t: Exception) {
|
} catch (t: Exception) {
|
||||||
ifExpected(t) {
|
ifExpected(t) {
|
||||||
Log.d(TAG, "Failed to vote in poll: " + status.actionableId, t)
|
Log.d(TAG, "Failed to vote in poll: " + status.actionableId, t)
|
||||||
|
|
|
@ -28,7 +28,6 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.filterIsInstance
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.rx3.asFlow
|
|
||||||
import okio.IOException
|
import okio.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -55,7 +54,7 @@ class TrendingViewModel @Inject constructor(
|
||||||
// or deleted. Unfortunately, there's nothing in the event to determine if it's a filter
|
// or deleted. Unfortunately, there's nothing in the event to determine if it's a filter
|
||||||
// that was modified, so refresh on every preference change.
|
// that was modified, so refresh on every preference change.
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
eventHub.events.asFlow()
|
eventHub.events
|
||||||
.filterIsInstance<PreferenceChangedEvent>()
|
.filterIsInstance<PreferenceChangedEvent>()
|
||||||
.collect {
|
.collect {
|
||||||
invalidate()
|
invalidate()
|
||||||
|
|
|
@ -20,6 +20,7 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import at.connyduck.calladapter.networkresult.fold
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
import at.connyduck.calladapter.networkresult.getOrElse
|
import at.connyduck.calladapter.networkresult.getOrElse
|
||||||
|
import at.connyduck.calladapter.networkresult.getOrThrow
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.keylesspalace.tusky.appstore.BlockEvent
|
import com.keylesspalace.tusky.appstore.BlockEvent
|
||||||
import com.keylesspalace.tusky.appstore.BookmarkEvent
|
import com.keylesspalace.tusky.appstore.BookmarkEvent
|
||||||
|
@ -50,8 +51,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.rx3.asFlow
|
|
||||||
import kotlinx.coroutines.rx3.await
|
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -85,7 +84,6 @@ class ViewThreadViewModel @Inject constructor(
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
eventHub.events
|
eventHub.events
|
||||||
.asFlow()
|
|
||||||
.collect { event ->
|
.collect { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
is FavoriteEvent -> handleFavEvent(event)
|
is FavoriteEvent -> handleFavEvent(event)
|
||||||
|
@ -195,7 +193,7 @@ class ViewThreadViewModel @Inject constructor(
|
||||||
|
|
||||||
fun reblog(reblog: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
fun reblog(reblog: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
timelineCases.reblog(status.actionableId, reblog).await()
|
timelineCases.reblog(status.actionableId, reblog).getOrThrow()
|
||||||
} catch (t: Exception) {
|
} catch (t: Exception) {
|
||||||
ifExpected(t) {
|
ifExpected(t) {
|
||||||
Log.d(TAG, "Failed to reblog status " + status.actionableId, t)
|
Log.d(TAG, "Failed to reblog status " + status.actionableId, t)
|
||||||
|
@ -205,7 +203,7 @@ class ViewThreadViewModel @Inject constructor(
|
||||||
|
|
||||||
fun favorite(favorite: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
fun favorite(favorite: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
timelineCases.favourite(status.actionableId, favorite).await()
|
timelineCases.favourite(status.actionableId, favorite).getOrThrow()
|
||||||
} catch (t: Exception) {
|
} catch (t: Exception) {
|
||||||
ifExpected(t) {
|
ifExpected(t) {
|
||||||
Log.d(TAG, "Failed to favourite status " + status.actionableId, t)
|
Log.d(TAG, "Failed to favourite status " + status.actionableId, t)
|
||||||
|
@ -215,7 +213,7 @@ class ViewThreadViewModel @Inject constructor(
|
||||||
|
|
||||||
fun bookmark(bookmark: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
fun bookmark(bookmark: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
timelineCases.bookmark(status.actionableId, bookmark).await()
|
timelineCases.bookmark(status.actionableId, bookmark).getOrThrow()
|
||||||
} catch (t: Exception) {
|
} catch (t: Exception) {
|
||||||
ifExpected(t) {
|
ifExpected(t) {
|
||||||
Log.d(TAG, "Failed to bookmark status " + status.actionableId, t)
|
Log.d(TAG, "Failed to bookmark status " + status.actionableId, t)
|
||||||
|
@ -235,7 +233,7 @@ class ViewThreadViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
timelineCases.voteInPoll(status.actionableId, poll.id, choices).await()
|
timelineCases.voteInPoll(status.actionableId, poll.id, choices).getOrThrow()
|
||||||
} catch (t: Exception) {
|
} catch (t: Exception) {
|
||||||
ifExpected(t) {
|
ifExpected(t) {
|
||||||
Log.d(TAG, "Failed to vote in poll: " + status.actionableId, t)
|
Log.d(TAG, "Failed to vote in poll: " + status.actionableId, t)
|
||||||
|
|
|
@ -33,11 +33,9 @@ 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.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import at.connyduck.calladapter.networkresult.fold
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
import autodispose2.AutoDispose
|
import at.connyduck.calladapter.networkresult.onFailure
|
||||||
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider
|
|
||||||
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.BottomSheetActivity
|
import com.keylesspalace.tusky.BottomSheetActivity
|
||||||
|
@ -61,7 +59,6 @@ import com.keylesspalace.tusky.util.openLink
|
||||||
import com.keylesspalace.tusky.util.parseAsMastodonHtml
|
import com.keylesspalace.tusky.util.parseAsMastodonHtml
|
||||||
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 io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -276,30 +273,19 @@ abstract class SFragment : Fragment(), Injectable {
|
||||||
return@setOnMenuItemClickListener true
|
return@setOnMenuItemClickListener true
|
||||||
}
|
}
|
||||||
R.id.pin -> {
|
R.id.pin -> {
|
||||||
timelineCases.pin(status.id, !status.isPinned())
|
lifecycleScope.launch {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
timelineCases.pin(status.id, !status.isPinned()).onFailure { e: Throwable ->
|
||||||
.doOnError { e: Throwable ->
|
val message = e.message
|
||||||
val message = e.message ?: getString(if (status.isPinned()) R.string.failed_to_unpin else R.string.failed_to_pin)
|
?: getString(if (status.isPinned()) R.string.failed_to_unpin else R.string.failed_to_pin)
|
||||||
Snackbar.make(requireView(), message, Snackbar.LENGTH_LONG).show()
|
Snackbar.make(requireView(), message, Snackbar.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
.to(
|
}
|
||||||
AutoDispose.autoDisposable(
|
|
||||||
AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.subscribe()
|
|
||||||
return@setOnMenuItemClickListener true
|
return@setOnMenuItemClickListener true
|
||||||
}
|
}
|
||||||
R.id.status_mute_conversation -> {
|
R.id.status_mute_conversation -> {
|
||||||
timelineCases.muteConversation(status.id, status.muted != true)
|
lifecycleScope.launch {
|
||||||
.onErrorReturnItem(status)
|
timelineCases.muteConversation(status.id, status.muted != true)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
}
|
||||||
.to(
|
|
||||||
AutoDispose.autoDisposable(
|
|
||||||
AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.subscribe()
|
|
||||||
return@setOnMenuItemClickListener true
|
return@setOnMenuItemClickListener true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,54 +235,54 @@ interface MastodonApi {
|
||||||
): NetworkResult<DeletedStatus>
|
): NetworkResult<DeletedStatus>
|
||||||
|
|
||||||
@POST("api/v1/statuses/{id}/reblog")
|
@POST("api/v1/statuses/{id}/reblog")
|
||||||
fun reblogStatus(
|
suspend fun reblogStatus(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Single<Status>
|
): NetworkResult<Status>
|
||||||
|
|
||||||
@POST("api/v1/statuses/{id}/unreblog")
|
@POST("api/v1/statuses/{id}/unreblog")
|
||||||
fun unreblogStatus(
|
suspend fun unreblogStatus(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Single<Status>
|
): NetworkResult<Status>
|
||||||
|
|
||||||
@POST("api/v1/statuses/{id}/favourite")
|
@POST("api/v1/statuses/{id}/favourite")
|
||||||
fun favouriteStatus(
|
suspend fun favouriteStatus(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Single<Status>
|
): NetworkResult<Status>
|
||||||
|
|
||||||
@POST("api/v1/statuses/{id}/unfavourite")
|
@POST("api/v1/statuses/{id}/unfavourite")
|
||||||
fun unfavouriteStatus(
|
suspend fun unfavouriteStatus(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Single<Status>
|
): NetworkResult<Status>
|
||||||
|
|
||||||
@POST("api/v1/statuses/{id}/bookmark")
|
@POST("api/v1/statuses/{id}/bookmark")
|
||||||
fun bookmarkStatus(
|
suspend fun bookmarkStatus(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Single<Status>
|
): NetworkResult<Status>
|
||||||
|
|
||||||
@POST("api/v1/statuses/{id}/unbookmark")
|
@POST("api/v1/statuses/{id}/unbookmark")
|
||||||
fun unbookmarkStatus(
|
suspend fun unbookmarkStatus(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Single<Status>
|
): NetworkResult<Status>
|
||||||
|
|
||||||
@POST("api/v1/statuses/{id}/pin")
|
@POST("api/v1/statuses/{id}/pin")
|
||||||
fun pinStatus(
|
suspend fun pinStatus(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Single<Status>
|
): NetworkResult<Status>
|
||||||
|
|
||||||
@POST("api/v1/statuses/{id}/unpin")
|
@POST("api/v1/statuses/{id}/unpin")
|
||||||
fun unpinStatus(
|
suspend fun unpinStatus(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Single<Status>
|
): NetworkResult<Status>
|
||||||
|
|
||||||
@POST("api/v1/statuses/{id}/mute")
|
@POST("api/v1/statuses/{id}/mute")
|
||||||
fun muteConversation(
|
suspend fun muteConversation(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Single<Status>
|
): NetworkResult<Status>
|
||||||
|
|
||||||
@POST("api/v1/statuses/{id}/unmute")
|
@POST("api/v1/statuses/{id}/unmute")
|
||||||
fun unmuteConversation(
|
suspend fun unmuteConversation(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Single<Status>
|
): NetworkResult<Status>
|
||||||
|
|
||||||
@GET("api/v1/scheduled_statuses")
|
@GET("api/v1/scheduled_statuses")
|
||||||
fun scheduledStatuses(
|
fun scheduledStatuses(
|
||||||
|
@ -450,14 +450,14 @@ interface MastodonApi {
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST("api/v1/domain_blocks")
|
@POST("api/v1/domain_blocks")
|
||||||
fun blockDomain(
|
suspend fun blockDomain(
|
||||||
@Field("domain") domain: String
|
@Field("domain") domain: String
|
||||||
): Call<Any>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
// @DELETE doesn't support fields
|
// @DELETE doesn't support fields
|
||||||
@HTTP(method = "DELETE", path = "api/v1/domain_blocks", hasBody = true)
|
@HTTP(method = "DELETE", path = "api/v1/domain_blocks", hasBody = true)
|
||||||
fun unblockDomain(@Field("domain") domain: String): Call<Any>
|
suspend fun unblockDomain(@Field("domain") domain: String): NetworkResult<Unit>
|
||||||
|
|
||||||
@GET("api/v1/favourites")
|
@GET("api/v1/favourites")
|
||||||
suspend fun favourites(
|
suspend fun favourites(
|
||||||
|
@ -648,10 +648,10 @@ interface MastodonApi {
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST("api/v1/polls/{id}/votes")
|
@POST("api/v1/polls/{id}/votes")
|
||||||
fun voteInPoll(
|
suspend fun voteInPoll(
|
||||||
@Path("id") id: String,
|
@Path("id") id: String,
|
||||||
@Field("choices[]") choices: List<Int>
|
@Field("choices[]") choices: List<Int>
|
||||||
): Single<Poll>
|
): NetworkResult<Poll>
|
||||||
|
|
||||||
@GET("api/v1/announcements")
|
@GET("api/v1/announcements")
|
||||||
suspend fun listAnnouncements(
|
suspend fun listAnnouncements(
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.keylesspalace.tusky.settings
|
package com.keylesspalace.tusky.settings
|
||||||
|
|
||||||
import androidx.preference.PreferenceDataStore
|
import androidx.preference.PreferenceDataStore
|
||||||
import com.keylesspalace.tusky.appstore.EventHub
|
|
||||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||||
import com.keylesspalace.tusky.db.AccountEntity
|
import com.keylesspalace.tusky.db.AccountEntity
|
||||||
import com.keylesspalace.tusky.db.AccountManager
|
import com.keylesspalace.tusky.db.AccountManager
|
||||||
|
@ -9,7 +8,7 @@ import com.keylesspalace.tusky.db.AccountManager
|
||||||
class AccountPreferenceHandler(
|
class AccountPreferenceHandler(
|
||||||
private val account: AccountEntity,
|
private val account: AccountEntity,
|
||||||
private val accountManager: AccountManager,
|
private val accountManager: AccountManager,
|
||||||
private val eventHub: EventHub
|
private val dispatchEvent: (PreferenceChangedEvent) -> Unit
|
||||||
) : PreferenceDataStore() {
|
) : PreferenceDataStore() {
|
||||||
|
|
||||||
override fun getBoolean(key: String, defValue: Boolean): Boolean {
|
override fun getBoolean(key: String, defValue: Boolean): Boolean {
|
||||||
|
@ -30,6 +29,6 @@ class AccountPreferenceHandler(
|
||||||
|
|
||||||
accountManager.saveAccount(account)
|
accountManager.saveAccount(account)
|
||||||
|
|
||||||
eventHub.dispatch(PreferenceChangedEvent(key))
|
dispatchEvent(PreferenceChangedEvent(key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ package com.keylesspalace.tusky.usecase
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import at.connyduck.calladapter.networkresult.NetworkResult
|
import at.connyduck.calladapter.networkresult.NetworkResult
|
||||||
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
import at.connyduck.calladapter.networkresult.onFailure
|
import at.connyduck.calladapter.networkresult.onFailure
|
||||||
import at.connyduck.calladapter.networkresult.onSuccess
|
import at.connyduck.calladapter.networkresult.onSuccess
|
||||||
import com.keylesspalace.tusky.appstore.BlockEvent
|
import com.keylesspalace.tusky.appstore.BlockEvent
|
||||||
|
@ -36,7 +37,6 @@ import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.util.getServerErrorMessage
|
import com.keylesspalace.tusky.util.getServerErrorMessage
|
||||||
import io.reactivex.rxjava3.core.Single
|
import io.reactivex.rxjava3.core.Single
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,52 +48,42 @@ class TimelineCases @Inject constructor(
|
||||||
private val eventHub: EventHub
|
private val eventHub: EventHub
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
suspend fun reblog(statusId: String, reblog: Boolean): NetworkResult<Status> {
|
||||||
* Unused yet but can be use for cancellation later. It's always a good idea to save
|
return if (reblog) {
|
||||||
* Disposables.
|
|
||||||
*/
|
|
||||||
private val cancelDisposable = CompositeDisposable()
|
|
||||||
|
|
||||||
fun reblog(statusId: String, reblog: Boolean): Single<Status> {
|
|
||||||
val call = if (reblog) {
|
|
||||||
mastodonApi.reblogStatus(statusId)
|
mastodonApi.reblogStatus(statusId)
|
||||||
} else {
|
} else {
|
||||||
mastodonApi.unreblogStatus(statusId)
|
mastodonApi.unreblogStatus(statusId)
|
||||||
}
|
}.onSuccess {
|
||||||
return call.doAfterSuccess {
|
|
||||||
eventHub.dispatch(ReblogEvent(statusId, reblog))
|
eventHub.dispatch(ReblogEvent(statusId, reblog))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun favourite(statusId: String, favourite: Boolean): Single<Status> {
|
suspend fun favourite(statusId: String, favourite: Boolean): NetworkResult<Status> {
|
||||||
val call = if (favourite) {
|
return if (favourite) {
|
||||||
mastodonApi.favouriteStatus(statusId)
|
mastodonApi.favouriteStatus(statusId)
|
||||||
} else {
|
} else {
|
||||||
mastodonApi.unfavouriteStatus(statusId)
|
mastodonApi.unfavouriteStatus(statusId)
|
||||||
}
|
}.onSuccess {
|
||||||
return call.doAfterSuccess {
|
|
||||||
eventHub.dispatch(FavoriteEvent(statusId, favourite))
|
eventHub.dispatch(FavoriteEvent(statusId, favourite))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bookmark(statusId: String, bookmark: Boolean): Single<Status> {
|
suspend fun bookmark(statusId: String, bookmark: Boolean): NetworkResult<Status> {
|
||||||
val call = if (bookmark) {
|
return if (bookmark) {
|
||||||
mastodonApi.bookmarkStatus(statusId)
|
mastodonApi.bookmarkStatus(statusId)
|
||||||
} else {
|
} else {
|
||||||
mastodonApi.unbookmarkStatus(statusId)
|
mastodonApi.unbookmarkStatus(statusId)
|
||||||
}
|
}.onSuccess {
|
||||||
return call.doAfterSuccess {
|
|
||||||
eventHub.dispatch(BookmarkEvent(statusId, bookmark))
|
eventHub.dispatch(BookmarkEvent(statusId, bookmark))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun muteConversation(statusId: String, mute: Boolean): Single<Status> {
|
suspend fun muteConversation(statusId: String, mute: Boolean): NetworkResult<Status> {
|
||||||
val call = if (mute) {
|
return if (mute) {
|
||||||
mastodonApi.muteConversation(statusId)
|
mastodonApi.muteConversation(statusId)
|
||||||
} else {
|
} else {
|
||||||
mastodonApi.unmuteConversation(statusId)
|
mastodonApi.unmuteConversation(statusId)
|
||||||
}
|
}.onSuccess {
|
||||||
return call.doAfterSuccess {
|
|
||||||
eventHub.dispatch(MuteConversationEvent(statusId, mute))
|
eventHub.dispatch(MuteConversationEvent(statusId, mute))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,25 +112,27 @@ class TimelineCases @Inject constructor(
|
||||||
.onFailure { Log.w(TAG, "Failed to delete status", it) }
|
.onFailure { Log.w(TAG, "Failed to delete status", it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pin(statusId: String, pin: Boolean): Single<Status> {
|
suspend fun pin(statusId: String, pin: Boolean): NetworkResult<Status> {
|
||||||
// Replace with extension method if we use RxKotlin
|
return if (pin) {
|
||||||
return (if (pin) mastodonApi.pinStatus(statusId) else mastodonApi.unpinStatus(statusId))
|
mastodonApi.pinStatus(statusId)
|
||||||
.doOnError { e ->
|
} else {
|
||||||
Log.w(TAG, "Failed to change pin state", e)
|
mastodonApi.unpinStatus(statusId)
|
||||||
}
|
}.fold({ status ->
|
||||||
.onErrorResumeNext(::convertError)
|
eventHub.dispatch(PinEvent(statusId, pin))
|
||||||
.doAfterSuccess {
|
NetworkResult.success(status)
|
||||||
eventHub.dispatch(PinEvent(statusId, pin))
|
}, { e ->
|
||||||
}
|
Log.w(TAG, "Failed to change pin state", e)
|
||||||
|
NetworkResult.failure(TimelineError(e.getServerErrorMessage()))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun voteInPoll(statusId: String, pollId: String, choices: List<Int>): Single<Poll> {
|
suspend fun voteInPoll(statusId: String, pollId: String, choices: List<Int>): NetworkResult<Poll> {
|
||||||
if (choices.isEmpty()) {
|
if (choices.isEmpty()) {
|
||||||
return Single.error(IllegalStateException())
|
return NetworkResult.failure(IllegalStateException())
|
||||||
}
|
}
|
||||||
|
|
||||||
return mastodonApi.voteInPoll(pollId, choices).doAfterSuccess {
|
return mastodonApi.voteInPoll(pollId, choices).onSuccess { poll ->
|
||||||
eventHub.dispatch(PollVoteEvent(statusId, it))
|
eventHub.dispatch(PollVoteEvent(statusId, poll))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,10 +144,6 @@ class TimelineCases @Inject constructor(
|
||||||
return mastodonApi.rejectFollowRequest(accountId)
|
return mastodonApi.rejectFollowRequest(accountId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T : Any> convertError(e: Throwable): Single<T> {
|
|
||||||
return Single.error(TimelineError(e.getServerErrorMessage()))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "TimelineCases"
|
private const val TAG = "TimelineCases"
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
package com.keylesspalace.tusky.components.notifications
|
package com.keylesspalace.tusky.components.notifications
|
||||||
|
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
|
import at.connyduck.calladapter.networkresult.NetworkResult
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import com.keylesspalace.tusky.FilterV1Test.Companion.mockStatus
|
import com.keylesspalace.tusky.FilterV1Test.Companion.mockStatus
|
||||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||||
import io.reactivex.rxjava3.core.Single
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -73,7 +73,7 @@ class NotificationsViewModelTestStatusAction : NotificationsViewModelTestBase()
|
||||||
@Test
|
@Test
|
||||||
fun `bookmark succeeds && emits UiSuccess`() = runTest {
|
fun `bookmark succeeds && emits UiSuccess`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
timelineCases.stub { onBlocking { bookmark(any(), any()) } doReturn Single.just(status) }
|
timelineCases.stub { onBlocking { bookmark(any(), any()) } doReturn NetworkResult.success(status) }
|
||||||
|
|
||||||
viewModel.uiSuccess.test {
|
viewModel.uiSuccess.test {
|
||||||
// When
|
// When
|
||||||
|
@ -111,7 +111,7 @@ class NotificationsViewModelTestStatusAction : NotificationsViewModelTestBase()
|
||||||
fun `favourite succeeds && emits UiSuccess`() = runTest {
|
fun `favourite succeeds && emits UiSuccess`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
timelineCases.stub {
|
timelineCases.stub {
|
||||||
onBlocking { favourite(any(), any()) } doReturn Single.just(status)
|
onBlocking { favourite(any(), any()) } doReturn NetworkResult.success(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.uiSuccess.test {
|
viewModel.uiSuccess.test {
|
||||||
|
@ -149,7 +149,7 @@ class NotificationsViewModelTestStatusAction : NotificationsViewModelTestBase()
|
||||||
@Test
|
@Test
|
||||||
fun `reblog succeeds && emits UiSuccess`() = runTest {
|
fun `reblog succeeds && emits UiSuccess`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
timelineCases.stub { onBlocking { reblog(any(), any()) } doReturn Single.just(status) }
|
timelineCases.stub { onBlocking { reblog(any(), any()) } doReturn NetworkResult.success(status) }
|
||||||
|
|
||||||
viewModel.uiSuccess.test {
|
viewModel.uiSuccess.test {
|
||||||
// When
|
// When
|
||||||
|
@ -187,7 +187,7 @@ class NotificationsViewModelTestStatusAction : NotificationsViewModelTestBase()
|
||||||
fun `voteinpoll succeeds && emits UiSuccess`() = runTest {
|
fun `voteinpoll succeeds && emits UiSuccess`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
timelineCases.stub {
|
timelineCases.stub {
|
||||||
onBlocking { voteInPoll(any(), any(), any()) } doReturn Single.just(status.poll!!)
|
onBlocking { voteInPoll(any(), any(), any()) } doReturn NetworkResult.success(status.poll!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.uiSuccess.test {
|
viewModel.uiSuccess.test {
|
||||||
|
|
|
@ -221,9 +221,9 @@ class ViewThreadViewModelTest {
|
||||||
|
|
||||||
viewModel.loadThread(threadId)
|
viewModel.loadThread(threadId)
|
||||||
|
|
||||||
eventHub.dispatch(FavoriteEvent(statusId = "1", false))
|
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
eventHub.dispatch(FavoriteEvent(statusId = "1", false))
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
ThreadUiState.Success(
|
ThreadUiState.Success(
|
||||||
statusViewData = listOf(
|
statusViewData = listOf(
|
||||||
|
@ -245,9 +245,9 @@ class ViewThreadViewModelTest {
|
||||||
|
|
||||||
viewModel.loadThread(threadId)
|
viewModel.loadThread(threadId)
|
||||||
|
|
||||||
eventHub.dispatch(ReblogEvent(statusId = "2", true))
|
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
eventHub.dispatch(ReblogEvent(statusId = "2", true))
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
ThreadUiState.Success(
|
ThreadUiState.Success(
|
||||||
statusViewData = listOf(
|
statusViewData = listOf(
|
||||||
|
@ -269,9 +269,9 @@ class ViewThreadViewModelTest {
|
||||||
|
|
||||||
viewModel.loadThread(threadId)
|
viewModel.loadThread(threadId)
|
||||||
|
|
||||||
eventHub.dispatch(BookmarkEvent(statusId = "3", false))
|
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
eventHub.dispatch(BookmarkEvent(statusId = "3", false))
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
ThreadUiState.Success(
|
ThreadUiState.Success(
|
||||||
statusViewData = listOf(
|
statusViewData = listOf(
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package com.keylesspalace.tusky.usecase
|
package com.keylesspalace.tusky.usecase
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import at.connyduck.calladapter.networkresult.NetworkResult
|
||||||
import com.keylesspalace.tusky.appstore.EventHub
|
import com.keylesspalace.tusky.appstore.EventHub
|
||||||
import com.keylesspalace.tusky.appstore.PinEvent
|
import com.keylesspalace.tusky.appstore.PinEvent
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import io.reactivex.rxjava3.core.Single
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
@ -16,7 +19,7 @@ import org.mockito.kotlin.stub
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import java.util.Date
|
import java.util.*
|
||||||
|
|
||||||
@Config(sdk = [28])
|
@Config(sdk = [28])
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@ -38,21 +41,21 @@ class TimelineCasesTest {
|
||||||
@Test
|
@Test
|
||||||
fun `pin success emits PinEvent`() {
|
fun `pin success emits PinEvent`() {
|
||||||
api.stub {
|
api.stub {
|
||||||
onBlocking { pinStatus(statusId) } doReturn Single.just(mockStatus(pinned = true))
|
onBlocking { pinStatus(statusId) } doReturn NetworkResult.success(mockStatus(pinned = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
val events = eventHub.events.test()
|
runBlocking {
|
||||||
timelineCases.pin(statusId, true)
|
eventHub.events.test {
|
||||||
.test()
|
timelineCases.pin(statusId, true)
|
||||||
.assertComplete()
|
assertEquals(PinEvent(statusId, true), awaitItem())
|
||||||
|
}
|
||||||
events.assertValue(PinEvent(statusId, true))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `pin failure with server error throws TimelineError with server message`() {
|
fun `pin failure with server error throws TimelineError with server message`() {
|
||||||
api.stub {
|
api.stub {
|
||||||
onBlocking { pinStatus(statusId) } doReturn Single.error(
|
onBlocking { pinStatus(statusId) } doReturn NetworkResult.failure(
|
||||||
HttpException(
|
HttpException(
|
||||||
Response.error<Status>(
|
Response.error<Status>(
|
||||||
422,
|
422,
|
||||||
|
@ -61,9 +64,12 @@ class TimelineCasesTest {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
timelineCases.pin(statusId, true)
|
runBlocking {
|
||||||
.test()
|
assertEquals(
|
||||||
.assertError { it.message == "Validation Failed: You have already pinned the maximum number of toots" }
|
"Validation Failed: You have already pinned the maximum number of toots",
|
||||||
|
timelineCases.pin(statusId, true).exceptionOrNull()?.message
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mockStatus(pinned: Boolean = false): Status {
|
private fun mockStatus(pinned: Boolean = false): Status {
|
||||||
|
|
Loading…
Reference in a new issue