ReportStatusesFragment.kt 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /* Copyright 2019 Joel Pyska
  2. *
  3. * This file is a part of Tusky.
  4. *
  5. * This program is free software; you can redistribute it and/or modify it under the terms of the
  6. * GNU General Public License as published by the Free Software Foundation; either version 3 of the
  7. * License, or (at your option) any later version.
  8. *
  9. * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
  10. * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
  11. * Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License along with Tusky; if not,
  14. * see <http://www.gnu.org/licenses>. */
  15. package com.keylesspalace.tusky.components.report.fragments
  16. import android.os.Bundle
  17. import android.view.Menu
  18. import android.view.MenuInflater
  19. import android.view.MenuItem
  20. import android.view.View
  21. import androidx.core.app.ActivityOptionsCompat
  22. import androidx.core.view.MenuProvider
  23. import androidx.core.view.ViewCompat
  24. import androidx.fragment.app.Fragment
  25. import androidx.fragment.app.activityViewModels
  26. import androidx.lifecycle.Lifecycle
  27. import androidx.lifecycle.lifecycleScope
  28. import androidx.paging.LoadState
  29. import androidx.preference.PreferenceManager
  30. import androidx.recyclerview.widget.DividerItemDecoration
  31. import androidx.recyclerview.widget.LinearLayoutManager
  32. import androidx.recyclerview.widget.SimpleItemAnimator
  33. import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
  34. import com.google.android.material.color.MaterialColors
  35. import com.google.android.material.snackbar.Snackbar
  36. import com.keylesspalace.tusky.R
  37. import com.keylesspalace.tusky.StatusListActivity
  38. import com.keylesspalace.tusky.ViewMediaActivity
  39. import com.keylesspalace.tusky.components.account.AccountActivity
  40. import com.keylesspalace.tusky.components.report.ReportViewModel
  41. import com.keylesspalace.tusky.components.report.Screen
  42. import com.keylesspalace.tusky.components.report.adapter.AdapterHandler
  43. import com.keylesspalace.tusky.components.report.adapter.StatusesAdapter
  44. import com.keylesspalace.tusky.databinding.FragmentReportStatusesBinding
  45. import com.keylesspalace.tusky.db.AccountManager
  46. import com.keylesspalace.tusky.di.Injectable
  47. import com.keylesspalace.tusky.di.ViewModelFactory
  48. import com.keylesspalace.tusky.entity.Attachment
  49. import com.keylesspalace.tusky.entity.Status
  50. import com.keylesspalace.tusky.settings.PrefKeys
  51. import com.keylesspalace.tusky.util.CardViewMode
  52. import com.keylesspalace.tusky.util.StatusDisplayOptions
  53. import com.keylesspalace.tusky.util.viewBinding
  54. import com.keylesspalace.tusky.util.visible
  55. import com.keylesspalace.tusky.viewdata.AttachmentViewData
  56. import com.mikepenz.iconics.IconicsDrawable
  57. import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
  58. import com.mikepenz.iconics.utils.colorInt
  59. import com.mikepenz.iconics.utils.sizeDp
  60. import kotlinx.coroutines.flow.collectLatest
  61. import kotlinx.coroutines.launch
  62. import javax.inject.Inject
  63. class ReportStatusesFragment :
  64. Fragment(R.layout.fragment_report_statuses),
  65. Injectable,
  66. OnRefreshListener,
  67. MenuProvider,
  68. AdapterHandler {
  69. @Inject
  70. lateinit var viewModelFactory: ViewModelFactory
  71. @Inject
  72. lateinit var accountManager: AccountManager
  73. private val viewModel: ReportViewModel by activityViewModels { viewModelFactory }
  74. private val binding by viewBinding(FragmentReportStatusesBinding::bind)
  75. private lateinit var adapter: StatusesAdapter
  76. private var snackbarErrorRetry: Snackbar? = null
  77. override fun showMedia(v: View?, status: Status?, idx: Int) {
  78. status?.actionableStatus?.let { actionable ->
  79. when (actionable.attachments[idx].type) {
  80. Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
  81. val attachments = AttachmentViewData.list(actionable)
  82. val intent = ViewMediaActivity.newIntent(context, attachments, idx)
  83. if (v != null) {
  84. val url = actionable.attachments[idx].url
  85. ViewCompat.setTransitionName(v, url)
  86. val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), v, url)
  87. startActivity(intent, options.toBundle())
  88. } else {
  89. startActivity(intent)
  90. }
  91. }
  92. Attachment.Type.UNKNOWN -> {
  93. }
  94. }
  95. }
  96. }
  97. override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  98. requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
  99. handleClicks()
  100. initStatusesView()
  101. setupSwipeRefreshLayout()
  102. }
  103. override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
  104. menuInflater.inflate(R.menu.fragment_report_statuses, menu)
  105. menu.findItem(R.id.action_refresh)?.apply {
  106. icon = IconicsDrawable(requireContext(), GoogleMaterial.Icon.gmd_refresh).apply {
  107. sizeDp = 20
  108. colorInt = MaterialColors.getColor(binding.root, android.R.attr.textColorPrimary)
  109. }
  110. }
  111. }
  112. override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
  113. return when (menuItem.itemId) {
  114. R.id.action_refresh -> {
  115. binding.swipeRefreshLayout.isRefreshing = true
  116. onRefresh()
  117. true
  118. }
  119. else -> false
  120. }
  121. }
  122. override fun onRefresh() {
  123. snackbarErrorRetry?.dismiss()
  124. adapter.refresh()
  125. }
  126. private fun setupSwipeRefreshLayout() {
  127. binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
  128. binding.swipeRefreshLayout.setOnRefreshListener(this)
  129. }
  130. private fun initStatusesView() {
  131. val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
  132. val statusDisplayOptions = StatusDisplayOptions(
  133. animateAvatars = false,
  134. mediaPreviewEnabled = accountManager.activeAccount?.mediaPreviewEnabled ?: true,
  135. useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false),
  136. showBotOverlay = false,
  137. useBlurhash = preferences.getBoolean("useBlurhash", true),
  138. cardViewMode = CardViewMode.NONE,
  139. confirmReblogs = preferences.getBoolean("confirmReblogs", true),
  140. confirmFavourites = preferences.getBoolean("confirmFavourites", false),
  141. hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
  142. animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false),
  143. showSensitiveMedia = accountManager.activeAccount!!.alwaysShowSensitiveMedia,
  144. openSpoiler = accountManager.activeAccount!!.alwaysOpenSpoiler
  145. )
  146. adapter = StatusesAdapter(statusDisplayOptions, viewModel.statusViewState, this)
  147. binding.recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
  148. binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
  149. binding.recyclerView.adapter = adapter
  150. (binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
  151. lifecycleScope.launch {
  152. viewModel.statusesFlow.collectLatest { pagingData ->
  153. adapter.submitData(pagingData)
  154. }
  155. }
  156. adapter.addLoadStateListener { loadState ->
  157. if (loadState.refresh is LoadState.Error ||
  158. loadState.append is LoadState.Error ||
  159. loadState.prepend is LoadState.Error
  160. ) {
  161. showError()
  162. }
  163. binding.progressBarBottom.visible(loadState.append == LoadState.Loading)
  164. binding.progressBarTop.visible(loadState.prepend == LoadState.Loading)
  165. binding.progressBarLoading.visible(loadState.refresh == LoadState.Loading && !binding.swipeRefreshLayout.isRefreshing)
  166. if (loadState.refresh != LoadState.Loading) {
  167. binding.swipeRefreshLayout.isRefreshing = false
  168. }
  169. }
  170. }
  171. private fun showError() {
  172. if (snackbarErrorRetry?.isShown != true) {
  173. snackbarErrorRetry = Snackbar.make(binding.swipeRefreshLayout, R.string.failed_fetch_posts, Snackbar.LENGTH_INDEFINITE)
  174. snackbarErrorRetry?.setAction(R.string.action_retry) {
  175. adapter.retry()
  176. }
  177. snackbarErrorRetry?.show()
  178. }
  179. }
  180. private fun handleClicks() {
  181. binding.buttonCancel.setOnClickListener {
  182. viewModel.navigateTo(Screen.Back)
  183. }
  184. binding.buttonContinue.setOnClickListener {
  185. viewModel.navigateTo(Screen.Note)
  186. }
  187. }
  188. override fun setStatusChecked(status: Status, isChecked: Boolean) {
  189. viewModel.setStatusChecked(status, isChecked)
  190. }
  191. override fun isStatusChecked(id: String): Boolean {
  192. return viewModel.isStatusChecked(id)
  193. }
  194. override fun onViewAccount(id: String) = startActivity(AccountActivity.getIntent(requireContext(), id))
  195. override fun onViewTag(tag: String) = startActivity(StatusListActivity.newHashtagIntent(requireContext(), tag))
  196. override fun onViewUrl(url: String) = viewModel.checkClickedUrl(url)
  197. companion object {
  198. fun newInstance() = ReportStatusesFragment()
  199. }
  200. }