refactor: Pass pachliAccountId to more functions (#1008)

While this isn't used in many places yet, it pushes the places where the
active account is referenced further up the call stack.
This commit is contained in:
Nik Clayton 2024-10-17 21:19:17 +02:00 committed by GitHub
parent ab186d4d07
commit 1b3b78a9dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 308 additions and 263 deletions

View File

@ -29,23 +29,24 @@ import app.pachli.interfaces.StatusActionListener
import app.pachli.viewdata.IStatusViewData
open class FilterableStatusViewHolder<T : IStatusViewData>(
private val pachliAccountId: Long,
private val binding: ItemStatusWrapperBinding,
) : StatusViewHolder<T>(pachliAccountId, binding.statusContainer, binding.root) {
) : StatusViewHolder<T>(binding.statusContainer, binding.root) {
/** The filter that matched the status, null if the status is not being filtered. */
var matchedFilter: Filter? = null
override fun setupWithStatus(
pachliAccountId: Long,
viewData: T,
listener: StatusActionListener<T>,
statusDisplayOptions: StatusDisplayOptions,
payloads: Any?,
) {
super.setupWithStatus(viewData, listener, statusDisplayOptions, payloads)
setupFilterPlaceholder(viewData, listener)
super.setupWithStatus(pachliAccountId, viewData, listener, statusDisplayOptions, payloads)
setupFilterPlaceholder(pachliAccountId, viewData, listener)
}
private fun setupFilterPlaceholder(
pachliAccountId: Long,
status: T,
listener: StatusActionListener<T>,
) {
@ -70,7 +71,7 @@ open class FilterableStatusViewHolder<T : IStatusViewData>(
binding.statusFilteredPlaceholder.statusFilterLabel.text = label
binding.statusFilteredPlaceholder.statusFilterShowAnyway.setOnClickListener {
listener.clearWarningAction(status)
listener.clearWarningAction(pachliAccountId, status)
}
binding.statusFilteredPlaceholder.statusFilterEditFilter.setOnClickListener {
listener.onEditFilterById(pachliAccountId, result.filter.id)

View File

@ -48,6 +48,7 @@ class FollowRequestViewHolder(
) : NotificationsPagingAdapter.ViewHolder, RecyclerView.ViewHolder(binding.root) {
override fun bind(
pachliAccountId: Long,
viewData: NotificationViewData,
payloads: List<*>?,
statusDisplayOptions: StatusDisplayOptions,

View File

@ -41,6 +41,7 @@ class ReportNotificationViewHolder(
) : NotificationsPagingAdapter.ViewHolder, RecyclerView.ViewHolder(binding.root) {
override fun bind(
pachliAccountId: Long,
viewData: NotificationViewData,
payloads: List<*>?,
statusDisplayOptions: StatusDisplayOptions,

View File

@ -64,7 +64,6 @@ import java.text.NumberFormat
import java.util.Date
abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
private val pachliAccountId: Long,
itemView: View,
) :
RecyclerView.ViewHolder(itemView) {
@ -146,6 +145,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
}
protected fun setSpoilerAndContent(
pachliAccountId: Long,
viewData: T,
statusDisplayOptions: StatusDisplayOptions,
listener: StatusActionListener<T>,
@ -165,6 +165,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
setContentWarningButtonText(expanded)
contentWarningButton.setOnClickListener {
toggleExpandedState(
pachliAccountId,
viewData,
true,
!expanded,
@ -196,6 +197,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
}
protected open fun toggleExpandedState(
pachliAccountId: Long,
viewData: T,
sensitive: Boolean,
expanded: Boolean,
@ -203,10 +205,11 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
listener: StatusActionListener<T>,
) {
contentWarningDescription.invalidate()
listener.onExpandedChange(viewData, expanded)
listener.onExpandedChange(pachliAccountId, viewData, expanded)
setContentWarningButtonText(expanded)
setTextVisible(sensitive, expanded, viewData, statusDisplayOptions, listener)
setupCard(
pachliAccountId,
viewData,
expanded,
statusDisplayOptions.cardViewMode,
@ -463,6 +466,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
}
protected fun setMediaPreviews(
pachliAccountId: Long,
viewData: T,
attachments: List<Attachment>,
sensitive: Boolean,
@ -494,7 +498,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
} else {
imageView.foreground = null
}
setAttachmentClickListener(viewData, imageView, listener, i, attachment, true)
setAttachmentClickListener(pachliAccountId, viewData, imageView, listener, i, attachment, true)
if (sensitive) {
sensitiveMediaWarning.setText(R.string.post_sensitive_media_title)
} else {
@ -505,13 +509,13 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
descriptionIndicator.visibility =
if (hasDescription && showingContent) View.VISIBLE else View.GONE
sensitiveMediaShow.setOnClickListener { v: View ->
listener.onContentHiddenChange(viewData, false)
listener.onContentHiddenChange(pachliAccountId, viewData, false)
v.visibility = View.GONE
sensitiveMediaWarning.visibility = View.VISIBLE
descriptionIndicator.visibility = View.GONE
}
sensitiveMediaWarning.setOnClickListener { v: View ->
listener.onContentHiddenChange(viewData, true)
listener.onContentHiddenChange(pachliAccountId, viewData, true)
v.visibility = View.GONE
sensitiveMediaShow.visibility = View.VISIBLE
descriptionIndicator.visibility = if (hasDescription) View.VISIBLE else View.GONE
@ -526,6 +530,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
}
protected fun setMediaLabel(
pachliAccountId: Long,
viewData: T,
attachments: List<Attachment>,
sensitive: Boolean,
@ -543,7 +548,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
// Set the icon next to the label.
val drawableId = attachments[0].iconResource()
mediaLabel.setCompoundDrawablesRelativeWithIntrinsicBounds(drawableId, 0, 0, 0)
setAttachmentClickListener(viewData, mediaLabel, listener, i, attachment, false)
setAttachmentClickListener(pachliAccountId, viewData, mediaLabel, listener, i, attachment, false)
} else {
mediaLabel.visibility = View.GONE
}
@ -551,6 +556,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
}
private fun setAttachmentClickListener(
pachliAccountId: Long,
viewData: T,
view: View,
listener: StatusActionListener<T>,
@ -560,7 +566,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
) {
view.setOnClickListener { v: View? ->
if (sensitiveMediaWarning.visibility == View.VISIBLE) {
listener.onContentHiddenChange(viewData, true)
listener.onContentHiddenChange(pachliAccountId, viewData, true)
} else {
listener.onViewMedia(viewData, index, if (animateTransition) v else null)
}
@ -676,6 +682,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
}
open fun setupWithStatus(
pachliAccountId: Long,
viewData: T,
listener: StatusActionListener<T>,
statusDisplayOptions: StatusDisplayOptions,
@ -707,6 +714,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
val sensitive = actionable.sensitive
if (statusDisplayOptions.mediaPreviewEnabled && hasPreviewableAttachment(attachments)) {
setMediaPreviews(
pachliAccountId,
viewData,
attachments,
sensitive,
@ -722,12 +730,13 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
mediaLabel.visibility = View.GONE
}
} else {
setMediaLabel(viewData, attachments, sensitive, listener, viewData.isShowingContent)
setMediaLabel(pachliAccountId, viewData, attachments, sensitive, listener, viewData.isShowingContent)
// Hide all unused views.
mediaPreview.visibility = View.GONE
hideSensitiveMediaWarning()
}
setupCard(
pachliAccountId,
viewData,
viewData.isExpanded,
statusDisplayOptions.cardViewMode,
@ -741,7 +750,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
statusDisplayOptions,
)
setRebloggingEnabled(actionable.rebloggingAllowed(), actionable.visibility)
setSpoilerAndContent(viewData, statusDisplayOptions, listener)
setSpoilerAndContent(pachliAccountId, viewData, statusDisplayOptions, listener)
setContentDescriptionForStatus(viewData, statusDisplayOptions)
// Workaround for RecyclerView 1.0.0 / androidx.core 1.0.0
@ -865,6 +874,7 @@ abstract class StatusBaseViewHolder<T : IStatusViewData> protected constructor(
}
protected fun setupCard(
pachliAccountId: Long,
viewData: T,
expanded: Boolean,
cardViewMode: CardViewMode,

View File

@ -23,9 +23,8 @@ import java.text.DateFormat
import java.util.Locale
class StatusDetailedViewHolder(
pachliAccountId: Long,
private val binding: ItemStatusDetailedBinding,
) : StatusBaseViewHolder<StatusViewData>(pachliAccountId, binding.root) {
) : StatusBaseViewHolder<StatusViewData>(binding.root) {
override fun setMetaData(
viewData: StatusViewData,
@ -107,6 +106,7 @@ class StatusDetailedViewHolder(
}
override fun setupWithStatus(
pachliAccountId: Long,
viewData: StatusViewData,
listener: StatusActionListener<StatusViewData>,
statusDisplayOptions: StatusDisplayOptions,
@ -115,8 +115,9 @@ class StatusDetailedViewHolder(
// We never collapse statuses in the detail view
val uncollapsedStatus =
if (viewData.isCollapsible && viewData.isCollapsed) viewData.copy(isCollapsed = false) else viewData
super.setupWithStatus(uncollapsedStatus, listener, statusDisplayOptions, payloads)
super.setupWithStatus(pachliAccountId, uncollapsedStatus, listener, statusDisplayOptions, payloads)
setupCard(
pachliAccountId,
uncollapsedStatus,
viewData.isExpanded,
CardViewMode.FULL_WIDTH,

View File

@ -36,12 +36,12 @@ import app.pachli.viewdata.IStatusViewData
import at.connyduck.sparkbutton.helpers.Utils
open class StatusViewHolder<T : IStatusViewData>(
pachliAccountId: Long,
private val binding: ItemStatusBinding,
root: View? = null,
) : StatusBaseViewHolder<T>(pachliAccountId, root ?: binding.root) {
) : StatusBaseViewHolder<T>(root ?: binding.root) {
override fun setupWithStatus(
pachliAccountId: Long,
viewData: T,
listener: StatusActionListener<T>,
statusDisplayOptions: StatusDisplayOptions,
@ -50,7 +50,7 @@ open class StatusViewHolder<T : IStatusViewData>(
if (payloads == null) {
val sensitive = !TextUtils.isEmpty(viewData.actionable.spoilerText)
val expanded = viewData.isExpanded
setupCollapsedState(viewData, sensitive, expanded, listener)
setupCollapsedState(pachliAccountId, viewData, sensitive, expanded, listener)
val reblogging = viewData.rebloggingStatus
if (reblogging == null || viewData.filterAction === FilterAction.WARN) {
statusInfo.hide()
@ -70,7 +70,7 @@ open class StatusViewHolder<T : IStatusViewData>(
statusFavouritesCount.visible(statusDisplayOptions.showStatsInline)
setFavouritedCount(viewData.actionable.favouritesCount)
setReblogsCount(viewData.actionable.reblogsCount)
super.setupWithStatus(viewData, listener, statusDisplayOptions, payloads)
super.setupWithStatus(pachliAccountId, viewData, listener, statusDisplayOptions, payloads)
}
private fun setRebloggedByDisplayName(
@ -109,6 +109,7 @@ open class StatusViewHolder<T : IStatusViewData>(
}
private fun setupCollapsedState(
pachliAccountId: Long,
viewData: T,
sensitive: Boolean,
expanded: Boolean,
@ -117,7 +118,7 @@ open class StatusViewHolder<T : IStatusViewData>(
/* input filter for TextViews have to be set before text */
if (viewData.isCollapsible && (!sensitive || expanded)) {
buttonToggleContent.setOnClickListener {
listener.onContentCollapsedChange(viewData, !viewData.isCollapsed)
listener.onContentCollapsedChange(pachliAccountId, viewData, !viewData.isCollapsed)
}
buttonToggleContent.show()
if (viewData.isCollapsed) {
@ -139,14 +140,15 @@ open class StatusViewHolder<T : IStatusViewData>(
}
override fun toggleExpandedState(
pachliAccountId: Long,
viewData: T,
sensitive: Boolean,
expanded: Boolean,
statusDisplayOptions: StatusDisplayOptions,
listener: StatusActionListener<T>,
) {
setupCollapsedState(viewData, sensitive, expanded, listener)
super.toggleExpandedState(viewData, sensitive, expanded, statusDisplayOptions, listener)
setupCollapsedState(pachliAccountId, viewData, sensitive, expanded, listener)
super.toggleExpandedState(pachliAccountId, viewData, sensitive, expanded, statusDisplayOptions, listener)
}
companion object {

View File

@ -41,7 +41,7 @@ class ConversationAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConversationViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_conversation, parent, false)
return ConversationViewHolder(pachliAccountId, view, statusDisplayOptions, listener)
return ConversationViewHolder(view, statusDisplayOptions, listener)
}
override fun onBindViewHolder(holder: ConversationViewHolder, position: Int) {
@ -54,7 +54,7 @@ class ConversationAdapter(
payloads: List<Any>,
) {
getItem(position)?.let { conversationViewData ->
holder.setupWithConversation(conversationViewData, payloads.firstOrNull())
holder.setupWithConversation(pachliAccountId, conversationViewData, payloads.firstOrNull())
}
}

View File

@ -33,11 +33,10 @@ import app.pachli.interfaces.StatusActionListener
import app.pachli.util.SmartLengthInputFilter
class ConversationViewHolder internal constructor(
pachliAccountId: Long,
itemView: View,
private val statusDisplayOptions: StatusDisplayOptions,
private val listener: StatusActionListener<ConversationViewData>,
) : StatusBaseViewHolder<ConversationViewData>(pachliAccountId, itemView) {
) : StatusBaseViewHolder<ConversationViewData>(itemView) {
private val conversationNameTextView: TextView = itemView.findViewById(R.id.conversation_name)
private val contentCollapseButton: Button = itemView.findViewById(R.id.button_toggle_content)
private val avatars: Array<ImageView> = arrayOf(
@ -47,12 +46,13 @@ class ConversationViewHolder internal constructor(
)
fun setupWithConversation(
pachliAccountId: Long,
viewData: ConversationViewData,
payloads: Any?,
) {
val (_, _, account, inReplyToId, _, _, _, _, _, _, _, _, _, _, favourited, bookmarked, sensitive, _, _, attachments) = viewData.status
if (payloads == null) {
setupCollapsedState(viewData, listener)
setupCollapsedState(pachliAccountId, viewData, listener)
setDisplayName(account.name, account.emojis, statusDisplayOptions)
setUsername(account.username)
setMetaData(viewData, statusDisplayOptions, listener)
@ -61,6 +61,7 @@ class ConversationViewHolder internal constructor(
setBookmarked(bookmarked)
if (statusDisplayOptions.mediaPreviewEnabled && hasPreviewableAttachment(attachments)) {
setMediaPreviews(
pachliAccountId,
viewData,
attachments,
sensitive,
@ -76,7 +77,7 @@ class ConversationViewHolder internal constructor(
mediaLabel.visibility = View.GONE
}
} else {
setMediaLabel(viewData, attachments, sensitive, listener, viewData.isShowingContent)
setMediaLabel(pachliAccountId, viewData, attachments, sensitive, listener, viewData.isShowingContent)
// Hide all unused views.
mediaPreview.visibility = View.GONE
hideSensitiveMediaWarning()
@ -87,7 +88,7 @@ class ConversationViewHolder internal constructor(
account.id,
statusDisplayOptions,
)
setSpoilerAndContent(viewData, statusDisplayOptions, listener)
setSpoilerAndContent(pachliAccountId, viewData, statusDisplayOptions, listener)
setConversationName(viewData.accounts)
setAvatars(viewData.accounts)
} else {
@ -137,13 +138,14 @@ class ConversationViewHolder internal constructor(
}
private fun setupCollapsedState(
pachliAccountId: Long,
viewData: ConversationViewData,
listener: StatusActionListener<ConversationViewData>,
) {
/* input filter for TextViews have to be set before text */
if (viewData.isCollapsible && (viewData.isExpanded || TextUtils.isEmpty(viewData.spoilerText))) {
contentCollapseButton.setOnClickListener {
listener.onContentCollapsedChange(viewData, !viewData.isCollapsed)
listener.onContentCollapsedChange(pachliAccountId, viewData, !viewData.isCollapsed)
}
contentCollapseButton.show()
if (viewData.isCollapsed) {

View File

@ -322,16 +322,16 @@ class ConversationsFragment :
// there are no reblogs in conversations
}
override fun onExpandedChange(viewData: ConversationViewData, expanded: Boolean) {
viewModel.expandHiddenStatus(expanded, viewData.lastStatus.id)
override fun onExpandedChange(pachliAccountId: Long, viewData: ConversationViewData, expanded: Boolean) {
viewModel.expandHiddenStatus(pachliAccountId, expanded, viewData.lastStatus.id)
}
override fun onContentHiddenChange(viewData: ConversationViewData, isShowing: Boolean) {
viewModel.showContent(isShowing, viewData.lastStatus.id)
override fun onContentHiddenChange(pachliAccountId: Long, viewData: ConversationViewData, isShowing: Boolean) {
viewModel.showContent(pachliAccountId, isShowing, viewData.lastStatus.id)
}
override fun onContentCollapsedChange(viewData: ConversationViewData, isCollapsed: Boolean) {
viewModel.collapseLongStatus(isCollapsed, viewData.lastStatus.id)
override fun onContentCollapsedChange(pachliAccountId: Long, viewData: ConversationViewData, isCollapsed: Boolean) {
viewModel.collapseLongStatus(pachliAccountId, isCollapsed, viewData.lastStatus.id)
}
override fun onViewAccount(id: String) {
@ -356,7 +356,7 @@ class ConversationsFragment :
viewModel.voteInPoll(choices, viewData.lastStatus.actionableId, poll.id)
}
override fun clearWarningAction(viewData: ConversationViewData) {
override fun clearWarningAction(pachliAccountId: Long, viewData: ConversationViewData) {
}
// Filters don't apply in conversations

View File

@ -136,30 +136,30 @@ class ConversationsViewModel @Inject constructor(
}
}
fun expandHiddenStatus(expanded: Boolean, lastStatusId: String) {
fun expandHiddenStatus(pachliAccountId: Long, expanded: Boolean, lastStatusId: String) {
viewModelScope.launch {
conversationsDao.setExpanded(
accountManager.activeAccount!!.id,
pachliAccountId,
lastStatusId,
expanded,
)
}
}
fun collapseLongStatus(collapsed: Boolean, lastStatusId: String) {
fun collapseLongStatus(pachliAccountId: Long, collapsed: Boolean, lastStatusId: String) {
viewModelScope.launch {
conversationsDao.setCollapsed(
accountManager.activeAccount!!.id,
pachliAccountId,
lastStatusId,
collapsed,
)
}
}
fun showContent(showingHiddenContent: Boolean, lastStatusId: String) {
fun showContent(pachliAccountId: Long, showingHiddenContent: Boolean, lastStatusId: String) {
viewModelScope.launch {
conversationsDao.setShowingHiddenContent(
accountManager.activeAccount!!.id,
pachliAccountId,
lastStatusId,
showingHiddenContent,
)

View File

@ -42,6 +42,7 @@ class FollowViewHolder(
)
override fun bind(
pachliAccountId: Long,
viewData: NotificationViewData,
payloads: List<*>?,
statusDisplayOptions: StatusDisplayOptions,

View File

@ -568,7 +568,7 @@ class NotificationsFragment :
onViewAccount(status.account.id)
}
override fun onExpandedChange(viewData: NotificationViewData, expanded: Boolean) {
override fun onExpandedChange(pachliAccountId: Long, viewData: NotificationViewData, expanded: Boolean) {
adapter.snapshot().withIndex()
.filter {
it.value?.statusViewData?.actionableId == viewData.statusViewData!!.actionableId
@ -579,7 +579,7 @@ class NotificationsFragment :
}
}
override fun onContentHiddenChange(viewData: NotificationViewData, isShowing: Boolean) {
override fun onContentHiddenChange(pachliAccountId: Long, viewData: NotificationViewData, isShowing: Boolean) {
adapter.snapshot().withIndex()
.filter {
it.value?.statusViewData?.actionableId == viewData.statusViewData!!.actionableId
@ -590,7 +590,7 @@ class NotificationsFragment :
}
}
override fun onContentCollapsedChange(viewData: NotificationViewData, isCollapsed: Boolean) {
override fun onContentCollapsedChange(pachliAccountId: Long, viewData: NotificationViewData, isCollapsed: Boolean) {
adapter.snapshot().withIndex().filter {
it.value?.statusViewData?.actionableId == viewData.statusViewData!!.actionableId
}
@ -608,13 +608,14 @@ class NotificationsFragment :
}
override fun onNotificationContentCollapsedChange(
pachliAccountId: Long,
isCollapsed: Boolean,
viewData: NotificationViewData,
) {
onContentCollapsedChange(viewData, isCollapsed)
onContentCollapsedChange(pachliAccountId, viewData, isCollapsed)
}
override fun clearWarningAction(viewData: NotificationViewData) {
override fun clearWarningAction(pachliAccountId: Long, viewData: NotificationViewData) {
adapter.snapshot().withIndex().filter { it.value?.statusViewData?.actionableId == viewData.statusViewData!!.actionableId }
.map {
it.value?.statusViewData = it.value?.statusViewData?.copy(

View File

@ -94,7 +94,7 @@ interface NotificationActionListener {
* @param expanded the desired state of the content behind the content warning
*
*/
fun onExpandedChange(viewData: NotificationViewData, expanded: Boolean)
fun onExpandedChange(pachliAccountId: Long, viewData: NotificationViewData, expanded: Boolean)
/**
* Called when the status [android.widget.ToggleButton] responsible for collapsing long
@ -103,6 +103,7 @@ interface NotificationActionListener {
* @param isCollapsed Whether the status content is shown in a collapsed state or fully.
*/
fun onNotificationContentCollapsedChange(
pachliAccountId: Long,
isCollapsed: Boolean,
viewData: NotificationViewData,
)
@ -125,6 +126,7 @@ class NotificationsPagingAdapter(
interface ViewHolder {
/** Bind the data from the notification and payloads to the view */
fun bind(
pachliAccountId: Long,
viewData: NotificationViewData,
payloads: List<*>?,
statusDisplayOptions: StatusDisplayOptions,
@ -146,7 +148,6 @@ class NotificationsPagingAdapter(
return when (NotificationViewKind.entries[viewType]) {
NotificationViewKind.STATUS -> {
StatusViewHolder(
pachliAccountId,
ItemStatusBinding.inflate(inflater, parent, false),
statusActionListener,
accountId,
@ -154,7 +155,6 @@ class NotificationsPagingAdapter(
}
NotificationViewKind.STATUS_FILTERED -> {
FilterableStatusViewHolder(
pachliAccountId,
ItemStatusWrapperBinding.inflate(inflater, parent, false),
statusActionListener,
accountId,
@ -203,7 +203,7 @@ class NotificationsPagingAdapter(
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
bindViewHolder(holder, position, null)
bindViewHolder(pachliAccountId, holder, position, null)
}
override fun onBindViewHolder(
@ -211,15 +211,16 @@ class NotificationsPagingAdapter(
position: Int,
payloads: MutableList<Any>,
) {
bindViewHolder(holder, position, payloads)
bindViewHolder(pachliAccountId, holder, position, payloads)
}
private fun bindViewHolder(
pachliAccountId: Long,
holder: RecyclerView.ViewHolder,
position: Int,
payloads: List<*>?,
) {
getItem(position)?.let { (holder as ViewHolder).bind(it, payloads, statusDisplayOptions) }
getItem(position)?.let { (holder as ViewHolder).bind(pachliAccountId, it, payloads, statusDisplayOptions) }
}
/**
@ -230,6 +231,7 @@ class NotificationsPagingAdapter(
val binding: SimpleListItem1Binding,
) : ViewHolder, RecyclerView.ViewHolder(binding.root) {
override fun bind(
pachliAccountId: Long,
viewData: NotificationViewData,
payloads: List<*>?,
statusDisplayOptions: StatusDisplayOptions,

View File

@ -30,7 +30,12 @@ import app.pachli.viewdata.NotificationViewData
class SeveredRelationshipsViewHolder(
private val binding: ItemSeveredRelationshipsBinding,
) : NotificationsPagingAdapter.ViewHolder, RecyclerView.ViewHolder(binding.root) {
override fun bind(viewData: NotificationViewData, payloads: List<*>?, statusDisplayOptions: StatusDisplayOptions) {
override fun bind(
pachliAccountId: Long,
viewData: NotificationViewData,
payloads: List<*>?,
statusDisplayOptions: StatusDisplayOptions,
) {
val event = viewData.relationshipSeveranceEvent!!
if (payloads.isNullOrEmpty()) {
binding.notificationTopText.text = HtmlCompat.fromHtml(

View File

@ -81,6 +81,7 @@ internal class StatusNotificationViewHolder(
)
override fun bind(
pachliAccountId: Long,
viewData: NotificationViewData,
payloads: List<*>?,
statusDisplayOptions: StatusDisplayOptions,
@ -124,7 +125,7 @@ internal class StatusNotificationViewHolder(
notificationActionListener.onViewAccount(viewData.account.id)
}
}
setMessage(viewData, statusActionListener, statusDisplayOptions.animateEmojis)
setMessage(pachliAccountId, viewData, statusActionListener, statusDisplayOptions.animateEmojis)
} else {
for (item in payloads) {
if (StatusBaseViewHolder.Key.KEY_CREATED == item && statusViewData != null) {
@ -238,6 +239,7 @@ internal class StatusNotificationViewHolder(
}
fun setMessage(
pachliAccountId: Long,
viewData: NotificationViewData,
listener: LinkListener,
animateEmojis: Boolean,
@ -311,6 +313,7 @@ internal class StatusNotificationViewHolder(
binding.notificationContentWarningButton.setOnClickListener {
if (bindingAdapterPosition != RecyclerView.NO_POSITION) {
notificationActionListener.onExpandedChange(
pachliAccountId,
viewData,
!statusViewData.isExpanded,
)
@ -318,10 +321,11 @@ internal class StatusNotificationViewHolder(
binding.notificationContent.visibility =
if (statusViewData.isExpanded) View.GONE else View.VISIBLE
}
setupContentAndSpoiler(listener, viewData, statusViewData, animateEmojis)
setupContentAndSpoiler(pachliAccountId, listener, viewData, statusViewData, animateEmojis)
}
private fun setupContentAndSpoiler(
pachliAccountId: Long,
listener: LinkListener,
viewData: NotificationViewData,
statusViewData: StatusViewData,
@ -341,6 +345,7 @@ internal class StatusNotificationViewHolder(
val position = bindingAdapterPosition
if (position != RecyclerView.NO_POSITION) {
notificationActionListener.onNotificationContentCollapsedChange(
pachliAccountId,
!statusViewData.isCollapsed,
viewData,
)

View File

@ -27,13 +27,13 @@ import app.pachli.interfaces.StatusActionListener
import app.pachli.viewdata.NotificationViewData
internal class StatusViewHolder(
pachliAccountId: Long,
binding: ItemStatusBinding,
private val statusActionListener: StatusActionListener<NotificationViewData>,
private val accountId: String,
) : NotificationsPagingAdapter.ViewHolder, StatusViewHolder<NotificationViewData>(pachliAccountId, binding) {
) : NotificationsPagingAdapter.ViewHolder, StatusViewHolder<NotificationViewData>(binding) {
override fun bind(
pachliAccountId: Long,
viewData: NotificationViewData,
payloads: List<*>?,
statusDisplayOptions: StatusDisplayOptions,
@ -48,6 +48,7 @@ internal class StatusViewHolder(
showStatusContent(true)
}
setupWithStatus(
pachliAccountId,
viewData,
statusActionListener,
statusDisplayOptions,
@ -63,13 +64,13 @@ internal class StatusViewHolder(
}
class FilterableStatusViewHolder(
private val pachliAccountId: Long,
binding: ItemStatusWrapperBinding,
private val statusActionListener: StatusActionListener<NotificationViewData>,
private val accountId: String,
) : NotificationsPagingAdapter.ViewHolder, FilterableStatusViewHolder<NotificationViewData>(pachliAccountId, binding) {
) : NotificationsPagingAdapter.ViewHolder, FilterableStatusViewHolder<NotificationViewData>(binding) {
// Note: Identical to bind() in StatusViewHolder above
override fun bind(
pachliAccountId: Long,
viewData: NotificationViewData,
payloads: List<*>?,
statusDisplayOptions: StatusDisplayOptions,
@ -84,6 +85,7 @@ class FilterableStatusViewHolder(
showStatusContent(true)
}
setupWithStatus(
pachliAccountId,
viewData,
statusActionListener,
statusDisplayOptions,

View File

@ -34,14 +34,13 @@ class SearchStatusesAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatusViewHolder<StatusViewData> {
return StatusViewHolder(
pachliAccountId,
ItemStatusBinding.inflate(LayoutInflater.from(parent.context), parent, false),
)
}
override fun onBindViewHolder(holder: StatusViewHolder<StatusViewData>, position: Int) {
getItem(position)?.let { item ->
holder.setupWithStatus(item, statusListener, statusDisplayOptions)
holder.setupWithStatus(pachliAccountId, item, statusListener, statusDisplayOptions)
}
}

View File

@ -88,7 +88,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
return SearchStatusesAdapter(viewModel.activeAccount!!.id, statusDisplayOptions, this)
}
override fun onContentHiddenChange(viewData: StatusViewData, isShowing: Boolean) {
override fun onContentHiddenChange(pachliAccountId: Long, viewData: StatusViewData, isShowing: Boolean) {
viewModel.contentHiddenChange(viewData, isShowing)
}
@ -148,11 +148,11 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
bottomSheetActivity?.viewAccount(pachliAccountId, status.account.id)
}
override fun onExpandedChange(viewData: StatusViewData, expanded: Boolean) {
override fun onExpandedChange(pachliAccountId: Long, viewData: StatusViewData, expanded: Boolean) {
viewModel.expandedChange(viewData, expanded)
}
override fun onContentCollapsedChange(viewData: StatusViewData, isCollapsed: Boolean) {
override fun onContentCollapsedChange(pachliAccountId: Long, viewData: StatusViewData, isCollapsed: Boolean) {
viewModel.collapsedChange(viewData, isCollapsed)
}
@ -160,7 +160,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
viewModel.voteInPoll(viewData, poll, choices)
}
override fun clearWarningAction(viewData: StatusViewData) {}
override fun clearWarningAction(pachliAccountId: Long, viewData: StatusViewData) {}
override fun onReblog(viewData: StatusViewData, reblog: Boolean) {
viewModel.reblog(viewData, reblog)

View File

@ -126,21 +126,21 @@ class CachedTimelineRepository @Inject constructor(
}
/** Invalidate the active paging source, see [androidx.paging.PagingSource.invalidate] */
suspend fun invalidate() {
suspend fun invalidate(pachliAccountId: Long) {
// Invalidating when no statuses have been loaded can cause empty timelines because it
// cancels the network load.
if (timelineDao.getStatusCount(activeAccount!!.id) < 1) {
if (timelineDao.getStatusCount(pachliAccountId) < 1) {
return
}
factory?.invalidate()
}
suspend fun saveStatusViewData(statusViewData: StatusViewData) = externalScope.launch {
suspend fun saveStatusViewData(pachliAccountId: Long, statusViewData: StatusViewData) = externalScope.launch {
timelineDao.upsertStatusViewData(
StatusViewDataEntity(
serverId = statusViewData.actionableId,
timelineUserId = activeAccount!!.id,
timelineUserId = pachliAccountId,
expanded = statusViewData.isExpanded,
contentShowing = statusViewData.isShowingContent,
contentCollapsed = statusViewData.isCollapsed,
@ -152,74 +152,75 @@ class CachedTimelineRepository @Inject constructor(
/**
* @return Map between statusIDs and any viewdata for them cached in the repository.
*/
suspend fun getStatusViewData(statusId: List<String>): Map<String, StatusViewDataEntity> {
return timelineDao.getStatusViewData(activeAccount!!.id, statusId)
suspend fun getStatusViewData(pachliAccountId: Long, statusId: List<String>): Map<String, StatusViewDataEntity> {
return timelineDao.getStatusViewData(pachliAccountId, statusId)
}
/**
* @return Map between statusIDs and any translations for them cached in the repository.
*/
suspend fun getStatusTranslations(statusIds: List<String>): Map<String, TranslatedStatusEntity> {
return translatedStatusDao.getTranslations(activeAccount!!.id, statusIds)
suspend fun getStatusTranslations(pachliAccountId: Long, statusIds: List<String>): Map<String, TranslatedStatusEntity> {
return translatedStatusDao.getTranslations(pachliAccountId, statusIds)
}
/** Remove all statuses authored/boosted by the given account, for the active account */
suspend fun removeAllByAccountId(accountId: String) = externalScope.launch {
timelineDao.removeAllByUser(activeAccount!!.id, accountId)
suspend fun removeAllByAccountId(pachliAccountId: Long, accountId: String) = externalScope.launch {
timelineDao.removeAllByUser(pachliAccountId, accountId)
}.join()
/** Remove all statuses from the given instance, for the active account */
suspend fun removeAllByInstance(instance: String) = externalScope.launch {
timelineDao.deleteAllFromInstance(activeAccount!!.id, instance)
suspend fun removeAllByInstance(pachliAccountId: Long, instance: String) = externalScope.launch {
timelineDao.deleteAllFromInstance(pachliAccountId, instance)
}.join()
/** Clear the warning (remove the "filtered" setting) for the given status, for the active account */
suspend fun clearStatusWarning(statusId: String) = externalScope.launch {
timelineDao.clearWarning(activeAccount!!.id, statusId)
suspend fun clearStatusWarning(pachliAccountId: Long, statusId: String) = externalScope.launch {
timelineDao.clearWarning(pachliAccountId, statusId)
}.join()
/** Remove all statuses and invalidate the pager, for the active account */
suspend fun clearAndReload() = externalScope.launch {
suspend fun clearAndReload(pachliAccountId: Long) = externalScope.launch {
Timber.d("clearAndReload()")
timelineDao.removeAll(activeAccount!!.id)
timelineDao.removeAll(pachliAccountId)
factory?.invalidate()
}.join()
suspend fun clearAndReloadFromNewest() = externalScope.launch {
activeAccount?.let {
timelineDao.removeAll(it.id)
remoteKeyDao.delete(it.id)
invalidate()
}
suspend fun clearAndReloadFromNewest(pachliAccountId: Long) = externalScope.launch {
timelineDao.removeAll(pachliAccountId)
remoteKeyDao.delete(pachliAccountId)
invalidate(pachliAccountId)
}
suspend fun translate(statusViewData: StatusViewData): NetworkResult<Translation> {
saveStatusViewData(statusViewData.copy(translationState = TranslationState.TRANSLATING))
suspend fun translate(pachliAccountId: Long, statusViewData: StatusViewData): NetworkResult<Translation> {
saveStatusViewData(pachliAccountId, statusViewData.copy(translationState = TranslationState.TRANSLATING))
val translation = mastodonApi.translate(statusViewData.actionableId)
translation.fold({
translatedStatusDao.upsert(
TranslatedStatusEntity(
serverId = statusViewData.actionableId,
timelineUserId = activeAccount!!.id,
// TODO: Should this embed the network type instead of copying data
// from one type to another?
content = it.content,
spoilerText = it.spoilerText,
poll = it.poll,
attachments = it.attachments,
provider = it.provider,
),
)
saveStatusViewData(statusViewData.copy(translationState = TranslationState.SHOW_TRANSLATION))
}, {
// Reset the translation state
saveStatusViewData(statusViewData)
})
translation.fold(
{
translatedStatusDao.upsert(
TranslatedStatusEntity(
serverId = statusViewData.actionableId,
timelineUserId = pachliAccountId,
// TODO: Should this embed the network type instead of copying data
// from one type to another?
content = it.content,
spoilerText = it.spoilerText,
poll = it.poll,
attachments = it.attachments,
provider = it.provider,
),
)
saveStatusViewData(pachliAccountId, statusViewData.copy(translationState = TranslationState.SHOW_TRANSLATION))
},
{
// Reset the translation state
saveStatusViewData(pachliAccountId, statusViewData)
},
)
return translation
}
suspend fun translateUndo(statusViewData: StatusViewData) {
saveStatusViewData(statusViewData.copy(translationState = TranslationState.SHOW_ORIGINAL))
suspend fun translateUndo(pachliAccountId: Long, statusViewData: StatusViewData) {
saveStatusViewData(pachliAccountId, statusViewData.copy(translationState = TranslationState.SHOW_ORIGINAL))
}
companion object {

View File

@ -649,8 +649,8 @@ class TimelineFragment :
viewModel.accept(StatusAction.VoteInPoll(poll, choices, viewData))
}
override fun clearWarningAction(viewData: StatusViewData) {
viewModel.clearWarning(viewData)
override fun clearWarningAction(pachliAccountId: Long, viewData: StatusViewData) {
viewModel.clearWarning(pachliAccountId, viewData)
}
override fun onEditFilterById(pachliAccountId: Long, filterId: String) {
@ -668,12 +668,12 @@ class TimelineFragment :
super.openReblog(status)
}
override fun onExpandedChange(viewData: StatusViewData, expanded: Boolean) {
viewModel.changeExpanded(expanded, viewData)
override fun onExpandedChange(pachliAccountId: Long, viewData: StatusViewData, expanded: Boolean) {
viewModel.changeExpanded(pachliAccountId, expanded, viewData)
}
override fun onContentHiddenChange(viewData: StatusViewData, isShowing: Boolean) {
viewModel.changeContentShowing(isShowing, viewData)
override fun onContentHiddenChange(pachliAccountId: Long, viewData: StatusViewData, isShowing: Boolean) {
viewModel.changeContentShowing(pachliAccountId, isShowing, viewData)
}
override fun onShowReblogs(statusId: String) {
@ -686,8 +686,8 @@ class TimelineFragment :
activity?.startActivityWithDefaultTransition(intent)
}
override fun onContentCollapsedChange(viewData: StatusViewData, isCollapsed: Boolean) {
viewModel.changeContentCollapsed(isCollapsed, viewData)
override fun onContentCollapsedChange(pachliAccountId: Long, viewData: StatusViewData, isCollapsed: Boolean) {
viewModel.changeContentCollapsed(pachliAccountId, isCollapsed, viewData)
}
// Can only translate the home timeline at the moment

View File

@ -41,10 +41,10 @@ class TimelinePagingAdapter(
val inflater = LayoutInflater.from(viewGroup.context)
return when (viewType) {
VIEW_TYPE_STATUS_FILTERED -> {
FilterableStatusViewHolder<StatusViewData>(pachliAccountId, ItemStatusWrapperBinding.inflate(inflater, viewGroup, false))
FilterableStatusViewHolder<StatusViewData>(ItemStatusWrapperBinding.inflate(inflater, viewGroup, false))
}
VIEW_TYPE_STATUS -> {
StatusViewHolder<StatusViewData>(pachliAccountId, ItemStatusBinding.inflate(inflater, viewGroup, false))
StatusViewHolder<StatusViewData>(ItemStatusBinding.inflate(inflater, viewGroup, false))
}
else -> return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.item_placeholder, viewGroup, false)) {}
}
@ -69,6 +69,7 @@ class TimelinePagingAdapter(
) {
getItem(position)?.let {
(viewHolder as StatusViewHolder<StatusViewData>).setupWithStatus(
pachliAccountId,
it,
statusListener,
statusDisplayOptions,

View File

@ -110,39 +110,39 @@ class CachedTimelineViewModel @Inject constructor(
// handled by CacheUpdater
}
override fun changeExpanded(expanded: Boolean, status: StatusViewData) {
override fun changeExpanded(pachliAccountId: Long, expanded: Boolean, status: StatusViewData) {
viewModelScope.launch {
repository.saveStatusViewData(status.copy(isExpanded = expanded))
repository.saveStatusViewData(pachliAccountId, status.copy(isExpanded = expanded))
}
}
override fun changeContentShowing(isShowing: Boolean, status: StatusViewData) {
override fun changeContentShowing(pachliAccountId: Long, isShowing: Boolean, status: StatusViewData) {
viewModelScope.launch {
repository.saveStatusViewData(status.copy(isShowingContent = isShowing))
repository.saveStatusViewData(pachliAccountId, status.copy(isShowingContent = isShowing))
}
}
override fun changeContentCollapsed(isCollapsed: Boolean, status: StatusViewData) {
override fun changeContentCollapsed(pachliAccountId: Long, isCollapsed: Boolean, status: StatusViewData) {
viewModelScope.launch {
repository.saveStatusViewData(status.copy(isCollapsed = isCollapsed))
repository.saveStatusViewData(pachliAccountId, status.copy(isCollapsed = isCollapsed))
}
}
override fun removeAllByAccountId(accountId: String) {
override fun removeAllByAccountId(pachliAccountId: Long, accountId: String) {
viewModelScope.launch {
repository.removeAllByAccountId(accountId)
repository.removeAllByAccountId(pachliAccountId, accountId)
}
}
override fun removeAllByInstance(instance: String) {
override fun removeAllByInstance(pachliAccountId: Long, instance: String) {
viewModelScope.launch {
repository.removeAllByInstance(instance)
repository.removeAllByInstance(pachliAccountId, instance)
}
}
override fun clearWarning(statusViewData: StatusViewData) {
override fun clearWarning(pachliAccountId: Long, statusViewData: StatusViewData) {
viewModelScope.launch {
repository.clearStatusWarning(statusViewData.actionableId)
repository.clearStatusWarning(pachliAccountId, statusViewData.actionableId)
}
}
@ -166,21 +166,21 @@ class CachedTimelineViewModel @Inject constructor(
// handled by CacheUpdater
}
override fun reloadKeepingReadingPosition() {
super.reloadKeepingReadingPosition()
override fun reloadKeepingReadingPosition(pachliAccountId: Long) {
super.reloadKeepingReadingPosition(pachliAccountId)
viewModelScope.launch {
repository.clearAndReload()
repository.clearAndReload(pachliAccountId)
}
}
override fun reloadFromNewest() {
super.reloadFromNewest()
override fun reloadFromNewest(pachliAccountId: Long) {
super.reloadFromNewest(pachliAccountId)
viewModelScope.launch {
repository.clearAndReloadFromNewest()
repository.clearAndReloadFromNewest(pachliAccountId)
}
}
override suspend fun invalidate() {
repository.invalidate()
override suspend fun invalidate(pachliAccountId: Long) {
repository.invalidate(pachliAccountId)
}
}

View File

@ -111,21 +111,21 @@ class NetworkTimelineViewModel @Inject constructor(
repository.invalidate()
}
override fun changeExpanded(expanded: Boolean, status: StatusViewData) {
override fun changeExpanded(pachliAccountId: Long, expanded: Boolean, status: StatusViewData) {
modifiedViewData[status.id] = status.copy(
isExpanded = expanded,
)
repository.invalidate()
}
override fun changeContentShowing(isShowing: Boolean, status: StatusViewData) {
override fun changeContentShowing(pachliAccountId: Long, isShowing: Boolean, status: StatusViewData) {
modifiedViewData[status.id] = status.copy(
isShowingContent = isShowing,
)
repository.invalidate()
}
override fun changeContentCollapsed(isCollapsed: Boolean, status: StatusViewData) {
override fun changeContentCollapsed(pachliAccountId: Long, isCollapsed: Boolean, status: StatusViewData) {
Timber.d("changeContentCollapsed: %s", isCollapsed)
Timber.d(" %s", status.content)
modifiedViewData[status.id] = status.copy(
@ -134,13 +134,13 @@ class NetworkTimelineViewModel @Inject constructor(
repository.invalidate()
}
override fun removeAllByAccountId(accountId: String) {
override fun removeAllByAccountId(pachliAccountId: Long, accountId: String) {
viewModelScope.launch {
repository.removeAllByAccountId(accountId)
}
}
override fun removeAllByInstance(instance: String) {
override fun removeAllByInstance(pachliAccountId: Long, instance: String) {
viewModelScope.launch {
repository.removeAllByInstance(instance)
}
@ -187,19 +187,19 @@ class NetworkTimelineViewModel @Inject constructor(
repository.invalidate()
}
override fun reloadKeepingReadingPosition() {
super.reloadKeepingReadingPosition()
override fun reloadKeepingReadingPosition(pachliAccountId: Long) {
super.reloadKeepingReadingPosition(pachliAccountId)
viewModelScope.launch {
repository.reload()
}
}
override fun reloadFromNewest() {
super.reloadFromNewest()
reloadKeepingReadingPosition()
override fun reloadFromNewest(pachliAccountId: Long) {
super.reloadFromNewest(pachliAccountId)
reloadKeepingReadingPosition(pachliAccountId)
}
override fun clearWarning(statusViewData: StatusViewData) {
override fun clearWarning(pachliAccountId: Long, statusViewData: StatusViewData) {
viewModelScope.launch {
repository.updateActionableStatusById(statusViewData.actionableId) {
it.copy(filtered = null)
@ -207,7 +207,7 @@ class NetworkTimelineViewModel @Inject constructor(
}
}
override suspend fun invalidate() {
override suspend fun invalidate(pachliAccountId: Long) {
repository.invalidate()
}
}

View File

@ -150,6 +150,8 @@ sealed interface UiSuccess {
/** Actions the user can trigger on an individual status */
sealed interface StatusAction : FallibleUiAction {
// TODO: Include a property for the PachliAccountId the action is being performed as.
val statusViewData: StatusViewData
/** Set the bookmark state for a status */
@ -342,7 +344,7 @@ abstract class TimelineViewModel(
else -> null
}
if (reload) {
reloadKeepingReadingPosition()
reloadKeepingReadingPosition(activeAccount.id)
}
}.onFailure {
_uiErrorChannel.send(UiError.GetFilters(RuntimeException(it.fmt(context))))
@ -381,7 +383,7 @@ abstract class TimelineViewModel(
action.choices,
)
is StatusAction.Translate -> {
timelineCases.translate(action.statusViewData)
timelineCases.translate(activeAccount.id, action.statusViewData)
}
}.getOrThrow()
uiSuccess.emit(StatusActionSuccess.from(action))
@ -468,14 +470,14 @@ abstract class TimelineViewModel(
accountManager.saveAccount(activeAccount)
}
Timber.d("Reload because InfallibleUiAction.LoadNewest")
reloadFromNewest()
reloadFromNewest(activeAccount.id)
}
}
// Undo status translations
viewModelScope.launch {
uiAction.filterIsInstance<InfallibleUiAction.TranslateUndo>().collectLatest {
timelineCases.translateUndo(it.statusViewData)
timelineCases.translateUndo(activeAccount.id, it.statusViewData)
}
}
@ -495,15 +497,15 @@ abstract class TimelineViewModel(
abstract fun updatePoll(newPoll: Poll, status: StatusViewData)
abstract fun changeExpanded(expanded: Boolean, status: StatusViewData)
abstract fun changeExpanded(pachliAccountId: Long, expanded: Boolean, status: StatusViewData)
abstract fun changeContentShowing(isShowing: Boolean, status: StatusViewData)
abstract fun changeContentShowing(pachliAccountId: Long, isShowing: Boolean, status: StatusViewData)
abstract fun changeContentCollapsed(isCollapsed: Boolean, status: StatusViewData)
abstract fun changeContentCollapsed(pachliAccountId: Long, isCollapsed: Boolean, status: StatusViewData)
abstract fun removeAllByAccountId(accountId: String)
abstract fun removeAllByAccountId(pachliAccountId: Long, accountId: String)
abstract fun removeAllByInstance(instance: String)
abstract fun removeAllByInstance(pachliAccountId: Long, instance: String)
abstract fun removeStatusWithId(id: String)
@ -521,7 +523,7 @@ abstract class TimelineViewModel(
* Subclasses should call this, then start loading data.
*/
@CallSuper
open fun reloadKeepingReadingPosition() {
open fun reloadKeepingReadingPosition(pachliAccountId: Long) {
reload.getAndUpdate { it + 1 }
}
@ -531,14 +533,14 @@ abstract class TimelineViewModel(
* Subclasses should call this, then start loading data.
*/
@CallSuper
open fun reloadFromNewest() {
open fun reloadFromNewest(pachliAccountId: Long) {
reload.getAndUpdate { it + 1 }
}
abstract fun clearWarning(statusViewData: StatusViewData)
abstract fun clearWarning(pachliAccountId: Long, statusViewData: StatusViewData)
/** Triggered when currently displayed data must be reloaded. */
protected abstract suspend fun invalidate()
protected abstract suspend fun invalidate(pachliAccountId: Long)
protected fun shouldFilterStatus(statusViewData: StatusViewData): FilterAction {
val status = statusViewData.status
@ -564,7 +566,7 @@ abstract class TimelineViewModel(
filterRemoveReplies = timeline is Timeline.Home && !filter
if (oldRemoveReplies != filterRemoveReplies) {
Timber.d("Reload because TAB_FILTER_HOME_REPLIES changed")
reloadKeepingReadingPosition()
reloadKeepingReadingPosition(activeAccount.id)
}
}
PrefKeys.TAB_FILTER_HOME_BOOSTS -> {
@ -573,7 +575,7 @@ abstract class TimelineViewModel(
filterRemoveReblogs = timeline is Timeline.Home && !filter
if (oldRemoveReblogs != filterRemoveReblogs) {
Timber.d("Reload because TAB_FILTER_HOME_BOOSTS changed")
reloadKeepingReadingPosition()
reloadKeepingReadingPosition(activeAccount.id)
}
}
PrefKeys.TAB_SHOW_HOME_SELF_BOOSTS -> {
@ -582,7 +584,7 @@ abstract class TimelineViewModel(
filterRemoveSelfReblogs = timeline is Timeline.Home && !filter
if (oldRemoveSelfReblogs != filterRemoveSelfReblogs) {
Timber.d("Reload because TAB_SHOW_SOME_SELF_BOOSTS changed")
reloadKeepingReadingPosition()
reloadKeepingReadingPosition(activeAccount.id)
}
}
}
@ -596,30 +598,30 @@ abstract class TimelineViewModel(
is PinEvent -> handlePinEvent(event)
is MuteConversationEvent -> {
Timber.d("Reload because MuteConversationEvent")
reloadKeepingReadingPosition()
reloadKeepingReadingPosition(activeAccount.id)
}
is UnfollowEvent -> {
if (timeline is Timeline.Home) {
val id = event.accountId
removeAllByAccountId(id)
removeAllByAccountId(activeAccount.id, id)
}
}
is BlockEvent -> {
if (timeline !is Timeline.User) {
val id = event.accountId
removeAllByAccountId(id)
removeAllByAccountId(activeAccount.id, id)
}
}
is MuteEvent -> {
if (timeline !is Timeline.User) {
val id = event.accountId
removeAllByAccountId(id)
removeAllByAccountId(activeAccount.id, id)
}
}
is DomainMuteEvent -> {
if (timeline !is Timeline.User) {
val instance = event.instance
removeAllByInstance(instance)
removeAllByInstance(activeAccount.id, instance)
}
}
is StatusDeletedEvent -> {

View File

@ -42,13 +42,13 @@ class ThreadAdapter(
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
VIEW_TYPE_STATUS -> {
StatusViewHolder(pachliAccountId, ItemStatusBinding.inflate(inflater, parent, false))
StatusViewHolder(ItemStatusBinding.inflate(inflater, parent, false))
}
VIEW_TYPE_STATUS_FILTERED -> {
FilterableStatusViewHolder(pachliAccountId, ItemStatusWrapperBinding.inflate(inflater, parent, false))
FilterableStatusViewHolder(ItemStatusWrapperBinding.inflate(inflater, parent, false))
}
VIEW_TYPE_STATUS_DETAILED -> {
StatusDetailedViewHolder(pachliAccountId, ItemStatusDetailedBinding.inflate(inflater, parent, false))
StatusDetailedViewHolder(ItemStatusDetailedBinding.inflate(inflater, parent, false))
}
else -> error("Unknown item type: $viewType")
}
@ -56,7 +56,7 @@ class ThreadAdapter(
override fun onBindViewHolder(viewHolder: StatusBaseViewHolder<StatusViewData>, position: Int) {
val status = getItem(position)
viewHolder.setupWithStatus(status, statusActionListener, statusDisplayOptions)
viewHolder.setupWithStatus(pachliAccountId, status, statusActionListener, statusDisplayOptions)
}
override fun getItemViewType(position: Int): Int {

View File

@ -339,11 +339,11 @@ class ViewThreadFragment :
)
}
override fun onExpandedChange(viewData: StatusViewData, expanded: Boolean) {
override fun onExpandedChange(pachliAccountId: Long, viewData: StatusViewData, expanded: Boolean) {
viewModel.changeExpanded(expanded, viewData)
}
override fun onContentHiddenChange(viewData: StatusViewData, isShowing: Boolean) {
override fun onContentHiddenChange(pachliAccountId: Long, viewData: StatusViewData, isShowing: Boolean) {
viewModel.changeContentShowing(isShowing, viewData)
}
@ -357,7 +357,7 @@ class ViewThreadFragment :
activity?.startActivityWithDefaultTransition(intent)
}
override fun onContentCollapsedChange(viewData: StatusViewData, isCollapsed: Boolean) {
override fun onContentCollapsedChange(pachliAccountId: Long, viewData: StatusViewData, isCollapsed: Boolean) {
viewModel.changeContentCollapsed(isCollapsed, viewData)
}
@ -397,7 +397,7 @@ class ViewThreadFragment :
}
}
override fun clearWarningAction(viewData: StatusViewData) {
override fun clearWarningAction(pachliAccountId: Long, viewData: StatusViewData) {
viewModel.clearWarning(viewData)
}

View File

@ -201,49 +201,52 @@ class ViewThreadViewModel @Inject constructor(
val contextResult = contextCall.await()
contextResult.fold({ statusContext ->
val ids = statusContext.ancestors.map { it.id } + statusContext.descendants.map { it.id }
val cachedViewData = repository.getStatusViewData(ids)
val cachedTranslations = repository.getStatusTranslations(ids)
val ancestors = statusContext.ancestors.map { status ->
val svd = cachedViewData[status.id]
StatusViewData.from(
status,
isShowingContent = svd?.contentShowing ?: (alwaysShowSensitiveMedia || !status.actionableStatus.sensitive),
isExpanded = svd?.expanded ?: alwaysOpenSpoiler,
isCollapsed = svd?.contentCollapsed ?: true,
isDetailed = false,
translationState = svd?.translationState ?: TranslationState.SHOW_ORIGINAL,
translation = cachedTranslations[status.id],
)
}.filterByFilterAction()
val descendants = statusContext.descendants.map { status ->
val svd = cachedViewData[status.id]
StatusViewData.from(
status,
isShowingContent = svd?.contentShowing ?: (alwaysShowSensitiveMedia || !status.actionableStatus.sensitive),
isExpanded = svd?.expanded ?: alwaysOpenSpoiler,
isCollapsed = svd?.contentCollapsed ?: true,
isDetailed = false,
translationState = svd?.translationState ?: TranslationState.SHOW_ORIGINAL,
translation = cachedTranslations[status.id],
)
}.filterByFilterAction()
val statuses = ancestors + detailedStatus + descendants
contextResult.fold(
{ statusContext ->
val ids = statusContext.ancestors.map { it.id } + statusContext.descendants.map { it.id }
val cachedViewData = repository.getStatusViewData(activeAccount.id, ids)
val cachedTranslations = repository.getStatusTranslations(activeAccount.id, ids)
val ancestors = statusContext.ancestors.map { status ->
val svd = cachedViewData[status.id]
StatusViewData.from(
status,
isShowingContent = svd?.contentShowing ?: (alwaysShowSensitiveMedia || !status.actionableStatus.sensitive),
isExpanded = svd?.expanded ?: alwaysOpenSpoiler,
isCollapsed = svd?.contentCollapsed ?: true,
isDetailed = false,
translationState = svd?.translationState ?: TranslationState.SHOW_ORIGINAL,
translation = cachedTranslations[status.id],
)
}.filterByFilterAction()
val descendants = statusContext.descendants.map { status ->
val svd = cachedViewData[status.id]
StatusViewData.from(
status,
isShowingContent = svd?.contentShowing ?: (alwaysShowSensitiveMedia || !status.actionableStatus.sensitive),
isExpanded = svd?.expanded ?: alwaysOpenSpoiler,
isCollapsed = svd?.contentCollapsed ?: true,
isDetailed = false,
translationState = svd?.translationState ?: TranslationState.SHOW_ORIGINAL,
translation = cachedTranslations[status.id],
)
}.filterByFilterAction()
val statuses = ancestors + detailedStatus + descendants
_uiState.value = ThreadUiState.Success(
statusViewData = statuses,
detailedStatusPosition = ancestors.size,
revealButton = statuses.getRevealButtonState(),
)
}, { throwable ->
_errors.emit(throwable)
_uiState.value = ThreadUiState.Success(
statusViewData = listOf(detailedStatus),
detailedStatusPosition = 0,
revealButton = RevealButtonState.NO_BUTTON,
)
})
_uiState.value = ThreadUiState.Success(
statusViewData = statuses,
detailedStatusPosition = ancestors.size,
revealButton = statuses.getRevealButtonState(),
)
},
{ throwable ->
_errors.emit(throwable)
_uiState.value = ThreadUiState.Success(
statusViewData = listOf(detailedStatus),
detailedStatusPosition = 0,
revealButton = RevealButtonState.NO_BUTTON,
)
},
)
}
}
@ -335,7 +338,7 @@ class ViewThreadViewModel @Inject constructor(
)
}
viewModelScope.launch {
repository.saveStatusViewData(status.copy(isExpanded = expanded))
repository.saveStatusViewData(activeAccount.id, status.copy(isExpanded = expanded))
}
}
@ -344,7 +347,7 @@ class ViewThreadViewModel @Inject constructor(
viewData.copy(isShowingContent = isShowing)
}
viewModelScope.launch {
repository.saveStatusViewData(status.copy(isShowingContent = isShowing))
repository.saveStatusViewData(activeAccount.id, status.copy(isShowingContent = isShowing))
}
}
@ -353,7 +356,7 @@ class ViewThreadViewModel @Inject constructor(
viewData.copy(isCollapsed = isCollapsed)
}
viewModelScope.launch {
repository.saveStatusViewData(status.copy(isCollapsed = isCollapsed))
repository.saveStatusViewData(activeAccount.id, status.copy(isCollapsed = isCollapsed))
}
}
@ -455,28 +458,31 @@ class ViewThreadViewModel @Inject constructor(
fun translate(statusViewData: StatusViewData) {
viewModelScope.launch {
repository.translate(statusViewData).fold({
val translatedEntity = TranslatedStatusEntity(
serverId = statusViewData.actionableId,
timelineUserId = activeAccount.id,
content = it.content,
spoilerText = it.spoilerText,
poll = it.poll,
attachments = it.attachments,
provider = it.provider,
)
updateStatusViewData(statusViewData.status.id) { viewData ->
viewData.copy(translation = translatedEntity, translationState = TranslationState.SHOW_TRANSLATION)
}
}, {
// Mastodon returns 403 if it thinks the original status language is the
// same as the user's language, ignoring the actual content of the status
// (https://github.com/mastodon/documentation/issues/1330). Nothing useful
// to do here so swallow the error
if (it is HttpException && it.code() == 403) return@fold
repository.translate(activeAccount.id, statusViewData).fold(
{
val translatedEntity = TranslatedStatusEntity(
serverId = statusViewData.actionableId,
timelineUserId = activeAccount.id,
content = it.content,
spoilerText = it.spoilerText,
poll = it.poll,
attachments = it.attachments,
provider = it.provider,
)
updateStatusViewData(statusViewData.status.id) { viewData ->
viewData.copy(translation = translatedEntity, translationState = TranslationState.SHOW_TRANSLATION)
}
},
{
// Mastodon returns 403 if it thinks the original status language is the
// same as the user's language, ignoring the actual content of the status
// (https://github.com/mastodon/documentation/issues/1330). Nothing useful
// to do here so swallow the error
if (it is HttpException && it.code() == 403) return@fold
_errors.emit(it)
})
_errors.emit(it)
},
)
}
}
@ -486,6 +492,7 @@ class ViewThreadViewModel @Inject constructor(
}
viewModelScope.launch {
repository.saveStatusViewData(
activeAccount.id,
statusViewData.copy(translationState = TranslationState.SHOW_ORIGINAL),
)
}

View File

@ -36,8 +36,8 @@ interface StatusActionListener<T : IStatusViewData> : LinkListener {
* Open reblog author for the status.
*/
fun onOpenReblog(status: Status)
fun onExpandedChange(viewData: T, expanded: Boolean)
fun onContentHiddenChange(viewData: T, isShowing: Boolean)
fun onExpandedChange(pachliAccountId: Long, viewData: T, expanded: Boolean)
fun onContentHiddenChange(pachliAccountId: Long, viewData: T, isShowing: Boolean)
/**
* Called when the status [android.widget.ToggleButton] responsible for collapsing long
@ -45,7 +45,7 @@ interface StatusActionListener<T : IStatusViewData> : LinkListener {
*
* @param isCollapsed Whether the status content is shown in a collapsed state or fully.
*/
fun onContentCollapsedChange(viewData: T, isCollapsed: Boolean)
fun onContentCollapsedChange(pachliAccountId: Long, viewData: T, isCollapsed: Boolean)
/**
* called when the reblog count has been clicked
@ -58,7 +58,7 @@ interface StatusActionListener<T : IStatusViewData> : LinkListener {
fun onShowFavs(statusId: String) {}
fun onVoteInPoll(viewData: T, poll: Poll, choices: List<Int>)
fun onShowEdits(statusId: String) {}
fun clearWarningAction(viewData: T)
fun clearWarningAction(pachliAccountId: Long, viewData: T)
/** Edit the filter that matched this status. */
fun onEditFilterById(pachliAccountId: Long, filterId: String)

View File

@ -143,11 +143,11 @@ class TimelineCases @Inject constructor(
return mastodonApi.rejectFollowRequest(accountId)
}
suspend fun translate(statusViewData: StatusViewData): NetworkResult<Translation> {
return cachedTimelineRepository.translate(statusViewData)
suspend fun translate(pachliAccountId: Long, statusViewData: StatusViewData): NetworkResult<Translation> {
return cachedTimelineRepository.translate(pachliAccountId, statusViewData)
}
suspend fun translateUndo(statusViewData: StatusViewData) {
cachedTimelineRepository.translateUndo(statusViewData)
suspend fun translateUndo(pachliAccountId: Long, statusViewData: StatusViewData) {
cachedTimelineRepository.translateUndo(pachliAccountId, statusViewData)
}
}

View File

@ -165,7 +165,7 @@ class ListStatusAccessibilityDelegate<T : IStatusViewData>(
forceFocus(host)
}
app.pachli.core.ui.R.id.action_collapse_cw -> {
statusActionListener.onExpandedChange(status, false)
statusActionListener.onExpandedChange(pachliAccountId, status, false)
interrupt()
}
app.pachli.core.ui.R.id.action_links -> showLinksDialog(host)
@ -192,7 +192,7 @@ class ListStatusAccessibilityDelegate<T : IStatusViewData>(
app.pachli.core.ui.R.id.action_more -> {
statusActionListener.onMore(host, status)
}
app.pachli.core.ui.R.id.action_show_anyway -> statusActionListener.clearWarningAction(status)
app.pachli.core.ui.R.id.action_show_anyway -> statusActionListener.clearWarningAction(pachliAccountId, status)
app.pachli.core.ui.R.id.action_edit_filter -> {
(recyclerView.findContainingViewHolder(host) as? FilterableStatusViewHolder<*>)?.matchedFilter?.let {
statusActionListener.onEditFilterById(pachliAccountId, it.id)

View File

@ -46,6 +46,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
@ -183,8 +184,8 @@ class ViewThreadViewModelTest {
)
val cachedTimelineRepository: CachedTimelineRepository = mock {
onBlocking { getStatusViewData(any()) } doReturn emptyMap()
onBlocking { getStatusTranslations(any()) } doReturn emptyMap()
onBlocking { getStatusViewData(anyLong(), any()) } doReturn emptyMap()
onBlocking { getStatusTranslations(anyLong(), any()) } doReturn emptyMap()
}
viewModel = ViewThreadViewModel(