From 54e92b215605361bde7ce5f6bbc9a6a4c59d7379 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 26 Sep 2023 09:08:58 +0200 Subject: [PATCH] improve local status updates (#3480) The idea here is: Everytime we get hold of a new version of a post, we update everything about that post everywhere. This makes the distincion between different event types unnecessary, as everythng is just a `StatusChangedEvent`. The main benefit is that posts should be up-to-date more often, which is important considering there is now editing and #3413 --- .../tusky/appstore/CacheUpdater.kt | 16 ++-- .../keylesspalace/tusky/appstore/Events.kt | 6 +- .../components/timeline/TimelineFragment.kt | 4 - .../viewmodel/CachedTimelineViewModel.kt | 19 +--- .../viewmodel/NetworkTimelineViewModel.kt | 32 ++----- .../timeline/viewmodel/TimelineViewModel.kt | 19 +--- .../viewthread/ViewThreadViewModel.kt | 69 +++++--------- .../com/keylesspalace/tusky/db/TimelineDao.kt | 89 +++++++++++++++++-- .../tusky/service/SendStatusService.kt | 4 +- .../tusky/usecase/TimelineCases.kt | 19 ++-- .../viewthread/ViewThreadViewModelTest.kt | 56 +----------- .../tusky/usecase/TimelineCasesTest.kt | 10 ++- 12 files changed, 145 insertions(+), 198 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/appstore/CacheUpdater.kt b/app/src/main/java/com/keylesspalace/tusky/appstore/CacheUpdater.kt index 9515457b3..b44a392bf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/appstore/CacheUpdater.kt +++ b/app/src/main/java/com/keylesspalace/tusky/appstore/CacheUpdater.kt @@ -26,12 +26,14 @@ class CacheUpdater @Inject constructor( eventHub.events.collect { event -> val accountId = accountManager.activeAccount?.id ?: return@collect when (event) { - is FavoriteEvent -> - timelineDao.setFavourited(accountId, event.statusId, event.favourite) - is ReblogEvent -> - timelineDao.setReblogged(accountId, event.statusId, event.reblog) - is BookmarkEvent -> - timelineDao.setBookmarked(accountId, event.statusId, event.bookmark) + is StatusChangedEvent -> { + val status = event.status + timelineDao.update( + accountId = accountId, + status = status, + gson = gson + ) + } is UnfollowEvent -> timelineDao.removeAllByUser(accountId, event.accountId) is StatusDeletedEvent -> @@ -40,8 +42,6 @@ class CacheUpdater @Inject constructor( val pollString = gson.toJson(event.poll) timelineDao.setVoted(accountId, event.statusId, pollString) } - is PinEvent -> - timelineDao.setPinned(accountId, event.statusId, event.pinned) } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt b/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt index 494d67974..57ed1886b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt +++ b/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt @@ -5,9 +5,7 @@ import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.Status -data class FavoriteEvent(val statusId: String, val favourite: Boolean) : Event -data class ReblogEvent(val statusId: String, val reblog: Boolean) : Event -data class BookmarkEvent(val statusId: String, val bookmark: Boolean) : Event +data class StatusChangedEvent(val status: Status) : Event data class MuteConversationEvent(val statusId: String, val mute: Boolean) : Event data class UnfollowEvent(val accountId: String) : Event data class BlockEvent(val accountId: String) : Event @@ -15,11 +13,9 @@ data class MuteEvent(val accountId: String) : Event data class StatusDeletedEvent(val statusId: String) : Event data class StatusComposedEvent(val status: Status) : Event data class StatusScheduledEvent(val status: Status) : Event -data class StatusEditedEvent(val originalId: String, val status: Status) : Event data class ProfileEditedEvent(val newProfileData: Account) : Event data class PreferenceChangedEvent(val preferenceKey: String) : Event data class MainTabsChangedEvent(val newTabs: List) : Event data class PollVoteEvent(val statusId: String, val poll: Poll) : Event data class DomainMuteEvent(val instance: String) : Event data class AnnouncementReadEvent(val announcementId: String) : Event -data class PinEvent(val statusId: String, val pinned: Boolean) : Event diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt index 4ab035652..40f8b7d2b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt @@ -45,7 +45,6 @@ import com.keylesspalace.tusky.adapter.StatusBaseViewHolder import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.PreferenceChangedEvent import com.keylesspalace.tusky.appstore.StatusComposedEvent -import com.keylesspalace.tusky.appstore.StatusEditedEvent import com.keylesspalace.tusky.components.accountlist.AccountListActivity import com.keylesspalace.tusky.components.accountlist.AccountListActivity.Companion.newIntent import com.keylesspalace.tusky.components.preference.PreferencesFragment.ReadingOrder @@ -306,9 +305,6 @@ class TimelineFragment : val status = event.status handleStatusComposeEvent(status) } - is StatusEditedEvent -> { - handleStatusComposeEvent(event.status) - } } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt index d33bf7e41..4ad77fd0b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt @@ -27,11 +27,7 @@ import androidx.paging.filter import androidx.paging.map import androidx.room.withTransaction import com.google.gson.Gson -import com.keylesspalace.tusky.appstore.BookmarkEvent import com.keylesspalace.tusky.appstore.EventHub -import com.keylesspalace.tusky.appstore.FavoriteEvent -import com.keylesspalace.tusky.appstore.PinEvent -import com.keylesspalace.tusky.appstore.ReblogEvent import com.keylesspalace.tusky.components.preference.PreferencesFragment.ReadingOrder.NEWEST_FIRST import com.keylesspalace.tusky.components.preference.PreferencesFragment.ReadingOrder.OLDEST_FIRST import com.keylesspalace.tusky.components.timeline.Placeholder @@ -43,6 +39,7 @@ import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.db.TimelineStatusWithAccount import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.entity.Poll +import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.network.FilterModel import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.usecase.TimelineCases @@ -253,19 +250,7 @@ class CachedTimelineViewModel @Inject constructor( .insertStatus(Placeholder(placeholderId, loading = false).toEntity(activeAccount.id)) } - override fun handleReblogEvent(reblogEvent: ReblogEvent) { - // handled by CacheUpdater - } - - override fun handleFavEvent(favEvent: FavoriteEvent) { - // handled by CacheUpdater - } - - override fun handleBookmarkEvent(bookmarkEvent: BookmarkEvent) { - // handled by CacheUpdater - } - - override fun handlePinEvent(pinEvent: PinEvent) { + override fun handleStatusChangedEvent(status: Status) { // handled by CacheUpdater } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt index 9c2874498..7b2608f0a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt @@ -23,11 +23,7 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.cachedIn import androidx.paging.filter -import com.keylesspalace.tusky.appstore.BookmarkEvent import com.keylesspalace.tusky.appstore.EventHub -import com.keylesspalace.tusky.appstore.FavoriteEvent -import com.keylesspalace.tusky.appstore.PinEvent -import com.keylesspalace.tusky.appstore.ReblogEvent import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.entity.Filter @@ -219,27 +215,13 @@ class NetworkTimelineViewModel @Inject constructor( currentSource?.invalidate() } - override fun handleReblogEvent(reblogEvent: ReblogEvent) { - updateStatusById(reblogEvent.statusId) { - it.copy(status = it.status.copy(reblogged = reblogEvent.reblog)) - } - } - - override fun handleFavEvent(favEvent: FavoriteEvent) { - updateActionableStatusById(favEvent.statusId) { - it.copy(favourited = favEvent.favourite) - } - } - - override fun handleBookmarkEvent(bookmarkEvent: BookmarkEvent) { - updateActionableStatusById(bookmarkEvent.statusId) { - it.copy(bookmarked = bookmarkEvent.bookmark) - } - } - - override fun handlePinEvent(pinEvent: PinEvent) { - updateActionableStatusById(pinEvent.statusId) { - it.copy(pinned = pinEvent.pinned) + override fun handleStatusChangedEvent(status: Status) { + updateStatusById(status.id) { oldViewData -> + status.toViewData( + isShowingContent = oldViewData.isShowingContent, + isExpanded = oldViewData.isExpanded, + isCollapsed = oldViewData.isCollapsed + ) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/TimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/TimelineViewModel.kt index e225e7e16..a2f32e8d5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/TimelineViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/TimelineViewModel.kt @@ -24,16 +24,13 @@ import at.connyduck.calladapter.networkresult.fold import at.connyduck.calladapter.networkresult.getOrElse import at.connyduck.calladapter.networkresult.getOrThrow import com.keylesspalace.tusky.appstore.BlockEvent -import com.keylesspalace.tusky.appstore.BookmarkEvent import com.keylesspalace.tusky.appstore.DomainMuteEvent import com.keylesspalace.tusky.appstore.Event import com.keylesspalace.tusky.appstore.EventHub -import com.keylesspalace.tusky.appstore.FavoriteEvent import com.keylesspalace.tusky.appstore.MuteConversationEvent import com.keylesspalace.tusky.appstore.MuteEvent -import com.keylesspalace.tusky.appstore.PinEvent import com.keylesspalace.tusky.appstore.PreferenceChangedEvent -import com.keylesspalace.tusky.appstore.ReblogEvent +import com.keylesspalace.tusky.appstore.StatusChangedEvent import com.keylesspalace.tusky.appstore.StatusDeletedEvent import com.keylesspalace.tusky.appstore.UnfollowEvent import com.keylesspalace.tusky.components.preference.PreferencesFragment.ReadingOrder @@ -42,6 +39,7 @@ import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.entity.FilterV1 import com.keylesspalace.tusky.entity.Poll +import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.network.FilterModel import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.settings.PrefKeys @@ -170,13 +168,7 @@ abstract class TimelineViewModel( abstract fun loadMore(placeholderId: String) - abstract fun handleReblogEvent(reblogEvent: ReblogEvent) - - abstract fun handleFavEvent(favEvent: FavoriteEvent) - - abstract fun handleBookmarkEvent(bookmarkEvent: BookmarkEvent) - - abstract fun handlePinEvent(pinEvent: PinEvent) + abstract fun handleStatusChangedEvent(status: Status) abstract fun fullReload() @@ -237,10 +229,7 @@ abstract class TimelineViewModel( private fun handleEvent(event: Event) { when (event) { - is FavoriteEvent -> handleFavEvent(event) - is ReblogEvent -> handleReblogEvent(event) - is BookmarkEvent -> handleBookmarkEvent(event) - is PinEvent -> handlePinEvent(event) + is StatusChangedEvent -> handleStatusChangedEvent(event.status) is MuteConversationEvent -> fullReload() is UnfollowEvent -> { if (kind == Kind.HOME) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModel.kt index da8a91e4d..9ff14d54b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModel.kt @@ -23,14 +23,10 @@ import at.connyduck.calladapter.networkresult.getOrElse import at.connyduck.calladapter.networkresult.getOrThrow import com.google.gson.Gson import com.keylesspalace.tusky.appstore.BlockEvent -import com.keylesspalace.tusky.appstore.BookmarkEvent import com.keylesspalace.tusky.appstore.EventHub -import com.keylesspalace.tusky.appstore.FavoriteEvent -import com.keylesspalace.tusky.appstore.PinEvent -import com.keylesspalace.tusky.appstore.ReblogEvent +import com.keylesspalace.tusky.appstore.StatusChangedEvent import com.keylesspalace.tusky.appstore.StatusComposedEvent import com.keylesspalace.tusky.appstore.StatusDeletedEvent -import com.keylesspalace.tusky.appstore.StatusEditedEvent import com.keylesspalace.tusky.components.timeline.toViewData import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.db.AccountManager @@ -59,7 +55,7 @@ class ViewThreadViewModel @Inject constructor( private val filterModel: FilterModel, private val timelineCases: TimelineCases, eventHub: EventHub, - accountManager: AccountManager, + private val accountManager: AccountManager, private val db: AppDatabase, private val gson: Gson ) : ViewModel() { @@ -86,14 +82,10 @@ class ViewThreadViewModel @Inject constructor( eventHub.events .collect { event -> when (event) { - is FavoriteEvent -> handleFavEvent(event) - is ReblogEvent -> handleReblogEvent(event) - is BookmarkEvent -> handleBookmarkEvent(event) - is PinEvent -> handlePinEvent(event) + is StatusChangedEvent -> handleStatusChangedEvent(event.status) is BlockEvent -> removeAllByAccountId(event.accountId) is StatusComposedEvent -> handleStatusComposedEvent(event) is StatusDeletedEvent -> handleStatusDeletedEvent(event) - is StatusEditedEvent -> handleStatusEditedEvent(event) } } } @@ -107,7 +99,7 @@ class ViewThreadViewModel @Inject constructor( viewModelScope.launch { Log.d(TAG, "Finding status with: $id") val contextCall = async { api.statusContext(id) } - val timelineStatus = db.timelineDao().getStatus(id) + val timelineStatus = db.timelineDao().getStatus(accountManager.activeAccount!!.id, id) var detailedStatus = if (timelineStatus != null) { Log.d(TAG, "Loaded status from local timeline") @@ -144,8 +136,14 @@ class ViewThreadViewModel @Inject constructor( // for the status. Ignore errors, the user still has a functioning UI if the fetch // failed. if (timelineStatus != null) { - val viewData = api.status(id).getOrNull()?.toViewData(isDetailed = true) - if (viewData != null) { detailedStatus = viewData } + api.status(id).getOrNull()?.let { result -> + db.timelineDao().update( + accountId = accountManager.activeAccount!!.id, + status = result, + gson = gson + ) + detailedStatus = result.toViewData(isDetailed = true) + } } val contextResult = contextCall.await() @@ -277,27 +275,14 @@ class ViewThreadViewModel @Inject constructor( } } - private fun handleFavEvent(event: FavoriteEvent) { - updateStatus(event.statusId) { status -> - status.copy(favourited = event.favourite) - } - } - - private fun handleReblogEvent(event: ReblogEvent) { - updateStatus(event.statusId) { status -> - status.copy(reblogged = event.reblog) - } - } - - private fun handleBookmarkEvent(event: BookmarkEvent) { - updateStatus(event.statusId) { status -> - status.copy(bookmarked = event.bookmark) - } - } - - private fun handlePinEvent(event: PinEvent) { - updateStatus(event.statusId) { status -> - status.copy(pinned = event.pinned) + private fun handleStatusChangedEvent(status: Status) { + updateStatusViewData(status.id) { viewData -> + status.toViewData( + isShowingContent = viewData.isShowingContent, + isExpanded = viewData.isExpanded, + isCollapsed = viewData.isCollapsed, + isDetailed = viewData.isDetailed + ) } } @@ -329,20 +314,6 @@ class ViewThreadViewModel @Inject constructor( } } - private fun handleStatusEditedEvent(event: StatusEditedEvent) { - updateSuccess { uiState -> - uiState.copy( - statusViewData = uiState.statusViewData.map { status -> - if (status.actionableId == event.originalId) { - event.status.toViewData() - } else { - status - } - } - ) - } - } - private fun handleStatusDeletedEvent(event: StatusDeletedEvent) { updateSuccess { uiState -> uiState.copy( diff --git a/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt index 0bf1267fa..621851bd5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt @@ -20,6 +20,10 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy.Companion.REPLACE import androidx.room.Query +import androidx.room.TypeConverters +import com.google.gson.Gson +import com.keylesspalace.tusky.entity.FilterResult +import com.keylesspalace.tusky.entity.Status @Dao abstract class TimelineDao { @@ -72,9 +76,10 @@ FROM TimelineStatusEntity s LEFT JOIN TimelineAccountEntity a ON (s.timelineUserId = a.timelineUserId AND s.authorServerId = a.serverId) LEFT JOIN TimelineAccountEntity rb ON (s.timelineUserId = rb.timelineUserId AND s.reblogAccountId = rb.serverId) WHERE (s.serverId = :statusId OR s.reblogServerId = :statusId) -AND s.authorServerId IS NOT NULL""" +AND s.authorServerId IS NOT NULL +AND s.timelineUserId = :accountId""" ) - abstract suspend fun getStatus(statusId: String): TimelineStatusWithAccount? + abstract suspend fun getStatus(accountId: Long, statusId: String): TimelineStatusWithAccount? @Query( """DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND @@ -85,11 +90,85 @@ AND ) abstract suspend fun deleteRange(accountId: Long, minId: String, maxId: String): Int + suspend fun update(accountId: Long, status: Status, gson: Gson) { + update( + accountId = accountId, + statusId = status.id, + content = status.content, + editedAt = status.editedAt?.time, + emojis = gson.toJson(status.emojis), + reblogsCount = status.reblogsCount, + favouritesCount = status.favouritesCount, + repliesCount = status.repliesCount, + reblogged = status.reblogged, + bookmarked = status.bookmarked, + favourited = status.favourited, + sensitive = status.sensitive, + spoilerText = status.spoilerText, + visibility = status.visibility, + attachments = gson.toJson(status.attachments), + mentions = gson.toJson(status.mentions), + tags = gson.toJson(status.tags), + poll = gson.toJson(status.poll), + muted = status.muted, + pinned = status.pinned ?: false, + card = gson.toJson(status.card), + language = status.language, + filtered = status.filtered + ) + } + @Query( - """UPDATE TimelineStatusEntity SET favourited = :favourited -WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""" + """UPDATE TimelineStatusEntity + SET content = :content, + editedAt = :editedAt, + emojis = :emojis, + reblogsCount = :reblogsCount, + favouritesCount = :favouritesCount, + repliesCount = :repliesCount, + reblogged = :reblogged, + bookmarked = :bookmarked, + favourited = :favourited, + sensitive = :sensitive, + spoilerText = :spoilerText, + visibility = :visibility, + attachments = :attachments, + mentions = :mentions, + tags = :tags, + poll = :poll, + muted = :muted, + pinned = :pinned, + card = :card, + language = :language, + filtered = :filtered + WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""" + ) + @TypeConverters(Converters::class) + abstract suspend fun update( + accountId: Long, + statusId: String, + content: String?, + editedAt: Long?, + emojis: String?, + reblogsCount: Int, + favouritesCount: Int, + repliesCount: Int, + reblogged: Boolean, + bookmarked: Boolean, + favourited: Boolean, + sensitive: Boolean, + spoilerText: String, + visibility: Status.Visibility, + attachments: String?, + mentions: String?, + tags: String?, + poll: String?, + muted: Boolean?, + pinned: Boolean, + card: String?, + language: String?, + filtered: List? ) - abstract suspend fun setFavourited(accountId: Long, statusId: String, favourited: Boolean) @Query( """UPDATE TimelineStatusEntity SET bookmarked = :bookmarked diff --git a/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt b/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt index 3aaad1b70..786dadb00 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt +++ b/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt @@ -21,8 +21,8 @@ import at.connyduck.calladapter.networkresult.fold import com.keylesspalace.tusky.MainActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.appstore.EventHub +import com.keylesspalace.tusky.appstore.StatusChangedEvent import com.keylesspalace.tusky.appstore.StatusComposedEvent -import com.keylesspalace.tusky.appstore.StatusEditedEvent import com.keylesspalace.tusky.appstore.StatusScheduledEvent import com.keylesspalace.tusky.components.compose.MediaUploader import com.keylesspalace.tusky.components.compose.UploadEvent @@ -253,7 +253,7 @@ class SendStatusService : Service(), Injectable { if (scheduled) { eventHub.dispatch(StatusScheduledEvent(sentStatus)) } else if (!isNew) { - eventHub.dispatch(StatusEditedEvent(statusToSend.statusId!!, sentStatus)) + eventHub.dispatch(StatusChangedEvent(sentStatus)) } else { eventHub.dispatch(StatusComposedEvent(sentStatus)) } diff --git a/app/src/main/java/com/keylesspalace/tusky/usecase/TimelineCases.kt b/app/src/main/java/com/keylesspalace/tusky/usecase/TimelineCases.kt index 093df2626..234c0bc57 100644 --- a/app/src/main/java/com/keylesspalace/tusky/usecase/TimelineCases.kt +++ b/app/src/main/java/com/keylesspalace/tusky/usecase/TimelineCases.kt @@ -21,14 +21,11 @@ import at.connyduck.calladapter.networkresult.fold import at.connyduck.calladapter.networkresult.onFailure import at.connyduck.calladapter.networkresult.onSuccess import com.keylesspalace.tusky.appstore.BlockEvent -import com.keylesspalace.tusky.appstore.BookmarkEvent import com.keylesspalace.tusky.appstore.EventHub -import com.keylesspalace.tusky.appstore.FavoriteEvent import com.keylesspalace.tusky.appstore.MuteConversationEvent import com.keylesspalace.tusky.appstore.MuteEvent -import com.keylesspalace.tusky.appstore.PinEvent import com.keylesspalace.tusky.appstore.PollVoteEvent -import com.keylesspalace.tusky.appstore.ReblogEvent +import com.keylesspalace.tusky.appstore.StatusChangedEvent import com.keylesspalace.tusky.appstore.StatusDeletedEvent import com.keylesspalace.tusky.entity.DeletedStatus import com.keylesspalace.tusky.entity.Poll @@ -53,8 +50,8 @@ class TimelineCases @Inject constructor( mastodonApi.reblogStatus(statusId) } else { mastodonApi.unreblogStatus(statusId) - }.onSuccess { - eventHub.dispatch(ReblogEvent(statusId, reblog)) + }.onSuccess { status -> + eventHub.dispatch(StatusChangedEvent(status)) } } @@ -63,8 +60,8 @@ class TimelineCases @Inject constructor( mastodonApi.favouriteStatus(statusId) } else { mastodonApi.unfavouriteStatus(statusId) - }.onSuccess { - eventHub.dispatch(FavoriteEvent(statusId, favourite)) + }.onSuccess { status -> + eventHub.dispatch(StatusChangedEvent(status)) } } @@ -73,8 +70,8 @@ class TimelineCases @Inject constructor( mastodonApi.bookmarkStatus(statusId) } else { mastodonApi.unbookmarkStatus(statusId) - }.onSuccess { - eventHub.dispatch(BookmarkEvent(statusId, bookmark)) + }.onSuccess { status -> + eventHub.dispatch(StatusChangedEvent(status)) } } @@ -162,7 +159,7 @@ class TimelineCases @Inject constructor( } else { mastodonApi.unpinStatus(statusId) }.fold({ status -> - eventHub.dispatch(PinEvent(statusId, pin)) + eventHub.dispatch(StatusChangedEvent(status)) NetworkResult.success(status) }, { e -> Log.w(TAG, "Failed to change pin state", e) diff --git a/app/src/test/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModelTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModelTest.kt index 0a07b5ab6..b39765a5c 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModelTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModelTest.kt @@ -7,10 +7,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import at.connyduck.calladapter.networkresult.NetworkResult import com.google.gson.Gson -import com.keylesspalace.tusky.appstore.BookmarkEvent import com.keylesspalace.tusky.appstore.EventHub -import com.keylesspalace.tusky.appstore.FavoriteEvent -import com.keylesspalace.tusky.appstore.ReblogEvent +import com.keylesspalace.tusky.appstore.StatusChangedEvent import com.keylesspalace.tusky.components.timeline.mockStatus import com.keylesspalace.tusky.components.timeline.mockStatusViewData import com.keylesspalace.tusky.db.AccountEntity @@ -216,13 +214,13 @@ class ViewThreadViewModelTest { } @Test - fun `should handle favorite event`() { + fun `should handle status changed event`() { mockSuccessResponses() viewModel.loadThread(threadId) runBlocking { - eventHub.dispatch(FavoriteEvent(statusId = "1", false)) + eventHub.dispatch(StatusChangedEvent(mockStatus(id = "1", spoilerText = "Test", favourited = false))) assertEquals( ThreadUiState.Success( @@ -239,54 +237,6 @@ class ViewThreadViewModelTest { } } - @Test - fun `should handle reblog event`() { - mockSuccessResponses() - - viewModel.loadThread(threadId) - - runBlocking { - eventHub.dispatch(ReblogEvent(statusId = "2", true)) - - assertEquals( - ThreadUiState.Success( - statusViewData = listOf( - mockStatusViewData(id = "1", spoilerText = "Test"), - mockStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test", reblogged = true), - mockStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test") - ), - detailedStatusPosition = 1, - revealButton = RevealButtonState.REVEAL - ), - viewModel.uiState.first() - ) - } - } - - @Test - fun `should handle bookmark event`() { - mockSuccessResponses() - - viewModel.loadThread(threadId) - - runBlocking { - eventHub.dispatch(BookmarkEvent(statusId = "3", false)) - - assertEquals( - ThreadUiState.Success( - statusViewData = listOf( - mockStatusViewData(id = "1", spoilerText = "Test"), - mockStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test"), - mockStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test", bookmarked = false) - ), - detailedStatusPosition = 1, - revealButton = RevealButtonState.REVEAL - ), - viewModel.uiState.first() - ) - } - } - @Test fun `should remove status`() { mockSuccessResponses() diff --git a/app/src/test/java/com/keylesspalace/tusky/usecase/TimelineCasesTest.kt b/app/src/test/java/com/keylesspalace/tusky/usecase/TimelineCasesTest.kt index bcb690641..b31e37a3f 100644 --- a/app/src/test/java/com/keylesspalace/tusky/usecase/TimelineCasesTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/usecase/TimelineCasesTest.kt @@ -4,7 +4,7 @@ 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.PinEvent +import com.keylesspalace.tusky.appstore.StatusChangedEvent import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.network.MastodonApi import kotlinx.coroutines.runBlocking @@ -39,15 +39,17 @@ class TimelineCasesTest { } @Test - fun `pin success emits PinEvent`() { + fun `pin success emits StatusChangedEvent`() { + val pinnedStatus = mockStatus(pinned = true) + api.stub { - onBlocking { pinStatus(statusId) } doReturn NetworkResult.success(mockStatus(pinned = true)) + onBlocking { pinStatus(statusId) } doReturn NetworkResult.success(pinnedStatus) } runBlocking { eventHub.events.test { timelineCases.pin(statusId, true) - assertEquals(PinEvent(statusId, true), awaitItem()) + assertEquals(StatusChangedEvent(pinnedStatus), awaitItem()) } } }