refactor: Store tab preferences as polymorphic JSON
This commit is contained in:
parent
e93e4ffb53
commit
a973f67ac8
|
@ -77,7 +77,7 @@ import app.pachli.core.common.extensions.viewBinding
|
|||
import app.pachli.core.data.repository.Lists
|
||||
import app.pachli.core.data.repository.ListsRepository
|
||||
import app.pachli.core.database.model.AccountEntity
|
||||
import app.pachli.core.database.model.TabKind
|
||||
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.navigation.AboutActivityIntent
|
||||
|
@ -866,8 +866,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].kind) {
|
||||
TabKind.LIST -> tabs[position].arguments[1]
|
||||
tab.contentDescription = when (tabs[position].tabData) {
|
||||
is TabData.UserList -> tabs[position].title(this@MainActivity)
|
||||
else -> getString(tabs[position].text)
|
||||
}
|
||||
}.also { it.attach() }
|
||||
|
@ -877,7 +877,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
// - The previously selected tab (if it hasn't been removed)
|
||||
// - Left-most tab
|
||||
val position = if (selectNotificationTab) {
|
||||
tabs.indexOfFirst { it.kind == TabKind.NOTIFICATIONS }
|
||||
tabs.indexOfFirst { it.tabData is TabData.Notifications }
|
||||
} else {
|
||||
previousTab?.let { tabs.indexOfFirst { it == previousTab } }
|
||||
}.takeIf { it != -1 } ?: 0
|
||||
|
|
|
@ -49,7 +49,6 @@ import app.pachli.core.common.extensions.visible
|
|||
import app.pachli.core.data.repository.Lists
|
||||
import app.pachli.core.data.repository.ListsRepository
|
||||
import app.pachli.core.database.model.TabData
|
||||
import app.pachli.core.database.model.TabKind
|
||||
import app.pachli.core.designsystem.R as DR
|
||||
import app.pachli.core.navigation.ListActivityIntent
|
||||
import app.pachli.core.network.model.MastoList
|
||||
|
@ -126,7 +125,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
|||
MaterialDividerItemDecoration(this, MaterialDividerItemDecoration.VERTICAL),
|
||||
)
|
||||
|
||||
addTabAdapter = TabAdapter(listOf(TabViewData.from(TabData(TabKind.DIRECT))), true, this)
|
||||
addTabAdapter = TabAdapter(listOf(TabViewData.from(TabData.Direct)), true, this)
|
||||
binding.addTabRecyclerView.adapter = addTabAdapter
|
||||
binding.addTabRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||
|
||||
|
@ -189,12 +188,12 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
|||
override fun onTabAdded(tab: TabViewData) {
|
||||
toggleFab(false)
|
||||
|
||||
if (tab.kind == TabKind.HASHTAG) {
|
||||
if (tab.tabData is TabData.Hashtag) {
|
||||
showAddHashtagDialog()
|
||||
return
|
||||
}
|
||||
|
||||
if (tab.kind == TabKind.LIST) {
|
||||
if (tab.tabData is TabData.UserList) {
|
||||
showSelectListDialog()
|
||||
return
|
||||
}
|
||||
|
@ -212,14 +211,16 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
|||
saveTabs()
|
||||
}
|
||||
|
||||
override fun onActionChipClicked(tab: TabViewData, tabPosition: Int) {
|
||||
showAddHashtagDialog(tab, tabPosition)
|
||||
override fun onActionChipClicked(tabData: TabData.Hashtag, tabPosition: Int) {
|
||||
showAddHashtagDialog(tabData, tabPosition)
|
||||
}
|
||||
|
||||
override fun onChipClicked(tab: TabViewData, tabPosition: Int, chipPosition: Int) {
|
||||
val newArguments = tab.arguments.filterIndexed { i, _ -> i != chipPosition }
|
||||
val newTab = tab.copy(tabData = tab.tabData.copy(arguments = newArguments))
|
||||
currentTabs[tabPosition] = newTab
|
||||
override fun onChipClicked(tabData: TabData.Hashtag, tabPosition: Int, chipPosition: Int) {
|
||||
currentTabs[tabPosition] = currentTabs[tabPosition].copy(
|
||||
tabData = tabData.copy(
|
||||
tags = tabData.tags.filterIndexed { i, _ -> i != chipPosition },
|
||||
),
|
||||
)
|
||||
saveTabs()
|
||||
|
||||
currentTabsAdapter.notifyItemChanged(tabPosition)
|
||||
|
@ -243,7 +244,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
|||
onFabDismissedCallback.isEnabled = expand
|
||||
}
|
||||
|
||||
private fun showAddHashtagDialog(tab: TabViewData? = null, tabPosition: Int = 0) {
|
||||
private fun showAddHashtagDialog(tabData: TabData.Hashtag? = null, tabPosition: Int = 0) {
|
||||
val frameLayout = FrameLayout(this)
|
||||
val padding = Utils.dpToPx(this, 8)
|
||||
frameLayout.updatePadding(left = padding, right = padding)
|
||||
|
@ -259,13 +260,14 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
|||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.action_save) { _, _ ->
|
||||
val input = editText.text.toString().trim()
|
||||
if (tab == null) {
|
||||
val newTab = TabViewData.from(TabData(TabKind.HASHTAG, listOf(input)))
|
||||
if (tabData == null) {
|
||||
val newTab = TabViewData.from(TabData.Hashtag(listOf(input)))
|
||||
currentTabs.add(newTab)
|
||||
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
||||
} else {
|
||||
val newTab = tab.copy(tabData = tab.tabData.copy(arguments = tab.arguments + input))
|
||||
currentTabs[tabPosition] = newTab
|
||||
currentTabs[tabPosition] = currentTabs[tabPosition].copy(
|
||||
tabData = tabData.copy(tags = tabData.tags + input),
|
||||
)
|
||||
|
||||
currentTabsAdapter.notifyItemChanged(tabPosition)
|
||||
}
|
||||
|
@ -315,7 +317,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
|||
.setView(statusLayout)
|
||||
.setAdapter(adapter) { _, position ->
|
||||
adapter.getItem(position)?.let { item ->
|
||||
val newTab = TabViewData.from(TabData(TabKind.LIST, listOf(item.id, item.title)))
|
||||
val newTab = TabViewData.from(TabData.UserList(item.id, item.title))
|
||||
currentTabs.add(newTab)
|
||||
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
||||
updateAvailableTabs()
|
||||
|
@ -377,45 +379,45 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
|||
private fun updateAvailableTabs() {
|
||||
val addableTabs: MutableList<TabViewData> = mutableListOf()
|
||||
|
||||
val homeTab = TabViewData.from(TabData(TabKind.HOME))
|
||||
val homeTab = TabViewData.from(TabData.Home)
|
||||
if (!currentTabs.contains(homeTab)) {
|
||||
addableTabs.add(homeTab)
|
||||
}
|
||||
val notificationTab = TabViewData.from(TabData(TabKind.NOTIFICATIONS))
|
||||
val notificationTab = TabViewData.from(TabData.Notifications)
|
||||
if (!currentTabs.contains(notificationTab)) {
|
||||
addableTabs.add(notificationTab)
|
||||
}
|
||||
val localTab = TabViewData.from(TabData(TabKind.LOCAL))
|
||||
val localTab = TabViewData.from(TabData.Local)
|
||||
if (!currentTabs.contains(localTab)) {
|
||||
addableTabs.add(localTab)
|
||||
}
|
||||
val federatedTab = TabViewData.from(TabData(TabKind.FEDERATED))
|
||||
val federatedTab = TabViewData.from(TabData.Federated)
|
||||
if (!currentTabs.contains(federatedTab)) {
|
||||
addableTabs.add(federatedTab)
|
||||
}
|
||||
val directMessagesTab = TabViewData.from(TabData(TabKind.DIRECT))
|
||||
val directMessagesTab = TabViewData.from(TabData.Direct)
|
||||
if (!currentTabs.contains(directMessagesTab)) {
|
||||
addableTabs.add(directMessagesTab)
|
||||
}
|
||||
val trendingTagsTab = TabViewData.from(TabData(TabKind.TRENDING_TAGS))
|
||||
val trendingTagsTab = TabViewData.from(TabData.TrendingTags)
|
||||
if (!currentTabs.contains(trendingTagsTab)) {
|
||||
addableTabs.add(trendingTagsTab)
|
||||
}
|
||||
val trendingLinksTab = TabViewData.from(TabData(TabKind.TRENDING_LINKS))
|
||||
val trendingLinksTab = TabViewData.from(TabData.TrendingLinks)
|
||||
if (!currentTabs.contains(trendingLinksTab)) {
|
||||
addableTabs.add(trendingLinksTab)
|
||||
}
|
||||
val trendingStatusesTab = TabViewData.from(TabData(TabKind.TRENDING_STATUSES))
|
||||
val trendingStatusesTab = TabViewData.from(TabData.TrendingStatuses)
|
||||
if (!currentTabs.contains(trendingStatusesTab)) {
|
||||
addableTabs.add(trendingStatusesTab)
|
||||
}
|
||||
val bookmarksTab = TabViewData.from(TabData(TabKind.BOOKMARKS))
|
||||
val bookmarksTab = TabViewData.from(TabData.Bookmarks)
|
||||
if (!currentTabs.contains(trendingTagsTab)) {
|
||||
addableTabs.add(bookmarksTab)
|
||||
}
|
||||
|
||||
addableTabs.add(TabViewData.from(TabData(TabKind.HASHTAG)))
|
||||
addableTabs.add(TabViewData.from(TabData(TabKind.LIST)))
|
||||
addableTabs.add(TabViewData.from(TabData.Hashtag(emptyList())))
|
||||
addableTabs.add(TabViewData.from(TabData.UserList("", "")))
|
||||
|
||||
addTabAdapter.updateData(addableTabs)
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ 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.database.model.TabKind
|
||||
import app.pachli.core.network.model.TimelineKind
|
||||
|
||||
/**
|
||||
|
@ -43,13 +42,9 @@ data class TabViewData(
|
|||
val tabData: TabData,
|
||||
@StringRes val text: Int,
|
||||
@DrawableRes val icon: Int,
|
||||
val fragment: (List<String>) -> Fragment,
|
||||
val fragment: () -> Fragment,
|
||||
val title: (Context) -> String = { context -> context.getString(text) },
|
||||
) {
|
||||
val kind get() = this.tabData.kind
|
||||
|
||||
val arguments get() = this.tabData.arguments
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
@ -64,64 +59,62 @@ data class TabViewData(
|
|||
override fun hashCode() = tabData.hashCode()
|
||||
|
||||
companion object {
|
||||
fun from(tabKind: TabKind) = from(TabData.from(tabKind))
|
||||
|
||||
fun from(tabData: TabData) = when (tabData.kind) {
|
||||
TabKind.HOME -> TabViewData(
|
||||
fun from(tabData: TabData) = when (tabData) {
|
||||
TabData.Home -> TabViewData(
|
||||
tabData = tabData,
|
||||
text = R.string.title_home,
|
||||
icon = R.drawable.ic_home_24dp,
|
||||
fragment = { TimelineFragment.newInstance(TimelineKind.Home) },
|
||||
)
|
||||
TabKind.NOTIFICATIONS -> TabViewData(
|
||||
TabData.Notifications -> TabViewData(
|
||||
tabData = tabData,
|
||||
text = R.string.title_notifications,
|
||||
icon = R.drawable.ic_notifications_24dp,
|
||||
fragment = { NotificationsFragment.newInstance() },
|
||||
)
|
||||
TabKind.LOCAL -> TabViewData(
|
||||
TabData.Local -> TabViewData(
|
||||
tabData = tabData,
|
||||
text = R.string.title_public_local,
|
||||
icon = R.drawable.ic_local_24dp,
|
||||
fragment = { TimelineFragment.newInstance(TimelineKind.PublicLocal) },
|
||||
)
|
||||
TabKind.FEDERATED -> TabViewData(
|
||||
TabData.Federated -> TabViewData(
|
||||
tabData = tabData,
|
||||
text = R.string.title_public_federated,
|
||||
icon = R.drawable.ic_public_24dp,
|
||||
fragment = { TimelineFragment.newInstance(TimelineKind.PublicFederated) },
|
||||
)
|
||||
TabKind.DIRECT -> TabViewData(
|
||||
TabData.Direct -> TabViewData(
|
||||
tabData = tabData,
|
||||
text = R.string.title_direct_messages,
|
||||
icon = R.drawable.ic_reblog_direct_24dp,
|
||||
fragment = { ConversationsFragment.newInstance() },
|
||||
)
|
||||
TabKind.TRENDING_TAGS -> TabViewData(
|
||||
TabData.TrendingTags -> TabViewData(
|
||||
tabData = tabData,
|
||||
text = R.string.title_public_trending_hashtags,
|
||||
icon = R.drawable.ic_trending_up_24px,
|
||||
fragment = { TrendingTagsFragment.newInstance() },
|
||||
)
|
||||
TabKind.TRENDING_LINKS -> TabViewData(
|
||||
TabData.TrendingLinks -> TabViewData(
|
||||
tabData = tabData,
|
||||
text = R.string.title_public_trending_links,
|
||||
icon = R.drawable.ic_trending_up_24px,
|
||||
fragment = { TrendingLinksFragment.newInstance() },
|
||||
)
|
||||
TabKind.TRENDING_STATUSES -> TabViewData(
|
||||
TabData.TrendingStatuses -> TabViewData(
|
||||
tabData = tabData,
|
||||
text = R.string.title_public_trending_statuses,
|
||||
icon = R.drawable.ic_trending_up_24px,
|
||||
fragment = { TimelineFragment.newInstance(TimelineKind.TrendingStatuses) },
|
||||
)
|
||||
TabKind.HASHTAG -> TabViewData(
|
||||
is TabData.Hashtag -> TabViewData(
|
||||
tabData = tabData,
|
||||
text = R.string.hashtags,
|
||||
icon = R.drawable.ic_hashtag,
|
||||
fragment = { args -> TimelineFragment.newInstance(TimelineKind.Tag(args)) },
|
||||
fragment = { TimelineFragment.newInstance(TimelineKind.Tag(tabData.tags)) },
|
||||
title = { context ->
|
||||
tabData.arguments.joinToString(separator = " ") {
|
||||
tabData.tags.joinToString(separator = " ") {
|
||||
context.getString(
|
||||
R.string.title_tag,
|
||||
it,
|
||||
|
@ -129,18 +122,18 @@ data class TabViewData(
|
|||
}
|
||||
},
|
||||
)
|
||||
TabKind.LIST -> TabViewData(
|
||||
is TabData.UserList -> TabViewData(
|
||||
tabData = tabData,
|
||||
text = R.string.list,
|
||||
icon = R.drawable.ic_list,
|
||||
fragment = { args ->
|
||||
fragment = {
|
||||
TimelineFragment.newInstance(
|
||||
TimelineKind.UserList(args.first(), args.last()),
|
||||
TimelineKind.UserList(tabData.listId, tabData.title),
|
||||
)
|
||||
},
|
||||
title = { tabData.arguments.getOrNull(1).orEmpty() },
|
||||
title = { tabData.title },
|
||||
)
|
||||
TabKind.BOOKMARKS -> TabViewData(
|
||||
TabData.Bookmarks -> TabViewData(
|
||||
tabData = tabData,
|
||||
text = R.string.title_bookmarks,
|
||||
icon = R.drawable.ic_bookmark_active_24dp,
|
||||
|
|
|
@ -26,7 +26,7 @@ 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.TabKind
|
||||
import app.pachli.core.database.model.TabData
|
||||
import app.pachli.core.designsystem.R as DR
|
||||
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(tab: TabViewData, tabPosition: Int)
|
||||
fun onChipClicked(tab: TabViewData, tabPosition: Int, chipPosition: Int)
|
||||
fun onActionChipClicked(tabData: TabData.Hashtag, tabPosition: Int)
|
||||
fun onChipClicked(tabData: TabData.Hashtag, tabPosition: Int, chipPosition: Int)
|
||||
}
|
||||
|
||||
class TabAdapter(
|
||||
|
@ -81,8 +81,8 @@ class TabAdapter(
|
|||
} else {
|
||||
val binding = holder.binding as ItemTabPreferenceBinding
|
||||
|
||||
if (tab.kind == TabKind.LIST) {
|
||||
binding.textView.text = tab.arguments.getOrNull(1).orEmpty()
|
||||
if (tab.tabData is TabData.UserList) {
|
||||
binding.textView.text = tab.tabData.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.kind == TabKind.HASHTAG) {
|
||||
if (tab.tabData is TabData.Hashtag) {
|
||||
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.arguments.forEachIndexed { i, arg ->
|
||||
tab.tabData.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.arguments.size <= 1) {
|
||||
if (tab.tabData.tags.size <= 1) {
|
||||
chip.isCloseIconVisible = false
|
||||
chip.setOnClickListener(null)
|
||||
} else {
|
||||
chip.isCloseIconVisible = true
|
||||
chip.setOnClickListener {
|
||||
listener.onChipClicked(tab, holder.bindingAdapterPosition, i)
|
||||
listener.onChipClicked(tab.tabData, holder.bindingAdapterPosition, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (binding.chipGroup.size - 1 > tab.arguments.size) {
|
||||
binding.chipGroup.removeViewAt(tab.arguments.size)
|
||||
while (binding.chipGroup.size - 1 > tab.tabData.tags.size) {
|
||||
binding.chipGroup.removeViewAt(tab.tabData.tags.size)
|
||||
}
|
||||
|
||||
binding.actionChip.setOnClickListener {
|
||||
listener.onActionChipClicked(tab, holder.bindingAdapterPosition)
|
||||
listener.onActionChipClicked(tab.tabData, holder.bindingAdapterPosition)
|
||||
}
|
||||
} else {
|
||||
binding.chipGroup.hide()
|
||||
|
|
|
@ -25,7 +25,7 @@ class MainPagerAdapter(var tabs: List<TabViewData>, activity: FragmentActivity)
|
|||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
val tab = tabs[position]
|
||||
return tab.fragment(tab.arguments)
|
||||
return tab.fragment()
|
||||
}
|
||||
|
||||
override fun getItemCount() = tabs.size
|
||||
|
|
|
@ -31,7 +31,7 @@ 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.TabKind
|
||||
import app.pachli.core.database.model.TabData
|
||||
import app.pachli.core.database.model.defaultTabs
|
||||
import app.pachli.core.navigation.AccountListActivityIntent
|
||||
import app.pachli.core.network.model.Account
|
||||
|
@ -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.kind == TabKind.NOTIFICATIONS }
|
||||
val notificationTab = defaultTabs().indexOfFirst { it is TabData.Notifications }
|
||||
assertEquals(currentTab, notificationTab)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,4 +39,7 @@ dependencies {
|
|||
implementation(libs.moshi)
|
||||
implementation(libs.moshi.adapters)
|
||||
ksp(libs.moshi.codegen)
|
||||
|
||||
implementation(libs.moshix.sealed.runtime)
|
||||
ksp(libs.moshix.sealed.codegen)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ import app.pachli.core.network.model.TranslatedPoll
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapter
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
import java.time.Instant
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
|
@ -68,17 +67,47 @@ class Converters @Inject constructor(
|
|||
|
||||
@TypeConverter
|
||||
fun stringToTabData(str: String?): List<TabData>? {
|
||||
return str?.split(";")
|
||||
?.map {
|
||||
val data = it.split(":")
|
||||
TabData.from(data[0], data.drop(1).map { s -> URLDecoder.decode(s, "UTF-8") })
|
||||
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)
|
||||
}
|
||||
|
||||
// Older is string of ';' delimited tuples, one per tab.
|
||||
// The per-tab data is ':' delimited tuples where the first item is the tab's kind,
|
||||
// any subsequent entries are tab-specific data.
|
||||
//
|
||||
// The "Trending_..." / "Trending..." is to work around
|
||||
// https://github.com/pachli/pachli-android/issues/329
|
||||
return str.split(";").map {
|
||||
val data = it.split(":")
|
||||
val kind = data[0]
|
||||
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
|
||||
// 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
|
||||
else -> throw IllegalStateException("Unrecognised tab kind: $kind")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun tabDataToString(tabData: List<TabData>?): String? {
|
||||
// List name may include ":"
|
||||
return tabData?.joinToString(";") { it.kind.repr + ":" + it.arguments.joinToString(":") { s -> URLEncoder.encode(s, "UTF-8") } }
|
||||
return moshi.adapter<List<TabData>>().toJson(tabData)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
|
|
|
@ -17,48 +17,54 @@
|
|||
|
||||
package app.pachli.core.database.model
|
||||
|
||||
/**
|
||||
* A tab's kind.
|
||||
*
|
||||
* @param repr String representation of the tab in the database
|
||||
*/
|
||||
enum class TabKind(val repr: String) {
|
||||
HOME("Home"),
|
||||
NOTIFICATIONS("Notifications"),
|
||||
LOCAL("Local"),
|
||||
FEDERATED("Federated"),
|
||||
DIRECT("Direct"),
|
||||
TRENDING_TAGS("Trending_Tags"),
|
||||
TRENDING_LINKS("Trending_Links"),
|
||||
TRENDING_STATUSES("Trending_Statuses"),
|
||||
HASHTAG("Hashtag"),
|
||||
LIST("List"),
|
||||
BOOKMARKS("Bookmarks"),
|
||||
}
|
||||
import com.squareup.moshi.JsonClass
|
||||
import dev.zacsweers.moshix.sealed.annotations.TypeLabel
|
||||
|
||||
/** this would be a good case for a sealed class, but that does not work nice with Room */
|
||||
@JsonClass(generateAdapter = true, generator = "sealed:kind")
|
||||
sealed interface TabData {
|
||||
@TypeLabel("home")
|
||||
data object Home : TabData
|
||||
|
||||
data class TabData(val kind: TabKind, val arguments: List<String> = emptyList()) {
|
||||
companion object {
|
||||
fun from(kind: TabKind, arguments: List<String> = emptyList()) =
|
||||
TabData(kind, arguments)
|
||||
@TypeLabel("notifications")
|
||||
data object Notifications : TabData
|
||||
|
||||
fun from(kind: String, arguments: List<String> = emptyList()): TabData {
|
||||
// Work around for https://github.com/pachli/pachli-android/issues/329,
|
||||
// as the Trending... kinds may have been serialised without the `_`
|
||||
return when (kind) {
|
||||
"TrendingTags" -> TabData(TabKind.TRENDING_TAGS, arguments)
|
||||
"TrendingLinks" -> TabData(TabKind.TRENDING_LINKS, arguments)
|
||||
"TrendingStatuses" -> TabData(TabKind.TRENDING_STATUSES, arguments)
|
||||
else -> TabData(TabKind.valueOf(kind.uppercase()), arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
@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.from(TabKind.HOME),
|
||||
TabData.from(TabKind.NOTIFICATIONS),
|
||||
TabData.from(TabKind.LOCAL),
|
||||
TabData.from(TabKind.DIRECT),
|
||||
TabData.Home,
|
||||
TabData.Notifications,
|
||||
TabData.Local,
|
||||
TabData.Direct,
|
||||
)
|
||||
|
|
|
@ -54,6 +54,7 @@ material-typeface = "4.0.0.2-kotlin"
|
|||
mockito-inline = "5.2.0"
|
||||
mockito-kotlin = "5.2.1"
|
||||
moshi = "1.15.1"
|
||||
moshix = "0.25.1"
|
||||
networkresult-calladapter = "1.0.0"
|
||||
okhttp = "4.12.0"
|
||||
quadrant = "1.9.1"
|
||||
|
@ -177,6 +178,8 @@ mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "
|
|||
moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
|
||||
moshi-adapters = { module = "com.squareup.moshi:moshi-adapters", version.ref = "moshi" }
|
||||
moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" }
|
||||
moshix-sealed-runtime = { module = "dev.zacsweers.moshix:moshi-sealed-runtime", version.ref = "moshix" }
|
||||
moshix-sealed-codegen = { module = "dev.zacsweers.moshix:moshi-sealed-codegen", version.ref = "moshix" }
|
||||
networkresult-calladapter = { module = "at.connyduck:networkresult-calladapter", version.ref = "networkresult-calladapter" }
|
||||
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||
okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
||||
|
|
Loading…
Reference in New Issue