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()
}
}

View file

@ -0,0 +1,170 @@
package ani.dantotsu.home
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.LayoutAnimationController
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.media.GenreActivity
import ani.dantotsu.MediaPageTransformer
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.databinding.ItemAnimePageBinding
import ani.dantotsu.loadData
import ani.dantotsu.loadImage
import ani.dantotsu.media.CalendarActivity
import ani.dantotsu.media.MediaAdaptor
import ani.dantotsu.media.SearchActivity
import ani.dantotsu.px
import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.setSlideIn
import ani.dantotsu.setSlideUp
import ani.dantotsu.settings.SettingsDialogFragment
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.statusBarHeight
class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHolder>() {
val ready = MutableLiveData(false)
lateinit var binding: ItemAnimePageBinding
private var trendHandler: Handler? = null
private lateinit var trendRun: Runnable
var trendingViewPager: ViewPager2? = null
private var uiSettings: UserInterfaceSettings = loadData("ui_settings") ?: UserInterfaceSettings()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnimePageViewHolder {
val binding = ItemAnimePageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return AnimePageViewHolder(binding)
}
override fun onBindViewHolder(holder: AnimePageViewHolder, position: Int) {
binding = holder.binding
trendingViewPager = binding.animeTrendingViewPager
binding.animeTitleContainer.updatePadding(top = statusBarHeight)
if (uiSettings.smallView) binding.animeTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = (-108f).px
}
updateAvatar()
binding.animeSearchBar.hint = "ANIME"
binding.animeSearchBarText.setOnClickListener {
ContextCompat.startActivity(
it.context,
Intent(it.context, SearchActivity::class.java).putExtra("type", "ANIME"),
null
)
}
binding.animeSearchBar.setEndIconOnClickListener {
binding.animeSearchBarText.performClick()
}
binding.animeUserAvatar.setSafeOnClickListener {
SettingsDialogFragment().show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
}
listOf(
binding.animePreviousSeason,
binding.animeThisSeason,
binding.animeNextSeason
).forEachIndexed { i, it ->
it.setSafeOnClickListener { onSeasonClick.invoke(i) }
it.setOnLongClickListener { onSeasonLongClick.invoke(i) }
}
binding.animeGenreImage.loadImage("https://s4.anilist.co/file/anilistcdn/media/anime/banner/16498-8jpFCOcDmneX.jpg")
binding.animeCalendarImage.loadImage("https://s4.anilist.co/file/anilistcdn/media/anime/banner/125367-hGPJLSNfprO3.jpg")
binding.animeGenre.setOnClickListener {
ContextCompat.startActivity(
it.context,
Intent(it.context, GenreActivity::class.java).putExtra("type", "ANIME"),
null
)
}
binding.animeCalendar.setOnClickListener {
ContextCompat.startActivity(
it.context,
Intent(it.context, CalendarActivity::class.java),
null
)
}
binding.animeIncludeList.visibility = if(Anilist.userid!=null) View.VISIBLE else View.GONE
binding.animeIncludeList.setOnCheckedChangeListener { _, isChecked ->
onIncludeListClick.invoke(isChecked)
}
if (ready.value == false)
ready.postValue(true)
}
lateinit var onSeasonClick : ((Int)->Unit)
lateinit var onSeasonLongClick : ((Int)->Boolean)
lateinit var onIncludeListClick : ((Boolean)->Unit)
override fun getItemCount(): Int = 1
fun updateHeight() {
trendingViewPager!!.updateLayoutParams { height += statusBarHeight }
}
fun updateTrending(adaptor: MediaAdaptor) {
binding.animeTrendingProgressBar.visibility = View.GONE
binding.animeTrendingViewPager.adapter = adaptor
binding.animeTrendingViewPager.offscreenPageLimit = 3
binding.animeTrendingViewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER
binding.animeTrendingViewPager.setPageTransformer(MediaPageTransformer())
trendHandler = Handler(Looper.getMainLooper())
trendRun = Runnable {
binding.animeTrendingViewPager.currentItem = binding.animeTrendingViewPager.currentItem + 1
}
binding.animeTrendingViewPager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
trendHandler!!.removeCallbacks(trendRun)
trendHandler!!.postDelayed(trendRun, 4000)
}
}
)
binding.animeTrendingViewPager.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
binding.animeTitleContainer.startAnimation(setSlideUp(uiSettings))
binding.animeListContainer.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
binding.animeSeasonsCont.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
}
fun updateRecent(adaptor: MediaAdaptor) {
binding.animeUpdatedProgressBar.visibility = View.GONE
binding.animeUpdatedRecyclerView.adapter = adaptor
binding.animeUpdatedRecyclerView.layoutManager =
LinearLayoutManager(binding.animeUpdatedRecyclerView.context, LinearLayoutManager.HORIZONTAL, false)
binding.animeUpdatedRecyclerView.visibility = View.VISIBLE
binding.animeRecently.visibility = View.VISIBLE
binding.animeRecently.startAnimation(setSlideUp(uiSettings))
binding.animeUpdatedRecyclerView.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
binding.animePopular.visibility = View.VISIBLE
binding.animePopular.startAnimation(setSlideUp(uiSettings))
}
fun updateAvatar() {
if (Anilist.avatar != null && ready.value == true) {
binding.animeUserAvatar.loadImage(Anilist.avatar)
}
}
inner class AnimePageViewHolder(val binding: ItemAnimePageBinding) : RecyclerView.ViewHolder(binding.root)
}

View file

@ -0,0 +1,346 @@
package ani.dantotsu.home
import android.animation.ObjectAnimator
import android.content.Intent
import android.graphics.drawable.Animatable
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.LayoutAnimationController
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
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.AnilistHomeViewModel
import ani.dantotsu.connections.anilist.getUserId
import ani.dantotsu.currContext
import ani.dantotsu.databinding.FragmentHomeBinding
import ani.dantotsu.loadData
import ani.dantotsu.loadImage
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaAdaptor
import ani.dantotsu.settings.SettingsDialogFragment
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.media.user.ListActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.setSlideIn
import ani.dantotsu.setSlideUp
import ani.dantotsu.snackString
import ani.dantotsu.statusBarHeight
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlin.math.max
import kotlin.math.min
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
val model: AnilistHomeViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val scope = lifecycleScope
var uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
fun load() {
if (activity != null && _binding != null) lifecycleScope.launch(Dispatchers.Main) {
binding.homeUserName.text = Anilist.username
binding.homeUserEpisodesWatched.text = Anilist.episodesWatched.toString()
binding.homeUserChaptersRead.text = Anilist.chapterRead.toString()
binding.homeUserAvatar.loadImage(Anilist.avatar)
if (!uiSettings.bannerAnimations) binding.homeUserBg.pause()
binding.homeUserBg.loadImage(Anilist.bg)
binding.homeUserDataProgressBar.visibility = View.GONE
binding.homeAnimeList.setOnClickListener {
ContextCompat.startActivity(
requireActivity(), Intent(requireActivity(), ListActivity::class.java)
.putExtra("anime", true)
.putExtra("userId", Anilist.userid)
.putExtra("username", Anilist.username), null
)
}
binding.homeMangaList.setOnClickListener {
ContextCompat.startActivity(
requireActivity(), Intent(requireActivity(), ListActivity::class.java)
.putExtra("anime", false)
.putExtra("userId", Anilist.userid)
.putExtra("username", Anilist.username), null
)
}
binding.homeUserAvatarContainer.startAnimation(setSlideUp(uiSettings))
binding.homeUserDataContainer.visibility = View.VISIBLE
binding.homeUserDataContainer.layoutAnimation = LayoutAnimationController(setSlideUp(uiSettings), 0.25f)
binding.homeAnimeList.visibility = View.VISIBLE
binding.homeMangaList.visibility = View.VISIBLE
binding.homeListContainer.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
}
else {
snackString(currContext()?.getString(R.string.please_reload))
}
}
binding.homeUserAvatarContainer.setSafeOnClickListener {
SettingsDialogFragment().show(parentFragmentManager, "dialog")
}
binding.homeContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = navBarHeight
}
binding.homeUserBg.updateLayoutParams { height += statusBarHeight }
binding.homeTopContainer.updatePadding(top = statusBarHeight)
var reached = false
val duration = (uiSettings.animationSpeed * 200).toLong()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
binding.homeScroll.setOnScrollChangeListener { _, _, _, _, _ ->
if (!binding.homeScroll.canScrollVertically(1)) {
reached = true
bottomBar.animate().translationZ(0f).setDuration(duration).start()
ObjectAnimator.ofFloat(bottomBar, "elevation", 4f, 0f).setDuration(duration).start()
} else {
if (reached) {
bottomBar.animate().translationZ(12f).setDuration(duration).start()
ObjectAnimator.ofFloat(bottomBar, "elevation", 0f, 4f).setDuration(duration).start()
}
}
}
}
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.homeRefresh.setSlingshotDistance(height + 128)
binding.homeRefresh.setProgressViewEndTarget(false, height + 128)
binding.homeRefresh.setOnRefreshListener {
Refresh.activity[1]!!.postValue(true)
}
//UserData
binding.homeUserDataProgressBar.visibility = View.VISIBLE
binding.homeUserDataContainer.visibility = View.GONE
if (model.loaded) {
load()
}
//List Images
model.getListImages().observe(viewLifecycleOwner) {
if (it.isNotEmpty()) {
binding.homeAnimeListImage.loadImage(it[0] ?: "https://bit.ly/31bsIHq")
binding.homeMangaListImage.loadImage(it[1] ?: "https://bit.ly/2ZGfcuG")
}
}
//Function For Recycler Views
fun initRecyclerView(
mode: LiveData<ArrayList<Media>>,
container: View,
recyclerView: RecyclerView,
progress: View,
empty: View,
title: View
) {
container.visibility = View.VISIBLE
progress.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
empty.visibility = View.GONE
title.visibility = View.INVISIBLE
mode.observe(viewLifecycleOwner) {
recyclerView.visibility = View.GONE
empty.visibility = View.GONE
if (it != null) {
if (it.isNotEmpty()) {
recyclerView.adapter = MediaAdaptor(0, it, requireActivity())
recyclerView.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.HORIZONTAL,
false
)
recyclerView.visibility = View.VISIBLE
recyclerView.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
} else {
empty.visibility = View.VISIBLE
}
title.visibility = View.VISIBLE
title.startAnimation(setSlideUp(uiSettings))
progress.visibility = View.GONE
}
}
}
// Recycler Views
initRecyclerView(
model.getAnimeContinue(),
binding.homeContinueWatchingContainer,
binding.homeWatchingRecyclerView,
binding.homeWatchingProgressBar,
binding.homeWatchingEmpty,
binding.homeContinueWatch
)
binding.homeWatchingBrowseButton.setOnClickListener {
bottomBar.selectTabAt(0)
}
initRecyclerView(
model.getAnimeFav(),
binding.homeFavAnimeContainer,
binding.homeFavAnimeRecyclerView,
binding.homeFavAnimeProgressBar,
binding.homeFavAnimeEmpty,
binding.homeFavAnime
)
initRecyclerView(
model.getAnimePlanned(),
binding.homePlannedAnimeContainer,
binding.homePlannedAnimeRecyclerView,
binding.homePlannedAnimeProgressBar,
binding.homePlannedAnimeEmpty,
binding.homePlannedAnime
)
binding.homePlannedAnimeBrowseButton.setOnClickListener {
bottomBar.selectTabAt(0)
}
initRecyclerView(
model.getMangaContinue(),
binding.homeContinueReadingContainer,
binding.homeReadingRecyclerView,
binding.homeReadingProgressBar,
binding.homeReadingEmpty,
binding.homeContinueRead
)
binding.homeReadingBrowseButton.setOnClickListener {
bottomBar.selectTabAt(2)
}
initRecyclerView(
model.getMangaFav(),
binding.homeFavMangaContainer,
binding.homeFavMangaRecyclerView,
binding.homeFavMangaProgressBar,
binding.homeFavMangaEmpty,
binding.homeFavManga
)
initRecyclerView(
model.getMangaPlanned(),
binding.homePlannedMangaContainer,
binding.homePlannedMangaRecyclerView,
binding.homePlannedMangaProgressBar,
binding.homePlannedMangaEmpty,
binding.homePlannedManga
)
binding.homePlannedMangaBrowseButton.setOnClickListener {
bottomBar.selectTabAt(2)
}
initRecyclerView(
model.getRecommendation(),
binding.homeRecommendedContainer,
binding.homeRecommendedRecyclerView,
binding.homeRecommendedProgressBar,
binding.homeRecommendedEmpty,
binding.homeRecommended
)
binding.homeUserAvatarContainer.startAnimation(setSlideUp(uiSettings))
model.empty.observe(viewLifecycleOwner) {
binding.homeDantotsuContainer.visibility = if (it == true) View.VISIBLE else View.GONE
(binding.homeDantotsuIcon.drawable as Animatable).start()
binding.homeDantotsuContainer.startAnimation(setSlideUp(uiSettings))
binding.homeDantotsuIcon.setSafeOnClickListener {
(binding.homeDantotsuIcon.drawable as Animatable).start()
}
}
val array = arrayOf(
Runnable { runBlocking { model.setAnimeContinue() } },
Runnable { runBlocking { model.setAnimeFav() } },
Runnable { runBlocking { model.setAnimePlanned() } },
Runnable { runBlocking { model.setMangaContinue() } },
Runnable { runBlocking { model.setMangaFav() } },
Runnable { runBlocking { model.setMangaPlanned() } },
Runnable { runBlocking { model.setRecommendation() } }
)
val containers = arrayOf(
binding.homeContinueWatchingContainer,
binding.homeFavAnimeContainer,
binding.homePlannedAnimeContainer,
binding.homeContinueReadingContainer,
binding.homeFavMangaContainer,
binding.homePlannedMangaContainer,
binding.homeRecommendedContainer
)
val live = Refresh.activity.getOrPut(1) { MutableLiveData(false) }
live.observe(viewLifecycleOwner) {
if (it) {
scope.launch {
uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
withContext(Dispatchers.IO) {
//Get userData First
getUserId(requireContext()) {
load()
}
model.loaded = true
model.setListImages()
var empty = true
(array.indices).forEach { i ->
if (uiSettings.homeLayoutShow[i]) {
array[i].run()
empty = false
} else withContext(Dispatchers.Main) {
containers[i].visibility = View.GONE
}
}
model.empty.postValue(empty)
}
live.postValue(false)
_binding?.homeRefresh?.isRefreshing = false
}
}
}
}
override fun onResume() {
if (!model.loaded) Refresh.activity[1]!!.postValue(true)
super.onResume()
}
}

View file

@ -0,0 +1,28 @@
package ani.dantotsu.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.databinding.FragmentLoginBinding
import ani.dantotsu.openLinkInBrowser
class LoginFragment : Fragment() {
private var _binding: FragmentLoginBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentLoginBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.loginButton.setOnClickListener { Anilist.loginIntent(requireActivity()) }
binding.loginDiscord.setOnClickListener { openLinkInBrowser(getString(R.string.discord)) }
binding.loginGithub.setOnClickListener { openLinkInBrowser(getString(R.string.github)) }
}
}

View file

@ -0,0 +1,250 @@
package ani.dantotsu.home
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
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.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.AnilistMangaViewModel
import ani.dantotsu.connections.anilist.SearchResults
import ani.dantotsu.connections.anilist.getUserId
import ani.dantotsu.databinding.FragmentMangaBinding
import ani.dantotsu.loadData
import ani.dantotsu.media.MediaAdaptor
import ani.dantotsu.media.ProgressAdapter
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 MangaFragment : Fragment() {
private var _binding: FragmentMangaBinding? = null
private val binding get() = _binding!!
private var uiSettings: UserInterfaceSettings = loadData("ui_settings") ?: UserInterfaceSettings()
val model: AnilistMangaViewModel by activityViewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentMangaBinding.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.mangaRefresh.setSlingshotDistance(height + 128)
binding.mangaRefresh.setProgressViewEndTarget(false, height + 128)
binding.mangaRefresh.setOnRefreshListener {
Refresh.activity[this.hashCode()]!!.postValue(true)
}
binding.mangaPageRecyclerView.updatePaddingRelative(bottom = navBarHeight + 160f.px)
val mangaPageAdapter = MangaPageAdapter()
var loading = true
if (model.notSet) {
model.notSet = false
model.searchResults = SearchResults(
"MANGA",
isAdult = false,
onList = false,
results = arrayListOf(),
hasNextPage = true,
sort = Anilist.sortBy[1]
)
}
val popularAdaptor = MediaAdaptor(1, model.searchResults.results, requireActivity())
val progressAdaptor = ProgressAdapter(searched = model.searched)
binding.mangaPageRecyclerView.adapter = ConcatAdapter(mangaPageAdapter, popularAdaptor, progressAdaptor)
val layout = LinearLayoutManager(requireContext())
binding.mangaPageRecyclerView.layoutManager = layout
var visible = false
fun animate() {
val start = if (visible) 0f else 1f
val end = if (!visible) 0f else 1f
ObjectAnimator.ofFloat(binding.mangaPageScrollTop, "scaleX", start, end).apply {
duration = 300
interpolator = OvershootInterpolator(2f)
start()
}
ObjectAnimator.ofFloat(binding.mangaPageScrollTop, "scaleY", start, end).apply {
duration = 300
interpolator = OvershootInterpolator(2f)
start()
}
}
binding.mangaPageScrollTop.setOnClickListener {
binding.mangaPageRecyclerView.scrollToPosition(4)
binding.mangaPageRecyclerView.smoothScrollToPosition(0)
}
binding.mangaPageRecyclerView.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.mangaPageScrollTop.visibility = View.VISIBLE
visible = true
animate()
}
if (!v.canScrollVertically(-1)) {
visible = false
animate()
scope.launch {
delay(300)
binding.mangaPageScrollTop.visibility = View.GONE
}
}
super.onScrolled(v, dx, dy)
}
})
mangaPageAdapter.ready.observe(viewLifecycleOwner) { i ->
if (i == true) {
model.getTrendingNovel().observe(viewLifecycleOwner) {
if (it != null) {
mangaPageAdapter.updateNovel(MediaAdaptor(0, it, requireActivity()))
}
}
if (mangaPageAdapter.trendingViewPager != null) {
mangaPageAdapter.updateHeight()
model.getTrending().observe(viewLifecycleOwner) {
if (it != null) {
mangaPageAdapter.updateTrending(
MediaAdaptor(
if (uiSettings.smallView) 3 else 2,
it,
requireActivity(),
viewPager = mangaPageAdapter.trendingViewPager
)
)
mangaPageAdapter.updateAvatar()
}
}
}
binding.mangaPageScrollTop.translationY = -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
}
}
var oldIncludeList = true
mangaPageAdapter.onIncludeListClick = { checked ->
oldIncludeList = !checked
loading = true
model.searchResults.results.clear()
popularAdaptor.notifyDataSetChanged()
scope.launch(Dispatchers.IO) {
model.loadPopular("MANGA", 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
}
}
fun load() = scope.launch(Dispatchers.Main) {
mangaPageAdapter.updateAvatar()
}
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()
model.loadTrendingNovel()
model.loadPopular("MANGA", sort = Anilist.sortBy[1])
}
live.postValue(false)
_binding?.mangaRefresh?.isRefreshing = false
}
}
}
}
override fun onResume() {
if (!model.loaded) Refresh.activity[this.hashCode()]!!.postValue(true)
super.onResume()
}
}

View file

@ -0,0 +1,160 @@
package ani.dantotsu.home
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.LayoutAnimationController
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.media.GenreActivity
import ani.dantotsu.MediaPageTransformer
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.databinding.ItemMangaPageBinding
import ani.dantotsu.loadData
import ani.dantotsu.loadImage
import ani.dantotsu.media.MediaAdaptor
import ani.dantotsu.media.SearchActivity
import ani.dantotsu.px
import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.setSlideIn
import ani.dantotsu.setSlideUp
import ani.dantotsu.settings.SettingsDialogFragment
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.statusBarHeight
class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHolder>() {
val ready = MutableLiveData(false)
lateinit var binding: ItemMangaPageBinding
private var trendHandler: Handler? = null
private lateinit var trendRun: Runnable
var trendingViewPager: ViewPager2? = null
private var uiSettings: UserInterfaceSettings = loadData("ui_settings") ?: UserInterfaceSettings()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MangaPageViewHolder {
val binding = ItemMangaPageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MangaPageViewHolder(binding)
}
override fun onBindViewHolder(holder: MangaPageViewHolder, position: Int) {
binding = holder.binding
trendingViewPager = binding.mangaTrendingViewPager
binding.mangaTitleContainer.updatePadding(top = statusBarHeight)
if (uiSettings.smallView) binding.mangaTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = (-108f).px
}
updateAvatar()
binding.mangaSearchBar.hint = "MANGA"
binding.mangaSearchBarText.setOnClickListener {
ContextCompat.startActivity(
it.context,
Intent(it.context, SearchActivity::class.java).putExtra("type", "MANGA"),
null
)
}
binding.mangaUserAvatar.setSafeOnClickListener {
SettingsDialogFragment().show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
}
binding.mangaSearchBar.setEndIconOnClickListener {
binding.mangaSearchBarText.performClick()
}
binding.mangaGenreImage.loadImage("https://s4.anilist.co/file/anilistcdn/media/manga/banner/105778-wk5qQ7zAaTGl.jpg")
binding.mangaTopScoreImage.loadImage("https://s4.anilist.co/file/anilistcdn/media/manga/banner/30002-3TuoSMl20fUX.jpg")
binding.mangaGenre.setOnClickListener {
ContextCompat.startActivity(
it.context,
Intent(it.context, GenreActivity::class.java).putExtra("type", "MANGA"),
null
)
}
binding.mangaTopScore.setOnClickListener {
ContextCompat.startActivity(
it.context,
Intent(it.context, SearchActivity::class.java)
.putExtra("type", "MANGA")
.putExtra("sortBy", Anilist.sortBy[0])
.putExtra("search", true),
null
)
}
binding.mangaIncludeList.visibility = if(Anilist.userid!=null) View.VISIBLE else View.GONE
binding.mangaIncludeList.setOnCheckedChangeListener { _, isChecked ->
onIncludeListClick.invoke(isChecked)
}
if (ready.value == false)
ready.postValue(true)
}
lateinit var onIncludeListClick : ((Boolean)->Unit)
override fun getItemCount(): Int = 1
fun updateHeight() {
trendingViewPager!!.updateLayoutParams { height += statusBarHeight }
}
fun updateTrending(adaptor: MediaAdaptor) {
binding.mangaTrendingProgressBar.visibility = View.GONE
binding.mangaTrendingViewPager.adapter = adaptor
binding.mangaTrendingViewPager.offscreenPageLimit = 3
binding.mangaTrendingViewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER
binding.mangaTrendingViewPager.setPageTransformer(MediaPageTransformer())
trendHandler = Handler(Looper.getMainLooper())
trendRun = Runnable {
binding.mangaTrendingViewPager.currentItem = binding.mangaTrendingViewPager.currentItem + 1
}
binding.mangaTrendingViewPager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
trendHandler!!.removeCallbacks(trendRun)
trendHandler!!.postDelayed(trendRun, 4000)
}
}
)
binding.mangaTrendingViewPager.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
binding.mangaTitleContainer.startAnimation(setSlideUp(uiSettings))
binding.mangaListContainer.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
}
fun updateNovel(adaptor: MediaAdaptor) {
binding.mangaNovelProgressBar.visibility = View.GONE
binding.mangaNovelRecyclerView.adapter = adaptor
binding.mangaNovelRecyclerView.layoutManager =
LinearLayoutManager(binding.mangaNovelRecyclerView.context, LinearLayoutManager.HORIZONTAL, false)
binding.mangaNovelRecyclerView.visibility = View.VISIBLE
binding.mangaNovel.visibility = View.VISIBLE
binding.mangaNovel.startAnimation(setSlideUp(uiSettings))
binding.mangaNovelRecyclerView.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
binding.mangaPopular.visibility = View.VISIBLE
binding.mangaPopular.startAnimation(setSlideUp(uiSettings))
}
fun updateAvatar() {
if (Anilist.avatar != null && ready.value == true) {
binding.mangaUserAvatar.loadImage(Anilist.avatar)
}
}
inner class MangaPageViewHolder(val binding: ItemMangaPageBinding) : RecyclerView.ViewHolder(binding.root)
}

View file

@ -0,0 +1,30 @@
package ani.dantotsu.home
import android.os.Bundle
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updateLayoutParams
import ani.dantotsu.databinding.ActivityNoInternetBinding
import ani.dantotsu.isOnline
import ani.dantotsu.navBarHeight
import ani.dantotsu.startMainActivity
import ani.dantotsu.statusBarHeight
class NoInternet : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityNoInternetBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.refreshContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight
bottomMargin = navBarHeight
}
binding.refreshButton.setOnClickListener {
if (isOnline(this)) {
startMainActivity(this)
}
}
}
}