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.Lists
|
||||||
import app.pachli.core.data.repository.ListsRepository
|
import app.pachli.core.data.repository.ListsRepository
|
||||||
import app.pachli.core.database.model.AccountEntity
|
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.EmbeddedFontFamily
|
||||||
import app.pachli.core.designsystem.R as DR
|
import app.pachli.core.designsystem.R as DR
|
||||||
import app.pachli.core.navigation.AboutActivityIntent
|
import app.pachli.core.navigation.AboutActivityIntent
|
||||||
|
@ -866,8 +866,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
||||||
tabLayoutMediator = TabLayoutMediator(activeTabLayout, binding.viewPager, true) {
|
tabLayoutMediator = TabLayoutMediator(activeTabLayout, binding.viewPager, true) {
|
||||||
tab: TabLayout.Tab, position: Int ->
|
tab: TabLayout.Tab, position: Int ->
|
||||||
tab.icon = AppCompatResources.getDrawable(this@MainActivity, tabs[position].icon)
|
tab.icon = AppCompatResources.getDrawable(this@MainActivity, tabs[position].icon)
|
||||||
tab.contentDescription = when (tabs[position].kind) {
|
tab.contentDescription = when (tabs[position].tabData) {
|
||||||
TabKind.LIST -> tabs[position].arguments[1]
|
is TabData.UserList -> tabs[position].title(this@MainActivity)
|
||||||
else -> getString(tabs[position].text)
|
else -> getString(tabs[position].text)
|
||||||
}
|
}
|
||||||
}.also { it.attach() }
|
}.also { it.attach() }
|
||||||
|
@ -877,7 +877,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
||||||
// - The previously selected tab (if it hasn't been removed)
|
// - The previously selected tab (if it hasn't been removed)
|
||||||
// - Left-most tab
|
// - Left-most tab
|
||||||
val position = if (selectNotificationTab) {
|
val position = if (selectNotificationTab) {
|
||||||
tabs.indexOfFirst { it.kind == TabKind.NOTIFICATIONS }
|
tabs.indexOfFirst { it.tabData is TabData.Notifications }
|
||||||
} else {
|
} else {
|
||||||
previousTab?.let { tabs.indexOfFirst { it == previousTab } }
|
previousTab?.let { tabs.indexOfFirst { it == previousTab } }
|
||||||
}.takeIf { it != -1 } ?: 0
|
}.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.Lists
|
||||||
import app.pachli.core.data.repository.ListsRepository
|
import app.pachli.core.data.repository.ListsRepository
|
||||||
import app.pachli.core.database.model.TabData
|
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.designsystem.R as DR
|
||||||
import app.pachli.core.navigation.ListActivityIntent
|
import app.pachli.core.navigation.ListActivityIntent
|
||||||
import app.pachli.core.network.model.MastoList
|
import app.pachli.core.network.model.MastoList
|
||||||
|
@ -126,7 +125,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
||||||
MaterialDividerItemDecoration(this, MaterialDividerItemDecoration.VERTICAL),
|
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.adapter = addTabAdapter
|
||||||
binding.addTabRecyclerView.layoutManager = LinearLayoutManager(this)
|
binding.addTabRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
|
||||||
|
@ -189,12 +188,12 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
||||||
override fun onTabAdded(tab: TabViewData) {
|
override fun onTabAdded(tab: TabViewData) {
|
||||||
toggleFab(false)
|
toggleFab(false)
|
||||||
|
|
||||||
if (tab.kind == TabKind.HASHTAG) {
|
if (tab.tabData is TabData.Hashtag) {
|
||||||
showAddHashtagDialog()
|
showAddHashtagDialog()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab.kind == TabKind.LIST) {
|
if (tab.tabData is TabData.UserList) {
|
||||||
showSelectListDialog()
|
showSelectListDialog()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -212,14 +211,16 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
||||||
saveTabs()
|
saveTabs()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActionChipClicked(tab: TabViewData, tabPosition: Int) {
|
override fun onActionChipClicked(tabData: TabData.Hashtag, tabPosition: Int) {
|
||||||
showAddHashtagDialog(tab, tabPosition)
|
showAddHashtagDialog(tabData, tabPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onChipClicked(tab: TabViewData, tabPosition: Int, chipPosition: Int) {
|
override fun onChipClicked(tabData: TabData.Hashtag, tabPosition: Int, chipPosition: Int) {
|
||||||
val newArguments = tab.arguments.filterIndexed { i, _ -> i != chipPosition }
|
currentTabs[tabPosition] = currentTabs[tabPosition].copy(
|
||||||
val newTab = tab.copy(tabData = tab.tabData.copy(arguments = newArguments))
|
tabData = tabData.copy(
|
||||||
currentTabs[tabPosition] = newTab
|
tags = tabData.tags.filterIndexed { i, _ -> i != chipPosition },
|
||||||
|
),
|
||||||
|
)
|
||||||
saveTabs()
|
saveTabs()
|
||||||
|
|
||||||
currentTabsAdapter.notifyItemChanged(tabPosition)
|
currentTabsAdapter.notifyItemChanged(tabPosition)
|
||||||
|
@ -243,7 +244,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
||||||
onFabDismissedCallback.isEnabled = expand
|
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 frameLayout = FrameLayout(this)
|
||||||
val padding = Utils.dpToPx(this, 8)
|
val padding = Utils.dpToPx(this, 8)
|
||||||
frameLayout.updatePadding(left = padding, right = padding)
|
frameLayout.updatePadding(left = padding, right = padding)
|
||||||
|
@ -259,13 +260,14 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.setPositiveButton(R.string.action_save) { _, _ ->
|
.setPositiveButton(R.string.action_save) { _, _ ->
|
||||||
val input = editText.text.toString().trim()
|
val input = editText.text.toString().trim()
|
||||||
if (tab == null) {
|
if (tabData == null) {
|
||||||
val newTab = TabViewData.from(TabData(TabKind.HASHTAG, listOf(input)))
|
val newTab = TabViewData.from(TabData.Hashtag(listOf(input)))
|
||||||
currentTabs.add(newTab)
|
currentTabs.add(newTab)
|
||||||
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
||||||
} else {
|
} else {
|
||||||
val newTab = tab.copy(tabData = tab.tabData.copy(arguments = tab.arguments + input))
|
currentTabs[tabPosition] = currentTabs[tabPosition].copy(
|
||||||
currentTabs[tabPosition] = newTab
|
tabData = tabData.copy(tags = tabData.tags + input),
|
||||||
|
)
|
||||||
|
|
||||||
currentTabsAdapter.notifyItemChanged(tabPosition)
|
currentTabsAdapter.notifyItemChanged(tabPosition)
|
||||||
}
|
}
|
||||||
|
@ -315,7 +317,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
||||||
.setView(statusLayout)
|
.setView(statusLayout)
|
||||||
.setAdapter(adapter) { _, position ->
|
.setAdapter(adapter) { _, position ->
|
||||||
adapter.getItem(position)?.let { item ->
|
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)
|
currentTabs.add(newTab)
|
||||||
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
||||||
updateAvailableTabs()
|
updateAvailableTabs()
|
||||||
|
@ -377,45 +379,45 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
|
||||||
private fun updateAvailableTabs() {
|
private fun updateAvailableTabs() {
|
||||||
val addableTabs: MutableList<TabViewData> = mutableListOf()
|
val addableTabs: MutableList<TabViewData> = mutableListOf()
|
||||||
|
|
||||||
val homeTab = TabViewData.from(TabData(TabKind.HOME))
|
val homeTab = TabViewData.from(TabData.Home)
|
||||||
if (!currentTabs.contains(homeTab)) {
|
if (!currentTabs.contains(homeTab)) {
|
||||||
addableTabs.add(homeTab)
|
addableTabs.add(homeTab)
|
||||||
}
|
}
|
||||||
val notificationTab = TabViewData.from(TabData(TabKind.NOTIFICATIONS))
|
val notificationTab = TabViewData.from(TabData.Notifications)
|
||||||
if (!currentTabs.contains(notificationTab)) {
|
if (!currentTabs.contains(notificationTab)) {
|
||||||
addableTabs.add(notificationTab)
|
addableTabs.add(notificationTab)
|
||||||
}
|
}
|
||||||
val localTab = TabViewData.from(TabData(TabKind.LOCAL))
|
val localTab = TabViewData.from(TabData.Local)
|
||||||
if (!currentTabs.contains(localTab)) {
|
if (!currentTabs.contains(localTab)) {
|
||||||
addableTabs.add(localTab)
|
addableTabs.add(localTab)
|
||||||
}
|
}
|
||||||
val federatedTab = TabViewData.from(TabData(TabKind.FEDERATED))
|
val federatedTab = TabViewData.from(TabData.Federated)
|
||||||
if (!currentTabs.contains(federatedTab)) {
|
if (!currentTabs.contains(federatedTab)) {
|
||||||
addableTabs.add(federatedTab)
|
addableTabs.add(federatedTab)
|
||||||
}
|
}
|
||||||
val directMessagesTab = TabViewData.from(TabData(TabKind.DIRECT))
|
val directMessagesTab = TabViewData.from(TabData.Direct)
|
||||||
if (!currentTabs.contains(directMessagesTab)) {
|
if (!currentTabs.contains(directMessagesTab)) {
|
||||||
addableTabs.add(directMessagesTab)
|
addableTabs.add(directMessagesTab)
|
||||||
}
|
}
|
||||||
val trendingTagsTab = TabViewData.from(TabData(TabKind.TRENDING_TAGS))
|
val trendingTagsTab = TabViewData.from(TabData.TrendingTags)
|
||||||
if (!currentTabs.contains(trendingTagsTab)) {
|
if (!currentTabs.contains(trendingTagsTab)) {
|
||||||
addableTabs.add(trendingTagsTab)
|
addableTabs.add(trendingTagsTab)
|
||||||
}
|
}
|
||||||
val trendingLinksTab = TabViewData.from(TabData(TabKind.TRENDING_LINKS))
|
val trendingLinksTab = TabViewData.from(TabData.TrendingLinks)
|
||||||
if (!currentTabs.contains(trendingLinksTab)) {
|
if (!currentTabs.contains(trendingLinksTab)) {
|
||||||
addableTabs.add(trendingLinksTab)
|
addableTabs.add(trendingLinksTab)
|
||||||
}
|
}
|
||||||
val trendingStatusesTab = TabViewData.from(TabData(TabKind.TRENDING_STATUSES))
|
val trendingStatusesTab = TabViewData.from(TabData.TrendingStatuses)
|
||||||
if (!currentTabs.contains(trendingStatusesTab)) {
|
if (!currentTabs.contains(trendingStatusesTab)) {
|
||||||
addableTabs.add(trendingStatusesTab)
|
addableTabs.add(trendingStatusesTab)
|
||||||
}
|
}
|
||||||
val bookmarksTab = TabViewData.from(TabData(TabKind.BOOKMARKS))
|
val bookmarksTab = TabViewData.from(TabData.Bookmarks)
|
||||||
if (!currentTabs.contains(trendingTagsTab)) {
|
if (!currentTabs.contains(trendingTagsTab)) {
|
||||||
addableTabs.add(bookmarksTab)
|
addableTabs.add(bookmarksTab)
|
||||||
}
|
}
|
||||||
|
|
||||||
addableTabs.add(TabViewData.from(TabData(TabKind.HASHTAG)))
|
addableTabs.add(TabViewData.from(TabData.Hashtag(emptyList())))
|
||||||
addableTabs.add(TabViewData.from(TabData(TabKind.LIST)))
|
addableTabs.add(TabViewData.from(TabData.UserList("", "")))
|
||||||
|
|
||||||
addTabAdapter.updateData(addableTabs)
|
addTabAdapter.updateData(addableTabs)
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ import app.pachli.components.timeline.TimelineFragment
|
||||||
import app.pachli.components.trending.TrendingLinksFragment
|
import app.pachli.components.trending.TrendingLinksFragment
|
||||||
import app.pachli.components.trending.TrendingTagsFragment
|
import app.pachli.components.trending.TrendingTagsFragment
|
||||||
import app.pachli.core.database.model.TabData
|
import app.pachli.core.database.model.TabData
|
||||||
import app.pachli.core.database.model.TabKind
|
|
||||||
import app.pachli.core.network.model.TimelineKind
|
import app.pachli.core.network.model.TimelineKind
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,13 +42,9 @@ data class TabViewData(
|
||||||
val tabData: TabData,
|
val tabData: TabData,
|
||||||
@StringRes val text: Int,
|
@StringRes val text: Int,
|
||||||
@DrawableRes val icon: Int,
|
@DrawableRes val icon: Int,
|
||||||
val fragment: (List<String>) -> Fragment,
|
val fragment: () -> Fragment,
|
||||||
val title: (Context) -> String = { context -> context.getString(text) },
|
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 {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
@ -64,64 +59,62 @@ data class TabViewData(
|
||||||
override fun hashCode() = tabData.hashCode()
|
override fun hashCode() = tabData.hashCode()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(tabKind: TabKind) = from(TabData.from(tabKind))
|
fun from(tabData: TabData) = when (tabData) {
|
||||||
|
TabData.Home -> TabViewData(
|
||||||
fun from(tabData: TabData) = when (tabData.kind) {
|
|
||||||
TabKind.HOME -> TabViewData(
|
|
||||||
tabData = tabData,
|
tabData = tabData,
|
||||||
text = R.string.title_home,
|
text = R.string.title_home,
|
||||||
icon = R.drawable.ic_home_24dp,
|
icon = R.drawable.ic_home_24dp,
|
||||||
fragment = { TimelineFragment.newInstance(TimelineKind.Home) },
|
fragment = { TimelineFragment.newInstance(TimelineKind.Home) },
|
||||||
)
|
)
|
||||||
TabKind.NOTIFICATIONS -> TabViewData(
|
TabData.Notifications -> TabViewData(
|
||||||
tabData = tabData,
|
tabData = tabData,
|
||||||
text = R.string.title_notifications,
|
text = R.string.title_notifications,
|
||||||
icon = R.drawable.ic_notifications_24dp,
|
icon = R.drawable.ic_notifications_24dp,
|
||||||
fragment = { NotificationsFragment.newInstance() },
|
fragment = { NotificationsFragment.newInstance() },
|
||||||
)
|
)
|
||||||
TabKind.LOCAL -> TabViewData(
|
TabData.Local -> TabViewData(
|
||||||
tabData = tabData,
|
tabData = tabData,
|
||||||
text = R.string.title_public_local,
|
text = R.string.title_public_local,
|
||||||
icon = R.drawable.ic_local_24dp,
|
icon = R.drawable.ic_local_24dp,
|
||||||
fragment = { TimelineFragment.newInstance(TimelineKind.PublicLocal) },
|
fragment = { TimelineFragment.newInstance(TimelineKind.PublicLocal) },
|
||||||
)
|
)
|
||||||
TabKind.FEDERATED -> TabViewData(
|
TabData.Federated -> TabViewData(
|
||||||
tabData = tabData,
|
tabData = tabData,
|
||||||
text = R.string.title_public_federated,
|
text = R.string.title_public_federated,
|
||||||
icon = R.drawable.ic_public_24dp,
|
icon = R.drawable.ic_public_24dp,
|
||||||
fragment = { TimelineFragment.newInstance(TimelineKind.PublicFederated) },
|
fragment = { TimelineFragment.newInstance(TimelineKind.PublicFederated) },
|
||||||
)
|
)
|
||||||
TabKind.DIRECT -> TabViewData(
|
TabData.Direct -> TabViewData(
|
||||||
tabData = tabData,
|
tabData = tabData,
|
||||||
text = R.string.title_direct_messages,
|
text = R.string.title_direct_messages,
|
||||||
icon = R.drawable.ic_reblog_direct_24dp,
|
icon = R.drawable.ic_reblog_direct_24dp,
|
||||||
fragment = { ConversationsFragment.newInstance() },
|
fragment = { ConversationsFragment.newInstance() },
|
||||||
)
|
)
|
||||||
TabKind.TRENDING_TAGS -> TabViewData(
|
TabData.TrendingTags -> TabViewData(
|
||||||
tabData = tabData,
|
tabData = tabData,
|
||||||
text = R.string.title_public_trending_hashtags,
|
text = R.string.title_public_trending_hashtags,
|
||||||
icon = R.drawable.ic_trending_up_24px,
|
icon = R.drawable.ic_trending_up_24px,
|
||||||
fragment = { TrendingTagsFragment.newInstance() },
|
fragment = { TrendingTagsFragment.newInstance() },
|
||||||
)
|
)
|
||||||
TabKind.TRENDING_LINKS -> TabViewData(
|
TabData.TrendingLinks -> TabViewData(
|
||||||
tabData = tabData,
|
tabData = tabData,
|
||||||
text = R.string.title_public_trending_links,
|
text = R.string.title_public_trending_links,
|
||||||
icon = R.drawable.ic_trending_up_24px,
|
icon = R.drawable.ic_trending_up_24px,
|
||||||
fragment = { TrendingLinksFragment.newInstance() },
|
fragment = { TrendingLinksFragment.newInstance() },
|
||||||
)
|
)
|
||||||
TabKind.TRENDING_STATUSES -> TabViewData(
|
TabData.TrendingStatuses -> TabViewData(
|
||||||
tabData = tabData,
|
tabData = tabData,
|
||||||
text = R.string.title_public_trending_statuses,
|
text = R.string.title_public_trending_statuses,
|
||||||
icon = R.drawable.ic_trending_up_24px,
|
icon = R.drawable.ic_trending_up_24px,
|
||||||
fragment = { TimelineFragment.newInstance(TimelineKind.TrendingStatuses) },
|
fragment = { TimelineFragment.newInstance(TimelineKind.TrendingStatuses) },
|
||||||
)
|
)
|
||||||
TabKind.HASHTAG -> TabViewData(
|
is TabData.Hashtag -> TabViewData(
|
||||||
tabData = tabData,
|
tabData = tabData,
|
||||||
text = R.string.hashtags,
|
text = R.string.hashtags,
|
||||||
icon = R.drawable.ic_hashtag,
|
icon = R.drawable.ic_hashtag,
|
||||||
fragment = { args -> TimelineFragment.newInstance(TimelineKind.Tag(args)) },
|
fragment = { TimelineFragment.newInstance(TimelineKind.Tag(tabData.tags)) },
|
||||||
title = { context ->
|
title = { context ->
|
||||||
tabData.arguments.joinToString(separator = " ") {
|
tabData.tags.joinToString(separator = " ") {
|
||||||
context.getString(
|
context.getString(
|
||||||
R.string.title_tag,
|
R.string.title_tag,
|
||||||
it,
|
it,
|
||||||
|
@ -129,18 +122,18 @@ data class TabViewData(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
TabKind.LIST -> TabViewData(
|
is TabData.UserList -> TabViewData(
|
||||||
tabData = tabData,
|
tabData = tabData,
|
||||||
text = R.string.list,
|
text = R.string.list,
|
||||||
icon = R.drawable.ic_list,
|
icon = R.drawable.ic_list,
|
||||||
fragment = { args ->
|
fragment = {
|
||||||
TimelineFragment.newInstance(
|
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,
|
tabData = tabData,
|
||||||
text = R.string.title_bookmarks,
|
text = R.string.title_bookmarks,
|
||||||
icon = R.drawable.ic_bookmark_active_24dp,
|
icon = R.drawable.ic_bookmark_active_24dp,
|
||||||
|
|
|
@ -26,7 +26,7 @@ import app.pachli.R
|
||||||
import app.pachli.TabViewData
|
import app.pachli.TabViewData
|
||||||
import app.pachli.core.common.extensions.hide
|
import app.pachli.core.common.extensions.hide
|
||||||
import app.pachli.core.common.extensions.show
|
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.core.designsystem.R as DR
|
||||||
import app.pachli.databinding.ItemTabPreferenceBinding
|
import app.pachli.databinding.ItemTabPreferenceBinding
|
||||||
import app.pachli.databinding.ItemTabPreferenceSmallBinding
|
import app.pachli.databinding.ItemTabPreferenceSmallBinding
|
||||||
|
@ -39,8 +39,8 @@ interface ItemInteractionListener {
|
||||||
fun onTabRemoved(position: Int)
|
fun onTabRemoved(position: Int)
|
||||||
fun onStartDelete(viewHolder: RecyclerView.ViewHolder)
|
fun onStartDelete(viewHolder: RecyclerView.ViewHolder)
|
||||||
fun onStartDrag(viewHolder: RecyclerView.ViewHolder)
|
fun onStartDrag(viewHolder: RecyclerView.ViewHolder)
|
||||||
fun onActionChipClicked(tab: TabViewData, tabPosition: Int)
|
fun onActionChipClicked(tabData: TabData.Hashtag, tabPosition: Int)
|
||||||
fun onChipClicked(tab: TabViewData, tabPosition: Int, chipPosition: Int)
|
fun onChipClicked(tabData: TabData.Hashtag, tabPosition: Int, chipPosition: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabAdapter(
|
class TabAdapter(
|
||||||
|
@ -81,8 +81,8 @@ class TabAdapter(
|
||||||
} else {
|
} else {
|
||||||
val binding = holder.binding as ItemTabPreferenceBinding
|
val binding = holder.binding as ItemTabPreferenceBinding
|
||||||
|
|
||||||
if (tab.kind == TabKind.LIST) {
|
if (tab.tabData is TabData.UserList) {
|
||||||
binding.textView.text = tab.arguments.getOrNull(1).orEmpty()
|
binding.textView.text = tab.tabData.title
|
||||||
} else {
|
} else {
|
||||||
binding.textView.setText(tab.text)
|
binding.textView.setText(tab.text)
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ class TabAdapter(
|
||||||
(if (removeButtonEnabled) android.R.attr.textColorTertiary else DR.attr.textColorDisabled),
|
(if (removeButtonEnabled) android.R.attr.textColorTertiary else DR.attr.textColorDisabled),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (tab.kind == TabKind.HASHTAG) {
|
if (tab.tabData is TabData.Hashtag) {
|
||||||
binding.chipGroup.show()
|
binding.chipGroup.show()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -115,7 +115,7 @@ class TabAdapter(
|
||||||
* The other dynamic chips are inserted in front of the actionChip.
|
* 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.
|
* 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?
|
val chip = binding.chipGroup.getChildAt(i).takeUnless { it.id == R.id.actionChip } as Chip?
|
||||||
?: Chip(context).apply {
|
?: Chip(context).apply {
|
||||||
|
@ -126,23 +126,23 @@ class TabAdapter(
|
||||||
|
|
||||||
chip.text = arg
|
chip.text = arg
|
||||||
|
|
||||||
if (tab.arguments.size <= 1) {
|
if (tab.tabData.tags.size <= 1) {
|
||||||
chip.isCloseIconVisible = false
|
chip.isCloseIconVisible = false
|
||||||
chip.setOnClickListener(null)
|
chip.setOnClickListener(null)
|
||||||
} else {
|
} else {
|
||||||
chip.isCloseIconVisible = true
|
chip.isCloseIconVisible = true
|
||||||
chip.setOnClickListener {
|
chip.setOnClickListener {
|
||||||
listener.onChipClicked(tab, holder.bindingAdapterPosition, i)
|
listener.onChipClicked(tab.tabData, holder.bindingAdapterPosition, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (binding.chipGroup.size - 1 > tab.arguments.size) {
|
while (binding.chipGroup.size - 1 > tab.tabData.tags.size) {
|
||||||
binding.chipGroup.removeViewAt(tab.arguments.size)
|
binding.chipGroup.removeViewAt(tab.tabData.tags.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.actionChip.setOnClickListener {
|
binding.actionChip.setOnClickListener {
|
||||||
listener.onActionChipClicked(tab, holder.bindingAdapterPosition)
|
listener.onActionChipClicked(tab.tabData, holder.bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.chipGroup.hide()
|
binding.chipGroup.hide()
|
||||||
|
|
|
@ -25,7 +25,7 @@ class MainPagerAdapter(var tabs: List<TabViewData>, activity: FragmentActivity)
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment {
|
override fun createFragment(position: Int): Fragment {
|
||||||
val tab = tabs[position]
|
val tab = tabs[position]
|
||||||
return tab.fragment(tab.arguments)
|
return tab.fragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = tabs.size
|
override fun getItemCount() = tabs.size
|
||||||
|
|
|
@ -31,7 +31,7 @@ import app.pachli.components.notifications.createNotificationChannelsForAccount
|
||||||
import app.pachli.components.notifications.makeNotification
|
import app.pachli.components.notifications.makeNotification
|
||||||
import app.pachli.core.accounts.AccountManager
|
import app.pachli.core.accounts.AccountManager
|
||||||
import app.pachli.core.database.model.AccountEntity
|
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.database.model.defaultTabs
|
||||||
import app.pachli.core.navigation.AccountListActivityIntent
|
import app.pachli.core.navigation.AccountListActivityIntent
|
||||||
import app.pachli.core.network.model.Account
|
import app.pachli.core.network.model.Account
|
||||||
|
@ -154,7 +154,7 @@ class MainActivityTest {
|
||||||
rule.launch(intent)
|
rule.launch(intent)
|
||||||
rule.getScenario().onActivity {
|
rule.getScenario().onActivity {
|
||||||
val currentTab = it.findViewById<ViewPager2>(R.id.viewPager).currentItem
|
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)
|
assertEquals(currentTab, notificationTab)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,4 +39,7 @@ dependencies {
|
||||||
implementation(libs.moshi)
|
implementation(libs.moshi)
|
||||||
implementation(libs.moshi.adapters)
|
implementation(libs.moshi.adapters)
|
||||||
ksp(libs.moshi.codegen)
|
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.Moshi
|
||||||
import com.squareup.moshi.adapter
|
import com.squareup.moshi.adapter
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.net.URLEncoder
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -68,17 +67,47 @@ class Converters @Inject constructor(
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun stringToTabData(str: String?): List<TabData>? {
|
fun stringToTabData(str: String?): List<TabData>? {
|
||||||
return str?.split(";")
|
str ?: return null
|
||||||
?.map {
|
|
||||||
|
// 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 data = it.split(":")
|
||||||
TabData.from(data[0], data.drop(1).map { s -> URLDecoder.decode(s, "UTF-8") })
|
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
|
@TypeConverter
|
||||||
fun tabDataToString(tabData: List<TabData>?): String? {
|
fun tabDataToString(tabData: List<TabData>?): String? {
|
||||||
// List name may include ":"
|
return moshi.adapter<List<TabData>>().toJson(tabData)
|
||||||
return tabData?.joinToString(";") { it.kind.repr + ":" + it.arguments.joinToString(":") { s -> URLEncoder.encode(s, "UTF-8") } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
|
|
|
@ -17,48 +17,54 @@
|
||||||
|
|
||||||
package app.pachli.core.database.model
|
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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A tab's kind.
|
* @property tags List of one or more hashtags (without the leading '#')
|
||||||
*
|
* to show in the tab.
|
||||||
* @param repr String representation of the tab in the database
|
|
||||||
*/
|
*/
|
||||||
enum class TabKind(val repr: String) {
|
@TypeLabel("hashtag")
|
||||||
HOME("Home"),
|
@JsonClass(generateAdapter = true)
|
||||||
NOTIFICATIONS("Notifications"),
|
data class Hashtag(val tags: List<String>) : TabData
|
||||||
LOCAL("Local"),
|
|
||||||
FEDERATED("Federated"),
|
|
||||||
DIRECT("Direct"),
|
|
||||||
TRENDING_TAGS("Trending_Tags"),
|
|
||||||
TRENDING_LINKS("Trending_Links"),
|
|
||||||
TRENDING_STATUSES("Trending_Statuses"),
|
|
||||||
HASHTAG("Hashtag"),
|
|
||||||
LIST("List"),
|
|
||||||
BOOKMARKS("Bookmarks"),
|
|
||||||
}
|
|
||||||
|
|
||||||
/** this would be a good case for a sealed class, but that does not work nice with Room */
|
@TypeLabel("list")
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class UserList(val listId: String, val title: String) : TabData
|
||||||
|
|
||||||
data class TabData(val kind: TabKind, val arguments: List<String> = emptyList()) {
|
@TypeLabel("bookmarks")
|
||||||
companion object {
|
data object Bookmarks : TabData
|
||||||
fun from(kind: TabKind, arguments: List<String> = emptyList()) =
|
|
||||||
TabData(kind, arguments)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun defaultTabs() = listOf(
|
fun defaultTabs() = listOf(
|
||||||
TabData.from(TabKind.HOME),
|
TabData.Home,
|
||||||
TabData.from(TabKind.NOTIFICATIONS),
|
TabData.Notifications,
|
||||||
TabData.from(TabKind.LOCAL),
|
TabData.Local,
|
||||||
TabData.from(TabKind.DIRECT),
|
TabData.Direct,
|
||||||
)
|
)
|
||||||
|
|
|
@ -54,6 +54,7 @@ material-typeface = "4.0.0.2-kotlin"
|
||||||
mockito-inline = "5.2.0"
|
mockito-inline = "5.2.0"
|
||||||
mockito-kotlin = "5.2.1"
|
mockito-kotlin = "5.2.1"
|
||||||
moshi = "1.15.1"
|
moshi = "1.15.1"
|
||||||
|
moshix = "0.25.1"
|
||||||
networkresult-calladapter = "1.0.0"
|
networkresult-calladapter = "1.0.0"
|
||||||
okhttp = "4.12.0"
|
okhttp = "4.12.0"
|
||||||
quadrant = "1.9.1"
|
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 = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
|
||||||
moshi-adapters = { module = "com.squareup.moshi:moshi-adapters", 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" }
|
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" }
|
networkresult-calladapter = { module = "at.connyduck:networkresult-calladapter", version.ref = "networkresult-calladapter" }
|
||||||
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||||
okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
||||||
|
|
Loading…
Reference in New Issue