refactor: Remove `TabData` type (#576)

`TabData` recorded the type of the timeline the user had added to a tab.
`TimelineKind` is another type that records general information about
configured timelines, with identical properties.

There's no need for both, so remove `TabData` and use `TimelineKind` in
its place.

`TimelineKind` is itself mis-named; it's not just the timeline's kind
but also holds data necessary to display that timeline (e.g., the list
ID if it's a `.UserList`, or the hashtags if it's a `.Hashtags`) so
rename to `Timeline` to better reflect its usage. Move it to a new
`core.model` module.
This commit is contained in:
Nik Clayton 2024-03-30 23:27:25 +01:00 committed by GitHub
parent 09ca6ae0a8
commit 8257ded395
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 444 additions and 390 deletions

View File

@ -127,6 +127,7 @@ dependencies {
implementation(projects.core.data)
implementation(projects.core.database)
implementation(projects.core.designsystem)
implementation(projects.core.model)
implementation(projects.core.navigation)
implementation(projects.core.network)
implementation(projects.core.preferences)

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0)" variant="all" version="8.3.0">
<issues format="6" by="lint 8.3.1" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.1)" variant="all" version="8.3.1">
<issue
id="InvalidPackage"
@ -2617,7 +2617,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/util/ShareShortcutHelper.kt"
line="84"
line="96"
column="5"/>
</issue>

View File

@ -47,7 +47,7 @@
-keepclassmembers class app.pachli.core.database.model.ConversationAccountEntity { *; }
-keepclassmembers class app.pachli.core.database.model.DraftAttachment { *; }
-keep class app.pachli.core.database.model.TabDataJsonAdapter { *; }
-keep class app.pachli.core.model.TimelineJsonAdapter { *; }
-keep enum app.pachli.core.database.model.DraftAttachment$Type {
public *;

View File

@ -79,9 +79,9 @@ import app.pachli.core.data.repository.Lists
import app.pachli.core.data.repository.ListsRepository
import app.pachli.core.data.repository.ListsRepository.Companion.compareByListTitle
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.database.model.TabData
import app.pachli.core.designsystem.EmbeddedFontFamily
import app.pachli.core.designsystem.R as DR
import app.pachli.core.model.Timeline
import app.pachli.core.navigation.AboutActivityIntent
import app.pachli.core.navigation.AccountActivityIntent
import app.pachli.core.navigation.AccountListActivityIntent
@ -369,7 +369,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
refreshMainDrawerItems(addSearchButton = hideTopToolbar)
// Any lists in tabs might have changed titles, update those
if (lists is Lists.Loaded && tabAdapter.tabs.any { it.tabData is TabData.UserList }) {
if (lists is Lists.Loaded && tabAdapter.tabs.any { it.timeline is Timeline.UserList }) {
setupTabs(false)
}
}
@ -868,8 +868,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
tabLayoutMediator = TabLayoutMediator(activeTabLayout, binding.viewPager, true) {
tab: TabLayout.Tab, position: Int ->
tab.icon = AppCompatResources.getDrawable(this@MainActivity, tabs[position].icon)
tab.contentDescription = when (tabs[position].tabData) {
is TabData.UserList -> tabs[position].title(this@MainActivity)
tab.contentDescription = when (tabs[position].timeline) {
is Timeline.UserList -> tabs[position].title(this@MainActivity)
else -> getString(tabs[position].text)
}
}.also { it.attach() }
@ -880,12 +880,12 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
// - Tabs containing lists are compared by list ID, in case the list was renamed
// - Left-most tab
val position = if (selectNotificationTab) {
tabs.indexOfFirst { it.tabData is TabData.Notifications }
tabs.indexOfFirst { it.timeline is Timeline.Notifications }
} else {
previousTab?.let {
tabs.indexOfFirst {
if (it.tabData is TabData.UserList && previousTab.tabData is TabData.UserList) {
it.tabData.listId == previousTab.tabData.listId
if (it.timeline is Timeline.UserList && previousTab.timeline is Timeline.UserList) {
it.timeline.listId == previousTab.timeline.listId
} else {
it == previousTab
}

View File

@ -28,6 +28,7 @@ import app.pachli.components.timeline.TimelineFragment
import app.pachli.core.activity.BottomSheetActivity
import app.pachli.core.common.extensions.viewBinding
import app.pachli.core.common.util.unsafeLazy
import app.pachli.core.model.Timeline
import app.pachli.core.navigation.ComposeActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.navigation.StatusListActivityIntent
@ -36,7 +37,6 @@ import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_FILTERS_SERVER
import app.pachli.core.network.model.Filter
import app.pachli.core.network.model.FilterContext
import app.pachli.core.network.model.FilterV1
import app.pachli.core.network.model.TimelineKind
import app.pachli.databinding.ActivityStatuslistBinding
import app.pachli.interfaces.ActionButtonActivity
import app.pachli.interfaces.AppBarLayoutHost
@ -66,7 +66,7 @@ class StatusListActivity : BottomSheetActivity(), AppBarLayoutHost, ActionButton
lateinit var serverRepository: ServerRepository
private val binding: ActivityStatuslistBinding by viewBinding(ActivityStatuslistBinding::inflate)
private lateinit var timelineKind: TimelineKind
private lateinit var timeline: Timeline
override val appBarLayout: AppBarLayout
get() = binding.includedToolbar.appbar
@ -94,16 +94,16 @@ class StatusListActivity : BottomSheetActivity(), AppBarLayoutHost, ActionButton
setSupportActionBar(binding.includedToolbar.toolbar)
timelineKind = StatusListActivityIntent.getKind(intent)
timeline = StatusListActivityIntent.getKind(intent)
val title = when (timelineKind) {
is TimelineKind.Favourites -> getString(R.string.title_favourites)
is TimelineKind.Bookmarks -> getString(R.string.title_bookmarks)
is TimelineKind.Tag -> {
hashtag = (timelineKind as TimelineKind.Tag).tags.first()
val title = when (timeline) {
is Timeline.Favourites -> getString(R.string.title_favourites)
is Timeline.Bookmarks -> getString(R.string.title_bookmarks)
is Timeline.Hashtags -> {
hashtag = (timeline as Timeline.Hashtags).tags.first()
getString(R.string.title_tag).format(hashtag)
}
is TimelineKind.UserList -> (timelineKind as TimelineKind.UserList).title
is Timeline.UserList -> (timeline as Timeline.UserList).title
else -> "Missing title!!!"
}
@ -115,14 +115,14 @@ class StatusListActivity : BottomSheetActivity(), AppBarLayoutHost, ActionButton
if (supportFragmentManager.findFragmentById(R.id.fragmentContainer) == null) {
supportFragmentManager.commit {
val fragment = TimelineFragment.newInstance(timelineKind)
val fragment = TimelineFragment.newInstance(timeline)
replace(R.id.fragmentContainer, fragment)
}
}
val composeIntent = when (timelineKind) {
is TimelineKind.Tag -> {
val tag = (timelineKind as TimelineKind.Tag).tags.first()
val composeIntent = when (timeline) {
is Timeline.Hashtags -> {
val tag = (timeline as Timeline.Hashtags).tags.first()
ComposeActivityIntent(
this,
ComposeOptions(
@ -131,9 +131,9 @@ class StatusListActivity : BottomSheetActivity(), AppBarLayoutHost, ActionButton
),
)
}
is TimelineKind.Bookmarks,
is TimelineKind.Favourites,
is TimelineKind.UserList,
is Timeline.Bookmarks,
is Timeline.Favourites,
is Timeline.UserList,
-> {
ComposeActivityIntent(this, ComposeOptions())
}
@ -150,7 +150,7 @@ class StatusListActivity : BottomSheetActivity(), AppBarLayoutHost, ActionButton
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val tag = hashtag
if (timelineKind is TimelineKind.Tag && tag != null) {
if (timeline is Timeline.Hashtags && tag != null) {
lifecycleScope.launch {
mastodonApi.tag(tag).fold(
{ tagEntity ->

View File

@ -46,8 +46,8 @@ import app.pachli.core.common.util.unsafeLazy
import app.pachli.core.data.repository.Lists
import app.pachli.core.data.repository.ListsRepository
import app.pachli.core.data.repository.ListsRepository.Companion.compareByListTitle
import app.pachli.core.database.model.TabData
import app.pachli.core.designsystem.R as DR
import app.pachli.core.model.Timeline
import app.pachli.core.navigation.ListActivityIntent
import app.pachli.core.network.model.MastoList
import app.pachli.core.network.retrofit.MastodonApi
@ -119,7 +119,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
MaterialDividerItemDecoration(this, MaterialDividerItemDecoration.VERTICAL),
)
addTabAdapter = TabAdapter(listOf(TabViewData.from(TabData.Direct)), true, this)
addTabAdapter = TabAdapter(listOf(TabViewData.from(Timeline.Conversations)), true, this)
binding.addTabRecyclerView.adapter = addTabAdapter
binding.addTabRecyclerView.layoutManager = LinearLayoutManager(this)
@ -182,12 +182,12 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
override fun onTabAdded(tab: TabViewData) {
toggleFab(false)
if (tab.tabData is TabData.Hashtag) {
if (tab.timeline is Timeline.Hashtags) {
showAddHashtagDialog()
return
}
if (tab.tabData is TabData.UserList) {
if (tab.timeline is Timeline.UserList) {
showSelectListDialog()
return
}
@ -205,14 +205,14 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
saveTabs()
}
override fun onActionChipClicked(tabData: TabData.Hashtag, tabPosition: Int) {
showAddHashtagDialog(tabData, tabPosition)
override fun onActionChipClicked(timelineHashtags: Timeline.Hashtags, tabPosition: Int) {
showAddHashtagDialog(timelineHashtags, tabPosition)
}
override fun onChipClicked(tabData: TabData.Hashtag, tabPosition: Int, chipPosition: Int) {
override fun onChipClicked(timeline: Timeline.Hashtags, tabPosition: Int, chipPosition: Int) {
currentTabs[tabPosition] = currentTabs[tabPosition].copy(
tabData = tabData.copy(
tags = tabData.tags.filterIndexed { i, _ -> i != chipPosition },
timeline = timeline.copy(
tags = timeline.tags.filterIndexed { i, _ -> i != chipPosition },
),
)
saveTabs()
@ -238,7 +238,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
onFabDismissedCallback.isEnabled = expand
}
private fun showAddHashtagDialog(tabData: TabData.Hashtag? = null, tabPosition: Int = 0) {
private fun showAddHashtagDialog(timeline: Timeline.Hashtags? = null, tabPosition: Int = 0) {
val frameLayout = FrameLayout(this)
val padding = Utils.dpToPx(this, 8)
frameLayout.updatePadding(left = padding, right = padding)
@ -254,13 +254,13 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_save) { _, _ ->
val input = editText.text.toString().trim()
if (tabData == null) {
val newTab = TabViewData.from(TabData.Hashtag(listOf(input)))
if (timeline == null) {
val newTab = TabViewData.from(Timeline.Hashtags(listOf(input)))
currentTabs.add(newTab)
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
} else {
currentTabs[tabPosition] = currentTabs[tabPosition].copy(
tabData = tabData.copy(tags = tabData.tags + input),
timeline = timeline.copy(tags = timeline.tags + input),
)
currentTabsAdapter.notifyItemChanged(tabPosition)
@ -298,7 +298,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
.setView(selectListBinding.root)
.setAdapter(adapter) { _, position ->
adapter.getItem(position)?.let { item ->
val newTab = TabViewData.from(TabData.UserList(item.id, item.title))
val newTab = TabViewData.from(Timeline.UserList(item.id, item.title))
currentTabs.add(newTab)
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
updateAvailableTabs()
@ -349,45 +349,45 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
private fun updateAvailableTabs() {
val addableTabs: MutableList<TabViewData> = mutableListOf()
val homeTab = TabViewData.from(TabData.Home)
val homeTab = TabViewData.from(Timeline.Home)
if (!currentTabs.contains(homeTab)) {
addableTabs.add(homeTab)
}
val notificationTab = TabViewData.from(TabData.Notifications)
val notificationTab = TabViewData.from(Timeline.Notifications)
if (!currentTabs.contains(notificationTab)) {
addableTabs.add(notificationTab)
}
val localTab = TabViewData.from(TabData.Local)
val localTab = TabViewData.from(Timeline.PublicLocal)
if (!currentTabs.contains(localTab)) {
addableTabs.add(localTab)
}
val federatedTab = TabViewData.from(TabData.Federated)
val federatedTab = TabViewData.from(Timeline.PublicFederated)
if (!currentTabs.contains(federatedTab)) {
addableTabs.add(federatedTab)
}
val directMessagesTab = TabViewData.from(TabData.Direct)
val directMessagesTab = TabViewData.from(Timeline.Conversations)
if (!currentTabs.contains(directMessagesTab)) {
addableTabs.add(directMessagesTab)
}
val trendingTagsTab = TabViewData.from(TabData.TrendingTags)
val trendingTagsTab = TabViewData.from(Timeline.TrendingHashtags)
if (!currentTabs.contains(trendingTagsTab)) {
addableTabs.add(trendingTagsTab)
}
val trendingLinksTab = TabViewData.from(TabData.TrendingLinks)
val trendingLinksTab = TabViewData.from(Timeline.TrendingLinks)
if (!currentTabs.contains(trendingLinksTab)) {
addableTabs.add(trendingLinksTab)
}
val trendingStatusesTab = TabViewData.from(TabData.TrendingStatuses)
val trendingStatusesTab = TabViewData.from(Timeline.TrendingStatuses)
if (!currentTabs.contains(trendingStatusesTab)) {
addableTabs.add(trendingStatusesTab)
}
val bookmarksTab = TabViewData.from(TabData.Bookmarks)
val bookmarksTab = TabViewData.from(Timeline.Bookmarks)
if (!currentTabs.contains(trendingTagsTab)) {
addableTabs.add(bookmarksTab)
}
addableTabs.add(TabViewData.from(TabData.Hashtag(emptyList())))
addableTabs.add(TabViewData.from(TabData.UserList("", "")))
addableTabs.add(TabViewData.from(Timeline.Hashtags(emptyList())))
addableTabs.add(TabViewData.from(Timeline.UserList("", "")))
addTabAdapter.updateData(addableTabs)
@ -405,7 +405,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
private fun saveTabs() {
accountManager.activeAccount?.let {
lifecycleScope.launch(Dispatchers.IO) {
it.tabPreferences = currentTabs.map { it.tabData }
it.tabPreferences = currentTabs.map { it.timeline }
accountManager.saveAccount(it)
}
}
@ -416,7 +416,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
super.onPause()
if (tabsChanged) {
lifecycleScope.launch {
eventHub.dispatch(MainTabsChangedEvent(currentTabs.map { it.tabData }))
eventHub.dispatch(MainTabsChangedEvent(currentTabs.map { it.timeline }))
}
}
}

View File

@ -26,20 +26,20 @@ import app.pachli.components.notifications.NotificationsFragment
import app.pachli.components.timeline.TimelineFragment
import app.pachli.components.trending.TrendingLinksFragment
import app.pachli.components.trending.TrendingTagsFragment
import app.pachli.core.database.model.TabData
import app.pachli.core.network.model.TimelineKind
import app.pachli.core.model.Timeline
/**
* Wrap a [TabData] with additional information to display a tab with that data.
* Wrap a [Timeline] with additional information to display a tab with that
* timeline.
*
* @param tabData wrapped [TabData]
* @param timeline wrapped [Timeline]
* @param text text to use for this tab when displayed in lists
* @param icon icon to use when displaying the tab
* @param fragment [Fragment] to display the tab's contents
* @param title title to display in the action bar if this tab is active
*/
data class TabViewData(
val tabData: TabData,
val timeline: Timeline,
@StringRes val text: Int,
@DrawableRes val icon: Int,
val fragment: () -> Fragment,
@ -51,70 +51,70 @@ data class TabViewData(
other as TabViewData
if (tabData != other.tabData) return false
if (timeline != other.timeline) return false
return true
}
override fun hashCode() = tabData.hashCode()
override fun hashCode() = timeline.hashCode()
companion object {
fun from(tabData: TabData) = when (tabData) {
TabData.Home -> TabViewData(
tabData = tabData,
fun from(timeline: Timeline) = when (timeline) {
Timeline.Home -> TabViewData(
timeline = timeline,
text = R.string.title_home,
icon = R.drawable.ic_home_24dp,
fragment = { TimelineFragment.newInstance(TimelineKind.Home) },
fragment = { TimelineFragment.newInstance(timeline) },
)
TabData.Notifications -> TabViewData(
tabData = tabData,
Timeline.Notifications -> TabViewData(
timeline = timeline,
text = R.string.title_notifications,
icon = R.drawable.ic_notifications_24dp,
fragment = { NotificationsFragment.newInstance() },
)
TabData.Local -> TabViewData(
tabData = tabData,
Timeline.PublicLocal -> TabViewData(
timeline = timeline,
text = R.string.title_public_local,
icon = R.drawable.ic_local_24dp,
fragment = { TimelineFragment.newInstance(TimelineKind.PublicLocal) },
fragment = { TimelineFragment.newInstance(timeline) },
)
TabData.Federated -> TabViewData(
tabData = tabData,
Timeline.PublicFederated -> TabViewData(
timeline = timeline,
text = R.string.title_public_federated,
icon = R.drawable.ic_public_24dp,
fragment = { TimelineFragment.newInstance(TimelineKind.PublicFederated) },
fragment = { TimelineFragment.newInstance(timeline) },
)
TabData.Direct -> TabViewData(
tabData = tabData,
Timeline.Conversations -> TabViewData(
timeline = timeline,
text = R.string.title_direct_messages,
icon = R.drawable.ic_reblog_direct_24dp,
fragment = { ConversationsFragment.newInstance() },
)
TabData.TrendingTags -> TabViewData(
tabData = tabData,
Timeline.TrendingHashtags -> TabViewData(
timeline = timeline,
text = R.string.title_public_trending_hashtags,
icon = R.drawable.ic_trending_up_24px,
fragment = { TrendingTagsFragment.newInstance() },
)
TabData.TrendingLinks -> TabViewData(
tabData = tabData,
Timeline.TrendingLinks -> TabViewData(
timeline = timeline,
text = R.string.title_public_trending_links,
icon = R.drawable.ic_trending_up_24px,
fragment = { TrendingLinksFragment.newInstance() },
)
TabData.TrendingStatuses -> TabViewData(
tabData = tabData,
Timeline.TrendingStatuses -> TabViewData(
timeline = timeline,
text = R.string.title_public_trending_statuses,
icon = R.drawable.ic_trending_up_24px,
fragment = { TimelineFragment.newInstance(TimelineKind.TrendingStatuses) },
fragment = { TimelineFragment.newInstance(timeline) },
)
is TabData.Hashtag -> TabViewData(
tabData = tabData,
is Timeline.Hashtags -> TabViewData(
timeline = timeline,
text = R.string.hashtags,
icon = R.drawable.ic_hashtag,
fragment = { TimelineFragment.newInstance(TimelineKind.Tag(tabData.tags)) },
fragment = { TimelineFragment.newInstance(timeline) },
title = { context ->
tabData.tags.joinToString(separator = " ") {
timeline.tags.joinToString(separator = " ") {
context.getString(
R.string.title_tag,
it,
@ -122,24 +122,23 @@ data class TabViewData(
}
},
)
is TabData.UserList -> TabViewData(
tabData = tabData,
is Timeline.UserList -> TabViewData(
timeline = timeline,
text = R.string.list,
icon = app.pachli.core.ui.R.drawable.ic_list,
fragment = {
TimelineFragment.newInstance(
TimelineKind.UserList(tabData.listId, tabData.title),
)
},
title = { tabData.title },
fragment = { TimelineFragment.newInstance(timeline) },
title = { timeline.title },
)
TabData.Bookmarks -> TabViewData(
tabData = tabData,
Timeline.Bookmarks -> TabViewData(
timeline = timeline,
text = R.string.title_bookmarks,
icon = R.drawable.ic_bookmark_active_24dp,
fragment = { TimelineFragment.newInstance(TimelineKind.Bookmarks) },
fragment = { TimelineFragment.newInstance(timeline) },
)
else -> throw IllegalArgumentException("unknown tab type: $tabData")
Timeline.Favourites -> throw IllegalArgumentException("can't add to tab: $timeline")
is Timeline.User.Pinned -> throw IllegalArgumentException("can't add to tab: $timeline")
is Timeline.User.Posts -> throw IllegalArgumentException("can't add to tab: $timeline")
is Timeline.User.Replies -> throw IllegalArgumentException("can't add to tab: $timeline")
}
}
}

View File

@ -26,8 +26,8 @@ import app.pachli.R
import app.pachli.TabViewData
import app.pachli.core.common.extensions.hide
import app.pachli.core.common.extensions.show
import app.pachli.core.database.model.TabData
import app.pachli.core.designsystem.R as DR
import app.pachli.core.model.Timeline
import app.pachli.core.ui.BindingHolder
import app.pachli.databinding.ItemTabPreferenceBinding
import app.pachli.databinding.ItemTabPreferenceSmallBinding
@ -39,8 +39,8 @@ interface ItemInteractionListener {
fun onTabRemoved(position: Int)
fun onStartDelete(viewHolder: RecyclerView.ViewHolder)
fun onStartDrag(viewHolder: RecyclerView.ViewHolder)
fun onActionChipClicked(tabData: TabData.Hashtag, tabPosition: Int)
fun onChipClicked(tabData: TabData.Hashtag, tabPosition: Int, chipPosition: Int)
fun onActionChipClicked(timelineHashtags: Timeline.Hashtags, tabPosition: Int)
fun onChipClicked(timelineHashtags: Timeline.Hashtags, tabPosition: Int, chipPosition: Int)
}
class TabAdapter(
@ -81,8 +81,8 @@ class TabAdapter(
} else {
val binding = holder.binding as ItemTabPreferenceBinding
if (tab.tabData is TabData.UserList) {
binding.textView.text = tab.tabData.title
if (tab.timeline is Timeline.UserList) {
binding.textView.text = tab.timeline.title
} else {
binding.textView.setText(tab.text)
}
@ -107,7 +107,7 @@ class TabAdapter(
(if (removeButtonEnabled) android.R.attr.textColorTertiary else DR.attr.textColorDisabled),
)
if (tab.tabData is TabData.Hashtag) {
if (tab.timeline is Timeline.Hashtags) {
binding.chipGroup.show()
/*
@ -115,7 +115,7 @@ class TabAdapter(
* The other dynamic chips are inserted in front of the actionChip.
* This code tries to reuse already added chips to reduce the number of Views created.
*/
tab.tabData.tags.forEachIndexed { i, arg ->
tab.timeline.tags.forEachIndexed { i, arg ->
val chip = binding.chipGroup.getChildAt(i).takeUnless { it.id == R.id.actionChip } as Chip?
?: Chip(context).apply {
@ -126,23 +126,23 @@ class TabAdapter(
chip.text = arg
if (tab.tabData.tags.size <= 1) {
if (tab.timeline.tags.size <= 1) {
chip.isCloseIconVisible = false
chip.setOnClickListener(null)
} else {
chip.isCloseIconVisible = true
chip.setOnClickListener {
listener.onChipClicked(tab.tabData, holder.bindingAdapterPosition, i)
listener.onChipClicked(tab.timeline, holder.bindingAdapterPosition, i)
}
}
}
while (binding.chipGroup.size - 1 > tab.tabData.tags.size) {
binding.chipGroup.removeViewAt(tab.tabData.tags.size)
while (binding.chipGroup.size - 1 > tab.timeline.tags.size) {
binding.chipGroup.removeViewAt(tab.timeline.tags.size)
}
binding.actionChip.setOnClickListener {
listener.onActionChipClicked(tab.tabData, holder.bindingAdapterPosition)
listener.onActionChipClicked(tab.timeline, holder.bindingAdapterPosition)
}
} else {
binding.chipGroup.hide()

View File

@ -1,6 +1,6 @@
package app.pachli.appstore
import app.pachli.core.database.model.TabData
import app.pachli.core.model.Timeline
import app.pachli.core.network.model.Account
import app.pachli.core.network.model.FilterContext
import app.pachli.core.network.model.Poll
@ -22,7 +22,7 @@ data object StatusScheduledEvent : Event
data class StatusEditedEvent(val originalId: String, val status: Status) : Event
data class ProfileEditedEvent(val newProfileData: Account) : Event
data class FilterChangedEvent(val filterContext: FilterContext) : Event
data class MainTabsChangedEvent(val newTabs: List<TabData>) : Event
data class MainTabsChangedEvent(val newTabs: List<Timeline>) : Event
data class PollVoteEvent(val statusId: String, val poll: Poll) : Event
data class DomainMuteEvent(val instance: String) : Event
data class AnnouncementReadEvent(val announcementId: String) : Event

View File

@ -22,7 +22,7 @@ import app.pachli.components.account.media.AccountMediaFragment
import app.pachli.components.timeline.TimelineFragment
import app.pachli.core.activity.CustomFragmentStateAdapter
import app.pachli.core.activity.RefreshableFragment
import app.pachli.core.network.model.TimelineKind
import app.pachli.core.model.Timeline
class AccountPagerAdapter(
activity: FragmentActivity,
@ -33,9 +33,9 @@ class AccountPagerAdapter(
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> TimelineFragment.newInstance(TimelineKind.User.Posts(accountId), false)
1 -> TimelineFragment.newInstance(TimelineKind.User.Replies(accountId), false)
2 -> TimelineFragment.newInstance(TimelineKind.User.Pinned(accountId), false)
0 -> TimelineFragment.newInstance(Timeline.User.Posts(accountId), false)
1 -> TimelineFragment.newInstance(Timeline.User.Replies(accountId), false)
2 -> TimelineFragment.newInstance(Timeline.User.Pinned(accountId), false)
3 -> AccountMediaFragment.newInstance(accountId)
else -> throw AssertionError("Page $position is out of AccountPagerAdapter bounds")
}

View File

@ -33,7 +33,7 @@ import app.pachli.core.database.model.StatusViewDataEntity
import app.pachli.core.database.model.TimelineStatusWithAccount
import app.pachli.core.database.model.TranslatedStatusEntity
import app.pachli.core.database.model.TranslationState
import app.pachli.core.network.model.TimelineKind
import app.pachli.core.model.Timeline
import app.pachli.core.network.model.Translation
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.util.EmptyPagingSource
@ -76,7 +76,7 @@ class CachedTimelineRepository @Inject constructor(
/** @return flow of Mastodon [TimelineStatusWithAccount], loaded in [pageSize] increments */
@OptIn(ExperimentalPagingApi::class, ExperimentalCoroutinesApi::class)
fun getStatusStream(
kind: TimelineKind,
kind: Timeline,
pageSize: Int = PAGE_SIZE,
initialKey: String? = null,
): Flow<PagingData<TimelineStatusWithAccount>> {

View File

@ -27,8 +27,8 @@ import app.pachli.components.timeline.viewmodel.NetworkTimelinePagingSource
import app.pachli.components.timeline.viewmodel.NetworkTimelineRemoteMediator
import app.pachli.components.timeline.viewmodel.PageCache
import app.pachli.core.accounts.AccountManager
import app.pachli.core.model.Timeline
import app.pachli.core.network.model.Status
import app.pachli.core.network.model.TimelineKind
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.util.getDomain
import javax.inject.Inject
@ -81,7 +81,7 @@ class NetworkTimelineRepository @Inject constructor(
@OptIn(ExperimentalPagingApi::class)
fun getStatusStream(
viewModelScope: CoroutineScope,
kind: TimelineKind,
kind: Timeline,
pageSize: Int = PAGE_SIZE,
initialKey: String? = null,
): Flow<PagingData<Status>> {

View File

@ -56,11 +56,11 @@ import app.pachli.core.common.extensions.show
import app.pachli.core.common.extensions.viewBinding
import app.pachli.core.common.extensions.visible
import app.pachli.core.database.model.TranslationState
import app.pachli.core.model.Timeline
import app.pachli.core.navigation.AccountListActivityIntent
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.network.model.Poll
import app.pachli.core.network.model.Status
import app.pachli.core.network.model.TimelineKind
import app.pachli.core.ui.BackgroundMessage
import app.pachli.core.ui.extensions.getErrorString
import app.pachli.databinding.FragmentTimelineBinding
@ -112,11 +112,11 @@ class TimelineFragment :
// If the navigation library was being used this would happen automatically, so this
// workaround can be removed when that change happens.
private val viewModel: TimelineViewModel by lazy {
if (timelineKind == TimelineKind.Home) {
if (timeline == Timeline.Home) {
viewModels<CachedTimelineViewModel>(
extrasProducer = {
MutableCreationExtras(defaultViewModelCreationExtras).apply {
set(DEFAULT_ARGS_KEY, TimelineViewModel.creationExtras(timelineKind))
set(DEFAULT_ARGS_KEY, TimelineViewModel.creationExtras(timeline))
}
},
).value
@ -124,7 +124,7 @@ class TimelineFragment :
viewModels<NetworkTimelineViewModel>(
extrasProducer = {
MutableCreationExtras(defaultViewModelCreationExtras).apply {
set(DEFAULT_ARGS_KEY, TimelineViewModel.creationExtras(timelineKind))
set(DEFAULT_ARGS_KEY, TimelineViewModel.creationExtras(timeline))
}
},
).value
@ -133,7 +133,7 @@ class TimelineFragment :
private val binding by viewBinding(FragmentTimelineBinding::bind)
private lateinit var timelineKind: TimelineKind
private lateinit var timeline: Timeline
private lateinit var adapter: TimelinePagingAdapter
@ -154,7 +154,7 @@ class TimelineFragment :
val arguments = requireArguments()
timelineKind = arguments.getParcelable(KIND_ARG)!!
timeline = arguments.getParcelable(KIND_ARG)!!
isSwipeToRefreshEnabled = arguments.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, true)
@ -466,7 +466,7 @@ class TimelineFragment :
PresentationState.PRESENTED -> {
if (adapter.itemCount == 0) {
binding.statusView.setup(BackgroundMessage.Empty())
if (timelineKind == TimelineKind.Home) {
if (timeline == Timeline.Home) {
binding.statusView.showHelp(R.string.help_empty_home)
}
binding.statusView.show()
@ -642,7 +642,7 @@ class TimelineFragment :
}
// Can only translate the home timeline at the moment
override fun canTranslate() = timelineKind == TimelineKind.Home
override fun canTranslate() = timeline == Timeline.Home
override fun onTranslate(statusViewData: StatusViewData) {
viewModel.accept(StatusAction.Translate(statusViewData))
@ -672,10 +672,10 @@ class TimelineFragment :
}
override fun onViewTag(tag: String) {
val timelineKind = viewModel.timelineKind
val timelineKind = viewModel.timeline
// If already viewing a tag page, then ignore any request to view that tag again.
if (timelineKind is TimelineKind.Tag && timelineKind.tags.contains(tag)) {
if ((timelineKind as? Timeline.Hashtags)?.tags?.contains(tag) == true) {
return
}
@ -683,10 +683,10 @@ class TimelineFragment :
}
override fun onViewAccount(id: String) {
val timelineKind = viewModel.timelineKind
val timelineKind = viewModel.timeline
// Ignore request to view the account page we're currently viewing
if (timelineKind is TimelineKind.User && timelineKind.id == id) {
if (timelineKind is Timeline.User && timelineKind.id == id) {
return
}
@ -703,21 +703,25 @@ class TimelineFragment :
* that have been made to it.
*/
private fun handleStatusSentOrEdit(status: Status) {
when (timelineKind) {
is TimelineKind.User.Pinned -> return
when (timeline) {
is Timeline.User.Pinned -> return
is TimelineKind.Home,
is TimelineKind.PublicFederated,
is TimelineKind.PublicLocal,
is Timeline.Home,
is Timeline.PublicFederated,
is Timeline.PublicLocal,
-> adapter.refresh()
is TimelineKind.User -> if (status.account.id == (timelineKind as TimelineKind.User).id) {
is Timeline.User -> if (status.account.id == (timeline as Timeline.User).id) {
adapter.refresh()
}
is TimelineKind.Bookmarks,
is TimelineKind.Favourites,
is TimelineKind.Tag,
is TimelineKind.TrendingStatuses,
is TimelineKind.UserList,
is Timeline.Bookmarks,
is Timeline.Favourites,
is Timeline.Hashtags,
is Timeline.TrendingStatuses,
is Timeline.UserList,
is Timeline.Conversations,
Timeline.Notifications,
Timeline.TrendingHashtags,
Timeline.TrendingLinks,
-> return
}
}
@ -727,10 +731,10 @@ class TimelineFragment :
}
private fun actionButtonPresent(): Boolean {
return viewModel.timelineKind !is TimelineKind.Tag &&
viewModel.timelineKind !is TimelineKind.Favourites &&
viewModel.timelineKind !is TimelineKind.Bookmarks &&
viewModel.timelineKind !is TimelineKind.TrendingStatuses &&
return viewModel.timeline !is Timeline.Hashtags &&
viewModel.timeline !is Timeline.Favourites &&
viewModel.timeline !is Timeline.Bookmarks &&
viewModel.timeline !is Timeline.TrendingStatuses &&
activity is ActionButtonActivity
}
@ -771,12 +775,12 @@ class TimelineFragment :
private const val ARG_ENABLE_SWIPE_TO_REFRESH = "enableSwipeToRefresh"
fun newInstance(
timelineKind: TimelineKind,
timeline: Timeline,
enableSwipeToRefresh: Boolean = true,
): TimelineFragment {
val fragment = TimelineFragment()
val arguments = Bundle(2)
arguments.putParcelable(KIND_ARG, timelineKind)
arguments.putParcelable(KIND_ARG, timeline)
arguments.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, enableSwipeToRefresh)
fragment.arguments = arguments
return fragment

View File

@ -82,12 +82,12 @@ class CachedTimelineViewModel @Inject constructor(
}.cachedIn(viewModelScope)
}
/** @return Flow of statuses that make up the timeline of [timelineKind] */
/** @return Flow of statuses that make up the timeline of [timeline] */
private fun getStatuses(
initialKey: String? = null,
): Flow<PagingData<StatusViewData>> {
Timber.d("getStatuses: kind: %s, initialKey: %s", timelineKind, initialKey)
return repository.getStatusStream(kind = timelineKind, initialKey = initialKey)
Timber.d("getStatuses: kind: %s, initialKey: %s", timeline, initialKey)
return repository.getStatusStream(kind = timeline, initialKey = initialKey)
.map { pagingData ->
pagingData
.map {

View File

@ -24,8 +24,8 @@ import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import app.pachli.BuildConfig
import app.pachli.core.accounts.AccountManager
import app.pachli.core.model.Timeline
import app.pachli.core.network.model.Status
import app.pachli.core.network.model.TimelineKind
import app.pachli.core.network.retrofit.MastodonApi
import java.io.IOException
import kotlinx.coroutines.CoroutineScope
@ -41,7 +41,7 @@ class NetworkTimelineRemoteMediator(
accountManager: AccountManager,
private val factory: InvalidatingPagingSourceFactory<String, Status>,
private val pageCache: PageCache,
private val timelineKind: TimelineKind,
private val timeline: Timeline,
) : RemoteMediator<String, Status>() {
private val activeAccount = accountManager.activeAccount!!
@ -116,7 +116,7 @@ class NetworkTimelineRemoteMediator(
Timber.d(
" Page %s complete for %s, now got %d pages",
loadType,
timelineKind,
timeline,
pageCache.size,
)
pageCache.debug()
@ -132,7 +132,7 @@ class NetworkTimelineRemoteMediator(
}
}
@Throws(IOException::class, HttpException::class)
@Throws(IOException::class, HttpException::class, IllegalStateException::class)
private suspend fun fetchStatusPageByKind(loadType: LoadType, key: String?, loadSize: Int): Response<List<Status>> {
val (maxId, minId) = when (loadType) {
// When refreshing fetch a page of statuses that are immediately *newer* than the key
@ -144,20 +144,20 @@ class NetworkTimelineRemoteMediator(
LoadType.PREPEND -> Pair(null, key)
}
return when (timelineKind) {
TimelineKind.Bookmarks -> api.bookmarks(maxId = maxId, minId = minId, limit = loadSize)
TimelineKind.Favourites -> api.favourites(maxId = maxId, minId = minId, limit = loadSize)
TimelineKind.Home -> api.homeTimeline(maxId = maxId, minId = minId, limit = loadSize)
TimelineKind.PublicFederated -> api.publicTimeline(local = false, maxId = maxId, minId = minId, limit = loadSize)
TimelineKind.PublicLocal -> api.publicTimeline(local = true, maxId = maxId, minId = minId, limit = loadSize)
TimelineKind.TrendingStatuses -> api.trendingStatuses()
is TimelineKind.Tag -> {
val firstHashtag = timelineKind.tags.first()
val additionalHashtags = timelineKind.tags.subList(1, timelineKind.tags.size)
return when (timeline) {
Timeline.Bookmarks -> api.bookmarks(maxId = maxId, minId = minId, limit = loadSize)
Timeline.Favourites -> api.favourites(maxId = maxId, minId = minId, limit = loadSize)
Timeline.Home -> api.homeTimeline(maxId = maxId, minId = minId, limit = loadSize)
Timeline.PublicFederated -> api.publicTimeline(local = false, maxId = maxId, minId = minId, limit = loadSize)
Timeline.PublicLocal -> api.publicTimeline(local = true, maxId = maxId, minId = minId, limit = loadSize)
Timeline.TrendingStatuses -> api.trendingStatuses()
is Timeline.Hashtags -> {
val firstHashtag = timeline.tags.first()
val additionalHashtags = timeline.tags.subList(1, timeline.tags.size)
api.hashtagTimeline(firstHashtag, additionalHashtags, null, maxId = maxId, minId = minId, limit = loadSize)
}
is TimelineKind.User.Pinned -> api.accountStatuses(
timelineKind.id,
is Timeline.User.Pinned -> api.accountStatuses(
timeline.id,
maxId = maxId,
minId = minId,
limit = loadSize,
@ -165,8 +165,8 @@ class NetworkTimelineRemoteMediator(
onlyMedia = null,
pinned = true,
)
is TimelineKind.User.Posts -> api.accountStatuses(
timelineKind.id,
is Timeline.User.Posts -> api.accountStatuses(
timeline.id,
maxId = maxId,
minId = minId,
limit = loadSize,
@ -174,8 +174,8 @@ class NetworkTimelineRemoteMediator(
onlyMedia = null,
pinned = null,
)
is TimelineKind.User.Replies -> api.accountStatuses(
timelineKind.id,
is Timeline.User.Replies -> api.accountStatuses(
timeline.id,
maxId = maxId,
minId = minId,
limit = loadSize,
@ -183,12 +183,13 @@ class NetworkTimelineRemoteMediator(
onlyMedia = null,
pinned = null,
)
is TimelineKind.UserList -> api.listTimeline(
timelineKind.id,
is Timeline.UserList -> api.listTimeline(
timeline.listId,
maxId = maxId,
minId = minId,
limit = loadSize,
)
else -> throw IllegalStateException("NetworkTimelineRemoteMediator does not support $timeline")
}
}
}

View File

@ -80,12 +80,12 @@ class NetworkTimelineViewModel @Inject constructor(
}.cachedIn(viewModelScope)
}
/** @return Flow of statuses that make up the timeline of [timelineKind] */
/** @return Flow of statuses that make up the timeline of [timeline] */
private fun getStatuses(
initialKey: String? = null,
): Flow<PagingData<StatusViewData>> {
Timber.d("getStatuses: kind: %s, initialKey: %s", timelineKind, initialKey)
return repository.getStatusStream(viewModelScope, kind = timelineKind, initialKey = initialKey)
Timber.d("getStatuses: kind: %s, initialKey: %s", timeline, initialKey)
return repository.getStatusStream(viewModelScope, kind = timeline, initialKey = initialKey)
.map { pagingData ->
pagingData.map {
modifiedViewData[it.id] ?: StatusViewData.from(

View File

@ -44,11 +44,11 @@ import app.pachli.appstore.UnfollowEvent
import app.pachli.components.timeline.FilterKind
import app.pachli.components.timeline.FiltersRepository
import app.pachli.core.accounts.AccountManager
import app.pachli.core.model.Timeline
import app.pachli.core.network.model.Filter
import app.pachli.core.network.model.FilterContext
import app.pachli.core.network.model.Poll
import app.pachli.core.network.model.Status
import app.pachli.core.network.model.TimelineKind
import app.pachli.core.preferences.PrefKeys
import app.pachli.core.preferences.SharedPreferencesRepository
import app.pachli.network.FilterModel
@ -303,7 +303,7 @@ abstract class TimelineViewModel(
viewModelScope.launch { uiAction.emit(action) }
}
val timelineKind: TimelineKind = savedStateHandle.get<TimelineKind>(TIMELINE_KIND_TAG)!!
val timeline: Timeline = savedStateHandle.get<Timeline>(TIMELINE_TAG)!!
private var filterRemoveReplies = false
private var filterRemoveReblogs = false
@ -396,7 +396,7 @@ abstract class TimelineViewModel(
initialValue = UiState(showFabWhileScrolling = !sharedPreferencesRepository.getBoolean(PrefKeys.FAB_HIDE, false)),
)
if (timelineKind is TimelineKind.Home) {
if (timeline is Timeline.Home) {
// Note the variable is "true if filter" but the underlying preference/settings text is "true if show"
filterRemoveReplies =
!sharedPreferencesRepository.getBoolean(PrefKeys.TAB_FILTER_HOME_REPLIES, true)
@ -407,7 +407,7 @@ abstract class TimelineViewModel(
}
// Save the visible status ID (if it's the home timeline)
if (timelineKind == TimelineKind.Home) {
if (timeline == Timeline.Home) {
viewModelScope.launch {
uiAction
.filterIsInstance<InfallibleUiAction.SaveVisibleId>()
@ -426,7 +426,7 @@ abstract class TimelineViewModel(
uiAction
.filterIsInstance<InfallibleUiAction.LoadNewest>()
.collectLatest {
if (timelineKind == TimelineKind.Home) {
if (timeline == Timeline.Home) {
activeAccount.lastVisibleHomeTimelineStatusId = null
accountManager.saveAccount(activeAccount)
}
@ -449,7 +449,7 @@ abstract class TimelineViewModel(
}
fun getInitialKey(): String? {
if (timelineKind != TimelineKind.Home) {
if (timeline != Timeline.Home) {
return null
}
@ -521,7 +521,7 @@ abstract class TimelineViewModel(
/** Updates the current set of filters if filter-related preferences change */
private fun updateFiltersFromPreferences() = eventHub.events
.filterIsInstance<FilterChangedEvent>()
.filter { filterContextMatchesKind(timelineKind, listOf(it.filterContext)) }
.filter { filterContextMatchesKind(timeline, listOf(it.filterContext)) }
.map {
getFilters()
Timber.d("Reload because FilterChangedEvent")
@ -534,10 +534,11 @@ abstract class TimelineViewModel(
viewModelScope.launch {
Timber.d("getFilters()")
try {
val filterContext = FilterContext.from(timelineKind)
filterModel = when (val filters = filtersRepository.getFilters()) {
is FilterKind.V1 -> FilterModel(filterContext, filters.filters)
is FilterKind.V2 -> FilterModel(filterContext)
FilterContext.from(timeline)?.let { filterContext ->
filterModel = when (val filters = filtersRepository.getFilters()) {
is FilterKind.V1 -> FilterModel(filterContext, filters.filters)
is FilterKind.V2 -> FilterModel(filterContext)
}
}
} catch (throwable: Throwable) {
Timber.d(throwable, "updateFilter(): Error fetching filters")
@ -552,7 +553,7 @@ abstract class TimelineViewModel(
PrefKeys.TAB_FILTER_HOME_REPLIES -> {
val filter = sharedPreferencesRepository.getBoolean(PrefKeys.TAB_FILTER_HOME_REPLIES, true)
val oldRemoveReplies = filterRemoveReplies
filterRemoveReplies = timelineKind is TimelineKind.Home && !filter
filterRemoveReplies = timeline is Timeline.Home && !filter
if (oldRemoveReplies != filterRemoveReplies) {
Timber.d("Reload because TAB_FILTER_HOME_REPLIES changed")
reloadKeepingReadingPosition()
@ -561,7 +562,7 @@ abstract class TimelineViewModel(
PrefKeys.TAB_FILTER_HOME_BOOSTS -> {
val filter = sharedPreferencesRepository.getBoolean(PrefKeys.TAB_FILTER_HOME_BOOSTS, true)
val oldRemoveReblogs = filterRemoveReblogs
filterRemoveReblogs = timelineKind is TimelineKind.Home && !filter
filterRemoveReblogs = timeline is Timeline.Home && !filter
if (oldRemoveReblogs != filterRemoveReblogs) {
Timber.d("Reload because TAB_FILTER_HOME_BOOSTS changed")
reloadKeepingReadingPosition()
@ -570,7 +571,7 @@ abstract class TimelineViewModel(
PrefKeys.TAB_SHOW_HOME_SELF_BOOSTS -> {
val filter = sharedPreferencesRepository.getBoolean(PrefKeys.TAB_SHOW_HOME_SELF_BOOSTS, true)
val oldRemoveSelfReblogs = filterRemoveSelfReblogs
filterRemoveSelfReblogs = timelineKind is TimelineKind.Home && !filter
filterRemoveSelfReblogs = timeline is Timeline.Home && !filter
if (oldRemoveSelfReblogs != filterRemoveSelfReblogs) {
Timber.d("Reload because TAB_SHOW_SOME_SELF_BOOSTS changed")
reloadKeepingReadingPosition()
@ -590,31 +591,31 @@ abstract class TimelineViewModel(
reloadKeepingReadingPosition()
}
is UnfollowEvent -> {
if (timelineKind is TimelineKind.Home) {
if (timeline is Timeline.Home) {
val id = event.accountId
removeAllByAccountId(id)
}
}
is BlockEvent -> {
if (timelineKind !is TimelineKind.User) {
if (timeline !is Timeline.User) {
val id = event.accountId
removeAllByAccountId(id)
}
}
is MuteEvent -> {
if (timelineKind !is TimelineKind.User) {
if (timeline !is Timeline.User) {
val id = event.accountId
removeAllByAccountId(id)
}
}
is DomainMuteEvent -> {
if (timelineKind !is TimelineKind.User) {
if (timeline !is Timeline.User) {
val instance = event.instance
removeAllByInstance(instance)
}
}
is StatusDeletedEvent -> {
if (timelineKind !is TimelineKind.User) {
if (timeline !is Timeline.User) {
removeStatusWithId(event.statusId)
}
}
@ -626,18 +627,18 @@ abstract class TimelineViewModel(
/** Tag for the timelineKind in `savedStateHandle` */
@VisibleForTesting(VisibleForTesting.PRIVATE)
const val TIMELINE_KIND_TAG = "timelineKind"
const val TIMELINE_TAG = "timeline"
/** Create extras for this view model */
fun creationExtras(timelineKind: TimelineKind) = bundleOf(
TIMELINE_KIND_TAG to timelineKind,
fun creationExtras(timeline: Timeline) = bundleOf(
TIMELINE_TAG to timeline,
)
fun filterContextMatchesKind(
timelineKind: TimelineKind,
timeline: Timeline,
filterContext: List<FilterContext>,
): Boolean {
return filterContext.contains(FilterContext.from(timelineKind))
return filterContext.contains(FilterContext.from(timeline))
}
}
}

View File

@ -30,7 +30,7 @@ import app.pachli.R
import app.pachli.components.timeline.TimelineFragment
import app.pachli.core.activity.BottomSheetActivity
import app.pachli.core.common.extensions.viewBinding
import app.pachli.core.network.model.TimelineKind
import app.pachli.core.model.Timeline
import app.pachli.core.ui.extensions.reduceSwipeSensitivity
import app.pachli.databinding.ActivityTrendingBinding
import app.pachli.interfaces.AppBarLayoutHost
@ -93,7 +93,7 @@ class TrendingFragmentAdapter(val activity: FragmentActivity) : FragmentStateAda
return when (position) {
0 -> TrendingTagsFragment.newInstance()
1 -> TrendingLinksFragment.newInstance()
2 -> TimelineFragment.newInstance(TimelineKind.TrendingStatuses)
2 -> TimelineFragment.newInstance(Timeline.TrendingStatuses)
else -> throw IllegalStateException()
}
}

View File

@ -31,8 +31,8 @@ import app.pachli.components.notifications.createNotificationChannelsForAccount
import app.pachli.components.notifications.makeNotification
import app.pachli.core.accounts.AccountManager
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.database.model.TabData
import app.pachli.core.database.model.defaultTabs
import app.pachli.core.model.Timeline
import app.pachli.core.navigation.AccountListActivityIntent
import app.pachli.core.network.model.Account
import app.pachli.core.network.model.Notification
@ -154,7 +154,7 @@ class MainActivityTest {
rule.launch(intent)
rule.getScenario().onActivity {
val currentTab = it.findViewById<ViewPager2>(R.id.viewPager).currentItem
val notificationTab = defaultTabs().indexOfFirst { it is TabData.Notifications }
val notificationTab = defaultTabs().indexOfFirst { it is Timeline.Notifications }
assertEquals(currentTab, notificationTab)
}
}

View File

@ -24,8 +24,8 @@ import app.pachli.appstore.EventHub
import app.pachli.components.timeline.viewmodel.CachedTimelineViewModel
import app.pachli.components.timeline.viewmodel.TimelineViewModel
import app.pachli.core.accounts.AccountManager
import app.pachli.core.model.Timeline
import app.pachli.core.network.model.Account
import app.pachli.core.network.model.TimelineKind
import app.pachli.core.network.model.nodeinfo.UnvalidatedJrd
import app.pachli.core.network.model.nodeinfo.UnvalidatedNodeInfo
import app.pachli.core.network.retrofit.MastodonApi
@ -155,7 +155,7 @@ abstract class CachedTimelineViewModelTestBase {
timelineCases = mock()
viewModel = CachedTimelineViewModel(
SavedStateHandle(mapOf(TimelineViewModel.TIMELINE_KIND_TAG to TimelineKind.Home)),
SavedStateHandle(mapOf(TimelineViewModel.TIMELINE_TAG to Timeline.Home)),
cachedTimelineRepository,
timelineCases,
eventHub,

View File

@ -18,7 +18,7 @@
package app.pachli.components.timeline
import app.pachli.components.timeline.viewmodel.InfallibleUiAction
import app.pachli.core.network.model.TimelineKind
import app.pachli.core.model.Timeline
import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.test.runTest
@ -32,7 +32,7 @@ class CachedTimelineViewModelTestVisibleId : CachedTimelineViewModelTestBase() {
// Given
assertThat(accountManager.activeAccount?.lastVisibleHomeTimelineStatusId)
.isNull()
assertThat(viewModel.timelineKind).isEqualTo(TimelineKind.Home)
assertThat(viewModel.timeline).isEqualTo(Timeline.Home)
// When
viewModel.accept(InfallibleUiAction.SaveVisibleId("1234"))

View File

@ -30,8 +30,8 @@ import app.pachli.components.timeline.viewmodel.Page
import app.pachli.components.timeline.viewmodel.PageCache
import app.pachli.core.accounts.AccountManager
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.model.Timeline
import app.pachli.core.network.model.Status
import app.pachli.core.network.model.TimelineKind
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import okhttp3.Headers
@ -79,7 +79,7 @@ class NetworkTimelineRemoteMediatorTest {
accountManager = accountManager,
factory = pagingSourceFactory,
pageCache = PageCache(),
timelineKind = TimelineKind.Home,
timeline = Timeline.Home,
)
// When
@ -101,7 +101,7 @@ class NetworkTimelineRemoteMediatorTest {
accountManager,
factory = pagingSourceFactory,
pageCache = PageCache(),
timelineKind = TimelineKind.Home,
timeline = Timeline.Home,
)
// When
@ -131,7 +131,7 @@ class NetworkTimelineRemoteMediatorTest {
accountManager = accountManager,
factory = pagingSourceFactory,
pageCache = pages,
timelineKind = TimelineKind.Home,
timeline = Timeline.Home,
)
val state = state(
@ -194,7 +194,7 @@ class NetworkTimelineRemoteMediatorTest {
accountManager = accountManager,
factory = pagingSourceFactory,
pageCache = pages,
timelineKind = TimelineKind.Home,
timeline = Timeline.Home,
)
val state = state(
@ -264,7 +264,7 @@ class NetworkTimelineRemoteMediatorTest {
accountManager = accountManager,
factory = pagingSourceFactory,
pageCache = pages,
timelineKind = TimelineKind.Home,
timeline = Timeline.Home,
)
val state = state(

View File

@ -23,8 +23,8 @@ import app.pachli.appstore.EventHub
import app.pachli.components.timeline.viewmodel.NetworkTimelineViewModel
import app.pachli.components.timeline.viewmodel.TimelineViewModel
import app.pachli.core.accounts.AccountManager
import app.pachli.core.model.Timeline
import app.pachli.core.network.model.Account
import app.pachli.core.network.model.TimelineKind
import app.pachli.core.network.model.nodeinfo.UnvalidatedJrd
import app.pachli.core.network.model.nodeinfo.UnvalidatedNodeInfo
import app.pachli.core.network.retrofit.MastodonApi
@ -145,7 +145,7 @@ abstract class NetworkTimelineViewModelTestBase {
timelineCases = mock()
viewModel = NetworkTimelineViewModel(
SavedStateHandle(mapOf(TimelineViewModel.TIMELINE_KIND_TAG to TimelineKind.Bookmarks)),
SavedStateHandle(mapOf(TimelineViewModel.TIMELINE_TAG to Timeline.Bookmarks)),
networkTimelineRepository,
timelineCases,
eventHub,

View File

@ -18,7 +18,7 @@
package app.pachli.components.timeline
import app.pachli.components.timeline.viewmodel.InfallibleUiAction
import app.pachli.core.network.model.TimelineKind
import app.pachli.core.model.Timeline
import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.test.runTest
@ -32,8 +32,8 @@ class NetworkTimelineViewModelTestVisibleId : NetworkTimelineViewModelTestBase()
// Given
assertThat(accountManager.activeAccount?.lastVisibleHomeTimelineStatusId)
.isNull()
assertThat(viewModel.timelineKind)
.isNotEqualTo(TimelineKind.Home)
assertThat(viewModel.timeline)
.isNotEqualTo(Timeline.Home)
// When
viewModel.accept(InfallibleUiAction.SaveVisibleId("1234"))

View File

@ -32,6 +32,7 @@ dependencies {
implementation(projects.core.accounts)
implementation(projects.core.common)
implementation(projects.core.database)
implementation(projects.core.model)
implementation(projects.core.network)
testImplementation(libs.bundles.mockito)

View File

@ -24,7 +24,7 @@ import app.pachli.core.data.repository.ListsError.Delete
import app.pachli.core.data.repository.ListsError.GetListsWithAccount
import app.pachli.core.data.repository.ListsError.Retrieve
import app.pachli.core.data.repository.ListsError.Update
import app.pachli.core.database.model.TabData
import app.pachli.core.model.Timeline
import app.pachli.core.network.model.MastoList
import app.pachli.core.network.model.TimelineAccount
import app.pachli.core.network.model.UserListRepliesPolicy
@ -89,7 +89,7 @@ class NetworkListsRepository @Inject constructor(
var changed = false
val newTabPreferences = buildList {
for (oldPref in oldTabPreferences) {
if (oldPref !is TabData.UserList) {
if (oldPref !is Timeline.UserList) {
add(oldPref)
continue
}

View File

@ -32,6 +32,7 @@ android {
dependencies {
implementation(projects.core.common)
implementation(projects.core.model)
implementation(projects.core.network)
implementation(projects.core.preferences)

View File

@ -20,7 +20,7 @@ import androidx.room.ProvidedTypeConverter
import androidx.room.TypeConverter
import app.pachli.core.database.model.ConversationAccountEntity
import app.pachli.core.database.model.DraftAttachment
import app.pachli.core.database.model.TabData
import app.pachli.core.model.Timeline
import app.pachli.core.network.model.Attachment
import app.pachli.core.network.model.Emoji
import app.pachli.core.network.model.FilterResult
@ -66,13 +66,13 @@ class Converters @Inject constructor(
}
@TypeConverter
fun stringToTabData(str: String?): List<TabData>? {
fun stringToTimeline(str: String?): List<Timeline>? {
str ?: return null
// Two possible storage formats. Newer (from Pachli 2.4.0) is polymorphic
// JSON, and the first character will be a '['
if (str.startsWith('[')) {
return moshi.adapter<List<TabData>>().fromJson(str)
return moshi.adapter<List<Timeline>>().fromJson(str)
}
// Older is string of ';' delimited tuples, one per tab.
@ -87,27 +87,27 @@ class Converters @Inject constructor(
val arguments = data.drop(1).map { s -> URLDecoder.decode(s, "UTF-8") }
when (kind) {
"Home" -> TabData.Home
"Notifications" -> TabData.Notifications
"Local" -> TabData.Local
"Federated" -> TabData.Federated
"Direct" -> TabData.Direct
"Home" -> Timeline.Home
"Notifications" -> Timeline.Notifications
"Local" -> Timeline.PublicLocal
"Federated" -> Timeline.PublicFederated
"Direct" -> Timeline.Conversations
// Work around for https://github.com/pachli/pachli-android/issues/329
// when the Trending... kinds may have been serialised without the '_'
"TrendingTags", "Trending_Tags" -> TabData.TrendingTags
"TrendingLinks", "Trending_Links" -> TabData.TrendingLinks
"TrendingStatuses", "Trending_Statuses" -> TabData.TrendingStatuses
"Hashtag" -> TabData.Hashtag(arguments)
"List" -> TabData.UserList(arguments[0], arguments[1])
"Bookmarks" -> TabData.Bookmarks
"TrendingTags", "Trending_Tags" -> Timeline.TrendingHashtags
"TrendingLinks", "Trending_Links" -> Timeline.TrendingLinks
"TrendingStatuses", "Trending_Statuses" -> Timeline.TrendingStatuses
"Hashtag" -> Timeline.Hashtags(arguments)
"List" -> Timeline.UserList(arguments[0], arguments[1])
"Bookmarks" -> Timeline.Bookmarks
else -> throw IllegalStateException("Unrecognised tab kind: $kind")
}
}
}
@TypeConverter
fun tabDataToString(tabData: List<TabData>?): String? {
return moshi.adapter<List<TabData>>().toJson(tabData)
fun timelineToString(timelines: List<Timeline>?): String? {
return moshi.adapter<List<Timeline>>().toJson(timelines)
}
@TypeConverter

View File

@ -23,6 +23,7 @@ import androidx.room.Index
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import app.pachli.core.database.Converters
import app.pachli.core.model.Timeline
import app.pachli.core.network.model.Emoji
import app.pachli.core.network.model.Status
@ -92,7 +93,7 @@ data class AccountEntity(
@ColumnInfo(defaultValue = "0")
var notificationMarkerId: String = "0",
var emojis: List<Emoji> = emptyList(),
var tabPreferences: List<TabData> = defaultTabs(),
var tabPreferences: List<Timeline> = defaultTabs(),
var notificationsFilter: String = "[\"follow_request\"]",
// Scope cannot be changed without re-login, so store it in case
// the scope needs to be changed in the future
@ -147,3 +148,10 @@ data class AccountEntity(
return result
}
}
fun defaultTabs() = listOf(
Timeline.Home,
Timeline.Notifications,
Timeline.PublicLocal,
Timeline.Conversations,
)

View File

@ -1,70 +0,0 @@
/*
* Copyright 2023 Pachli Association
*
* This file is a part of Pachli.
*
* 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.
*
* Pachli 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 Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.core.database.model
import com.squareup.moshi.JsonClass
import dev.zacsweers.moshix.sealed.annotations.TypeLabel
@JsonClass(generateAdapter = true, generator = "sealed:kind")
sealed interface TabData {
@TypeLabel("home")
data object Home : TabData
@TypeLabel("notifications")
data object Notifications : TabData
@TypeLabel("local")
data object Local : TabData
@TypeLabel("federated")
data object Federated : TabData
@TypeLabel("direct")
data object Direct : TabData
@TypeLabel("trending_tags")
data object TrendingTags : TabData
@TypeLabel("trending_links")
data object TrendingLinks : TabData
@TypeLabel("trending_statuses")
data object TrendingStatuses : TabData
/**
* @property tags List of one or more hashtags (without the leading '#')
* to show in the tab.
*/
@TypeLabel("hashtag")
@JsonClass(generateAdapter = true)
data class Hashtag(val tags: List<String>) : TabData
@TypeLabel("list")
@JsonClass(generateAdapter = true)
data class UserList(val listId: String, val title: String) : TabData
@TypeLabel("bookmarks")
data object Bookmarks : TabData
}
fun defaultTabs() = listOf(
TabData.Home,
TabData.Notifications,
TabData.Local,
TabData.Direct,
)

View File

@ -0,0 +1,39 @@
/*
* Copyright 2024 Pachli Association
*
* This file is a part of Pachli.
*
* 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.
*
* Pachli 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 Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
plugins {
alias(libs.plugins.pachli.android.library)
alias(libs.plugins.pachli.android.hilt)
alias(libs.plugins.kotlin.parcelize)
}
android {
namespace = "app.pachli.core.model"
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
implementation(libs.moshi)
implementation(libs.moshi.adapters)
ksp(libs.moshi.codegen)
implementation(libs.moshix.sealed.runtime)
ksp(libs.moshix.sealed.codegen)
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.1" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.1)" variant="all" version="8.3.1">
</issues>

View File

@ -0,0 +1,118 @@
/*
* Copyright 2024 Pachli Association
*
* This file is a part of Pachli.
*
* 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.
*
* Pachli 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 Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.core.model
import android.os.Parcelable
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import dev.zacsweers.moshix.sealed.annotations.NestedSealed
import dev.zacsweers.moshix.sealed.annotations.TypeLabel
import kotlinx.parcelize.Parcelize
/** A timeline's type. Hold's data necessary to display that timeline. */
@Parcelize
@JsonClass(generateAdapter = true, generator = "sealed:kind")
sealed interface Timeline : Parcelable {
@Parcelize
@TypeLabel("home")
data object Home : Timeline
@Parcelize
@TypeLabel("notifications")
data object Notifications : Timeline
/** Federated timeline */
@Parcelize
@TypeLabel("federated")
data object PublicFederated : Timeline
/** Local timeline of the user's server */
@Parcelize
@TypeLabel("local")
data object PublicLocal : Timeline
// TODO: LOCAL_REMOTE
@Parcelize
@TypeLabel("hashtag")
@JsonClass(generateAdapter = true)
data class Hashtags(val tags: List<String>) : Timeline
@Parcelize
@TypeLabel("direct")
data object Conversations : Timeline
/** Any timeline showing statuses from a single user */
@Parcelize
@NestedSealed
sealed interface User : Timeline {
val id: String
/** Timeline showing just a user's statuses (no replies) */
@Parcelize
@TypeLabel("userPosts")
@JsonClass(generateAdapter = true)
data class Posts(override val id: String) : User
/** Timeline showing a user's pinned statuses */
@Parcelize
@TypeLabel("userPinned")
@JsonClass(generateAdapter = true)
data class Pinned(override val id: String) : User
/** Timeline showing a user's top-level statuses and replies they have made */
@Parcelize
@TypeLabel("userReplies")
@JsonClass(generateAdapter = true)
data class Replies(override val id: String) : User
}
@Parcelize
@TypeLabel("favourites")
data object Favourites : Timeline
@Parcelize
@TypeLabel("bookmarks")
data object Bookmarks : Timeline
@Parcelize
@TypeLabel("list")
@JsonClass(generateAdapter = true)
data class UserList(
@Json(name = "listId")
val listId: String,
@Json(name = "title")
val title: String,
) : Timeline
@Parcelize
@TypeLabel("trending_tags")
data object TrendingHashtags : Timeline
@Parcelize
@TypeLabel("trending_links")
data object TrendingLinks : Timeline
@Parcelize
@TypeLabel("trending_statuses")
data object TrendingStatuses : Timeline
// TODO: DRAFTS
// TODO: SCHEDULED
}

View File

@ -31,6 +31,7 @@ android {
dependencies {
implementation(projects.core.database) // For DraftAttachment, used in ComposeOptions
implementation(projects.core.model)
implementation(projects.core.network) // For Attachment, used in AttachmentViewData
implementation(libs.androidx.core.ktx) // IntentCompat

View File

@ -22,6 +22,7 @@ import android.content.Intent
import android.os.Parcelable
import androidx.core.content.IntentCompat
import app.pachli.core.database.model.DraftAttachment
import app.pachli.core.model.Timeline
import app.pachli.core.navigation.LoginActivityIntent.LoginMode
import app.pachli.core.navigation.StatusListActivityIntent.Companion.bookmarks
import app.pachli.core.navigation.StatusListActivityIntent.Companion.favourites
@ -32,7 +33,6 @@ import app.pachli.core.network.model.Filter
import app.pachli.core.network.model.NewPoll
import app.pachli.core.network.model.Notification
import app.pachli.core.network.model.Status
import app.pachli.core.network.model.TimelineKind
import com.gaelmarhic.quadrant.QuadrantConstants
import kotlinx.parcelize.Parcelize
@ -402,7 +402,7 @@ class StatusListActivityIntent private constructor(context: Context) : Intent()
* @param context
*/
fun bookmarks(context: Context) = StatusListActivityIntent(context).apply {
putExtra(EXTRA_KIND, TimelineKind.Bookmarks)
putExtra(EXTRA_KIND, Timeline.Bookmarks)
}
/**
@ -411,7 +411,7 @@ class StatusListActivityIntent private constructor(context: Context) : Intent()
* @param context
*/
fun favourites(context: Context) = StatusListActivityIntent(context).apply {
putExtra(EXTRA_KIND, TimelineKind.Favourites)
putExtra(EXTRA_KIND, Timeline.Favourites)
}
/**
@ -421,7 +421,7 @@ class StatusListActivityIntent private constructor(context: Context) : Intent()
* @param hashtag The hashtag to show, without the leading "`#`"
*/
fun hashtag(context: Context, hashtag: String) = StatusListActivityIntent(context).apply {
putExtra(EXTRA_KIND, TimelineKind.Tag(listOf(hashtag)))
putExtra(EXTRA_KIND, Timeline.Hashtags(listOf(hashtag)))
}
/**
@ -432,11 +432,11 @@ class StatusListActivityIntent private constructor(context: Context) : Intent()
* @param title The title to display
*/
fun list(context: Context, listId: String, title: String) = StatusListActivityIntent(context).apply {
putExtra(EXTRA_KIND, TimelineKind.UserList(listId, title))
putExtra(EXTRA_KIND, Timeline.UserList(listId, title))
}
/** @return The [TimelineKind] to show */
fun getKind(intent: Intent) = IntentCompat.getParcelableExtra(intent, EXTRA_KIND, TimelineKind::class.java)!!
fun getKind(intent: Intent) = IntentCompat.getParcelableExtra(intent, EXTRA_KIND, Timeline::class.java)!!
}
}

View File

@ -31,6 +31,7 @@ android {
dependencies {
implementation(projects.core.common)
implementation(projects.core.model)
implementation(projects.core.preferences)
implementation(libs.moshi)

View File

@ -17,6 +17,7 @@
package app.pachli.core.network.model
import app.pachli.core.model.Timeline
import app.pachli.core.network.json.Default
import app.pachli.core.network.json.HasDefault
import com.squareup.moshi.Json
@ -51,18 +52,28 @@ enum class FilterContext {
/** Filter applies when viewing a profile */
@Json(name = "account")
ACCOUNT,
;
companion object {
fun from(kind: TimelineKind): FilterContext = when (kind) {
is TimelineKind.Home, is TimelineKind.UserList -> HOME
is TimelineKind.PublicFederated,
is TimelineKind.PublicLocal,
is TimelineKind.Tag,
is TimelineKind.Favourites,
/**
* @return The filter context for [timeline], or null if filters are not applied
* to this timeline.
*/
fun from(timeline: Timeline): FilterContext? = when (timeline) {
is Timeline.Home, is Timeline.UserList -> HOME
is Timeline.User -> ACCOUNT
Timeline.Notifications -> NOTIFICATIONS
Timeline.Bookmarks,
Timeline.Favourites,
Timeline.PublicFederated,
Timeline.PublicLocal,
is Timeline.Hashtags,
Timeline.TrendingStatuses,
Timeline.TrendingHashtags,
Timeline.TrendingLinks,
-> PUBLIC
is TimelineKind.User -> ACCOUNT
else -> PUBLIC
Timeline.Conversations -> null
}
}
}

View File

@ -1,67 +0,0 @@
/*
* Copyright 2023 Pachli Association
*
* This file is a part of Pachli.
*
* 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.
*
* Pachli 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 Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.core.network.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
/** A timeline's type. Hold's data necessary to display that timeline. */
@Parcelize
sealed interface TimelineKind : Parcelable {
@Parcelize
data object Home : TimelineKind
@Parcelize
data object PublicFederated : TimelineKind
@Parcelize
data object PublicLocal : TimelineKind
@Parcelize
data class Tag(val tags: List<String>) : TimelineKind
/** Any timeline showing statuses from a single user */
@Parcelize
sealed interface User : TimelineKind {
val id: String
/** Timeline showing just the user's statuses (no replies) */
@Parcelize
data class Posts(override val id: String) : User
/** Timeline showing the user's pinned statuses */
@Parcelize
data class Pinned(override val id: String) : User
/** Timeline showing the user's top-level statuses and replies they have made */
@Parcelize
data class Replies(override val id: String) : User
}
@Parcelize
data object Favourites : TimelineKind
@Parcelize
data object Bookmarks : TimelineKind
@Parcelize
data class UserList(val id: String, val title: String) : TimelineKind
@Parcelize
data object TrendingStatuses : TimelineKind
}

View File

@ -57,6 +57,7 @@ include(":core:common")
include(":core:data")
include(":core:database")
include(":core:designsystem")
include(":core:model")
include(":core:preferences")
include(":core:navigation")
include(":core:network")