Refactor "trending hashtags" code (#3595)
- Fix codeformatting - Add new refreshing state - Disable LogConditional lint rule - Update lint-baseline
This commit is contained in:
parent
071e00774e
commit
f23c0cc634
13 changed files with 335 additions and 480 deletions
File diff suppressed because it is too large
Load diff
|
@ -1,71 +0,0 @@
|
|||
/* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Tusky 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 Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.adapter
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.ItemTrendingCellBinding
|
||||
import com.keylesspalace.tusky.entity.TrendingTagHistory
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.util.formatNumber
|
||||
import com.keylesspalace.tusky.viewdata.TrendingViewData
|
||||
|
||||
class TrendingTagViewHolder(
|
||||
private val binding: ItemTrendingCellBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun setup(
|
||||
tagViewData: TrendingViewData.Tag,
|
||||
maxTrendingValue: Long,
|
||||
trendingListener: LinkListener
|
||||
) {
|
||||
val reversedHistory = tagViewData.tag.history.reversed()
|
||||
setGraph(reversedHistory, maxTrendingValue)
|
||||
setTag(tagViewData.tag.name)
|
||||
|
||||
val totalUsage = tagViewData.tag.history.sumOf { it.uses.toLongOrNull() ?: 0 }
|
||||
binding.totalUsage.text = formatNumber(totalUsage)
|
||||
|
||||
val totalAccounts = tagViewData.tag.history.sumOf { it.accounts.toLongOrNull() ?: 0 }
|
||||
binding.totalAccounts.text = formatNumber(totalAccounts)
|
||||
|
||||
binding.currentUsage.text = reversedHistory.last().uses
|
||||
binding.currentAccounts.text = reversedHistory.last().accounts
|
||||
|
||||
itemView.setOnClickListener {
|
||||
trendingListener.onViewTag(tagViewData.tag.name)
|
||||
}
|
||||
|
||||
setAccessibility(totalAccounts, tagViewData.tag.name)
|
||||
}
|
||||
|
||||
private fun setGraph(history: List<TrendingTagHistory>, maxTrendingValue: Long) {
|
||||
binding.graph.maxTrendingValue = maxTrendingValue
|
||||
binding.graph.primaryLineData = history
|
||||
.mapNotNull { it.uses.toLongOrNull() }
|
||||
binding.graph.secondaryLineData = history
|
||||
.mapNotNull { it.accounts.toLongOrNull() }
|
||||
}
|
||||
|
||||
private fun setTag(tag: String) {
|
||||
binding.tag.text = binding.root.context.getString(R.string.title_tag, tag)
|
||||
}
|
||||
|
||||
private fun setAccessibility(totalAccounts: Long, tag: String) {
|
||||
itemView.contentDescription =
|
||||
itemView.context.getString(R.string.accessibility_talking_about_tag, totalAccounts, tag)
|
||||
}
|
||||
}
|
|
@ -19,23 +19,19 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.commit
|
||||
import com.keylesspalace.tusky.BottomSheetActivity
|
||||
import com.keylesspalace.tusky.BaseActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.databinding.ActivityTrendingBinding
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import javax.inject.Inject
|
||||
|
||||
class TrendingActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||
class TrendingActivity : BaseActivity(), HasAndroidInjector {
|
||||
|
||||
@Inject
|
||||
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
|
||||
|
||||
@Inject
|
||||
lateinit var eventHub: EventHub
|
||||
|
||||
private val binding: ActivityTrendingBinding by viewBinding(ActivityTrendingBinding::inflate)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -44,10 +40,8 @@ class TrendingActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
|
||||
val title = getString(R.string.title_public_trending_hashtags)
|
||||
|
||||
supportActionBar?.run {
|
||||
setTitle(title)
|
||||
setTitle(R.string.title_public_trending_hashtags)
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
}
|
||||
|
@ -63,10 +57,6 @@ class TrendingActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
override fun androidInjector() = dispatchingAndroidInjector
|
||||
|
||||
companion object {
|
||||
const val TAG = "TrendingActivity"
|
||||
|
||||
@JvmStatic
|
||||
fun getIntent(context: Context) =
|
||||
Intent(context, TrendingActivity::class.java)
|
||||
fun getIntent(context: Context) = Intent(context, TrendingActivity::class.java)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,15 +20,12 @@ import android.view.ViewGroup
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.adapter.TrendingDateViewHolder
|
||||
import com.keylesspalace.tusky.adapter.TrendingTagViewHolder
|
||||
import com.keylesspalace.tusky.databinding.ItemTrendingCellBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemTrendingDateBinding
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.viewdata.TrendingViewData
|
||||
|
||||
class TrendingAdapter(
|
||||
private val trendingListener: LinkListener
|
||||
private val onViewTag: (String) -> Unit
|
||||
) : ListAdapter<TrendingViewData, RecyclerView.ViewHolder>(TrendingDifferCallback) {
|
||||
|
||||
init {
|
||||
|
@ -42,7 +39,6 @@ class TrendingAdapter(
|
|||
ItemTrendingCellBinding.inflate(LayoutInflater.from(viewGroup.context))
|
||||
TrendingTagViewHolder(binding)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val binding =
|
||||
ItemTrendingDateBinding.inflate(LayoutInflater.from(viewGroup.context))
|
||||
|
@ -52,38 +48,15 @@ class TrendingAdapter(
|
|||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
|
||||
bindViewHolder(viewHolder, position, null)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
position: Int,
|
||||
payloads: List<*>
|
||||
) {
|
||||
bindViewHolder(viewHolder, position, payloads)
|
||||
}
|
||||
|
||||
private fun bindViewHolder(
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
position: Int,
|
||||
payloads: List<*>?
|
||||
) {
|
||||
when (val header = getItem(position)) {
|
||||
when (val viewData = getItem(position)) {
|
||||
is TrendingViewData.Tag -> {
|
||||
val maxTrendingValue = currentList
|
||||
.flatMap { trendingViewData ->
|
||||
trendingViewData.asTagOrNull()?.tag?.history.orEmpty()
|
||||
}
|
||||
.mapNotNull { it.uses.toLongOrNull() }
|
||||
.maxOrNull() ?: 1
|
||||
|
||||
val holder = viewHolder as TrendingTagViewHolder
|
||||
holder.setup(header, maxTrendingValue, trendingListener)
|
||||
holder.setup(viewData, onViewTag)
|
||||
}
|
||||
|
||||
is TrendingViewData.Header -> {
|
||||
val holder = viewHolder as TrendingDateViewHolder
|
||||
holder.setup(header.start, header.end)
|
||||
holder.setup(viewData.start, viewData.end)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,14 +85,7 @@ class TrendingAdapter(
|
|||
oldItem: TrendingViewData,
|
||||
newItem: TrendingViewData
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getChangePayload(
|
||||
oldItem: TrendingViewData,
|
||||
newItem: TrendingViewData
|
||||
): Any? {
|
||||
return null
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.adapter
|
||||
package com.keylesspalace.tusky.components.trending
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
|
@ -15,17 +15,14 @@
|
|||
|
||||
package com.keylesspalace.tusky.components.trending
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
|
||||
|
@ -33,18 +30,14 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
|
||||
import at.connyduck.sparkbutton.helpers.Utils
|
||||
import com.keylesspalace.tusky.BottomSheetActivity
|
||||
import com.keylesspalace.tusky.PostLookupFallbackBehavior
|
||||
import com.keylesspalace.tusky.BaseActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.StatusListActivity
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.components.trending.viewmodel.TrendingViewModel
|
||||
import com.keylesspalace.tusky.databinding.FragmentTrendingBinding
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.interfaces.RefreshableFragment
|
||||
import com.keylesspalace.tusky.interfaces.ReselectableFragment
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
|
@ -56,48 +49,20 @@ import kotlinx.coroutines.launch
|
|||
import javax.inject.Inject
|
||||
|
||||
class TrendingFragment :
|
||||
Fragment(),
|
||||
Fragment(R.layout.fragment_trending),
|
||||
OnRefreshListener,
|
||||
LinkListener,
|
||||
Injectable,
|
||||
ReselectableFragment,
|
||||
RefreshableFragment {
|
||||
|
||||
private lateinit var bottomSheetActivity: BottomSheetActivity
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
@Inject
|
||||
lateinit var accountManager: AccountManager
|
||||
|
||||
@Inject
|
||||
lateinit var eventHub: EventHub
|
||||
|
||||
private val viewModel: TrendingViewModel by lazy {
|
||||
ViewModelProvider(this, viewModelFactory)[TrendingViewModel::class.java]
|
||||
}
|
||||
private val viewModel: TrendingViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private val binding by viewBinding(FragmentTrendingBinding::bind)
|
||||
|
||||
private lateinit var adapter: TrendingAdapter
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
bottomSheetActivity = if (context is BottomSheetActivity) {
|
||||
context
|
||||
} else {
|
||||
throw IllegalStateException("Fragment must be attached to a BottomSheetActivity!")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
adapter = TrendingAdapter(
|
||||
this
|
||||
)
|
||||
}
|
||||
private val adapter = TrendingAdapter(::onViewTag)
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
@ -106,14 +71,6 @@ class TrendingFragment :
|
|||
setupLayoutManager(columnCount)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_trending, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
setupSwipeRefreshLayout()
|
||||
setupRecyclerView()
|
||||
|
@ -175,25 +132,19 @@ class TrendingFragment :
|
|||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
viewModel.invalidate()
|
||||
viewModel.invalidate(true)
|
||||
}
|
||||
|
||||
override fun onViewUrl(url: String) {
|
||||
bottomSheetActivity.viewUrl(url, PostLookupFallbackBehavior.OPEN_IN_BROWSER)
|
||||
}
|
||||
|
||||
override fun onViewTag(tag: String) {
|
||||
bottomSheetActivity.startActivityWithSlideInAnimation(StatusListActivity.newHashtagIntent(requireContext(), tag))
|
||||
}
|
||||
|
||||
override fun onViewAccount(id: String) {
|
||||
bottomSheetActivity.viewAccount(id)
|
||||
fun onViewTag(tag: String) {
|
||||
(requireActivity() as BaseActivity).startActivityWithSlideInAnimation(StatusListActivity.newHashtagIntent(requireContext(), tag))
|
||||
}
|
||||
|
||||
private fun processViewState(uiState: TrendingViewModel.TrendingUiState) {
|
||||
Log.d(TAG, uiState.loadingState.name)
|
||||
when (uiState.loadingState) {
|
||||
TrendingViewModel.LoadingState.INITIAL -> clearLoadingState()
|
||||
TrendingViewModel.LoadingState.LOADING -> applyLoadingState()
|
||||
TrendingViewModel.LoadingState.REFRESHING -> applyRefreshingState()
|
||||
TrendingViewModel.LoadingState.LOADED -> applyLoadedState(uiState.trendingViewData)
|
||||
TrendingViewModel.LoadingState.ERROR_NETWORK -> networkError()
|
||||
TrendingViewModel.LoadingState.ERROR_OTHER -> otherError()
|
||||
|
@ -203,8 +154,9 @@ class TrendingFragment :
|
|||
private fun applyLoadedState(viewData: List<TrendingViewData>) {
|
||||
clearLoadingState()
|
||||
|
||||
adapter.submitList(viewData)
|
||||
|
||||
if (viewData.isEmpty()) {
|
||||
adapter.submitList(emptyList())
|
||||
binding.recyclerView.hide()
|
||||
binding.messageView.show()
|
||||
binding.messageView.setup(
|
||||
|
@ -213,16 +165,16 @@ class TrendingFragment :
|
|||
null
|
||||
)
|
||||
} else {
|
||||
val viewDataWithDates = listOf(viewData.first().asHeaderOrNull()) + viewData
|
||||
|
||||
adapter.submitList(viewDataWithDates)
|
||||
|
||||
binding.recyclerView.show()
|
||||
binding.messageView.hide()
|
||||
}
|
||||
binding.progressBar.hide()
|
||||
}
|
||||
|
||||
private fun applyRefreshingState() {
|
||||
binding.swipeRefreshLayout.isRefreshing = true
|
||||
}
|
||||
|
||||
private fun applyLoadingState() {
|
||||
binding.recyclerView.hide()
|
||||
binding.messageView.hide()
|
||||
|
@ -297,8 +249,6 @@ class TrendingFragment :
|
|||
companion object {
|
||||
private const val TAG = "TrendingFragment"
|
||||
|
||||
fun newInstance(): TrendingFragment {
|
||||
return TrendingFragment()
|
||||
}
|
||||
fun newInstance() = TrendingFragment()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Tusky 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 Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.trending
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.ItemTrendingCellBinding
|
||||
import com.keylesspalace.tusky.util.formatNumber
|
||||
import com.keylesspalace.tusky.viewdata.TrendingViewData
|
||||
|
||||
class TrendingTagViewHolder(
|
||||
private val binding: ItemTrendingCellBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun setup(
|
||||
tagViewData: TrendingViewData.Tag,
|
||||
onViewTag: (String) -> Unit
|
||||
) {
|
||||
binding.tag.text = binding.root.context.getString(R.string.title_tag, tagViewData.name)
|
||||
|
||||
binding.graph.maxTrendingValue = tagViewData.maxTrendingValue
|
||||
binding.graph.primaryLineData = tagViewData.usage
|
||||
binding.graph.secondaryLineData = tagViewData.accounts
|
||||
|
||||
binding.totalUsage.text = formatNumber(tagViewData.usage.sum(), 1000)
|
||||
|
||||
val totalAccounts = tagViewData.accounts.sum()
|
||||
binding.totalAccounts.text = formatNumber(totalAccounts, 1000)
|
||||
|
||||
binding.currentUsage.text = tagViewData.usage.last().toString()
|
||||
binding.currentAccounts.text = tagViewData.usage.last().toString()
|
||||
|
||||
itemView.setOnClickListener {
|
||||
onViewTag(tagViewData.name)
|
||||
}
|
||||
|
||||
itemView.contentDescription =
|
||||
itemView.context.getString(
|
||||
R.string.accessibility_talking_about_tag,
|
||||
totalAccounts,
|
||||
tagViewData.name
|
||||
)
|
||||
}
|
||||
}
|
|
@ -15,11 +15,15 @@
|
|||
|
||||
package com.keylesspalace.tusky.components.trending.viewmodel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import at.connyduck.calladapter.networkresult.fold
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.entity.end
|
||||
import com.keylesspalace.tusky.entity.start
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.toViewData
|
||||
import com.keylesspalace.tusky.viewdata.TrendingViewData
|
||||
|
@ -28,7 +32,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.launch
|
||||
import okio.IOException
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
class TrendingViewModel @Inject constructor(
|
||||
|
@ -36,7 +40,7 @@ class TrendingViewModel @Inject constructor(
|
|||
private val eventHub: EventHub
|
||||
) : ViewModel() {
|
||||
enum class LoadingState {
|
||||
INITIAL, LOADING, LOADED, ERROR_NETWORK, ERROR_OTHER
|
||||
INITIAL, LOADING, REFRESHING, LOADED, ERROR_NETWORK, ERROR_OTHER
|
||||
}
|
||||
|
||||
data class TrendingUiState(
|
||||
|
@ -67,37 +71,43 @@ class TrendingViewModel @Inject constructor(
|
|||
*
|
||||
* A tag is excluded if it is filtered by the user on their home timeline.
|
||||
*/
|
||||
fun invalidate() = viewModelScope.launch {
|
||||
_uiState.value = TrendingUiState(emptyList(), LoadingState.LOADING)
|
||||
|
||||
try {
|
||||
val deferredFilters = async { mastodonApi.getFilters() }
|
||||
val response = mastodonApi.trendingTags()
|
||||
if (!response.isSuccessful) {
|
||||
_uiState.value = TrendingUiState(emptyList(), LoadingState.ERROR_NETWORK)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val homeFilters = deferredFilters.await().getOrNull()?.filter {
|
||||
it.context.contains(Filter.Kind.HOME.kind)
|
||||
}
|
||||
|
||||
val tags = response.body()!!
|
||||
.filter {
|
||||
homeFilters?.none { filter ->
|
||||
filter.keywords.any { keyword -> keyword.keyword.equals(it.name, ignoreCase = true) }
|
||||
} ?: false
|
||||
}
|
||||
.sortedBy { tag -> tag.history.sumOf { it.uses.toLongOrNull() ?: 0 } }
|
||||
.map { it.toViewData() }
|
||||
.asReversed()
|
||||
|
||||
_uiState.value = TrendingUiState(tags, LoadingState.LOADED)
|
||||
} catch (e: IOException) {
|
||||
_uiState.value = TrendingUiState(emptyList(), LoadingState.ERROR_NETWORK)
|
||||
} catch (e: Exception) {
|
||||
_uiState.value = TrendingUiState(emptyList(), LoadingState.ERROR_OTHER)
|
||||
fun invalidate(refresh: Boolean = false) = viewModelScope.launch {
|
||||
if (refresh) {
|
||||
_uiState.value = TrendingUiState(emptyList(), LoadingState.REFRESHING)
|
||||
} else {
|
||||
_uiState.value = TrendingUiState(emptyList(), LoadingState.LOADING)
|
||||
}
|
||||
|
||||
val deferredFilters = async { mastodonApi.getFilters() }
|
||||
|
||||
mastodonApi.trendingTags().fold(
|
||||
{ tagResponse ->
|
||||
val homeFilters = deferredFilters.await().getOrNull()?.filter { filter ->
|
||||
filter.context.contains(Filter.Kind.HOME.kind)
|
||||
}
|
||||
val tags = tagResponse
|
||||
.filter { tag ->
|
||||
homeFilters?.none { filter ->
|
||||
filter.keywords.any { keyword -> keyword.keyword.equals(tag.name, ignoreCase = true) }
|
||||
} ?: false
|
||||
}
|
||||
.sortedByDescending { tag -> tag.history.sumOf { it.uses.toLongOrNull() ?: 0 } }
|
||||
.toViewData()
|
||||
|
||||
val firstTag = tagResponse.first()
|
||||
val header = TrendingViewData.Header(firstTag.start(), firstTag.end())
|
||||
|
||||
_uiState.value = TrendingUiState(listOf(header) + tags, LoadingState.LOADED)
|
||||
},
|
||||
{ error ->
|
||||
Log.w(TAG, "failed loading trending tags", error)
|
||||
if (error is IOException) {
|
||||
_uiState.value = TrendingUiState(emptyList(), LoadingState.ERROR_NETWORK)
|
||||
} else {
|
||||
_uiState.value = TrendingUiState(emptyList(), LoadingState.ERROR_OTHER)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -21,15 +21,13 @@ import java.util.Date
|
|||
* Mastodon API Documentation: https://docs.joinmastodon.org/methods/trends/#tags
|
||||
*
|
||||
* @param name The name of the hashtag (after the #). The "caturday" in "#caturday".
|
||||
* @param url The URL to your mastodon instance list for this hashtag.
|
||||
* (@param url The URL to your mastodon instance list for this hashtag.)
|
||||
* @param history A list of [TrendingTagHistory]. Each element contains metrics per day for this hashtag.
|
||||
* @param following This is not listed in the APIs at the time of writing, but an instance is delivering it.
|
||||
* (@param following This is not listed in the APIs at the time of writing, but an instance is delivering it.)
|
||||
*/
|
||||
data class TrendingTag(
|
||||
val name: String,
|
||||
val url: String,
|
||||
val history: List<TrendingTagHistory>,
|
||||
val following: Boolean
|
||||
val history: List<TrendingTagHistory>
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
|
@ -782,5 +782,5 @@ interface MastodonApi {
|
|||
suspend fun unfollowTag(@Path("name") name: String): NetworkResult<HashTag>
|
||||
|
||||
@GET("api/v1/trends/tags")
|
||||
suspend fun trendingTags(): Response<List<TrendingTag>>
|
||||
suspend fun trendingTags(): NetworkResult<List<TrendingTag>>
|
||||
}
|
||||
|
|
|
@ -40,7 +40,6 @@ import com.keylesspalace.tusky.viewdata.NotificationViewData
|
|||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import com.keylesspalace.tusky.viewdata.TrendingViewData
|
||||
|
||||
@JvmName("statusToViewData")
|
||||
fun Status.toViewData(
|
||||
isShowingContent: Boolean,
|
||||
isExpanded: Boolean,
|
||||
|
@ -56,7 +55,6 @@ fun Status.toViewData(
|
|||
)
|
||||
}
|
||||
|
||||
@JvmName("notificationToViewData")
|
||||
fun Notification.toViewData(
|
||||
isShowingContent: Boolean,
|
||||
isExpanded: Boolean,
|
||||
|
@ -71,9 +69,20 @@ fun Notification.toViewData(
|
|||
)
|
||||
}
|
||||
|
||||
@JvmName("tagToViewData")
|
||||
fun TrendingTag.toViewData(): TrendingViewData.Tag {
|
||||
return TrendingViewData.Tag(
|
||||
tag = this
|
||||
)
|
||||
fun List<TrendingTag>.toViewData(): List<TrendingViewData.Tag> {
|
||||
val maxTrendingValue = flatMap { tag -> tag.history }
|
||||
.mapNotNull { it.uses.toLongOrNull() }
|
||||
.maxOrNull() ?: 1
|
||||
|
||||
return map { tag ->
|
||||
|
||||
val reversedHistory = tag.history.asReversed()
|
||||
|
||||
TrendingViewData.Tag(
|
||||
name = tag.name,
|
||||
usage = reversedHistory.mapNotNull { it.uses.toLongOrNull() },
|
||||
accounts = reversedHistory.mapNotNull { it.accounts.toLongOrNull() },
|
||||
maxTrendingValue = maxTrendingValue
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,10 +22,9 @@ import android.graphics.Path
|
|||
import android.graphics.PathMeasure
|
||||
import android.graphics.Rect
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.Dimension
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.use
|
||||
import com.keylesspalace.tusky.R
|
||||
import kotlin.math.max
|
||||
|
@ -33,9 +32,8 @@ import kotlin.math.max
|
|||
class GraphView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = 0
|
||||
) : AppCompatImageView(context, attrs, defStyleAttr) {
|
||||
defStyleAttr: Int = 0
|
||||
) : View(context, attrs, defStyleAttr) {
|
||||
@get:ColorInt
|
||||
@ColorInt
|
||||
var primaryLineColor = 0
|
||||
|
@ -55,7 +53,7 @@ class GraphView @JvmOverloads constructor(
|
|||
@ColorInt
|
||||
var metaColor = 0
|
||||
|
||||
var proportionalTrending = false
|
||||
private var proportionalTrending = false
|
||||
|
||||
private lateinit var primaryLinePaint: Paint
|
||||
private lateinit var secondaryLinePaint: Paint
|
||||
|
@ -129,16 +127,14 @@ class GraphView @JvmOverloads constructor(
|
|||
|
||||
private fun initFromXML(attr: AttributeSet?) {
|
||||
context.obtainStyledAttributes(attr, R.styleable.GraphView).use { a ->
|
||||
primaryLineColor = ContextCompat.getColor(
|
||||
context,
|
||||
primaryLineColor = context.getColor(
|
||||
a.getResourceId(
|
||||
R.styleable.GraphView_primaryLineColor,
|
||||
R.color.tusky_blue
|
||||
)
|
||||
)
|
||||
|
||||
secondaryLineColor = ContextCompat.getColor(
|
||||
context,
|
||||
secondaryLineColor = context.getColor(
|
||||
a.getResourceId(
|
||||
R.styleable.GraphView_secondaryLineColor,
|
||||
R.color.tusky_red
|
||||
|
@ -150,16 +146,14 @@ class GraphView @JvmOverloads constructor(
|
|||
R.dimen.graph_line_thickness
|
||||
).toFloat()
|
||||
|
||||
graphColor = ContextCompat.getColor(
|
||||
context,
|
||||
graphColor = context.getColor(
|
||||
a.getResourceId(
|
||||
R.styleable.GraphView_graphColor,
|
||||
R.color.colorBackground
|
||||
)
|
||||
)
|
||||
|
||||
metaColor = ContextCompat.getColor(
|
||||
context,
|
||||
metaColor = context.getColor(
|
||||
a.getResourceId(
|
||||
R.styleable.GraphView_metaColor,
|
||||
R.color.dividerColor
|
||||
|
|
|
@ -15,9 +15,6 @@
|
|||
|
||||
package com.keylesspalace.tusky.viewdata
|
||||
|
||||
import com.keylesspalace.tusky.entity.TrendingTag
|
||||
import com.keylesspalace.tusky.entity.end
|
||||
import com.keylesspalace.tusky.entity.start
|
||||
import java.util.Date
|
||||
|
||||
sealed class TrendingViewData {
|
||||
|
@ -31,18 +28,13 @@ sealed class TrendingViewData {
|
|||
get() = start.toString() + end.toString()
|
||||
}
|
||||
|
||||
fun asHeaderOrNull(): Header? {
|
||||
val tag = (this as? Tag)?.tag
|
||||
?: return null
|
||||
return Header(tag.start(), tag.end())
|
||||
}
|
||||
|
||||
data class Tag(
|
||||
val tag: TrendingTag
|
||||
val name: String,
|
||||
val usage: List<Long>,
|
||||
val accounts: List<Long>,
|
||||
val maxTrendingValue: Long
|
||||
) : TrendingViewData() {
|
||||
override val id: String
|
||||
get() = tag.name
|
||||
get() = name
|
||||
}
|
||||
|
||||
fun asTagOrNull() = this as? Tag
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue