Initial commit

This commit is contained in:
Finnley Somdahl 2023-10-17 18:42:43 -05:00
commit 21bfbfb139
520 changed files with 47819 additions and 0 deletions

View file

@ -0,0 +1,280 @@
package ani.dantotsu.home
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.OvershootInterpolator
import androidx.core.content.ContextCompat
import androidx.core.view.marginBottom
import androidx.core.view.updatePaddingRelative
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R
import ani.dantotsu.Refresh
import ani.dantotsu.bottomBar
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistAnimeViewModel
import ani.dantotsu.connections.anilist.SearchResults
import ani.dantotsu.connections.anilist.getUserId
import ani.dantotsu.databinding.FragmentAnimeBinding
import ani.dantotsu.loadData
import ani.dantotsu.media.MediaAdaptor
import ani.dantotsu.media.ProgressAdapter
import ani.dantotsu.media.SearchActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.px
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.snackString
import ani.dantotsu.statusBarHeight
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.max
import kotlin.math.min
class AnimeFragment : Fragment() {
private var _binding: FragmentAnimeBinding? = null
private val binding get() = _binding!!
private var uiSettings: UserInterfaceSettings = loadData("ui_settings") ?: UserInterfaceSettings()
val model: AnilistAnimeViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAnimeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView();_binding = null
}
@SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val scope = viewLifecycleOwner.lifecycleScope
var height = statusBarHeight
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val displayCutout = activity?.window?.decorView?.rootWindowInsets?.displayCutout
if (displayCutout != null) {
if (displayCutout.boundingRects.size > 0) {
height = max(
statusBarHeight,
min(
displayCutout.boundingRects[0].width(),
displayCutout.boundingRects[0].height()
)
)
}
}
}
binding.animeRefresh.setSlingshotDistance(height + 128)
binding.animeRefresh.setProgressViewEndTarget(false, height + 128)
binding.animeRefresh.setOnRefreshListener {
Refresh.activity[this.hashCode()]!!.postValue(true)
}
binding.animePageRecyclerView.updatePaddingRelative(bottom = navBarHeight + 160f.px)
val animePageAdapter = AnimePageAdapter()
var loading = true
if (model.notSet) {
model.notSet = false
model.searchResults = SearchResults(
"ANIME",
isAdult = false,
onList = false,
results = mutableListOf(),
hasNextPage = true,
sort = Anilist.sortBy[1]
)
}
val popularAdaptor = MediaAdaptor(1, model.searchResults.results, requireActivity())
val progressAdaptor = ProgressAdapter(searched = model.searched)
val adapter = ConcatAdapter(animePageAdapter, popularAdaptor, progressAdaptor)
binding.animePageRecyclerView.adapter = adapter
val layout = LinearLayoutManager(requireContext())
binding.animePageRecyclerView.layoutManager = layout
var visible = false
fun animate() {
val start = if (visible) 0f else 1f
val end = if (!visible) 0f else 1f
ObjectAnimator.ofFloat(binding.animePageScrollTop, "scaleX", start, end).apply {
duration = 300
interpolator = OvershootInterpolator(2f)
start()
}
ObjectAnimator.ofFloat(binding.animePageScrollTop, "scaleY", start, end).apply {
duration = 300
interpolator = OvershootInterpolator(2f)
start()
}
}
binding.animePageScrollTop.setOnClickListener {
binding.animePageRecyclerView.scrollToPosition(4)
binding.animePageRecyclerView.smoothScrollToPosition(0)
}
var oldIncludeList = true
animePageAdapter.onIncludeListClick = { checked ->
oldIncludeList = !checked
loading = true
model.searchResults.results.clear()
popularAdaptor.notifyDataSetChanged()
scope.launch(Dispatchers.IO) {
model.loadPopular("ANIME", sort = Anilist.sortBy[1], onList = checked)
}
}
model.getPopular().observe(viewLifecycleOwner) {
if (it != null) {
if (oldIncludeList == (it.onList != false)) {
val prev = model.searchResults.results.size
model.searchResults.results.addAll(it.results)
popularAdaptor.notifyItemRangeInserted(prev, it.results.size)
} else {
model.searchResults.results.addAll(it.results)
popularAdaptor.notifyDataSetChanged()
oldIncludeList = it.onList ?: true
}
model.searchResults.onList = it.onList
model.searchResults.hasNextPage = it.hasNextPage
model.searchResults.page = it.page
if (it.hasNextPage)
progressAdaptor.bar?.visibility = View.VISIBLE
else {
snackString(getString(R.string.jobless_message))
progressAdaptor.bar?.visibility = View.GONE
}
loading = false
}
}
binding.animePageRecyclerView.addOnScrollListener(object :
RecyclerView.OnScrollListener() {
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
if (!v.canScrollVertically(1)) {
if (model.searchResults.hasNextPage && model.searchResults.results.isNotEmpty() && !loading) {
scope.launch(Dispatchers.IO) {
loading = true
model.loadNextPage(model.searchResults)
}
}
}
if (layout.findFirstVisibleItemPosition() > 1 && !visible) {
binding.animePageScrollTop.visibility = View.VISIBLE
visible = true
animate()
}
if (!v.canScrollVertically(-1)) {
visible = false
animate()
scope.launch {
delay(300)
binding.animePageScrollTop.visibility = View.GONE
}
}
super.onScrolled(v, dx, dy)
}
})
animePageAdapter.ready.observe(viewLifecycleOwner) { i ->
if (i) {
model.getUpdated().observe(viewLifecycleOwner) {
if (it != null) {
animePageAdapter.updateRecent(MediaAdaptor(0, it, requireActivity()))
}
}
if (animePageAdapter.trendingViewPager != null) {
animePageAdapter.updateHeight()
model.getTrending().observe(viewLifecycleOwner) {
if (it != null) {
animePageAdapter.updateTrending(
MediaAdaptor(
if (uiSettings.smallView) 3 else 2,
it,
requireActivity(),
viewPager = animePageAdapter.trendingViewPager
)
)
animePageAdapter.updateAvatar()
}
}
}
binding.animePageScrollTop.translationY = -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
}
}
fun load() = scope.launch(Dispatchers.Main) {
animePageAdapter.updateAvatar()
}
animePageAdapter.onSeasonClick = { i ->
scope.launch(Dispatchers.IO) {
model.loadTrending(i)
}
}
animePageAdapter.onSeasonLongClick = { i ->
val (season, year) = Anilist.currentSeasons[i]
ContextCompat.startActivity(
requireContext(),
Intent(requireContext(), SearchActivity::class.java)
.putExtra("type", "ANIME")
.putExtra("season", season)
.putExtra("seasonYear", year.toString())
.putExtra("search", true),
null
)
true
}
val live = Refresh.activity.getOrPut(this.hashCode()) { MutableLiveData(false) }
live.observe(viewLifecycleOwner) {
if (it) {
scope.launch {
withContext(Dispatchers.IO) {
getUserId(requireContext()) {
load()
}
model.loaded = true
model.loadTrending(1)
model.loadUpdated()
model.loadPopular("ANIME", sort = Anilist.sortBy[1])
}
live.postValue(false)
_binding?.animeRefresh?.isRefreshing = false
}
}
}
}
override fun onResume() {
if (!model.loaded) Refresh.activity[this.hashCode()]!!.postValue(true)
super.onResume()
}
}