Timeline refactor (#2175)

* Move Timeline files into their own package

* Introduce TimelineViewModel, add coroutines

* Simplify StatusViewData

* Handle timeilne fetch errors

* Rework filters, fix ViewThreadFragment

* Fix NotificationsFragment

* Simplify Notifications and Thread, handle pin

* Redo loading in TimelineViewModel

* Improve error handling in TimelineViewModel

* Rewrite actions in TimelineViewModel

* Apply feedback after timeline factoring review

* Handle initial failure in timeline correctly
This commit is contained in:
Ivan Kupalov 2021-06-11 20:15:40 +02:00 committed by GitHub
commit 44a5b42cac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 3956 additions and 3618 deletions

View file

@ -73,7 +73,7 @@ data class ConversationStatusEntity(
val sensitive: Boolean,
val spoilerText: String,
val attachments: ArrayList<Attachment>,
val mentions: Array<Status.Mention>,
val mentions: List<Status.Mention>,
val showingHiddenContent: Boolean,
val expanded: Boolean,
val collapsible: Boolean,
@ -101,7 +101,7 @@ data class ConversationStatusEntity(
if (sensitive != other.sensitive) return false
if (spoilerText != other.spoilerText) return false
if (attachments != other.attachments) return false
if (!mentions.contentEquals(other.mentions)) return false
if (mentions != other.mentions) return false
if (showingHiddenContent != other.showingHiddenContent) return false
if (expanded != other.expanded) return false
if (collapsible != other.collapsible) return false
@ -125,7 +125,7 @@ data class ConversationStatusEntity(
result = 31 * result + sensitive.hashCode()
result = 31 * result + spoilerText.hashCode()
result = 31 * result + attachments.hashCode()
result = 31 * result + mentions.contentHashCode()
result = 31 * result + mentions.hashCode()
result = 31 * result + showingHiddenContent.hashCode()
result = 31 * result + expanded.hashCode()
result = 31 * result + collapsible.hashCode()

View file

@ -40,6 +40,7 @@ import com.keylesspalace.tusky.util.NetworkState
import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.viewdata.AttachmentViewData
import javax.inject.Inject
class ConversationsFragment : SFragment(), StatusActionListener, Injectable, ReselectableFragment {
@ -132,13 +133,14 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
viewModel.conversations.value?.getOrNull(position)?.lastStatus?.let {
viewMedia(attachmentIndex, it.toStatus(), view)
viewMedia(attachmentIndex, AttachmentViewData.list(it.toStatus()), view)
}
}
override fun onViewThread(position: Int) {
viewModel.conversations.value?.getOrNull(position)?.lastStatus?.let {
viewThread(it.toStatus())
val status = it.toStatus()
viewThread(status.actionableId, status.actionableStatus.url)
}
}

View file

@ -15,17 +15,20 @@ import io.reactivex.rxjava3.schedulers.Schedulers
import javax.inject.Inject
class ConversationsViewModel @Inject constructor(
private val repository: ConversationsRepository,
private val timelineCases: TimelineCases,
private val database: AppDatabase,
private val accountManager: AccountManager
private val repository: ConversationsRepository,
private val timelineCases: TimelineCases,
private val database: AppDatabase,
private val accountManager: AccountManager
) : RxAwareViewModel() {
private val repoResult = MutableLiveData<Listing<ConversationEntity>>()
val conversations: LiveData<PagedList<ConversationEntity>> = Transformations.switchMap(repoResult) { it.pagedList }
val networkState: LiveData<NetworkState> = Transformations.switchMap(repoResult) { it.networkState }
val refreshState: LiveData<NetworkState> = Transformations.switchMap(repoResult) { it.refreshState }
val conversations: LiveData<PagedList<ConversationEntity>> =
Transformations.switchMap(repoResult) { it.pagedList }
val networkState: LiveData<NetworkState> =
Transformations.switchMap(repoResult) { it.networkState }
val refreshState: LiveData<NetworkState> =
Transformations.switchMap(repoResult) { it.refreshState }
fun load() {
val accountId = accountManager.activeAccount?.id ?: return
@ -45,57 +48,76 @@ class ConversationsViewModel @Inject constructor(
fun favourite(favourite: Boolean, position: Int) {
conversations.value?.getOrNull(position)?.let { conversation ->
timelineCases.favourite(conversation.lastStatus.toStatus(), favourite)
.flatMap {
val newConversation = conversation.copy(
lastStatus = conversation.lastStatus.copy(favourited = favourite)
)
timelineCases.favourite(conversation.lastStatus.id, favourite)
.flatMap {
val newConversation = conversation.copy(
lastStatus = conversation.lastStatus.copy(favourited = favourite)
)
database.conversationDao().insert(newConversation)
}
.subscribeOn(Schedulers.io())
.doOnError { t -> Log.w("ConversationViewModel", "Failed to favourite conversation", t) }
.onErrorReturnItem(0)
.subscribe()
.autoDispose()
database.conversationDao().insert(newConversation)
}
.subscribeOn(Schedulers.io())
.doOnError { t ->
Log.w(
"ConversationViewModel",
"Failed to favourite conversation",
t
)
}
.onErrorReturnItem(0)
.subscribe()
.autoDispose()
}
}
fun bookmark(bookmark: Boolean, position: Int) {
conversations.value?.getOrNull(position)?.let { conversation ->
timelineCases.bookmark(conversation.lastStatus.toStatus(), bookmark)
.flatMap {
val newConversation = conversation.copy(
lastStatus = conversation.lastStatus.copy(bookmarked = bookmark)
)
timelineCases.bookmark(conversation.lastStatus.id, bookmark)
.flatMap {
val newConversation = conversation.copy(
lastStatus = conversation.lastStatus.copy(bookmarked = bookmark)
)
database.conversationDao().insert(newConversation)
}
.subscribeOn(Schedulers.io())
.doOnError { t -> Log.w("ConversationViewModel", "Failed to bookmark conversation", t) }
.onErrorReturnItem(0)
.subscribe()
.autoDispose()
database.conversationDao().insert(newConversation)
}
.subscribeOn(Schedulers.io())
.doOnError { t ->
Log.w(
"ConversationViewModel",
"Failed to bookmark conversation",
t
)
}
.onErrorReturnItem(0)
.subscribe()
.autoDispose()
}
}
fun voteInPoll(position: Int, choices: MutableList<Int>) {
conversations.value?.getOrNull(position)?.let { conversation ->
timelineCases.voteInPoll(conversation.lastStatus.toStatus(), choices)
.flatMap { poll ->
val newConversation = conversation.copy(
lastStatus = conversation.lastStatus.copy(poll = poll)
)
val poll = conversation.lastStatus.poll ?: return
timelineCases.voteInPoll(conversation.lastStatus.id, poll.id, choices)
.flatMap { newPoll ->
val newConversation = conversation.copy(
lastStatus = conversation.lastStatus.copy(poll = newPoll)
)
database.conversationDao().insert(newConversation)
}
.subscribeOn(Schedulers.io())
.doOnError { t -> Log.w("ConversationViewModel", "Failed to favourite conversation", t) }
.onErrorReturnItem(0)
.subscribe()
.autoDispose()
database.conversationDao().insert(newConversation)
}
.subscribeOn(Schedulers.io())
.doOnError { t ->
Log.w(
"ConversationViewModel",
"Failed to favourite conversation",
t
)
}
.onErrorReturnItem(0)
.subscribe()
.autoDispose()
}
}
@ -103,7 +125,7 @@ class ConversationsViewModel @Inject constructor(
fun expandHiddenStatus(expanded: Boolean, position: Int) {
conversations.value?.getOrNull(position)?.let { conversation ->
val newConversation = conversation.copy(
lastStatus = conversation.lastStatus.copy(expanded = expanded)
lastStatus = conversation.lastStatus.copy(expanded = expanded)
)
saveConversationToDb(newConversation)
}
@ -112,7 +134,7 @@ class ConversationsViewModel @Inject constructor(
fun collapseLongStatus(collapsed: Boolean, position: Int) {
conversations.value?.getOrNull(position)?.let { conversation ->
val newConversation = conversation.copy(
lastStatus = conversation.lastStatus.copy(collapsed = collapsed)
lastStatus = conversation.lastStatus.copy(collapsed = collapsed)
)
saveConversationToDb(newConversation)
}
@ -121,7 +143,7 @@ class ConversationsViewModel @Inject constructor(
fun showContent(showing: Boolean, position: Int) {
conversations.value?.getOrNull(position)?.let { conversation ->
val newConversation = conversation.copy(
lastStatus = conversation.lastStatus.copy(showingHiddenContent = showing)
lastStatus = conversation.lastStatus.copy(showingHiddenContent = showing)
)
saveConversationToDb(newConversation)
}
@ -135,8 +157,8 @@ class ConversationsViewModel @Inject constructor(
private fun saveConversationToDb(conversation: ConversationEntity) {
database.conversationDao().insert(conversation)
.subscribeOn(Schedulers.io())
.subscribe()
.subscribeOn(Schedulers.io())
.subscribe()
}
}