diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bb0e63efd..fcf981109 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index bc1f53250..d1f475ce5 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -1,5 +1,5 @@ - + diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 919426b67..547717ca5 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -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 *; diff --git a/app/src/main/java/app/pachli/MainActivity.kt b/app/src/main/java/app/pachli/MainActivity.kt index cbc8a1861..00b22bd63 100644 --- a/app/src/main/java/app/pachli/MainActivity.kt +++ b/app/src/main/java/app/pachli/MainActivity.kt @@ -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 } diff --git a/app/src/main/java/app/pachli/StatusListActivity.kt b/app/src/main/java/app/pachli/StatusListActivity.kt index f1f463a97..a58e5b444 100644 --- a/app/src/main/java/app/pachli/StatusListActivity.kt +++ b/app/src/main/java/app/pachli/StatusListActivity.kt @@ -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 -> diff --git a/app/src/main/java/app/pachli/TabPreferenceActivity.kt b/app/src/main/java/app/pachli/TabPreferenceActivity.kt index 5876192eb..d9e74f75a 100644 --- a/app/src/main/java/app/pachli/TabPreferenceActivity.kt +++ b/app/src/main/java/app/pachli/TabPreferenceActivity.kt @@ -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 = 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 })) } } } diff --git a/app/src/main/java/app/pachli/TabViewData.kt b/app/src/main/java/app/pachli/TabViewData.kt index 2840cb24b..c7c03d9b7 100644 --- a/app/src/main/java/app/pachli/TabViewData.kt +++ b/app/src/main/java/app/pachli/TabViewData.kt @@ -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") } } } diff --git a/app/src/main/java/app/pachli/adapter/TabAdapter.kt b/app/src/main/java/app/pachli/adapter/TabAdapter.kt index 367e98d7c..545799114 100644 --- a/app/src/main/java/app/pachli/adapter/TabAdapter.kt +++ b/app/src/main/java/app/pachli/adapter/TabAdapter.kt @@ -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() diff --git a/app/src/main/java/app/pachli/appstore/Events.kt b/app/src/main/java/app/pachli/appstore/Events.kt index 5388782ff..05e5823ee 100644 --- a/app/src/main/java/app/pachli/appstore/Events.kt +++ b/app/src/main/java/app/pachli/appstore/Events.kt @@ -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) : Event +data class MainTabsChangedEvent(val newTabs: List) : 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 diff --git a/app/src/main/java/app/pachli/components/account/AccountPagerAdapter.kt b/app/src/main/java/app/pachli/components/account/AccountPagerAdapter.kt index 570722891..873b57328 100644 --- a/app/src/main/java/app/pachli/components/account/AccountPagerAdapter.kt +++ b/app/src/main/java/app/pachli/components/account/AccountPagerAdapter.kt @@ -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") } diff --git a/app/src/main/java/app/pachli/components/timeline/CachedTimelineRepository.kt b/app/src/main/java/app/pachli/components/timeline/CachedTimelineRepository.kt index d55c64573..009ccf867 100644 --- a/app/src/main/java/app/pachli/components/timeline/CachedTimelineRepository.kt +++ b/app/src/main/java/app/pachli/components/timeline/CachedTimelineRepository.kt @@ -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> { diff --git a/app/src/main/java/app/pachli/components/timeline/NetworkTimelineRepository.kt b/app/src/main/java/app/pachli/components/timeline/NetworkTimelineRepository.kt index 37643675b..43b0c9fcd 100644 --- a/app/src/main/java/app/pachli/components/timeline/NetworkTimelineRepository.kt +++ b/app/src/main/java/app/pachli/components/timeline/NetworkTimelineRepository.kt @@ -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> { diff --git a/app/src/main/java/app/pachli/components/timeline/TimelineFragment.kt b/app/src/main/java/app/pachli/components/timeline/TimelineFragment.kt index bae17dc62..eceb1e9ae 100644 --- a/app/src/main/java/app/pachli/components/timeline/TimelineFragment.kt +++ b/app/src/main/java/app/pachli/components/timeline/TimelineFragment.kt @@ -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( 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( 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 diff --git a/app/src/main/java/app/pachli/components/timeline/viewmodel/CachedTimelineViewModel.kt b/app/src/main/java/app/pachli/components/timeline/viewmodel/CachedTimelineViewModel.kt index c913db62b..a3ab2158f 100644 --- a/app/src/main/java/app/pachli/components/timeline/viewmodel/CachedTimelineViewModel.kt +++ b/app/src/main/java/app/pachli/components/timeline/viewmodel/CachedTimelineViewModel.kt @@ -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> { - 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 { diff --git a/app/src/main/java/app/pachli/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt b/app/src/main/java/app/pachli/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt index 0f01af729..bce3718e7 100644 --- a/app/src/main/java/app/pachli/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt +++ b/app/src/main/java/app/pachli/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt @@ -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, private val pageCache: PageCache, - private val timelineKind: TimelineKind, + private val timeline: Timeline, ) : RemoteMediator() { 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> { 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") } } } diff --git a/app/src/main/java/app/pachli/components/timeline/viewmodel/NetworkTimelineViewModel.kt b/app/src/main/java/app/pachli/components/timeline/viewmodel/NetworkTimelineViewModel.kt index e8ed6c424..b6505065f 100644 --- a/app/src/main/java/app/pachli/components/timeline/viewmodel/NetworkTimelineViewModel.kt +++ b/app/src/main/java/app/pachli/components/timeline/viewmodel/NetworkTimelineViewModel.kt @@ -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> { - 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( diff --git a/app/src/main/java/app/pachli/components/timeline/viewmodel/TimelineViewModel.kt b/app/src/main/java/app/pachli/components/timeline/viewmodel/TimelineViewModel.kt index 0976fa58d..ea04b1a28 100644 --- a/app/src/main/java/app/pachli/components/timeline/viewmodel/TimelineViewModel.kt +++ b/app/src/main/java/app/pachli/components/timeline/viewmodel/TimelineViewModel.kt @@ -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(TIMELINE_KIND_TAG)!! + val timeline: Timeline = savedStateHandle.get(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() @@ -426,7 +426,7 @@ abstract class TimelineViewModel( uiAction .filterIsInstance() .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() - .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, ): Boolean { - return filterContext.contains(FilterContext.from(timelineKind)) + return filterContext.contains(FilterContext.from(timeline)) } } } diff --git a/app/src/main/java/app/pachli/components/trending/TrendingActivity.kt b/app/src/main/java/app/pachli/components/trending/TrendingActivity.kt index 5108a9c38..1bf5ac1ec 100644 --- a/app/src/main/java/app/pachli/components/trending/TrendingActivity.kt +++ b/app/src/main/java/app/pachli/components/trending/TrendingActivity.kt @@ -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() } } diff --git a/app/src/test/java/app/pachli/MainActivityTest.kt b/app/src/test/java/app/pachli/MainActivityTest.kt index 86662d2cf..f71d49e29 100644 --- a/app/src/test/java/app/pachli/MainActivityTest.kt +++ b/app/src/test/java/app/pachli/MainActivityTest.kt @@ -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(R.id.viewPager).currentItem - val notificationTab = defaultTabs().indexOfFirst { it is TabData.Notifications } + val notificationTab = defaultTabs().indexOfFirst { it is Timeline.Notifications } assertEquals(currentTab, notificationTab) } } diff --git a/app/src/test/java/app/pachli/components/timeline/CachedTimelineViewModelTestBase.kt b/app/src/test/java/app/pachli/components/timeline/CachedTimelineViewModelTestBase.kt index 80f8a5129..0362ae344 100644 --- a/app/src/test/java/app/pachli/components/timeline/CachedTimelineViewModelTestBase.kt +++ b/app/src/test/java/app/pachli/components/timeline/CachedTimelineViewModelTestBase.kt @@ -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, diff --git a/app/src/test/java/app/pachli/components/timeline/CachedTimelineViewModelTestVisibleId.kt b/app/src/test/java/app/pachli/components/timeline/CachedTimelineViewModelTestVisibleId.kt index 35e0f8645..f25de5aa0 100644 --- a/app/src/test/java/app/pachli/components/timeline/CachedTimelineViewModelTestVisibleId.kt +++ b/app/src/test/java/app/pachli/components/timeline/CachedTimelineViewModelTestVisibleId.kt @@ -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")) diff --git a/app/src/test/java/app/pachli/components/timeline/NetworkTimelineRemoteMediatorTest.kt b/app/src/test/java/app/pachli/components/timeline/NetworkTimelineRemoteMediatorTest.kt index 6166dc409..169f2cf0a 100644 --- a/app/src/test/java/app/pachli/components/timeline/NetworkTimelineRemoteMediatorTest.kt +++ b/app/src/test/java/app/pachli/components/timeline/NetworkTimelineRemoteMediatorTest.kt @@ -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( diff --git a/app/src/test/java/app/pachli/components/timeline/NetworkTimelineViewModelTestBase.kt b/app/src/test/java/app/pachli/components/timeline/NetworkTimelineViewModelTestBase.kt index caee067f2..f9a493b1d 100644 --- a/app/src/test/java/app/pachli/components/timeline/NetworkTimelineViewModelTestBase.kt +++ b/app/src/test/java/app/pachli/components/timeline/NetworkTimelineViewModelTestBase.kt @@ -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, diff --git a/app/src/test/java/app/pachli/components/timeline/NetworkTimelineViewModelTestVisibleId.kt b/app/src/test/java/app/pachli/components/timeline/NetworkTimelineViewModelTestVisibleId.kt index cf80cb222..ce7b34c1b 100644 --- a/app/src/test/java/app/pachli/components/timeline/NetworkTimelineViewModelTestVisibleId.kt +++ b/app/src/test/java/app/pachli/components/timeline/NetworkTimelineViewModelTestVisibleId.kt @@ -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")) diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index 1f3e6edb7..0eae13cbc 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -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) diff --git a/core/data/src/main/kotlin/app/pachli/core/data/repository/NetworkListsRepository.kt b/core/data/src/main/kotlin/app/pachli/core/data/repository/NetworkListsRepository.kt index cc3fe47a8..f4c09c98e 100644 --- a/core/data/src/main/kotlin/app/pachli/core/data/repository/NetworkListsRepository.kt +++ b/core/data/src/main/kotlin/app/pachli/core/data/repository/NetworkListsRepository.kt @@ -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 } diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts index 827bd148d..9397d8fbb 100644 --- a/core/database/build.gradle.kts +++ b/core/database/build.gradle.kts @@ -32,6 +32,7 @@ android { dependencies { implementation(projects.core.common) + implementation(projects.core.model) implementation(projects.core.network) implementation(projects.core.preferences) diff --git a/core/database/src/main/kotlin/app/pachli/core/database/Converters.kt b/core/database/src/main/kotlin/app/pachli/core/database/Converters.kt index 3be775275..2712f7c8a 100644 --- a/core/database/src/main/kotlin/app/pachli/core/database/Converters.kt +++ b/core/database/src/main/kotlin/app/pachli/core/database/Converters.kt @@ -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? { + fun stringToTimeline(str: String?): List? { 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>().fromJson(str) + return moshi.adapter>().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?): String? { - return moshi.adapter>().toJson(tabData) + fun timelineToString(timelines: List?): String? { + return moshi.adapter>().toJson(timelines) } @TypeConverter diff --git a/core/database/src/main/kotlin/app/pachli/core/database/model/AccountEntity.kt b/core/database/src/main/kotlin/app/pachli/core/database/model/AccountEntity.kt index 85406e33b..8657354bb 100644 --- a/core/database/src/main/kotlin/app/pachli/core/database/model/AccountEntity.kt +++ b/core/database/src/main/kotlin/app/pachli/core/database/model/AccountEntity.kt @@ -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 = emptyList(), - var tabPreferences: List = defaultTabs(), + var tabPreferences: List = 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, +) diff --git a/core/database/src/main/kotlin/app/pachli/core/database/model/TabData.kt b/core/database/src/main/kotlin/app/pachli/core/database/model/TabData.kt deleted file mode 100644 index 4c31d45c7..000000000 --- a/core/database/src/main/kotlin/app/pachli/core/database/model/TabData.kt +++ /dev/null @@ -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 . - */ - -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) : 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, -) diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts new file mode 100644 index 000000000..6a6c62f41 --- /dev/null +++ b/core/model/build.gradle.kts @@ -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 . + */ + +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) +} diff --git a/core/model/lint-baseline.xml b/core/model/lint-baseline.xml new file mode 100644 index 000000000..091bffee2 --- /dev/null +++ b/core/model/lint-baseline.xml @@ -0,0 +1,4 @@ + + + + diff --git a/core/model/src/main/kotlin/app/pachli/core/model/Timeline.kt b/core/model/src/main/kotlin/app/pachli/core/model/Timeline.kt new file mode 100644 index 000000000..2b7b4df16 --- /dev/null +++ b/core/model/src/main/kotlin/app/pachli/core/model/Timeline.kt @@ -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 . + */ + +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) : 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 +} diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts index a7e28fd7a..20a4ddebd 100644 --- a/core/navigation/build.gradle.kts +++ b/core/navigation/build.gradle.kts @@ -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 diff --git a/core/navigation/src/main/kotlin/app/pachli/core/navigation/Navigation.kt b/core/navigation/src/main/kotlin/app/pachli/core/navigation/Navigation.kt index a07ba63dd..be7d1049f 100644 --- a/core/navigation/src/main/kotlin/app/pachli/core/navigation/Navigation.kt +++ b/core/navigation/src/main/kotlin/app/pachli/core/navigation/Navigation.kt @@ -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)!! } } diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 1a2767c33..09b753063 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -31,6 +31,7 @@ android { dependencies { implementation(projects.core.common) + implementation(projects.core.model) implementation(projects.core.preferences) implementation(libs.moshi) diff --git a/core/network/src/main/kotlin/app/pachli/core/network/model/FilterContext.kt b/core/network/src/main/kotlin/app/pachli/core/network/model/FilterContext.kt index ada0f6006..feaaf3b41 100644 --- a/core/network/src/main/kotlin/app/pachli/core/network/model/FilterContext.kt +++ b/core/network/src/main/kotlin/app/pachli/core/network/model/FilterContext.kt @@ -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 } } } diff --git a/core/network/src/main/kotlin/app/pachli/core/network/model/TimelineKind.kt b/core/network/src/main/kotlin/app/pachli/core/network/model/TimelineKind.kt deleted file mode 100644 index 910769c05..000000000 --- a/core/network/src/main/kotlin/app/pachli/core/network/model/TimelineKind.kt +++ /dev/null @@ -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 . - */ - -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) : 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 -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 7804060e9..66cbf1cce 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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")