Add "Trending posts" (statuses) feed (#4007)
Add "Trending posts" (statuses) feed. This feed is a good source of interesting accounts to follow and, personally, a sort of "Front page of the Fediverse". Since #3908 and #3910 (which would provide a more thorough, albeit complex, access to trending things) won't get merged, I'd like to address this missing feed by simply adding another tab/feed. ~~If desired, I can move the second commit (fixing lint) to another PR.~~ ## Screenshots ### Tab <img src="https://github.com/tuskyapp/Tusky/assets/1063155/6a71a97e-673e-44c7-b67d-9b1df0bed4f5" width=320 /> <img src="https://github.com/tuskyapp/Tusky/assets/1063155/9bf60b23-d2f3-4dd8-8af6-e96647b02121" width=320 /> ### Activity <img src="https://github.com/tuskyapp/Tusky/assets/1063155/4e07dea3-d97f-42c6-8551-492a3116fcfa" width=320 /> <img src="https://github.com/tuskyapp/Tusky/assets/1063155/ad00a134-d622-43f4-8305-84cfa7fed706" width=320 />
This commit is contained in:
parent
bb1868fd67
commit
fa80a0123a
12 changed files with 148 additions and 11 deletions
|
|
@ -292,7 +292,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
setupDrawer(
|
||||
savedInstanceState,
|
||||
addSearchButton = hideTopToolbar,
|
||||
addTrendingTagsButton = !accountManager.activeAccount!!.tabPreferences.hasTab(TRENDING_TAGS)
|
||||
addTrendingTagsButton = !accountManager.activeAccount!!.tabPreferences.hasTab(TRENDING_TAGS),
|
||||
addTrendingStatusesButton = !accountManager.activeAccount!!.tabPreferences.hasTab(TRENDING_STATUSES),
|
||||
)
|
||||
|
||||
/* Fetch user info while we're doing other things. This has to be done after setting up the
|
||||
|
|
@ -317,7 +318,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
is MainTabsChangedEvent -> {
|
||||
refreshMainDrawerItems(
|
||||
addSearchButton = hideTopToolbar,
|
||||
addTrendingTagsButton = !event.newTabs.hasTab(TRENDING_TAGS)
|
||||
addTrendingTagsButton = !event.newTabs.hasTab(TRENDING_TAGS),
|
||||
addTrendingStatusesButton = !event.newTabs.hasTab(TRENDING_STATUSES),
|
||||
)
|
||||
|
||||
setupTabs(false)
|
||||
|
|
@ -482,7 +484,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
private fun setupDrawer(
|
||||
savedInstanceState: Bundle?,
|
||||
addSearchButton: Boolean,
|
||||
addTrendingTagsButton: Boolean
|
||||
addTrendingTagsButton: Boolean,
|
||||
addTrendingStatusesButton: Boolean,
|
||||
) {
|
||||
val drawerOpenClickListener = View.OnClickListener { binding.mainDrawerLayout.open() }
|
||||
|
||||
|
|
@ -543,12 +546,20 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
})
|
||||
|
||||
binding.mainDrawer.apply {
|
||||
refreshMainDrawerItems(addSearchButton, addTrendingTagsButton)
|
||||
refreshMainDrawerItems(
|
||||
addSearchButton = addSearchButton,
|
||||
addTrendingTagsButton = addTrendingTagsButton,
|
||||
addTrendingStatusesButton = addTrendingStatusesButton,
|
||||
)
|
||||
setSavedInstance(savedInstanceState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshMainDrawerItems(addSearchButton: Boolean, addTrendingTagsButton: Boolean) {
|
||||
private fun refreshMainDrawerItems(
|
||||
addSearchButton: Boolean,
|
||||
addTrendingTagsButton: Boolean,
|
||||
addTrendingStatusesButton: Boolean,
|
||||
) {
|
||||
binding.mainDrawer.apply {
|
||||
itemAdapter.clear()
|
||||
tintStatusBar = true
|
||||
|
|
@ -677,6 +688,19 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (addTrendingStatusesButton) {
|
||||
binding.mainDrawer.addItemsAtPosition(
|
||||
6,
|
||||
primaryDrawerItem {
|
||||
nameRes = R.string.title_public_trending_statuses
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_local_fire_department
|
||||
onClick = {
|
||||
startActivityWithSlideInAnimation(StatusListActivity.newTrendingIntent(context))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
Kind.FAVOURITES -> getString(R.string.title_favourites)
|
||||
Kind.BOOKMARKS -> getString(R.string.title_bookmarks)
|
||||
Kind.TAG -> getString(R.string.title_tag).format(hashtag)
|
||||
Kind.PUBLIC_TRENDING_STATUSES -> getString(R.string.title_public_trending_statuses)
|
||||
else -> intent.getStringExtra(EXTRA_LIST_TITLE)
|
||||
}
|
||||
|
||||
|
|
@ -383,5 +384,10 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
putExtra(EXTRA_KIND, Kind.TAG.name)
|
||||
putExtra(EXTRA_HASHTAG, hashtag)
|
||||
}
|
||||
|
||||
fun newTrendingIntent(context: Context) =
|
||||
Intent(context, StatusListActivity::class.java).apply {
|
||||
putExtra(EXTRA_KIND, Kind.PUBLIC_TRENDING_STATUSES.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const val LOCAL = "Local"
|
|||
const val FEDERATED = "Federated"
|
||||
const val DIRECT = "Direct"
|
||||
const val TRENDING_TAGS = "TrendingTags"
|
||||
const val TRENDING_STATUSES = "TrendingStatuses"
|
||||
const val HASHTAG = "Hashtag"
|
||||
const val LIST = "List"
|
||||
const val BOOKMARKS = "Bookmarks"
|
||||
|
|
@ -99,6 +100,12 @@ fun createTabDataFromId(id: String, arguments: List<String> = emptyList()): TabD
|
|||
icon = R.drawable.ic_trending_up_24px,
|
||||
fragment = { TrendingTagsFragment.newInstance() }
|
||||
)
|
||||
TRENDING_STATUSES -> TabData(
|
||||
id = TRENDING_STATUSES,
|
||||
text = R.string.title_public_trending_statuses,
|
||||
icon = R.drawable.ic_hot_24dp,
|
||||
fragment = { TimelineFragment.newInstance(TimelineViewModel.Kind.PUBLIC_TRENDING_STATUSES) }
|
||||
)
|
||||
HASHTAG -> TabData(
|
||||
id = HASHTAG,
|
||||
text = R.string.hashtags,
|
||||
|
|
|
|||
|
|
@ -386,6 +386,10 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
if (!currentTabs.contains(trendingTagsTab)) {
|
||||
addableTabs.add(bookmarksTab)
|
||||
}
|
||||
val trendingStatusesTab = createTabDataFromId(TRENDING_STATUSES)
|
||||
if (!currentTabs.contains(trendingStatusesTab)) {
|
||||
addableTabs.add(trendingStatusesTab)
|
||||
}
|
||||
|
||||
addableTabs.add(createTabDataFromId(HASHTAG))
|
||||
addableTabs.add(createTabDataFromId(LIST))
|
||||
|
|
|
|||
|
|
@ -540,7 +540,8 @@ class TimelineFragment :
|
|||
when (kind) {
|
||||
TimelineViewModel.Kind.HOME,
|
||||
TimelineViewModel.Kind.PUBLIC_FEDERATED,
|
||||
TimelineViewModel.Kind.PUBLIC_LOCAL -> adapter.refresh()
|
||||
TimelineViewModel.Kind.PUBLIC_LOCAL,
|
||||
TimelineViewModel.Kind.PUBLIC_TRENDING_STATUSES -> adapter.refresh()
|
||||
TimelineViewModel.Kind.USER,
|
||||
TimelineViewModel.Kind.USER_WITH_REPLIES -> if (status.account.id == viewModel.id) {
|
||||
adapter.refresh()
|
||||
|
|
|
|||
|
|
@ -33,6 +33,14 @@ class NetworkTimelineRemoteMediator(
|
|||
private val viewModel: NetworkTimelineViewModel
|
||||
) : RemoteMediator<String, StatusViewData>() {
|
||||
|
||||
private val statusIds = mutableSetOf<String>()
|
||||
|
||||
init {
|
||||
if (viewModel.kind == TimelineViewModel.Kind.PUBLIC_TRENDING_STATUSES) {
|
||||
statusIds.addAll(viewModel.statusData.map { it.id })
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(
|
||||
loadType: LoadType,
|
||||
state: PagingState<String, StatusViewData>
|
||||
|
|
@ -88,6 +96,10 @@ class NetworkTimelineRemoteMediator(
|
|||
false
|
||||
}
|
||||
|
||||
if (viewModel.kind == TimelineViewModel.Kind.PUBLIC_TRENDING_STATUSES) {
|
||||
statusIds.addAll(data.map { it.id })
|
||||
}
|
||||
|
||||
viewModel.statusData.addAll(0, data)
|
||||
|
||||
if (insertPlaceholder) {
|
||||
|
|
@ -96,11 +108,22 @@ class NetworkTimelineRemoteMediator(
|
|||
} else {
|
||||
val linkHeader = statusResponse.headers()["Link"]
|
||||
val links = HttpHeaderLink.parse(linkHeader)
|
||||
val nextId = HttpHeaderLink.findByRelationType(links, "next")?.uri?.getQueryParameter("max_id")
|
||||
val next = HttpHeaderLink.findByRelationType(links, "next")
|
||||
|
||||
viewModel.nextKey = nextId
|
||||
var filteredData = data
|
||||
if (viewModel.kind == TimelineViewModel.Kind.PUBLIC_TRENDING_STATUSES) {
|
||||
// Trending statuses use offset for paging, not IDs. If a new status has been added to the remote
|
||||
// feed after we performed the initial fetch, then the feed will have moved, but our offset won't.
|
||||
// As a result, we'd get repeat statuses. This addresses that.
|
||||
filteredData = data.filter { !statusIds.contains(it.id) }
|
||||
statusIds.addAll(filteredData.map { it.id })
|
||||
|
||||
viewModel.statusData.addAll(data)
|
||||
viewModel.nextKey = next?.uri?.getQueryParameter("offset")
|
||||
} else {
|
||||
viewModel.nextKey = next?.uri?.getQueryParameter("max_id")
|
||||
}
|
||||
|
||||
viewModel.statusData.addAll(filteredData)
|
||||
}
|
||||
|
||||
viewModel.currentSource?.invalidate()
|
||||
|
|
|
|||
|
|
@ -308,6 +308,7 @@ class NetworkTimelineViewModel @Inject constructor(
|
|||
Kind.FAVOURITES -> api.favourites(fromId, uptoId, limit)
|
||||
Kind.BOOKMARKS -> api.bookmarks(fromId, uptoId, limit)
|
||||
Kind.LIST -> api.listTimeline(id!!, fromId, uptoId, limit)
|
||||
Kind.PUBLIC_TRENDING_STATUSES -> api.trendingStatuses(limit = limit, offset = fromId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -321,12 +321,12 @@ abstract class TimelineViewModel(
|
|||
}
|
||||
|
||||
enum class Kind {
|
||||
HOME, PUBLIC_LOCAL, PUBLIC_FEDERATED, TAG, USER, USER_PINNED, USER_WITH_REPLIES, FAVOURITES, LIST, BOOKMARKS;
|
||||
HOME, PUBLIC_LOCAL, PUBLIC_FEDERATED, TAG, USER, USER_PINNED, USER_WITH_REPLIES, FAVOURITES, LIST, BOOKMARKS, PUBLIC_TRENDING_STATUSES;
|
||||
|
||||
fun toFilterKind(): Filter.Kind {
|
||||
return when (valueOf(name)) {
|
||||
HOME, LIST -> Filter.Kind.HOME
|
||||
PUBLIC_FEDERATED, PUBLIC_LOCAL, TAG, FAVOURITES -> Filter.Kind.PUBLIC
|
||||
PUBLIC_FEDERATED, PUBLIC_LOCAL, TAG, FAVOURITES, PUBLIC_TRENDING_STATUSES -> Filter.Kind.PUBLIC
|
||||
USER, USER_WITH_REPLIES, USER_PINNED -> Filter.Kind.ACCOUNT
|
||||
else -> Filter.Kind.PUBLIC
|
||||
}
|
||||
|
|
|
|||
|
|
@ -848,4 +848,10 @@ interface MastodonApi {
|
|||
|
||||
@GET("api/v1/trends/tags")
|
||||
suspend fun trendingTags(): NetworkResult<List<TrendingTag>>
|
||||
|
||||
@GET("api/v1/trends/statuses")
|
||||
suspend fun trendingStatuses(
|
||||
@Query("limit") limit: Int? = null,
|
||||
@Query("offset") offset: String? = null
|
||||
): Response<List<Status>>
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue