move Html parsing to ViewData (#2414)
* move Html parsing to ViewData * refactor reports to use viewdata * cleanup code * refactor conversations * fix getEditableText * rename StatusParsingHelper * fix tests * commit db schema file * add file header * rename helper function to parseAsMastodonHtml * order imports correctly * move mapping off main thread to default dispatcher * fix ktlint
This commit is contained in:
parent
ffbc4b6403
commit
3e849244f9
34 changed files with 1232 additions and 500 deletions
|
@ -78,7 +78,7 @@ import com.keylesspalace.tusky.util.emojify
|
|||
import com.keylesspalace.tusky.util.getDomain
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.loadAvatar
|
||||
import com.keylesspalace.tusky.util.openLink
|
||||
import com.keylesspalace.tusky.util.parseAsMastodonHtml
|
||||
import com.keylesspalace.tusky.util.setClickableText
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
|
@ -375,12 +375,11 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
}
|
||||
}
|
||||
viewModel.accountFieldData.observe(
|
||||
this,
|
||||
{
|
||||
accountFieldAdapter.fields = it
|
||||
accountFieldAdapter.notifyDataSetChanged()
|
||||
}
|
||||
)
|
||||
this
|
||||
) {
|
||||
accountFieldAdapter.fields = it
|
||||
accountFieldAdapter.notifyDataSetChanged()
|
||||
}
|
||||
viewModel.noteSaved.observe(this) {
|
||||
binding.saveNoteInfo.visible(it, View.INVISIBLE)
|
||||
}
|
||||
|
@ -395,11 +394,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
adapter.refreshContent()
|
||||
}
|
||||
viewModel.isRefreshing.observe(
|
||||
this,
|
||||
{ isRefreshing ->
|
||||
binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true
|
||||
}
|
||||
)
|
||||
this
|
||||
) { isRefreshing ->
|
||||
binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true
|
||||
}
|
||||
binding.swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
}
|
||||
|
||||
|
@ -410,7 +408,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
binding.accountUsernameTextView.text = usernameFormatted
|
||||
binding.accountDisplayNameTextView.text = account.name.emojify(account.emojis, binding.accountDisplayNameTextView, animateEmojis)
|
||||
|
||||
val emojifiedNote = account.note.emojify(account.emojis, binding.accountNoteTextView, animateEmojis)
|
||||
val emojifiedNote = account.note.parseAsMastodonHtml().emojify(account.emojis, binding.accountNoteTextView, animateEmojis)
|
||||
setClickableText(binding.accountNoteTextView, emojifiedNote, emptyList(), null, this)
|
||||
|
||||
// accountFieldAdapter.fields = account.fields ?: emptyList()
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.keylesspalace.tusky.util.BindingHolder
|
|||
import com.keylesspalace.tusky.util.Either
|
||||
import com.keylesspalace.tusky.util.createClickableText
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.parseAsMastodonHtml
|
||||
import com.keylesspalace.tusky.util.setClickableText
|
||||
|
||||
class AccountFieldAdapter(
|
||||
|
@ -65,7 +66,7 @@ class AccountFieldAdapter(
|
|||
val emojifiedName = field.name.emojify(emojis, nameTextView, animateEmojis)
|
||||
nameTextView.text = emojifiedName
|
||||
|
||||
val emojifiedValue = field.value.emojify(emojis, valueTextView, animateEmojis)
|
||||
val emojifiedValue = field.value.parseAsMastodonHtml().emojify(emojis, valueTextView, animateEmojis)
|
||||
setClickableText(valueTextView, emojifiedValue, emptyList(), null, linkListener)
|
||||
|
||||
if (field.verifiedAt != null) {
|
||||
|
|
|
@ -26,7 +26,7 @@ import com.keylesspalace.tusky.util.StatusDisplayOptions
|
|||
class ConversationAdapter(
|
||||
private val statusDisplayOptions: StatusDisplayOptions,
|
||||
private val listener: StatusActionListener
|
||||
) : PagingDataAdapter<ConversationEntity, ConversationViewHolder>(CONVERSATION_COMPARATOR) {
|
||||
) : PagingDataAdapter<ConversationViewData, ConversationViewHolder>(CONVERSATION_COMPARATOR) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConversationViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_conversation, parent, false)
|
||||
|
@ -37,17 +37,13 @@ class ConversationAdapter(
|
|||
holder.setupWithConversation(getItem(position))
|
||||
}
|
||||
|
||||
fun item(position: Int): ConversationEntity? {
|
||||
return getItem(position)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CONVERSATION_COMPARATOR = object : DiffUtil.ItemCallback<ConversationEntity>() {
|
||||
override fun areItemsTheSame(oldItem: ConversationEntity, newItem: ConversationEntity): Boolean {
|
||||
val CONVERSATION_COMPARATOR = object : DiffUtil.ItemCallback<ConversationViewData>() {
|
||||
override fun areItemsTheSame(oldItem: ConversationViewData, newItem: ConversationViewData): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ConversationEntity, newItem: ConversationEntity): Boolean {
|
||||
override fun areContentsTheSame(oldItem: ConversationViewData, newItem: ConversationViewData): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
package com.keylesspalace.tusky.components.conversation
|
||||
|
||||
import android.text.Spanned
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.TypeConverters
|
||||
|
@ -27,7 +26,7 @@ import com.keylesspalace.tusky.entity.HashTag
|
|||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.entity.TimelineAccount
|
||||
import com.keylesspalace.tusky.util.shouldTrimStatus
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import java.util.Date
|
||||
|
||||
@Entity(primaryKeys = ["id", "accountId"])
|
||||
|
@ -38,7 +37,16 @@ data class ConversationEntity(
|
|||
val accounts: List<ConversationAccountEntity>,
|
||||
val unread: Boolean,
|
||||
@Embedded(prefix = "s_") val lastStatus: ConversationStatusEntity
|
||||
)
|
||||
) {
|
||||
fun toViewData(): ConversationViewData {
|
||||
return ConversationViewData(
|
||||
id = id,
|
||||
accounts = accounts,
|
||||
unread = unread,
|
||||
lastStatus = lastStatus.toViewData()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class ConversationAccountEntity(
|
||||
val id: String,
|
||||
|
@ -67,7 +75,7 @@ data class ConversationStatusEntity(
|
|||
val inReplyToId: String?,
|
||||
val inReplyToAccountId: String?,
|
||||
val account: ConversationAccountEntity,
|
||||
val content: Spanned,
|
||||
val content: String,
|
||||
val createdAt: Date,
|
||||
val emojis: List<Emoji>,
|
||||
val favouritesCount: Int,
|
||||
|
@ -80,95 +88,43 @@ data class ConversationStatusEntity(
|
|||
val tags: List<HashTag>?,
|
||||
val showingHiddenContent: Boolean,
|
||||
val expanded: Boolean,
|
||||
val collapsible: Boolean,
|
||||
val collapsed: Boolean,
|
||||
val muted: Boolean,
|
||||
val poll: Poll?
|
||||
) {
|
||||
/** its necessary to override this because Spanned.equals does not work as expected */
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ConversationStatusEntity
|
||||
|
||||
if (id != other.id) return false
|
||||
if (url != other.url) return false
|
||||
if (inReplyToId != other.inReplyToId) return false
|
||||
if (inReplyToAccountId != other.inReplyToAccountId) return false
|
||||
if (account != other.account) return false
|
||||
if (content.toString() != other.content.toString()) return false
|
||||
if (createdAt != other.createdAt) return false
|
||||
if (emojis != other.emojis) return false
|
||||
if (favouritesCount != other.favouritesCount) return false
|
||||
if (favourited != other.favourited) return false
|
||||
if (sensitive != other.sensitive) return false
|
||||
if (spoilerText != other.spoilerText) return false
|
||||
if (attachments != other.attachments) return false
|
||||
if (mentions != other.mentions) return false
|
||||
if (tags != other.tags) return false
|
||||
if (showingHiddenContent != other.showingHiddenContent) return false
|
||||
if (expanded != other.expanded) return false
|
||||
if (collapsible != other.collapsible) return false
|
||||
if (collapsed != other.collapsed) return false
|
||||
if (muted != other.muted) return false
|
||||
if (poll != other.poll) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id.hashCode()
|
||||
result = 31 * result + (url?.hashCode() ?: 0)
|
||||
result = 31 * result + (inReplyToId?.hashCode() ?: 0)
|
||||
result = 31 * result + (inReplyToAccountId?.hashCode() ?: 0)
|
||||
result = 31 * result + account.hashCode()
|
||||
result = 31 * result + content.toString().hashCode()
|
||||
result = 31 * result + createdAt.hashCode()
|
||||
result = 31 * result + emojis.hashCode()
|
||||
result = 31 * result + favouritesCount
|
||||
result = 31 * result + favourited.hashCode()
|
||||
result = 31 * result + sensitive.hashCode()
|
||||
result = 31 * result + spoilerText.hashCode()
|
||||
result = 31 * result + attachments.hashCode()
|
||||
result = 31 * result + mentions.hashCode()
|
||||
result = 31 * result + tags.hashCode()
|
||||
result = 31 * result + showingHiddenContent.hashCode()
|
||||
result = 31 * result + expanded.hashCode()
|
||||
result = 31 * result + collapsible.hashCode()
|
||||
result = 31 * result + collapsed.hashCode()
|
||||
result = 31 * result + muted.hashCode()
|
||||
result = 31 * result + poll.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
fun toStatus(): Status {
|
||||
return Status(
|
||||
id = id,
|
||||
url = url,
|
||||
account = account.toAccount(),
|
||||
inReplyToId = inReplyToId,
|
||||
inReplyToAccountId = inReplyToAccountId,
|
||||
content = content,
|
||||
reblog = null,
|
||||
createdAt = createdAt,
|
||||
emojis = emojis,
|
||||
reblogsCount = 0,
|
||||
favouritesCount = favouritesCount,
|
||||
reblogged = false,
|
||||
favourited = favourited,
|
||||
bookmarked = bookmarked,
|
||||
sensitive = sensitive,
|
||||
spoilerText = spoilerText,
|
||||
visibility = Status.Visibility.DIRECT,
|
||||
attachments = attachments,
|
||||
mentions = mentions,
|
||||
tags = tags,
|
||||
application = null,
|
||||
pinned = false,
|
||||
muted = muted,
|
||||
poll = poll,
|
||||
card = null
|
||||
fun toViewData(): StatusViewData.Concrete {
|
||||
return StatusViewData.Concrete(
|
||||
status = Status(
|
||||
id = id,
|
||||
url = url,
|
||||
account = account.toAccount(),
|
||||
inReplyToId = inReplyToId,
|
||||
inReplyToAccountId = inReplyToAccountId,
|
||||
content = content,
|
||||
reblog = null,
|
||||
createdAt = createdAt,
|
||||
emojis = emojis,
|
||||
reblogsCount = 0,
|
||||
favouritesCount = favouritesCount,
|
||||
reblogged = false,
|
||||
favourited = favourited,
|
||||
bookmarked = bookmarked,
|
||||
sensitive = sensitive,
|
||||
spoilerText = spoilerText,
|
||||
visibility = Status.Visibility.DIRECT,
|
||||
attachments = attachments,
|
||||
mentions = mentions,
|
||||
tags = tags,
|
||||
application = null,
|
||||
pinned = false,
|
||||
muted = muted,
|
||||
poll = poll,
|
||||
card = null
|
||||
),
|
||||
isExpanded = expanded,
|
||||
isShowingContent = showingHiddenContent,
|
||||
isCollapsed = collapsed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -202,7 +158,6 @@ fun Status.toEntity() =
|
|||
tags = tags,
|
||||
showingHiddenContent = false,
|
||||
expanded = false,
|
||||
collapsible = shouldTrimStatus(content),
|
||||
collapsed = true,
|
||||
muted = muted ?: false,
|
||||
poll = poll
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/* Copyright 2022 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.conversation
|
||||
|
||||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
|
||||
data class ConversationViewData(
|
||||
val id: String,
|
||||
val accounts: List<ConversationAccountEntity>,
|
||||
val unread: Boolean,
|
||||
val lastStatus: StatusViewData.Concrete
|
||||
) {
|
||||
fun toEntity(
|
||||
accountId: Long,
|
||||
favourited: Boolean = lastStatus.status.favourited,
|
||||
bookmarked: Boolean = lastStatus.status.bookmarked,
|
||||
muted: Boolean = lastStatus.status.muted ?: false,
|
||||
poll: Poll? = lastStatus.status.poll,
|
||||
expanded: Boolean = lastStatus.isExpanded,
|
||||
collapsed: Boolean = lastStatus.isCollapsed,
|
||||
showingHiddenContent: Boolean = lastStatus.isShowingContent
|
||||
): ConversationEntity {
|
||||
return ConversationEntity(
|
||||
accountId = accountId,
|
||||
id = id,
|
||||
accounts = accounts,
|
||||
unread = unread,
|
||||
lastStatus = lastStatus.toConversationStatusEntity(
|
||||
favourited = favourited,
|
||||
bookmarked = bookmarked,
|
||||
muted = muted,
|
||||
poll = poll,
|
||||
expanded = expanded,
|
||||
collapsed = collapsed,
|
||||
showingHiddenContent = showingHiddenContent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun StatusViewData.Concrete.toConversationStatusEntity(
|
||||
favourited: Boolean = status.favourited,
|
||||
bookmarked: Boolean = status.bookmarked,
|
||||
muted: Boolean = status.muted ?: false,
|
||||
poll: Poll? = status.poll,
|
||||
expanded: Boolean = isExpanded,
|
||||
collapsed: Boolean = isCollapsed,
|
||||
showingHiddenContent: Boolean = isShowingContent
|
||||
): ConversationStatusEntity {
|
||||
return ConversationStatusEntity(
|
||||
id = id,
|
||||
url = status.url,
|
||||
inReplyToId = status.inReplyToId,
|
||||
inReplyToAccountId = status.inReplyToAccountId,
|
||||
account = status.account.toEntity(),
|
||||
content = status.content,
|
||||
createdAt = status.createdAt,
|
||||
emojis = status.emojis,
|
||||
favouritesCount = status.favouritesCount,
|
||||
favourited = favourited,
|
||||
bookmarked = bookmarked,
|
||||
sensitive = status.sensitive,
|
||||
spoilerText = status.spoilerText,
|
||||
attachments = status.attachments,
|
||||
mentions = status.mentions,
|
||||
tags = status.tags,
|
||||
showingHiddenContent = showingHiddenContent,
|
||||
expanded = expanded,
|
||||
collapsed = collapsed,
|
||||
muted = muted,
|
||||
poll = poll
|
||||
)
|
||||
}
|
|
@ -28,11 +28,14 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder;
|
||||
import com.keylesspalace.tusky.entity.Attachment;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.entity.TimelineAccount;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.util.ImageLoadingHelper;
|
||||
import com.keylesspalace.tusky.util.SmartLengthInputFilter;
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions;
|
||||
import com.keylesspalace.tusky.viewdata.PollViewDataKt;
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -69,11 +72,12 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
|||
return context.getResources().getDimensionPixelSize(R.dimen.status_media_preview_height);
|
||||
}
|
||||
|
||||
void setupWithConversation(ConversationEntity conversation) {
|
||||
ConversationStatusEntity status = conversation.getLastStatus();
|
||||
ConversationAccountEntity account = status.getAccount();
|
||||
void setupWithConversation(ConversationViewData conversation) {
|
||||
StatusViewData.Concrete statusViewData = conversation.getLastStatus();
|
||||
Status status = statusViewData.getStatus();
|
||||
TimelineAccount account = status.getAccount();
|
||||
|
||||
setupCollapsedState(status.getCollapsible(), status.getCollapsed(), status.getExpanded(), status.getSpoilerText(), listener);
|
||||
setupCollapsedState(statusViewData.isCollapsible(), statusViewData.isCollapsed(), statusViewData.isExpanded(), statusViewData.getSpoilerText(), listener);
|
||||
|
||||
setDisplayName(account.getDisplayName(), account.getEmojis(), statusDisplayOptions);
|
||||
setUsername(account.getUsername());
|
||||
|
@ -84,7 +88,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
|||
List<Attachment> attachments = status.getAttachments();
|
||||
boolean sensitive = status.getSensitive();
|
||||
if (statusDisplayOptions.mediaPreviewEnabled() && hasPreviewableAttachment(attachments)) {
|
||||
setMediaPreviews(attachments, sensitive, listener, status.getShowingHiddenContent(),
|
||||
setMediaPreviews(attachments, sensitive, listener, statusViewData.isShowingContent(),
|
||||
statusDisplayOptions.useBlurhash());
|
||||
|
||||
if (attachments.size() == 0) {
|
||||
|
@ -95,7 +99,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
|||
mediaLabel.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
setMediaLabel(attachments, sensitive, listener, status.getShowingHiddenContent());
|
||||
setMediaLabel(attachments, sensitive, listener, statusViewData.isShowingContent());
|
||||
// Hide all unused views.
|
||||
mediaPreviews[0].setVisibility(View.GONE);
|
||||
mediaPreviews[1].setVisibility(View.GONE);
|
||||
|
@ -104,10 +108,10 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
|||
hideSensitiveMediaWarning();
|
||||
}
|
||||
|
||||
setupButtons(listener, account.getId(), status.getContent().toString(),
|
||||
setupButtons(listener, account.getId(), statusViewData.getContent().toString(),
|
||||
statusDisplayOptions);
|
||||
|
||||
setSpoilerAndContent(status.getExpanded(), status.getContent(), status.getSpoilerText(),
|
||||
setSpoilerAndContent(statusViewData.isExpanded(), statusViewData.getContent(), status.getSpoilerText(),
|
||||
status.getMentions(), status.getTags(), status.getEmojis(),
|
||||
PollViewDataKt.toViewData(status.getPoll()), statusDisplayOptions, listener);
|
||||
|
||||
|
|
|
@ -153,24 +153,24 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
}
|
||||
|
||||
override fun onFavourite(favourite: Boolean, position: Int) {
|
||||
adapter.item(position)?.let { conversation ->
|
||||
adapter.peek(position)?.let { conversation ->
|
||||
viewModel.favourite(favourite, conversation)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBookmark(favourite: Boolean, position: Int) {
|
||||
adapter.item(position)?.let { conversation ->
|
||||
adapter.peek(position)?.let { conversation ->
|
||||
viewModel.bookmark(favourite, conversation)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMore(view: View, position: Int) {
|
||||
adapter.item(position)?.let { conversation ->
|
||||
adapter.peek(position)?.let { conversation ->
|
||||
|
||||
val popup = PopupMenu(requireContext(), view)
|
||||
popup.inflate(R.menu.conversation_more)
|
||||
|
||||
if (conversation.lastStatus.muted) {
|
||||
if (conversation.lastStatus.status.muted == true) {
|
||||
popup.menu.removeItem(R.id.status_mute_conversation)
|
||||
} else {
|
||||
popup.menu.removeItem(R.id.status_unmute_conversation)
|
||||
|
@ -189,14 +189,14 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
}
|
||||
|
||||
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
|
||||
adapter.item(position)?.let { conversation ->
|
||||
viewMedia(attachmentIndex, AttachmentViewData.list(conversation.lastStatus.toStatus()), view)
|
||||
adapter.peek(position)?.let { conversation ->
|
||||
viewMedia(attachmentIndex, AttachmentViewData.list(conversation.lastStatus.status), view)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewThread(position: Int) {
|
||||
adapter.item(position)?.let { conversation ->
|
||||
viewThread(conversation.lastStatus.id, conversation.lastStatus.url)
|
||||
adapter.peek(position)?.let { conversation ->
|
||||
viewThread(conversation.lastStatus.id, conversation.lastStatus.status.url)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,13 +205,13 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
}
|
||||
|
||||
override fun onExpandedChange(expanded: Boolean, position: Int) {
|
||||
adapter.item(position)?.let { conversation ->
|
||||
adapter.peek(position)?.let { conversation ->
|
||||
viewModel.expandHiddenStatus(expanded, conversation)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onContentHiddenChange(isShowing: Boolean, position: Int) {
|
||||
adapter.item(position)?.let { conversation ->
|
||||
adapter.peek(position)?.let { conversation ->
|
||||
viewModel.showContent(isShowing, conversation)
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +221,7 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
}
|
||||
|
||||
override fun onContentCollapsedChange(isCollapsed: Boolean, position: Int) {
|
||||
adapter.item(position)?.let { conversation ->
|
||||
adapter.peek(position)?.let { conversation ->
|
||||
viewModel.collapseLongStatus(isCollapsed, conversation)
|
||||
}
|
||||
}
|
||||
|
@ -241,12 +241,12 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
}
|
||||
|
||||
override fun onReply(position: Int) {
|
||||
adapter.item(position)?.let { conversation ->
|
||||
reply(conversation.lastStatus.toStatus())
|
||||
adapter.peek(position)?.let { conversation ->
|
||||
reply(conversation.lastStatus.status)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteConversation(conversation: ConversationEntity) {
|
||||
private fun deleteConversation(conversation: ConversationViewData) {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setMessage(R.string.dialog_delete_conversation_warning)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
|
@ -268,7 +268,7 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
}
|
||||
|
||||
override fun onVoteInPoll(position: Int, choices: MutableList<Int>) {
|
||||
adapter.item(position)?.let { conversation ->
|
||||
adapter.peek(position)?.let { conversation ->
|
||||
viewModel.voteInPoll(choices, conversation)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,16 +16,18 @@
|
|||
package com.keylesspalace.tusky.components.conversation
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.ExperimentalPagingApi
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.paging.map
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.db.AppDatabase
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.network.TimelineCases
|
||||
import com.keylesspalace.tusky.util.RxAwareViewModel
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.rx3.await
|
||||
import javax.inject.Inject
|
||||
|
@ -35,7 +37,7 @@ class ConversationsViewModel @Inject constructor(
|
|||
private val database: AppDatabase,
|
||||
private val accountManager: AccountManager,
|
||||
private val api: MastodonApi
|
||||
) : RxAwareViewModel() {
|
||||
) : ViewModel() {
|
||||
|
||||
@OptIn(ExperimentalPagingApi::class)
|
||||
val conversationFlow = Pager(
|
||||
|
@ -44,104 +46,117 @@ class ConversationsViewModel @Inject constructor(
|
|||
pagingSourceFactory = { database.conversationDao().conversationsForAccount(accountManager.activeAccount!!.id) }
|
||||
)
|
||||
.flow
|
||||
.map { pagingData ->
|
||||
pagingData.map { conversation -> conversation.toViewData() }
|
||||
}
|
||||
.cachedIn(viewModelScope)
|
||||
|
||||
fun favourite(favourite: Boolean, conversation: ConversationEntity) {
|
||||
fun favourite(favourite: Boolean, conversation: ConversationViewData) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
timelineCases.favourite(conversation.lastStatus.id, favourite).await()
|
||||
|
||||
val newConversation = conversation.copy(
|
||||
lastStatus = conversation.lastStatus.copy(favourited = favourite)
|
||||
val newConversation = conversation.toEntity(
|
||||
accountId = accountManager.activeAccount!!.id,
|
||||
favourited = favourite
|
||||
)
|
||||
|
||||
database.conversationDao().insert(newConversation)
|
||||
saveConversationToDb(newConversation)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "failed to favourite status", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bookmark(bookmark: Boolean, conversation: ConversationEntity) {
|
||||
fun bookmark(bookmark: Boolean, conversation: ConversationViewData) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
timelineCases.bookmark(conversation.lastStatus.id, bookmark).await()
|
||||
|
||||
val newConversation = conversation.copy(
|
||||
lastStatus = conversation.lastStatus.copy(bookmarked = bookmark)
|
||||
val newConversation = conversation.toEntity(
|
||||
accountId = accountManager.activeAccount!!.id,
|
||||
bookmarked = bookmark
|
||||
)
|
||||
|
||||
database.conversationDao().insert(newConversation)
|
||||
saveConversationToDb(newConversation)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "failed to bookmark status", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun voteInPoll(choices: List<Int>, conversation: ConversationEntity) {
|
||||
fun voteInPoll(choices: List<Int>, conversation: ConversationViewData) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val poll = timelineCases.voteInPoll(conversation.lastStatus.id, conversation.lastStatus.poll?.id!!, choices).await()
|
||||
val newConversation = conversation.copy(
|
||||
lastStatus = conversation.lastStatus.copy(poll = poll)
|
||||
val poll = timelineCases.voteInPoll(conversation.lastStatus.id, conversation.lastStatus.status.poll?.id!!, choices).await()
|
||||
val newConversation = conversation.toEntity(
|
||||
accountId = accountManager.activeAccount!!.id,
|
||||
poll = poll
|
||||
)
|
||||
|
||||
database.conversationDao().insert(newConversation)
|
||||
saveConversationToDb(newConversation)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "failed to vote in poll", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun expandHiddenStatus(expanded: Boolean, conversation: ConversationEntity) {
|
||||
fun expandHiddenStatus(expanded: Boolean, conversation: ConversationViewData) {
|
||||
viewModelScope.launch {
|
||||
val newConversation = conversation.copy(
|
||||
lastStatus = conversation.lastStatus.copy(expanded = expanded)
|
||||
val newConversation = conversation.toEntity(
|
||||
accountId = accountManager.activeAccount!!.id,
|
||||
expanded = expanded
|
||||
)
|
||||
saveConversationToDb(newConversation)
|
||||
}
|
||||
}
|
||||
|
||||
fun collapseLongStatus(collapsed: Boolean, conversation: ConversationEntity) {
|
||||
fun collapseLongStatus(collapsed: Boolean, conversation: ConversationViewData) {
|
||||
viewModelScope.launch {
|
||||
val newConversation = conversation.copy(
|
||||
lastStatus = conversation.lastStatus.copy(collapsed = collapsed)
|
||||
val newConversation = conversation.toEntity(
|
||||
accountId = accountManager.activeAccount!!.id,
|
||||
collapsed = collapsed
|
||||
)
|
||||
saveConversationToDb(newConversation)
|
||||
}
|
||||
}
|
||||
|
||||
fun showContent(showing: Boolean, conversation: ConversationEntity) {
|
||||
fun showContent(showing: Boolean, conversation: ConversationViewData) {
|
||||
viewModelScope.launch {
|
||||
val newConversation = conversation.copy(
|
||||
lastStatus = conversation.lastStatus.copy(showingHiddenContent = showing)
|
||||
val newConversation = conversation.toEntity(
|
||||
accountId = accountManager.activeAccount!!.id,
|
||||
showingHiddenContent = showing
|
||||
)
|
||||
saveConversationToDb(newConversation)
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(conversation: ConversationEntity) {
|
||||
fun remove(conversation: ConversationViewData) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
api.deleteConversation(conversationId = conversation.id)
|
||||
|
||||
database.conversationDao().delete(conversation)
|
||||
database.conversationDao().delete(
|
||||
id = conversation.id,
|
||||
accountId = accountManager.activeAccount!!.id
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "failed to delete conversation", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun muteConversation(conversation: ConversationEntity) {
|
||||
fun muteConversation(conversation: ConversationViewData) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val newStatus = timelineCases.muteConversation(
|
||||
timelineCases.muteConversation(
|
||||
conversation.lastStatus.id,
|
||||
!conversation.lastStatus.muted
|
||||
!(conversation.lastStatus.status.muted ?: false)
|
||||
).await()
|
||||
|
||||
val newConversation = conversation.copy(
|
||||
lastStatus = newStatus.toEntity()
|
||||
val newConversation = conversation.toEntity(
|
||||
accountId = accountManager.activeAccount!!.id,
|
||||
muted = !(conversation.lastStatus.status.muted ?: false)
|
||||
)
|
||||
|
||||
database.conversationDao().insert(newConversation)
|
||||
|
@ -151,7 +166,7 @@ class ConversationsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun saveConversationToDb(conversation: ConversationEntity) {
|
||||
private suspend fun saveConversationToDb(conversation: ConversationEntity) {
|
||||
database.conversationDao().insert(conversation)
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.lifecycle.viewModelScope
|
|||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.paging.map
|
||||
import com.keylesspalace.tusky.appstore.BlockEvent
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.MuteEvent
|
||||
|
@ -34,11 +35,13 @@ import com.keylesspalace.tusky.util.Loading
|
|||
import com.keylesspalace.tusky.util.Resource
|
||||
import com.keylesspalace.tusky.util.RxAwareViewModel
|
||||
import com.keylesspalace.tusky.util.Success
|
||||
import com.keylesspalace.tusky.util.toViewData
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -74,6 +77,11 @@ class ReportViewModel @Inject constructor(
|
|||
pagingSourceFactory = { StatusesPagingSource(accountId, mastodonApi) }
|
||||
).flow
|
||||
}
|
||||
.map { pagingData ->
|
||||
/* TODO: refactor reports to use the isShowingContent / isExpanded / isCollapsed attributes from StatusViewData.Concrete
|
||||
instead of StatusViewState */
|
||||
pagingData.map { status -> status.toViewData(false, false, false) }
|
||||
}
|
||||
.cachedIn(viewModelScope)
|
||||
|
||||
private val selectedIds = HashSet<String>()
|
||||
|
@ -155,7 +163,7 @@ class ReportViewModel @Inject constructor(
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ relationship ->
|
||||
val muting = relationship?.muting == true
|
||||
val muting = relationship.muting
|
||||
muteStateMutable.value = Success(muting)
|
||||
if (muting) {
|
||||
eventHub.dispatch(MuteEvent(accountId))
|
||||
|
@ -180,7 +188,7 @@ class ReportViewModel @Inject constructor(
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ relationship ->
|
||||
val blocking = relationship?.blocking == true
|
||||
val blocking = relationship.blocking
|
||||
blockStateMutable.value = Success(blocking)
|
||||
if (blocking) {
|
||||
eventHub.dispatch(BlockEvent(accountId))
|
||||
|
|
|
@ -37,6 +37,7 @@ import com.keylesspalace.tusky.util.setClickableMentions
|
|||
import com.keylesspalace.tusky.util.setClickableText
|
||||
import com.keylesspalace.tusky.util.shouldTrimStatus
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import com.keylesspalace.tusky.viewdata.toViewData
|
||||
import java.util.Date
|
||||
|
||||
|
@ -45,20 +46,21 @@ class StatusViewHolder(
|
|||
private val statusDisplayOptions: StatusDisplayOptions,
|
||||
private val viewState: StatusViewState,
|
||||
private val adapterHandler: AdapterHandler,
|
||||
private val getStatusForPosition: (Int) -> Status?
|
||||
private val getStatusForPosition: (Int) -> StatusViewData.Concrete?
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
private val mediaViewHeight = itemView.context.resources.getDimensionPixelSize(R.dimen.status_media_preview_height)
|
||||
private val statusViewHelper = StatusViewHelper(itemView)
|
||||
|
||||
private val previewListener = object : StatusViewHelper.MediaPreviewListener {
|
||||
override fun onViewMedia(v: View?, idx: Int) {
|
||||
status()?.let { status ->
|
||||
adapterHandler.showMedia(v, status, idx)
|
||||
viewdata()?.let { viewdata ->
|
||||
adapterHandler.showMedia(v, viewdata.status, idx)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onContentHiddenChange(isShowing: Boolean) {
|
||||
status()?.id?.let { id ->
|
||||
viewdata()?.id?.let { id ->
|
||||
viewState.setMediaShow(id, isShowing)
|
||||
}
|
||||
}
|
||||
|
@ -66,57 +68,57 @@ class StatusViewHolder(
|
|||
|
||||
init {
|
||||
binding.statusSelection.setOnCheckedChangeListener { _, isChecked ->
|
||||
status()?.let { status ->
|
||||
adapterHandler.setStatusChecked(status, isChecked)
|
||||
viewdata()?.let { viewdata ->
|
||||
adapterHandler.setStatusChecked(viewdata.status, isChecked)
|
||||
}
|
||||
}
|
||||
binding.statusMediaPreviewContainer.clipToOutline = true
|
||||
}
|
||||
|
||||
fun bind(status: Status) {
|
||||
binding.statusSelection.isChecked = adapterHandler.isStatusChecked(status.id)
|
||||
fun bind(viewData: StatusViewData.Concrete) {
|
||||
binding.statusSelection.isChecked = adapterHandler.isStatusChecked(viewData.id)
|
||||
|
||||
updateTextView()
|
||||
|
||||
val sensitive = status.sensitive
|
||||
val sensitive = viewData.status.sensitive
|
||||
|
||||
statusViewHelper.setMediasPreview(
|
||||
statusDisplayOptions, status.attachments,
|
||||
sensitive, previewListener, viewState.isMediaShow(status.id, status.sensitive),
|
||||
statusDisplayOptions, viewData.status.attachments,
|
||||
sensitive, previewListener, viewState.isMediaShow(viewData.id, viewData.status.sensitive),
|
||||
mediaViewHeight
|
||||
)
|
||||
|
||||
statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, statusDisplayOptions)
|
||||
setCreatedAt(status.createdAt)
|
||||
statusViewHelper.setupPollReadonly(viewData.status.poll.toViewData(), viewData.status.emojis, statusDisplayOptions)
|
||||
setCreatedAt(viewData.status.createdAt)
|
||||
}
|
||||
|
||||
private fun updateTextView() {
|
||||
status()?.let { status ->
|
||||
viewdata()?.let { viewdata ->
|
||||
setupCollapsedState(
|
||||
shouldTrimStatus(status.content), viewState.isCollapsed(status.id, true),
|
||||
viewState.isContentShow(status.id, status.sensitive), status.spoilerText
|
||||
shouldTrimStatus(viewdata.content), viewState.isCollapsed(viewdata.id, true),
|
||||
viewState.isContentShow(viewdata.id, viewdata.status.sensitive), viewdata.spoilerText
|
||||
)
|
||||
|
||||
if (status.spoilerText.isBlank()) {
|
||||
setTextVisible(true, status.content, status.mentions, status.tags, status.emojis, adapterHandler)
|
||||
if (viewdata.spoilerText.isBlank()) {
|
||||
setTextVisible(true, viewdata.content, viewdata.status.mentions, viewdata.status.tags, viewdata.status.emojis, adapterHandler)
|
||||
binding.statusContentWarningButton.hide()
|
||||
binding.statusContentWarningDescription.hide()
|
||||
} else {
|
||||
val emojiSpoiler = status.spoilerText.emojify(status.emojis, binding.statusContentWarningDescription, statusDisplayOptions.animateEmojis)
|
||||
val emojiSpoiler = viewdata.spoilerText.emojify(viewdata.status.emojis, binding.statusContentWarningDescription, statusDisplayOptions.animateEmojis)
|
||||
binding.statusContentWarningDescription.text = emojiSpoiler
|
||||
binding.statusContentWarningDescription.show()
|
||||
binding.statusContentWarningButton.show()
|
||||
setContentWarningButtonText(viewState.isContentShow(status.id, true))
|
||||
setContentWarningButtonText(viewState.isContentShow(viewdata.id, true))
|
||||
binding.statusContentWarningButton.setOnClickListener {
|
||||
status()?.let { status ->
|
||||
val contentShown = viewState.isContentShow(status.id, true)
|
||||
viewdata()?.let { viewdata ->
|
||||
val contentShown = viewState.isContentShow(viewdata.id, true)
|
||||
binding.statusContentWarningDescription.invalidate()
|
||||
viewState.setContentShow(status.id, !contentShown)
|
||||
setTextVisible(!contentShown, status.content, status.mentions, status.tags, status.emojis, adapterHandler)
|
||||
viewState.setContentShow(viewdata.id, !contentShown)
|
||||
setTextVisible(!contentShown, viewdata.content, viewdata.status.mentions, viewdata.status.tags, viewdata.status.emojis, adapterHandler)
|
||||
setContentWarningButtonText(!contentShown)
|
||||
}
|
||||
}
|
||||
setTextVisible(viewState.isContentShow(status.id, true), status.content, status.mentions, status.tags, status.emojis, adapterHandler)
|
||||
setTextVisible(viewState.isContentShow(viewdata.id, true), viewdata.content, viewdata.status.mentions, viewdata.status.tags, viewdata.status.emojis, adapterHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,8 +171,8 @@ class StatusViewHolder(
|
|||
/* input filter for TextViews have to be set before text */
|
||||
if (collapsible && (expanded || TextUtils.isEmpty(spoilerText))) {
|
||||
binding.buttonToggleContent.setOnClickListener {
|
||||
status()?.let { status ->
|
||||
viewState.setCollapsed(status.id, !collapsed)
|
||||
viewdata()?.let { viewdata ->
|
||||
viewState.setCollapsed(viewdata.id, !collapsed)
|
||||
updateTextView()
|
||||
}
|
||||
}
|
||||
|
@ -189,5 +191,5 @@ class StatusViewHolder(
|
|||
}
|
||||
}
|
||||
|
||||
private fun status() = getStatusForPosition(bindingAdapterPosition)
|
||||
private fun viewdata() = getStatusForPosition(bindingAdapterPosition)
|
||||
}
|
||||
|
|
|
@ -22,16 +22,16 @@ import androidx.recyclerview.widget.DiffUtil
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.components.report.model.StatusViewState
|
||||
import com.keylesspalace.tusky.databinding.ItemReportStatusBinding
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
|
||||
class StatusesAdapter(
|
||||
private val statusDisplayOptions: StatusDisplayOptions,
|
||||
private val statusViewState: StatusViewState,
|
||||
private val adapterHandler: AdapterHandler
|
||||
) : PagingDataAdapter<Status, StatusViewHolder>(STATUS_COMPARATOR) {
|
||||
) : PagingDataAdapter<StatusViewData.Concrete, StatusViewHolder>(STATUS_COMPARATOR) {
|
||||
|
||||
private val statusForPosition: (Int) -> Status? = { position: Int ->
|
||||
private val statusForPosition: (Int) -> StatusViewData.Concrete? = { position: Int ->
|
||||
if (position != RecyclerView.NO_POSITION) getItem(position) else null
|
||||
}
|
||||
|
||||
|
@ -50,11 +50,11 @@ class StatusesAdapter(
|
|||
}
|
||||
|
||||
companion object {
|
||||
val STATUS_COMPARATOR = object : DiffUtil.ItemCallback<Status>() {
|
||||
override fun areContentsTheSame(oldItem: Status, newItem: Status): Boolean =
|
||||
val STATUS_COMPARATOR = object : DiffUtil.ItemCallback<StatusViewData.Concrete>() {
|
||||
override fun areContentsTheSame(oldItem: StatusViewData.Concrete, newItem: StatusViewData.Concrete): Boolean =
|
||||
oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(oldItem: Status, newItem: Status): Boolean =
|
||||
override fun areItemsTheSame(oldItem: StatusViewData.Concrete, newItem: StatusViewData.Concrete): Boolean =
|
||||
oldItem.id == newItem.id
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,9 +15,6 @@
|
|||
|
||||
package com.keylesspalace.tusky.components.timeline
|
||||
|
||||
import android.text.SpannedString
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.core.text.toHtml
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.keylesspalace.tusky.db.TimelineAccountEntity
|
||||
|
@ -29,8 +26,6 @@ import com.keylesspalace.tusky.entity.HashTag
|
|||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.entity.TimelineAccount
|
||||
import com.keylesspalace.tusky.util.shouldTrimStatus
|
||||
import com.keylesspalace.tusky.util.trimTrailingWhitespace
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import java.util.Date
|
||||
|
||||
|
@ -119,7 +114,7 @@ fun Status.toEntity(
|
|||
authorServerId = actionableStatus.account.id,
|
||||
inReplyToId = actionableStatus.inReplyToId,
|
||||
inReplyToAccountId = actionableStatus.inReplyToAccountId,
|
||||
content = actionableStatus.content.toHtml(),
|
||||
content = actionableStatus.content,
|
||||
createdAt = actionableStatus.createdAt.time,
|
||||
emojis = actionableStatus.emojis.let(gson::toJson),
|
||||
reblogsCount = actionableStatus.reblogsCount,
|
||||
|
@ -165,8 +160,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
|||
inReplyToId = status.inReplyToId,
|
||||
inReplyToAccountId = status.inReplyToAccountId,
|
||||
reblog = null,
|
||||
content = status.content?.parseAsHtml()?.trimTrailingWhitespace()
|
||||
?: SpannedString(""),
|
||||
content = status.content.orEmpty(),
|
||||
createdAt = Date(status.createdAt),
|
||||
emojis = emojis,
|
||||
reblogsCount = status.reblogsCount,
|
||||
|
@ -195,7 +189,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
|||
inReplyToId = null,
|
||||
inReplyToAccountId = null,
|
||||
reblog = reblog,
|
||||
content = SpannedString(""),
|
||||
content = "",
|
||||
createdAt = Date(status.createdAt), // lie but whatever?
|
||||
emojis = listOf(),
|
||||
reblogsCount = 0,
|
||||
|
@ -223,8 +217,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
|||
inReplyToId = status.inReplyToId,
|
||||
inReplyToAccountId = status.inReplyToAccountId,
|
||||
reblog = null,
|
||||
content = status.content?.parseAsHtml()?.trimTrailingWhitespace()
|
||||
?: SpannedString(""),
|
||||
content = status.content.orEmpty(),
|
||||
createdAt = Date(status.createdAt),
|
||||
emojis = emojis,
|
||||
reblogsCount = status.reblogsCount,
|
||||
|
@ -249,7 +242,6 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
|||
status = status,
|
||||
isExpanded = this.status.expanded,
|
||||
isShowingContent = this.status.contentShowing,
|
||||
isCollapsible = shouldTrimStatus(status.content),
|
||||
isCollapsed = this.status.contentCollapsed
|
||||
)
|
||||
}
|
||||
|
|
|
@ -42,7 +42,10 @@ import com.keylesspalace.tusky.network.FilterModel
|
|||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.network.TimelineCases
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.rx3.await
|
||||
|
@ -79,15 +82,13 @@ class CachedTimelineViewModel @Inject constructor(
|
|||
}
|
||||
).flow
|
||||
.map { pagingData ->
|
||||
pagingData.map { timelineStatus ->
|
||||
pagingData.map(Dispatchers.Default.asExecutor()) { timelineStatus ->
|
||||
timelineStatus.toViewData(gson)
|
||||
}
|
||||
}
|
||||
.map { pagingData ->
|
||||
pagingData.filter { statusViewData ->
|
||||
}.filter(Dispatchers.Default.asExecutor()) { statusViewData ->
|
||||
!shouldFilterStatus(statusViewData)
|
||||
}
|
||||
}
|
||||
.flowOn(Dispatchers.Default)
|
||||
.cachedIn(viewModelScope)
|
||||
|
||||
init {
|
||||
|
|
|
@ -40,6 +40,9 @@ import com.keylesspalace.tusky.util.isLessThan
|
|||
import com.keylesspalace.tusky.util.isLessThanOrEqual
|
||||
import com.keylesspalace.tusky.util.toViewData
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.rx3.await
|
||||
|
@ -79,10 +82,11 @@ class NetworkTimelineViewModel @Inject constructor(
|
|||
remoteMediator = NetworkTimelineRemoteMediator(accountManager, this)
|
||||
).flow
|
||||
.map { pagingData ->
|
||||
pagingData.filter { statusViewData ->
|
||||
pagingData.filter(Dispatchers.Default.asExecutor()) { statusViewData ->
|
||||
!shouldFilterStatus(statusViewData)
|
||||
}
|
||||
}
|
||||
.flowOn(Dispatchers.Default)
|
||||
.cachedIn(viewModelScope)
|
||||
|
||||
override fun updatePoll(newPoll: Poll, status: StatusViewData.Concrete) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue