Yuito-app-android/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingFragment.kt

255 lines
8.8 KiB
Kotlin

/* 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 android.content.res.Configuration
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.accessibility.AccessibilityManager
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
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.BaseActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.StatusListActivity
import com.keylesspalace.tusky.components.trending.viewmodel.TrendingViewModel
import com.keylesspalace.tusky.databinding.FragmentTrendingBinding
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
import com.keylesspalace.tusky.interfaces.RefreshableFragment
import com.keylesspalace.tusky.interfaces.ReselectableFragment
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.viewdata.TrendingViewData
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import javax.inject.Inject
class TrendingFragment :
Fragment(R.layout.fragment_trending),
OnRefreshListener,
Injectable,
ReselectableFragment,
RefreshableFragment {
@Inject
lateinit var viewModelFactory: ViewModelFactory
private val viewModel: TrendingViewModel by viewModels { viewModelFactory }
private val binding by viewBinding(FragmentTrendingBinding::bind)
private val adapter = TrendingAdapter(::onViewTag)
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val columnCount =
requireContext().resources.getInteger(R.integer.trending_column_count)
setupLayoutManager(columnCount)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setupSwipeRefreshLayout()
setupRecyclerView()
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == 0 && adapter.itemCount != itemCount) {
binding.recyclerView.post {
if (getView() != null) {
binding.recyclerView.scrollBy(
0,
Utils.dpToPx(requireContext(), -30)
)
}
}
}
}
})
viewLifecycleOwner.lifecycleScope.launch {
viewModel.uiState.collectLatest { trendingState ->
processViewState(trendingState)
}
}
if (activity is ActionButtonActivity) {
(activity as ActionButtonActivity).actionButton?.visibility = View.GONE
}
}
private fun setupSwipeRefreshLayout() {
binding.swipeRefreshLayout.setOnRefreshListener(this)
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
}
private fun setupLayoutManager(columnCount: Int) {
binding.recyclerView.layoutManager = GridLayoutManager(context, columnCount).apply {
spanSizeLookup = object : SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when (adapter.getItemViewType(position)) {
TrendingAdapter.VIEW_TYPE_HEADER -> columnCount
TrendingAdapter.VIEW_TYPE_TAG -> 1
else -> -1
}
}
}
}
}
private fun setupRecyclerView() {
val columnCount =
requireContext().resources.getInteger(R.integer.trending_column_count)
setupLayoutManager(columnCount)
binding.recyclerView.setHasFixedSize(true)
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
binding.recyclerView.adapter = adapter
}
override fun onRefresh() {
viewModel.invalidate(true)
}
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()
}
}
private fun applyLoadedState(viewData: List<TrendingViewData>) {
clearLoadingState()
adapter.submitList(viewData)
if (viewData.isEmpty()) {
binding.recyclerView.hide()
binding.messageView.show()
binding.messageView.setup(
R.drawable.elephant_friend_empty,
R.string.message_empty,
null
)
} else {
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()
binding.progressBar.show()
}
private fun clearLoadingState() {
binding.swipeRefreshLayout.isRefreshing = false
binding.progressBar.hide()
binding.messageView.hide()
}
private fun networkError() {
binding.recyclerView.hide()
binding.messageView.show()
binding.progressBar.hide()
binding.swipeRefreshLayout.isRefreshing = false
binding.messageView.setup(
R.drawable.elephant_offline,
R.string.error_network
) { refreshContent() }
}
private fun otherError() {
binding.recyclerView.hide()
binding.messageView.show()
binding.progressBar.hide()
binding.swipeRefreshLayout.isRefreshing = false
binding.messageView.setup(
R.drawable.elephant_error,
R.string.error_generic
) { refreshContent() }
}
private fun actionButtonPresent(): Boolean {
return activity is ActionButtonActivity
}
private var talkBackWasEnabled = false
override fun onResume() {
super.onResume()
val a11yManager =
ContextCompat.getSystemService(requireContext(), AccessibilityManager::class.java)
val wasEnabled = talkBackWasEnabled
talkBackWasEnabled = a11yManager?.isEnabled == true
Log.d(TAG, "talkback was enabled: $wasEnabled, now $talkBackWasEnabled")
if (talkBackWasEnabled && !wasEnabled) {
adapter.notifyItemRangeChanged(0, adapter.itemCount)
}
if (actionButtonPresent()) {
val composeButton = (activity as ActionButtonActivity).actionButton
composeButton?.hide()
}
}
override fun onReselect() {
if (isAdded) {
binding.recyclerView.layoutManager?.scrollToPosition(0)
binding.recyclerView.stopScroll()
}
}
override fun refreshContent() {
onRefresh()
}
companion object {
private const val TAG = "TrendingFragment"
fun newInstance() = TrendingFragment()
}
}