diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/LoadMoreViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/LoadMoreViewHolder.kt new file mode 100644 index 000000000..79dcc24ba --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/LoadMoreViewHolder.kt @@ -0,0 +1,46 @@ +/* Copyright 2021 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 . */ +package com.keylesspalace.tusky.adapter + +import androidx.recyclerview.widget.RecyclerView +import com.keylesspalace.tusky.databinding.ItemLoadMoreBinding +import com.keylesspalace.tusky.interfaces.StatusActionListener +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.visible + +/** + * Placeholder for missing parts in timelines. + * + * Displays a "Load more" button to load the gap, or a + * circular progress bar if the missing page is being loaded. + */ +class LoadMoreViewHolder( + private val binding: ItemLoadMoreBinding, + listener: StatusActionListener +) : RecyclerView.ViewHolder(binding.root) { + + init { + binding.loadMoreButton.setOnClickListener { + binding.loadMoreButton.hide() + binding.loadMoreProgressBar.show() + listener.onLoadMore(bindingAdapterPosition) + } + } + + fun setup(loading: Boolean) { + binding.loadMoreButton.visible(!loading) + binding.loadMoreProgressBar.visible(loading) + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.kt index d64780a60..da67f33df 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.kt @@ -1,4 +1,4 @@ -/* Copyright 2021 Tusky Contributors +/* Copyright 2025 Tusky Contributors * * This file is a part of Tusky. * @@ -12,35 +12,40 @@ * * You should have received a copy of the GNU General Public License along with Tusky; if not, * see . */ + package com.keylesspalace.tusky.adapter +import android.view.ViewGroup +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePaddingRelative import androidx.recyclerview.widget.RecyclerView -import com.keylesspalace.tusky.databinding.ItemStatusPlaceholderBinding -import com.keylesspalace.tusky.interfaces.StatusActionListener -import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.databinding.ItemPlaceholderBinding import com.keylesspalace.tusky.util.visible -/** - * Placeholder for missing parts in timelines. - * - * Displays a "Load more" button to load the gap, or a - * circular progress bar if the missing page is being loaded. - */ class PlaceholderViewHolder( - private val binding: ItemStatusPlaceholderBinding, - listener: StatusActionListener + binding: ItemPlaceholderBinding, + mode: Mode, ) : RecyclerView.ViewHolder(binding.root) { - init { - binding.loadMoreButton.setOnClickListener { - binding.loadMoreButton.hide() - binding.loadMoreProgressBar.show() - listener.onLoadMore(bindingAdapterPosition) + val res = binding.root.context.resources + binding.topPlaceholder.visible(mode != Mode.STATUS) + binding.reblogButtonPlaceholder.visible(mode != Mode.CONVERSATION) + if (mode == Mode.NOTIFICATION) { + binding.topPlaceholder.updatePaddingRelative( + start = res.getDimensionPixelSize(R.dimen.status_info_padding_large) + ) + } + if (mode == Mode.CONVERSATION) { + binding.moreButtonPlaceHolder.updateLayoutParams { + marginEnd = res.getDimensionPixelSize(R.dimen.conversation_placeholder_more_button_inset) + } } } - fun setup(loading: Boolean) { - binding.loadMoreButton.visible(!loading) - binding.loadMoreProgressBar.visible(loading) + enum class Mode { + STATUS, + NOTIFICATION, + CONVERSATION } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationPagingAdapter.kt similarity index 64% rename from app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationAdapter.kt rename to app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationPagingAdapter.kt index 184ff1745..97ad9d5fb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationPagingAdapter.kt @@ -19,15 +19,18 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.adapter.PlaceholderViewHolder import com.keylesspalace.tusky.adapter.StatusBaseViewHolder +import com.keylesspalace.tusky.databinding.ItemPlaceholderBinding import com.keylesspalace.tusky.interfaces.StatusActionListener import com.keylesspalace.tusky.util.StatusDisplayOptions -class ConversationAdapter( +class ConversationPagingAdapter( private var statusDisplayOptions: StatusDisplayOptions, private val listener: StatusActionListener -) : PagingDataAdapter(CONVERSATION_COMPARATOR) { +) : PagingDataAdapter(CONVERSATION_COMPARATOR) { var mediaPreviewEnabled: Boolean get() = statusDisplayOptions.mediaPreviewEnabled @@ -37,25 +40,42 @@ 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(view, statusDisplayOptions, listener) + override fun getItemViewType(position: Int): Int { + return if (getItem(position) == null) { + VIEW_TYPE_PLACEHOLDER + } else { + VIEW_TYPE_CONVERSATION + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + return if (viewType == VIEW_TYPE_CONVERSATION) { + ConversationViewHolder(layoutInflater.inflate(R.layout.item_conversation, parent, false), statusDisplayOptions, listener) + } else { + PlaceholderViewHolder( + ItemPlaceholderBinding.inflate(layoutInflater, parent, false), + mode = PlaceholderViewHolder.Mode.CONVERSATION + ) + } } - override fun onBindViewHolder(holder: ConversationViewHolder, position: Int) { + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { onBindViewHolder(holder, position, emptyList()) } - override fun onBindViewHolder(holder: ConversationViewHolder, position: Int, payloads: List) { - getItem(position)?.let { conversationViewData -> + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List) { + val conversationViewData = getItem(position) + if (holder is ConversationViewHolder && conversationViewData != null) { holder.setupWithConversation(conversationViewData, payloads) } } companion object { - val CONVERSATION_COMPARATOR = object : DiffUtil.ItemCallback() { + private const val VIEW_TYPE_PLACEHOLDER = 0 + private const val VIEW_TYPE_CONVERSATION = 1 + + private val CONVERSATION_COMPARATOR = object : DiffUtil.ItemCallback() { override fun areItemsTheSame( oldItem: ConversationViewData, newItem: ConversationViewData diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt index ec56ebff2..3d7171d16 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt @@ -77,7 +77,7 @@ class ConversationsFragment : private val binding by viewBinding(FragmentTimelineBinding::bind) - private var adapter: ConversationAdapter? = null + private var adapter: ConversationPagingAdapter? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) @@ -98,7 +98,7 @@ class ConversationsFragment : openSpoiler = accountManager.activeAccount!!.alwaysOpenSpoiler ) - val adapter = ConversationAdapter(statusDisplayOptions, this) + val adapter = ConversationPagingAdapter(statusDisplayOptions, this) this.adapter = adapter setupRecyclerView(adapter) @@ -213,7 +213,7 @@ class ConversationsFragment : } } - private fun setupRecyclerView(adapter: ConversationAdapter) { + private fun setupRecyclerView(adapter: ConversationPagingAdapter) { binding.recyclerView.ensureBottomPadding(fab = true) binding.recyclerView.setHasFixedSize(true) binding.recyclerView.layoutManager = LinearLayoutManager(context) @@ -372,7 +372,7 @@ class ConversationsFragment : .show() } - private fun onPreferenceChanged(adapter: ConversationAdapter, key: String) { + private fun onPreferenceChanged(adapter: ConversationPagingAdapter, key: String) { when (key) { PrefKeys.MEDIA_PREVIEW_ENABLED -> { val enabled = accountManager.activeAccount!!.mediaPreviewEnabled diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationTypeMappers.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationTypeMappers.kt index f455a0265..8bb6c2f02 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationTypeMappers.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationTypeMappers.kt @@ -15,7 +15,7 @@ package com.keylesspalace.tusky.components.notifications -import com.keylesspalace.tusky.components.timeline.Placeholder +import com.keylesspalace.tusky.components.timeline.LoadMorePlaceholder import com.keylesspalace.tusky.components.timeline.toAccount import com.keylesspalace.tusky.components.timeline.toStatus import com.keylesspalace.tusky.db.entity.NotificationDataEntity @@ -30,7 +30,7 @@ import com.keylesspalace.tusky.viewdata.NotificationViewData import com.keylesspalace.tusky.viewdata.StatusViewData import com.keylesspalace.tusky.viewdata.TranslationViewData -fun Placeholder.toNotificationEntity( +fun LoadMorePlaceholder.toNotificationEntity( tuskyAccountId: Long ) = NotificationEntity( id = this.id, @@ -93,7 +93,7 @@ fun NotificationDataEntity.toViewData( translation: TranslationViewData? = null ): NotificationViewData { if (type == null || account == null) { - return NotificationViewData.Placeholder(id = id, isLoading = loading) + return NotificationViewData.LoadMore(id = id, isLoading = loading) } return NotificationViewData.Concrete( diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingAdapter.kt index a3cdce87c..69f597ce4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingAdapter.kt @@ -23,16 +23,18 @@ import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R import com.keylesspalace.tusky.adapter.FilteredStatusViewHolder import com.keylesspalace.tusky.adapter.FollowRequestViewHolder +import com.keylesspalace.tusky.adapter.LoadMoreViewHolder import com.keylesspalace.tusky.adapter.PlaceholderViewHolder import com.keylesspalace.tusky.adapter.StatusBaseViewHolder import com.keylesspalace.tusky.databinding.ItemFollowBinding import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding +import com.keylesspalace.tusky.databinding.ItemLoadMoreBinding import com.keylesspalace.tusky.databinding.ItemModerationWarningNotificationBinding +import com.keylesspalace.tusky.databinding.ItemPlaceholderBinding import com.keylesspalace.tusky.databinding.ItemReportNotificationBinding import com.keylesspalace.tusky.databinding.ItemSeveredRelationshipNotificationBinding import com.keylesspalace.tusky.databinding.ItemStatusFilteredBinding import com.keylesspalace.tusky.databinding.ItemStatusNotificationBinding -import com.keylesspalace.tusky.databinding.ItemStatusPlaceholderBinding import com.keylesspalace.tusky.databinding.ItemUnknownNotificationBinding import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.entity.Notification @@ -80,6 +82,7 @@ class NotificationsPagingAdapter( override fun getItemViewType(position: Int): Int { return when (val notification = getItem(position)) { + is NotificationViewData.LoadMore -> VIEW_TYPE_LOAD_MORE is NotificationViewData.Concrete -> { when (notification.type) { Notification.Type.Mention, @@ -105,13 +108,17 @@ class NotificationsPagingAdapter( else -> VIEW_TYPE_UNKNOWN } } - else -> VIEW_TYPE_PLACEHOLDER + null -> VIEW_TYPE_PLACEHOLDER } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) return when (viewType) { + VIEW_TYPE_PLACEHOLDER -> PlaceholderViewHolder( + ItemPlaceholderBinding.inflate(inflater, parent, false), + mode = PlaceholderViewHolder.Mode.NOTIFICATION + ) VIEW_TYPE_STATUS -> StatusViewHolder( inflater.inflate(R.layout.item_status, parent, false), statusListener, @@ -137,8 +144,8 @@ class NotificationsPagingAdapter( statusListener, true ) - VIEW_TYPE_PLACEHOLDER -> PlaceholderViewHolder( - ItemStatusPlaceholderBinding.inflate(inflater, parent, false), + VIEW_TYPE_LOAD_MORE -> LoadMoreViewHolder( + ItemLoadMoreBinding.inflate(inflater, parent, false), statusListener ) VIEW_TYPE_REPORT -> ReportNotificationViewHolder( @@ -169,24 +176,25 @@ class NotificationsPagingAdapter( when (notification) { is NotificationViewData.Concrete -> (viewHolder as NotificationsViewHolder).bind(notification, payloads, statusDisplayOptions) - is NotificationViewData.Placeholder -> { - (viewHolder as PlaceholderViewHolder).setup(notification.isLoading) + is NotificationViewData.LoadMore -> { + (viewHolder as LoadMoreViewHolder).setup(notification.isLoading) } } } } companion object { - private const val VIEW_TYPE_STATUS = 0 - private const val VIEW_TYPE_STATUS_FILTERED = 1 - private const val VIEW_TYPE_STATUS_NOTIFICATION = 2 - private const val VIEW_TYPE_FOLLOW = 3 - private const val VIEW_TYPE_FOLLOW_REQUEST = 4 - private const val VIEW_TYPE_PLACEHOLDER = 5 - private const val VIEW_TYPE_REPORT = 6 - private const val VIEW_TYPE_SEVERED_RELATIONSHIP = 7 - private const val VIEW_TYPE_MODERATION_WARNING = 8 - private const val VIEW_TYPE_UNKNOWN = 9 + private const val VIEW_TYPE_PLACEHOLDER = 0 + private const val VIEW_TYPE_STATUS = 1 + private const val VIEW_TYPE_STATUS_FILTERED = 2 + private const val VIEW_TYPE_STATUS_NOTIFICATION = 3 + private const val VIEW_TYPE_FOLLOW = 4 + private const val VIEW_TYPE_FOLLOW_REQUEST = 5 + private const val VIEW_TYPE_LOAD_MORE = 6 + private const val VIEW_TYPE_REPORT = 7 + private const val VIEW_TYPE_SEVERED_RELATIONSHIP = 8 + private const val VIEW_TYPE_MODERATION_WARNING = 9 + private const val VIEW_TYPE_UNKNOWN = 10 val NotificationsDifferCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame( diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediator.kt index 57c6f0411..3ddb30fef 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediator.kt @@ -22,7 +22,7 @@ import androidx.paging.PagingState import androidx.paging.RemoteMediator import androidx.room.withTransaction import com.keylesspalace.tusky.components.systemnotifications.toTypes -import com.keylesspalace.tusky.components.timeline.Placeholder +import com.keylesspalace.tusky.components.timeline.LoadMorePlaceholder import com.keylesspalace.tusky.components.timeline.toEntity import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.db.AccountManager @@ -119,7 +119,7 @@ class NotificationsRemoteMediator( to guarantee the placeholder has an id that exists on the server as not all servers handle client generated ids as expected */ notificationsDao.insertNotification( - Placeholder(notifications.last().id, loading = false).toNotificationEntity(activeAccount.id) + LoadMorePlaceholder(notifications.last().id, loading = false).toNotificationEntity(activeAccount.id) ) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt index a557e9324..139f2d758 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt @@ -36,7 +36,7 @@ import com.keylesspalace.tusky.appstore.PreferenceChangedEvent import com.keylesspalace.tusky.components.preference.PreferencesFragment.ReadingOrder import com.keylesspalace.tusky.components.systemnotifications.NotificationChannelData import com.keylesspalace.tusky.components.systemnotifications.toTypes -import com.keylesspalace.tusky.components.timeline.Placeholder +import com.keylesspalace.tusky.components.timeline.LoadMorePlaceholder import com.keylesspalace.tusky.components.timeline.toEntity import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.components.timeline.viewmodel.TimelineViewModel @@ -312,7 +312,7 @@ class NotificationsViewModel @Inject constructor( val notificationsDao = db.notificationsDao() notificationsDao.insertNotification( - Placeholder(placeholderId, loading = true).toNotificationEntity( + LoadMorePlaceholder(placeholderId, loading = true).toNotificationEntity( accountId ) ) @@ -401,7 +401,7 @@ class NotificationsViewModel @Inject constructor( ReadingOrder.NEWEST_FIRST -> notifications.last().id } notificationsDao.insertNotification( - Placeholder( + LoadMorePlaceholder( idToConvert, loading = false ).toNotificationEntity(accountId) @@ -421,7 +421,7 @@ class NotificationsViewModel @Inject constructor( val activeAccount = accountManager.activeAccount!! db.notificationsDao() .insertNotification( - Placeholder(placeholderId, loading = false).toNotificationEntity(activeAccount.id) + LoadMorePlaceholder(placeholderId, loading = false).toNotificationEntity(activeAccount.id) ) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelinePagingAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelinePagingAdapter.kt index 0c401ba5a..d4a0ad194 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelinePagingAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelinePagingAdapter.kt @@ -22,11 +22,13 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R import com.keylesspalace.tusky.adapter.FilteredStatusViewHolder +import com.keylesspalace.tusky.adapter.LoadMoreViewHolder import com.keylesspalace.tusky.adapter.PlaceholderViewHolder import com.keylesspalace.tusky.adapter.StatusBaseViewHolder import com.keylesspalace.tusky.adapter.StatusViewHolder +import com.keylesspalace.tusky.databinding.ItemLoadMoreBinding +import com.keylesspalace.tusky.databinding.ItemPlaceholderBinding import com.keylesspalace.tusky.databinding.ItemStatusFilteredBinding -import com.keylesspalace.tusky.databinding.ItemStatusPlaceholderBinding import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.interfaces.StatusActionListener import com.keylesspalace.tusky.util.StatusDisplayOptions @@ -49,23 +51,29 @@ class TimelinePagingAdapter( stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY } - override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val inflater = LayoutInflater.from(viewGroup.context) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) return when (viewType) { + VIEW_TYPE_PLACEHOLDER -> { + PlaceholderViewHolder( + ItemPlaceholderBinding.inflate(inflater, parent, false), + mode = PlaceholderViewHolder.Mode.STATUS + ) + } VIEW_TYPE_STATUS_FILTERED -> { FilteredStatusViewHolder( - ItemStatusFilteredBinding.inflate(inflater, viewGroup, false), + ItemStatusFilteredBinding.inflate(inflater, parent, false), statusListener ) } - VIEW_TYPE_PLACEHOLDER -> { - PlaceholderViewHolder( - ItemStatusPlaceholderBinding.inflate(inflater, viewGroup, false), + VIEW_TYPE_LOAD_MORE -> { + LoadMoreViewHolder( + ItemLoadMoreBinding.inflate(inflater, parent, false), statusListener ) } else -> { - StatusViewHolder(inflater.inflate(R.layout.item_status, viewGroup, false)) + StatusViewHolder(inflater.inflate(R.layout.item_status, parent, false)) } } } @@ -80,8 +88,8 @@ class TimelinePagingAdapter( payloads: List ) { val viewData = getItem(position) - if (viewData is StatusViewData.Placeholder) { - val holder = viewHolder as PlaceholderViewHolder + if (viewData is StatusViewData.LoadMore) { + val holder = viewHolder as LoadMoreViewHolder holder.setup(viewData.isLoading) } else if (viewData is StatusViewData.Concrete) { if (viewData.filter?.action == Filter.Action.WARN) { @@ -102,21 +110,21 @@ class TimelinePagingAdapter( override fun getItemViewType(position: Int): Int { val viewData = getItem(position) - return if (viewData is StatusViewData.Placeholder) { - VIEW_TYPE_PLACEHOLDER - } else if (viewData?.filter?.action == Filter.Action.WARN) { - VIEW_TYPE_STATUS_FILTERED - } else { - VIEW_TYPE_STATUS + return when { + viewData == null -> VIEW_TYPE_PLACEHOLDER + viewData is StatusViewData.LoadMore -> VIEW_TYPE_LOAD_MORE + viewData.filter?.action == Filter.Action.WARN -> VIEW_TYPE_STATUS_FILTERED + else -> VIEW_TYPE_STATUS } } companion object { - private const val VIEW_TYPE_STATUS = 0 - private const val VIEW_TYPE_STATUS_FILTERED = 1 - private const val VIEW_TYPE_PLACEHOLDER = 2 + private const val VIEW_TYPE_PLACEHOLDER = 0 + private const val VIEW_TYPE_STATUS = 1 + private const val VIEW_TYPE_STATUS_FILTERED = 2 + private const val VIEW_TYPE_LOAD_MORE = 3 - val TimelineDifferCallback = object : DiffUtil.ItemCallback() { + private val TimelineDifferCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame( oldItem: StatusViewData, newItem: StatusViewData diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineTypeMappers.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineTypeMappers.kt index b7dcf6e46..701fe7901 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineTypeMappers.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineTypeMappers.kt @@ -26,7 +26,7 @@ import com.keylesspalace.tusky.viewdata.StatusViewData import com.keylesspalace.tusky.viewdata.TranslationViewData import java.util.Date -data class Placeholder( +data class LoadMorePlaceholder( val id: String, val loading: Boolean ) @@ -60,7 +60,7 @@ fun TimelineAccountEntity.toAccount(): TimelineAccount { ) } -fun Placeholder.toEntity(tuskyAccountId: Long): HomeTimelineEntity { +fun LoadMorePlaceholder.toEntity(tuskyAccountId: Long): HomeTimelineEntity { return HomeTimelineEntity( id = this.id, tuskyAccountId = tuskyAccountId, @@ -150,7 +150,7 @@ fun HomeTimelineData.toViewData( filter: Filter? = null, ): StatusViewData { if (this.account == null || this.status == null) { - return StatusViewData.Placeholder(this.id, loading) + return StatusViewData.LoadMore(this.id, loading) } val originalStatus = status.toStatus(account) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt index 3a099bd2c..d8e6b4a04 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt @@ -21,7 +21,7 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import androidx.room.withTransaction -import com.keylesspalace.tusky.components.timeline.Placeholder +import com.keylesspalace.tusky.components.timeline.LoadMorePlaceholder import com.keylesspalace.tusky.components.timeline.toEntity import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.db.AppDatabase @@ -113,7 +113,7 @@ class CachedTimelineRemoteMediator( to guarantee the placeholder has an id that exists on the server as not all servers handle client generated ids as expected */ timelineDao.insertHomeTimelineItem( - Placeholder(statuses.last().id, loading = false).toEntity(activeAccount.id) + LoadMorePlaceholder(statuses.last().id, loading = false).toEntity(activeAccount.id) ) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt index d2df03cea..c1bd36d8d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt @@ -32,7 +32,7 @@ import at.connyduck.calladapter.networkresult.onFailure import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.components.preference.PreferencesFragment.ReadingOrder.NEWEST_FIRST import com.keylesspalace.tusky.components.preference.PreferencesFragment.ReadingOrder.OLDEST_FIRST -import com.keylesspalace.tusky.components.timeline.Placeholder +import com.keylesspalace.tusky.components.timeline.LoadMorePlaceholder import com.keylesspalace.tusky.components.timeline.toEntity import com.keylesspalace.tusky.components.timeline.toStatus import com.keylesspalace.tusky.components.timeline.toViewData @@ -153,7 +153,7 @@ class CachedTimelineViewModel @Inject constructor( val accountDao = db.timelineAccountDao() timelineDao.insertHomeTimelineItem( - Placeholder(placeholderId, loading = true).toEntity(tuskyAccountId = accountId) + LoadMorePlaceholder(placeholderId, loading = true).toEntity(tuskyAccountId = accountId) ) val (idAbovePlaceholder, idBelowPlaceholder) = db.withTransaction { @@ -240,7 +240,7 @@ class CachedTimelineViewModel @Inject constructor( NEWEST_FIRST -> statuses.last().id } timelineDao.insertHomeTimelineItem( - Placeholder( + LoadMorePlaceholder( idToConvert, loading = false ).toEntity(accountId) @@ -259,7 +259,7 @@ class CachedTimelineViewModel @Inject constructor( Log.w(TAG, "failed loading statuses", e) val activeAccount = accountManager.activeAccount!! db.timelineDao() - .insertHomeTimelineItem(Placeholder(placeholderId, loading = false).toEntity(activeAccount.id)) + .insertHomeTimelineItem(LoadMorePlaceholder(placeholderId, loading = false).toEntity(activeAccount.id)) } override fun fullReload() { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt index 4207ac85e..b9d84e4c1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt @@ -103,7 +103,7 @@ class NetworkTimelineRemoteMediator( viewModel.statusData.addAll(0, data) if (insertPlaceholder) { - viewModel.statusData[statuses.size - 1] = StatusViewData.Placeholder(statuses.last().id, false) + viewModel.statusData[statuses.size - 1] = StatusViewData.LoadMore(statuses.last().id, false) } } else { val linkHeader = statusResponse.headers()["Link"] diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt index 647c762ea..60bd6c76d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt @@ -200,9 +200,9 @@ class NetworkTimelineViewModel @Inject constructor( viewModelScope.launch { try { val placeholderIndex = - statusData.indexOfFirst { it is StatusViewData.Placeholder && it.id == placeholderId } + statusData.indexOfFirst { it is StatusViewData.LoadMore && it.id == placeholderId } statusData[placeholderIndex] = - StatusViewData.Placeholder(placeholderId, isLoading = true) + StatusViewData.LoadMore(placeholderId, isLoading = true) val idAbovePlaceholder = statusData.getOrNull(placeholderIndex - 1)?.id @@ -258,7 +258,7 @@ class NetworkTimelineViewModel @Inject constructor( statusData.removeAll { status -> when (status) { - is StatusViewData.Placeholder -> lastId.isLessThan(status.id) && status.id.isLessThanOrEqual( + is StatusViewData.LoadMore -> lastId.isLessThan(status.id) && status.id.isLessThanOrEqual( firstId ) @@ -269,7 +269,7 @@ class NetworkTimelineViewModel @Inject constructor( } } else { data[data.size - 1] = - StatusViewData.Placeholder(statuses.last().id, isLoading = false) + StatusViewData.LoadMore(statuses.last().id, isLoading = false) } } @@ -288,8 +288,8 @@ class NetworkTimelineViewModel @Inject constructor( Log.w("NetworkTimelineVM", "failed loading statuses", e) val index = - statusData.indexOfFirst { it is StatusViewData.Placeholder && it.id == placeholderId } - statusData[index] = StatusViewData.Placeholder(placeholderId, isLoading = false) + statusData.indexOfFirst { it is StatusViewData.LoadMore && it.id == placeholderId } + statusData[index] = StatusViewData.LoadMore(placeholderId, isLoading = false) currentSource?.invalidate() } diff --git a/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.kt b/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.kt index ed53bf603..873e20653 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.kt @@ -25,7 +25,7 @@ sealed class NotificationViewData { abstract val id: String abstract fun asStatusOrNull(): StatusViewData.Concrete? - abstract fun asPlaceholderOrNull(): Placeholder? + abstract fun asPlaceholderOrNull(): LoadMore? data class Concrete( override val id: String, @@ -41,7 +41,7 @@ sealed class NotificationViewData { override fun asPlaceholderOrNull() = null } - data class Placeholder( + data class LoadMore( override val id: String, val isLoading: Boolean ) : NotificationViewData() { diff --git a/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.kt b/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.kt index 0751a9846..eb16ebc98 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.kt @@ -38,7 +38,7 @@ sealed interface TranslationViewData { * Created by charlag on 11/07/2017. * * Class to represent data required to display either a notification or a placeholder. - * It is either a [StatusViewData.Concrete] or a [StatusViewData.Placeholder]. + * It is either a [StatusViewData.Concrete] or a [StatusViewData.LoadMore]. */ sealed class StatusViewData { abstract val id: String @@ -133,12 +133,12 @@ sealed class StatusViewData { } } - data class Placeholder( + data class LoadMore( override val id: String, val isLoading: Boolean ) : StatusViewData() fun asStatusOrNull() = this as? Concrete - fun asPlaceholderOrNull() = this as? Placeholder + fun asPlaceholderOrNull() = this as? LoadMore } diff --git a/app/src/main/res/drawable/text_placeholder.xml b/app/src/main/res/drawable/text_placeholder.xml new file mode 100644 index 000000000..9ab36c7f0 --- /dev/null +++ b/app/src/main/res/drawable/text_placeholder.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/item_conversation.xml b/app/src/main/res/layout/item_conversation.xml index 9d13f84f6..3cbc1cc40 100644 --- a/app/src/main/res/layout/item_conversation.xml +++ b/app/src/main/res/layout/item_conversation.xml @@ -314,7 +314,6 @@ style="@style/TuskyImageButton" android:layout_width="52dp" android:layout_height="48dp" - android:layout_marginEnd="8dp" android:contentDescription="@string/action_more" app:layout_constraintBottom_toBottomOf="@id/status_reply" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/item_follow.xml b/app/src/main/res/layout/item_follow.xml index bc11923a3..d93a7459a 100644 --- a/app/src/main/res/layout/item_follow.xml +++ b/app/src/main/res/layout/item_follow.xml @@ -13,11 +13,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:drawablePadding="10dp" + android:drawablePadding="@dimen/status_info_drawable_padding_large" android:ellipsize="end" android:gravity="center_vertical" android:maxLines="1" - android:paddingStart="28dp" + android:paddingStart="@dimen/status_info_padding_large" android:paddingEnd="0dp" android:textColor="?android:textColorTertiary" android:textSize="?attr/status_text_medium" diff --git a/app/src/main/res/layout/item_follow_request.xml b/app/src/main/res/layout/item_follow_request.xml index d4120f4b8..377acf963 100644 --- a/app/src/main/res/layout/item_follow_request.xml +++ b/app/src/main/res/layout/item_follow_request.xml @@ -13,11 +13,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:drawablePadding="10dp" + android:drawablePadding="@dimen/status_info_drawable_padding_large" android:ellipsize="end" android:gravity="center_vertical" android:maxLines="1" - android:paddingStart="28dp" + android:paddingStart="@dimen/status_info_padding_large" android:textColor="?android:textColorSecondary" android:textSize="?attr/status_text_medium" app:drawableStartCompat="@drawable/ic_person_add_24dp_mirrored_filled" diff --git a/app/src/main/res/layout/item_status_placeholder.xml b/app/src/main/res/layout/item_load_more.xml similarity index 100% rename from app/src/main/res/layout/item_status_placeholder.xml rename to app/src/main/res/layout/item_load_more.xml diff --git a/app/src/main/res/layout/item_placeholder.xml b/app/src/main/res/layout/item_placeholder.xml new file mode 100644 index 000000000..ec7649abc --- /dev/null +++ b/app/src/main/res/layout/item_placeholder.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_report_notification.xml b/app/src/main/res/layout/item_report_notification.xml index 341845354..d6aba535f 100644 --- a/app/src/main/res/layout/item_report_notification.xml +++ b/app/src/main/res/layout/item_report_notification.xml @@ -15,11 +15,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:drawablePadding="10dp" + android:drawablePadding="@dimen/status_info_drawable_padding_large" android:ellipsize="end" android:gravity="center_vertical" android:maxLines="1" - android:paddingStart="28dp" + android:paddingStart="@dimen/status_info_padding_large" android:paddingEnd="0dp" android:textColor="?android:textColorSecondary" android:textSize="?attr/status_text_medium" diff --git a/app/src/main/res/layout/item_status_notification.xml b/app/src/main/res/layout/item_status_notification.xml index ae1a712d4..1d5a80634 100644 --- a/app/src/main/res/layout/item_status_notification.xml +++ b/app/src/main/res/layout/item_status_notification.xml @@ -15,11 +15,11 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginBottom="6dp" - android:drawablePadding="10dp" + android:drawablePadding="@dimen/status_info_drawable_padding_large" android:ellipsize="end" android:gravity="center_vertical" android:maxLines="1" - android:paddingStart="28dp" + android:paddingStart="@dimen/status_info_padding_large" android:paddingEnd="0dp" android:textColor="?android:textColorSecondary" android:textSize="?attr/status_text_medium" diff --git a/app/src/main/res/layout/item_unknown_notification.xml b/app/src/main/res/layout/item_unknown_notification.xml index 923efb3a7..b1732d8ba 100644 --- a/app/src/main/res/layout/item_unknown_notification.xml +++ b/app/src/main/res/layout/item_unknown_notification.xml @@ -14,11 +14,11 @@ android:paddingBottom="14dp"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:drawablePadding="10dp" + android:drawablePadding="@dimen/status_info_drawable_padding_large" android:ellipsize="end" android:gravity="center_vertical" android:maxLines="1" - android:paddingStart="28dp" + android:paddingStart="@dimen/status_info_padding_large" android:paddingEnd="0dp" android:textColor="?android:textColorSecondary" android:textSize="?attr/status_text_medium" diff --git a/app/src/main/res/values-night/theme_colors.xml b/app/src/main/res/values-night/theme_colors.xml index 1c0ce3c0a..41ebaaf7a 100644 --- a/app/src/main/res/values-night/theme_colors.xml +++ b/app/src/main/res/values-night/theme_colors.xml @@ -37,6 +37,8 @@ #CC444B5D + @color/tusky_grey_40 + @color/tusky_green_dark @color/tusky_red diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 6acbcbe5f..2f44de2e0 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -42,6 +42,7 @@ + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 768cd25ad..5a9b285dc 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -98,4 +98,6 @@ 64dp + 14dp + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e6997f5a7..dfadb37c9 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -115,6 +115,8 @@ @style/TuskySearchViewStyle @color/toolbarIconBackgroundColor + @color/placeholderColor +