NewPipe/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt

315 lines
11 KiB
Kotlin
Raw Normal View History

/*
* Copyright 2019 Mauricio Colli <mauriciocolli@outlook.com>
* FeedFragment.kt is part of NewPipe
*
* License: GPL-3.0+
* 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.schabi.newpipe.local.feed
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
2020-05-01 20:13:01 +02:00
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
2020-11-18 23:29:58 +01:00
import androidx.core.content.edit
2020-10-17 12:08:45 +02:00
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
2020-08-27 22:56:12 +02:00
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import icepick.State
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
2020-11-03 09:21:37 +01:00
import org.schabi.newpipe.databinding.FragmentFeedBinding
import org.schabi.newpipe.error.ErrorInfo
import org.schabi.newpipe.error.UserAction
import org.schabi.newpipe.fragments.list.BaseListFragment
import org.schabi.newpipe.ktx.animate
import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling
import org.schabi.newpipe.local.feed.service.FeedLoadService
import org.schabi.newpipe.util.Localization
2020-10-31 21:55:45 +01:00
import java.util.Calendar
class FeedFragment : BaseListFragment<FeedState, Unit>() {
2020-11-03 09:21:37 +01:00
private var _feedBinding: FragmentFeedBinding? = null
private val feedBinding get() = _feedBinding!!
private lateinit var viewModel: FeedViewModel
@State
@JvmField
var listState: Parcelable? = null
private var groupId = FeedGroupEntity.GROUP_ALL_ID
private var groupName = ""
private var oldestSubscriptionUpdate: Calendar? = null
init {
setHasOptionsMenu(true)
setUseDefaultStateSaving(false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
groupId = arguments?.getLong(KEY_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
2020-10-31 21:55:45 +01:00
?: FeedGroupEntity.GROUP_ALL_ID
groupName = arguments?.getString(KEY_GROUP_NAME) ?: ""
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_feed, container, false)
}
override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) {
// super.onViewCreated() calls initListeners() which require the binding to be initialized
2020-11-03 09:21:37 +01:00
_feedBinding = FragmentFeedBinding.bind(rootView)
super.onViewCreated(rootView, savedInstanceState)
2020-11-03 09:21:37 +01:00
2020-08-27 22:56:12 +02:00
viewModel = ViewModelProvider(this, FeedViewModel.Factory(requireContext(), groupId)).get(FeedViewModel::class.java)
viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(::handleResult) }
}
override fun onPause() {
super.onPause()
2021-01-18 11:35:45 +01:00
listState = feedBinding.itemsList.layoutManager?.onSaveInstanceState()
}
override fun onResume() {
super.onResume()
updateRelativeTimeViews()
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (!isVisibleToUser && view != null) {
updateRelativeTimeViews()
}
}
override fun initListeners() {
super.initListeners()
feedBinding.refreshRootView.setOnClickListener { reloadContent() }
feedBinding.swipeRefreshLayout.setOnRefreshListener { reloadContent() }
}
2020-05-01 20:13:21 +02:00
// /////////////////////////////////////////////////////////////////////////
// Menu
2020-05-01 20:13:21 +02:00
// /////////////////////////////////////////////////////////////////////////
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
activity.supportActionBar?.setTitle(R.string.fragment_feed_title)
activity.supportActionBar?.subtitle = groupName
inflater.inflate(R.menu.menu_feed_fragment, menu)
if (useAsFrontPage) {
menu.findItem(R.id.menu_item_feed_help).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.menu_item_feed_help) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val usingDedicatedMethod = sharedPreferences.getBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), false)
val enableDisableButtonText = when {
usingDedicatedMethod -> R.string.feed_use_dedicated_fetch_method_disable_button
else -> R.string.feed_use_dedicated_fetch_method_enable_button
}
AlertDialog.Builder(requireContext())
2020-10-31 21:55:45 +01:00
.setMessage(R.string.feed_use_dedicated_fetch_method_help_text)
.setNeutralButton(enableDisableButtonText) { _, _ ->
sharedPreferences.edit {
putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), !usingDedicatedMethod)
}
2020-10-31 21:55:45 +01:00
}
.setPositiveButton(resources.getString(R.string.finish), null)
.create()
.show()
return true
}
return super.onOptionsItemSelected(item)
}
override fun onDestroyOptionsMenu() {
super.onDestroyOptionsMenu()
activity?.supportActionBar?.subtitle = null
}
override fun onDestroy() {
super.onDestroy()
activity?.supportActionBar?.subtitle = null
}
2020-11-03 09:21:37 +01:00
override fun onDestroyView() {
_feedBinding = null
super.onDestroyView()
}
2020-05-01 20:13:21 +02:00
// /////////////////////////////////////////////////////////////////////////
// Handling
2020-05-01 20:13:21 +02:00
// /////////////////////////////////////////////////////////////////////////
override fun showLoading() {
super.showLoading()
feedBinding.itemsList.animateHideRecyclerViewAllowingScrolling()
feedBinding.refreshRootView.animate(false, 0)
feedBinding.loadingProgressText.animate(true, 200)
feedBinding.swipeRefreshLayout.isRefreshing = true
}
override fun hideLoading() {
super.hideLoading()
feedBinding.refreshRootView.animate(true, 200)
feedBinding.loadingProgressText.animate(false, 0)
feedBinding.swipeRefreshLayout.isRefreshing = false
}
override fun showEmptyState() {
super.showEmptyState()
feedBinding.itemsList.animateHideRecyclerViewAllowingScrolling()
feedBinding.refreshRootView.animate(true, 200)
feedBinding.loadingProgressText.animate(false, 0)
feedBinding.swipeRefreshLayout.isRefreshing = false
}
override fun handleResult(result: FeedState) {
when (result) {
is FeedState.ProgressState -> handleProgressState(result)
is FeedState.LoadedState -> handleLoadedState(result)
is FeedState.ErrorState -> if (handleErrorState(result)) return
}
updateRefreshViewState()
}
override fun handleError() {
super.handleError()
infoListAdapter.clearStreamItemList()
feedBinding.itemsList.animateHideRecyclerViewAllowingScrolling()
feedBinding.refreshRootView.animate(false, 0)
feedBinding.loadingProgressText.animate(false, 0)
feedBinding.swipeRefreshLayout.isRefreshing = false
}
private fun handleProgressState(progressState: FeedState.ProgressState) {
showLoading()
val isIndeterminate = progressState.currentProgress == -1 &&
2020-10-31 21:55:45 +01:00
progressState.maxProgress == -1
2021-01-18 11:35:45 +01:00
feedBinding.loadingProgressText.text = if (!isIndeterminate) {
"${progressState.currentProgress}/${progressState.maxProgress}"
} else if (progressState.progressMessage > 0) {
getString(progressState.progressMessage)
} else {
2021-01-18 11:35:45 +01:00
"∞/∞"
}
2020-11-03 09:21:37 +01:00
feedBinding.loadingProgressBar.isIndeterminate = isIndeterminate ||
(progressState.maxProgress > 0 && progressState.currentProgress == 0)
2020-11-03 09:21:37 +01:00
feedBinding.loadingProgressBar.progress = progressState.currentProgress
2020-11-03 09:21:37 +01:00
feedBinding.loadingProgressBar.max = progressState.maxProgress
}
private fun handleLoadedState(loadedState: FeedState.LoadedState) {
infoListAdapter.setInfoItemList(loadedState.items)
listState?.run {
2020-11-03 09:21:37 +01:00
feedBinding.itemsList.layoutManager?.onRestoreInstanceState(listState)
listState = null
}
oldestSubscriptionUpdate = loadedState.oldestUpdate
2020-11-18 23:29:58 +01:00
val loadedCount = loadedState.notLoadedCount > 0
2020-11-03 09:21:37 +01:00
feedBinding.refreshSubtitleText.isVisible = loadedCount
2020-11-18 23:29:58 +01:00
if (loadedCount) {
feedBinding.refreshSubtitleText.text = getString(
R.string.feed_subscription_not_loaded_count,
loadedState.notLoadedCount
)
}
if (loadedState.items.isEmpty()) {
showEmptyState()
} else {
hideLoading()
}
}
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
return if (errorState.error == null) {
hideLoading()
false
} else {
showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed"))
true
}
}
private fun updateRelativeTimeViews() {
updateRefreshViewState()
infoListAdapter.notifyDataSetChanged()
}
private fun updateRefreshViewState() {
val oldestSubscriptionUpdateText = when {
oldestSubscriptionUpdate != null -> Localization.relativeTime(oldestSubscriptionUpdate!!)
else -> ""
}
2020-11-03 09:21:37 +01:00
feedBinding.refreshText.text = getString(R.string.feed_oldest_subscription_update, oldestSubscriptionUpdateText)
}
2020-05-01 20:13:21 +02:00
// /////////////////////////////////////////////////////////////////////////
// Load Service Handling
2020-05-01 20:13:21 +02:00
// /////////////////////////////////////////////////////////////////////////
override fun doInitialLoadLogic() {}
override fun loadMoreItems() {}
override fun hasMoreItems() = false
override fun reloadContent() {
2020-10-31 21:55:45 +01:00
getActivity()?.startService(
Intent(requireContext(), FeedLoadService::class.java).apply {
putExtra(FeedLoadService.EXTRA_GROUP_ID, groupId)
}
)
listState = null
}
companion object {
const val KEY_GROUP_ID = "ARG_GROUP_ID"
const val KEY_GROUP_NAME = "ARG_GROUP_NAME"
@JvmStatic
fun newInstance(groupId: Long = FeedGroupEntity.GROUP_ALL_ID, groupName: String? = null): FeedFragment {
val feedFragment = FeedFragment()
2020-10-17 12:08:45 +02:00
feedFragment.arguments = bundleOf(KEY_GROUP_ID to groupId, KEY_GROUP_NAME to groupName)
return feedFragment
}
}
}