From 1b4c8704eaef5b4babdd8210315e93bba3be8503 Mon Sep 17 00:00:00 2001 From: rebelonion <87634197+rebelonion@users.noreply.github.com> Date: Sat, 1 Jun 2024 08:25:08 -0500 Subject: [PATCH 01/26] fix: buffer manga image names --- .../ani/dantotsu/download/manga/MangaDownloaderService.kt | 3 ++- app/src/main/java/ani/dantotsu/util/NumberConverter.kt | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt index 4452a2df..ed3cb02c 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt @@ -32,6 +32,7 @@ import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_STAR import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER import ani.dantotsu.snackString import ani.dantotsu.util.Logger +import ani.dantotsu.util.NumberConverter.Companion.ofLength import com.anggrayudi.storage.file.deleteRecursively import com.anggrayudi.storage.file.forceDelete import com.anggrayudi.storage.file.openOutputStream @@ -235,7 +236,7 @@ class MangaDownloaderService : Service() { } if (bitmap != null) { - saveToDisk("$index.jpg", outputDir, bitmap) + saveToDisk("${index.ofLength(3)}.jpg", outputDir, bitmap) } farthest++ diff --git a/app/src/main/java/ani/dantotsu/util/NumberConverter.kt b/app/src/main/java/ani/dantotsu/util/NumberConverter.kt index 49d41de7..61aefe4e 100644 --- a/app/src/main/java/ani/dantotsu/util/NumberConverter.kt +++ b/app/src/main/java/ani/dantotsu/util/NumberConverter.kt @@ -47,5 +47,9 @@ class NumberConverter { val intBits = java.lang.Float.floatToIntBits(number) return Integer.toBinaryString(intBits) } + + fun Int.ofLength(length: Int): String { + return this.toString().padStart(length, '0') + } } } \ No newline at end of file From d488d115733bc84a55b7f71099b5678f5978325a Mon Sep 17 00:00:00 2001 From: aayush262 Date: Thu, 6 Jun 2024 21:03:43 +0530 Subject: [PATCH 02/26] fix(notifications): extra padding --- .../profile/activity/ActivityFragment.kt | 60 ++++++----------- .../notification/NotificationActivity.kt | 9 ++- .../notification/NotificationFragment.kt | 66 +++++++------------ 3 files changed, 46 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt index fa7826b7..b56f947d 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt @@ -43,30 +43,14 @@ class ActivityFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - type = arguments?.getSerializableCompat("type") as ActivityType - userId = arguments?.getInt("userId") - activityId = arguments?.getInt("activityId") + arguments?.let { + type = it.getSerializableCompat("type") as ActivityType + userId = it.getInt("userId") + activityId = it.getInt("activityId") + } binding.titleBar.visibility = if (type == ActivityType.OTHER_USER) View.VISIBLE else View.GONE binding.titleText.text = if (userId == Anilist.userid) getString(R.string.create_new_activity) else getString(R.string.write_a_message) - binding.titleImage.setOnClickListener { - if(userId == Anilist.userid) { - ContextCompat.startActivity( - requireContext(), - Intent(context, ActivityMarkdownCreator::class.java) - .putExtra("type", "activity"), - null - ) - } else{ - ContextCompat.startActivity( - requireContext(), - Intent(context, ActivityMarkdownCreator::class.java) - .putExtra("type", "message") - .putExtra("userId", userId), - - null - ) - } - } + binding.titleImage.setOnClickListener{handleTitleImageClick() } binding.listRecyclerView.adapter = adapter binding.listRecyclerView.layoutManager = LinearLayoutManager(context) binding.listProgressBar.isVisible = true @@ -104,7 +88,13 @@ class ActivityFragment : Fragment() { } }) } - + private fun handleTitleImageClick() { + val intent = Intent(context, ActivityMarkdownCreator::class.java).apply { + putExtra("type", if (userId == Anilist.userid) "activity" else "message") + putExtra("userId", userId) + } + ContextCompat.startActivity(requireContext(), intent, null) + } private suspend fun getList() { val list = when (type) { @@ -141,35 +131,23 @@ class ActivityFragment : Fragment() { } private fun onActivityClick(id: Int, type: String) { - when (type) { - "USER" -> { - ContextCompat.startActivity( - requireContext(), Intent(requireContext(), ProfileActivity::class.java) - .putExtra("userId", id), null - ) - } - - "MEDIA" -> { - ContextCompat.startActivity( - requireContext(), Intent(requireContext(), MediaDetailsActivity::class.java) - .putExtra("mediaId", id), null - ) - } + val intent = when (type) { + "USER" -> Intent(requireContext(), ProfileActivity::class.java).putExtra("userId", id) + "MEDIA" -> Intent(requireContext(), MediaDetailsActivity::class.java).putExtra("mediaId", id) + else -> return } + ContextCompat.startActivity(requireContext(), intent, null) } override fun onResume() { super.onResume() if (this::binding.isInitialized) { binding.root.requestLayout() - } } companion object { - enum class ActivityType { - GLOBAL, USER, OTHER_USER, ONE - } + enum class ActivityType { GLOBAL, USER, OTHER_USER, ONE } fun newInstance(type: ActivityType, userId: Int? = null, activityId: Int? = null): ActivityFragment { return ActivityFragment().apply { diff --git a/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt b/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt index e1e77cf3..75089031 100644 --- a/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt @@ -20,7 +20,7 @@ import ani.dantotsu.profile.notification.NotificationFragment.Companion.Notifica import nl.joery.animatedbottombar.AnimatedBottomBar class NotificationActivity : AppCompatActivity() { - private lateinit var binding: ActivityNotificationBinding + lateinit var binding: ActivityNotificationBinding private var selected: Int = 0 lateinit var navBar: AnimatedBottomBar override fun onCreate(savedInstanceState: Bundle?) { @@ -38,8 +38,8 @@ class NotificationActivity : AppCompatActivity() { bottomMargin = navBarHeight } val tabs = listOf( - Pair(R.drawable.ic_round_movie_filter_24, "Media"), Pair(R.drawable.ic_round_person_24, "User"), + Pair(R.drawable.ic_round_movie_filter_24, "Media"), Pair(R.drawable.ic_round_notifications_active_24, "Subs"), Pair(R.drawable.ic_round_comment_24, "Comments") ) @@ -49,7 +49,6 @@ class NotificationActivity : AppCompatActivity() { val getOne = intent.getIntExtra("activityId", -1) if (getOne != -1) navBar.isVisible = false binding.notificationViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, getOne) - binding.notificationViewPager.setOffscreenPageLimit(4) binding.notificationViewPager.setCurrentItem(selected, false) navBar.selectTabAt(selected) navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { @@ -84,8 +83,8 @@ class NotificationActivity : AppCompatActivity() { override fun getItemCount(): Int = if (id != -1) 1 else 4 override fun createFragment(position: Int): Fragment = when (position) { - 0 -> NotificationFragment.newInstance(if (id != -1) NotificationType.ONE else NotificationType.MEDIA, id) - 1 -> NotificationFragment.newInstance(NotificationType.USER) + 0 -> NotificationFragment.newInstance(NotificationType.USER) + 1 -> NotificationFragment.newInstance(if (id != -1) NotificationType.ONE else NotificationType.MEDIA, id) 2 -> NotificationFragment.newInstance(NotificationType.SUBSCRIPTION) 3 -> NotificationFragment.newInstance(NotificationType.COMMENT) else -> NotificationFragment.newInstance(NotificationType.MEDIA) diff --git a/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt b/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt index 2e6f2d28..6360763d 100644 --- a/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt @@ -46,8 +46,10 @@ class NotificationFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - type = arguments?.getSerializableCompat("type") as NotificationType - getID = arguments?.getInt("id") ?: -1 + arguments?.let { + getID = it.getInt("id") + type = it.getSerializableCompat("type") as NotificationType + } binding.notificationRecyclerView.adapter = adapter binding.notificationRecyclerView.layoutManager = LinearLayoutManager(context) binding.notificationProgressBar.isVisible = true @@ -158,47 +160,31 @@ class NotificationFragment : Fragment() { !binding.notificationRecyclerView.canScrollVertically(1) } - fun onClick( - id: Int, - optional: Int?, - type: NotificationClickType - ) { - when (type) { - NotificationClickType.USER -> { - ContextCompat.startActivity( - requireContext(), Intent(requireContext(), ProfileActivity::class.java) - .putExtra("userId", id), null - ) + fun onClick(id: Int, optional: Int?, type: NotificationClickType) { + val intent = when (type) { + NotificationClickType.USER -> Intent(requireContext(), ProfileActivity::class.java).apply { + putExtra("userId", id) } - NotificationClickType.MEDIA -> { - ContextCompat.startActivity( - requireContext(), Intent(requireContext(), MediaDetailsActivity::class.java) - .putExtra("mediaId", id), null - ) + NotificationClickType.MEDIA -> Intent(requireContext(), MediaDetailsActivity::class.java).apply { + putExtra("mediaId", id) } - NotificationClickType.ACTIVITY -> { - ContextCompat.startActivity( - requireContext(), Intent(requireContext(), FeedActivity::class.java) - .putExtra("activityId", id), null - ) + NotificationClickType.ACTIVITY -> Intent(requireContext(), FeedActivity::class.java).apply { + putExtra("activityId", id) } - NotificationClickType.COMMENT -> { - ContextCompat.startActivity( - requireContext(), Intent(requireContext(), MediaDetailsActivity::class.java) - .putExtra("FRAGMENT_TO_LOAD", "COMMENTS") - .putExtra("mediaId", id) - .putExtra("commentId", optional ?: -1), - null - ) - + NotificationClickType.COMMENT -> Intent(requireContext(), MediaDetailsActivity::class.java).apply { + putExtra("FRAGMENT_TO_LOAD", "COMMENTS") + putExtra("mediaId", id) + putExtra("commentId", optional ?: -1) } - NotificationClickType.UNDEFINED -> { - // Do nothing - } + NotificationClickType.UNDEFINED -> null + } + + intent?.let { + ContextCompat.startActivity(requireContext(), it, null) } } @@ -206,18 +192,12 @@ class NotificationFragment : Fragment() { super.onResume() if (this::binding.isInitialized) { binding.root.requestLayout() - binding.root.setBaseline((activity as NotificationActivity).navBar) } } companion object { - enum class NotificationClickType { - USER, MEDIA, ACTIVITY, COMMENT, UNDEFINED - } - - enum class NotificationType { - MEDIA, USER, SUBSCRIPTION, COMMENT, ONE - } + enum class NotificationClickType { USER, MEDIA, ACTIVITY, COMMENT, UNDEFINED } + enum class NotificationType { MEDIA, USER, SUBSCRIPTION, COMMENT, ONE } fun newInstance(type: NotificationType, id: Int = -1): NotificationFragment { return NotificationFragment().apply { From 3ae59b8d22d6ec05bc77e3f772d2d031aba4c3a3 Mon Sep 17 00:00:00 2001 From: aayush262 Date: Fri, 7 Jun 2024 00:29:13 +0530 Subject: [PATCH 03/26] fix(notifications): extra padding --- .../main/java/ani/dantotsu/MainActivity.kt | 1 + .../connections/anilist/AnilistQueries.kt | 218 +++++++----------- .../dantotsu/connections/anilist/api/Data.kt | 9 - 3 files changed, 79 insertions(+), 149 deletions(-) diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt index d57a9e64..2562ce93 100644 --- a/app/src/main/java/ani/dantotsu/MainActivity.kt +++ b/app/src/main/java/ani/dantotsu/MainActivity.kt @@ -312,6 +312,7 @@ class MainActivity : AppCompatActivity() { mainViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle) mainViewPager.setPageTransformer(ZoomOutPageTransformer()) + mainViewPager.offscreenPageLimit = 1 navbar.selectTabAt(selectedOption) navbar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt index 75ab4ee1..a53b2c41 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -1055,172 +1055,110 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult: return null } - private val onListAnime = - (if (PrefManager.getVal(PrefName.IncludeAnimeList)) "" else "onList:false").replace( - "\"", - "" - ) - private val isAdult = - (if (PrefManager.getVal(PrefName.AdultOnly)) "isAdult:true" else "").replace("\"", "") + private fun mediaList(media1: Page?): ArrayList { + val combinedList = arrayListOf() + media1?.media?.mapTo(combinedList) { Media(it) } + return combinedList + } + private fun getPreference(pref: PrefName): Boolean = PrefManager.getVal(pref) + private fun buildQueryString(sort: String, type: String, format: String? = null, country: String? = null): String { + val includeList = if (type == "ANIME" && !getPreference(PrefName.IncludeAnimeList)) "onList:false" else if (type == "MANGA" && !getPreference(PrefName.IncludeMangaList)) "onList:false" else "" + val isAdult = if (getPreference(PrefName.AdultOnly)) "isAdult:true" else "" + val formatFilter = format?.let { "format: $it, " } ?: "" + val countryFilter = country?.let { "countryOfOrigin: $it, " } ?: "" + + return """Page(page:1,perPage:50){ + pageInfo{hasNextPage total} + media(sort:$sort, type:$type, $formatFilter $countryFilter $includeList $isAdult){ + id idMal status chapters episodes nextAiringEpisode{episode} + isAdult type meanScore isFavourite format bannerImage countryOfOrigin + coverImage{large} title{english romaji userPreferred} + mediaListEntry{progress private score(format:POINT_100) status} + } + }""" + } private fun recentAnimeUpdates(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}airingSchedules(airingAt_greater:0 airingAt_lesser:${System.currentTimeMillis() / 1000 - 10000} sort:TIME_DESC){episode airingAt media{id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}}""" + val currentTime = System.currentTimeMillis() / 1000 + return """Page(page:$page,perPage:50){ + pageInfo{hasNextPage total} + airingSchedules(airingAt_greater:0 airingAt_lesser:${currentTime - 10000} sort:TIME_DESC){ + episode airingAt media{ + id idMal status chapters episodes nextAiringEpisode{episode} + isAdult type meanScore isFavourite format bannerImage countryOfOrigin + coverImage{large} title{english romaji userPreferred} + mediaListEntry{progress private score(format:POINT_100) status} + } + } + }""" } - - private fun trendingMovies(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: ANIME, format: MOVIE, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" + private fun queryAnimeList(): String { + return """{ + recentUpdates:${recentAnimeUpdates(1)} + recentUpdates2:${recentAnimeUpdates(2)} + trendingMovies:${buildQueryString("POPULARITY_DESC", "ANIME", "MOVIE")} + topRated:${buildQueryString("SCORE_DESC", "ANIME")} + mostFav:${buildQueryString("FAVOURITES_DESC", "ANIME")} + }""" } - - private fun topRatedAnime(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort: SCORE_DESC, type: ANIME, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - private fun mostFavAnime(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:FAVOURITES_DESC,type: ANIME, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" + private fun queryMangaList(): String { + return """{ + trendingManga:${buildQueryString("POPULARITY_DESC", "MANGA", country = "JP")} + trendingManhwa:${buildQueryString("POPULARITY_DESC", "MANGA", country = "KR")} + trendingNovel:${buildQueryString("POPULARITY_DESC", "MANGA", format = "NOVEL", country = "JP")} + topRated:${buildQueryString("SCORE_DESC", "MANGA")} + mostFav:${buildQueryString("FAVOURITES_DESC", "MANGA")} + + }""" } suspend fun loadAnimeList(): Map> { val list = mutableMapOf>() - fun query(): String { - return """{ - recentUpdates:${recentAnimeUpdates(1)} - recentUpdates2:${recentAnimeUpdates(2)} - trendingMovies:${trendingMovies(1)} - trendingMovies2:${trendingMovies(2)} - topRated:${topRatedAnime(1)} - topRated2:${topRatedAnime(2)} - mostFav:${mostFavAnime(1)} - mostFav2:${mostFavAnime(2)} - }""".trimIndent() - } - executeQuery(query(), force = true)?.data?.apply { + + fun filterRecentUpdates( + page: Page?, + ): ArrayList { val listOnly: Boolean = PrefManager.getVal(PrefName.RecentlyListOnly) val adultOnly: Boolean = PrefManager.getVal(PrefName.AdultOnly) val idArr = mutableListOf() - list["recentUpdates"] = recentUpdates?.airingSchedules?.mapNotNull { i -> + return page?.airingSchedules?.mapNotNull { i -> i.media?.let { - if (!idArr.contains(it.id)) - if (!listOnly && it.countryOfOrigin == "JP" && Anilist.adult && adultOnly && it.isAdult == true) { - idArr.add(it.id) - Media(it) - } else if (!listOnly && !adultOnly && (it.countryOfOrigin == "JP" && it.isAdult == false)) { - idArr.add(it.id) - Media(it) - } else if ((listOnly && it.mediaListEntry != null)) { + if (!idArr.contains(it.id)) { + val shouldAdd = when { + !listOnly && it.countryOfOrigin == "JP" && Anilist.adult && adultOnly && it.isAdult == true -> true + !listOnly && !adultOnly && it.countryOfOrigin == "JP" && it.isAdult == false -> true + listOnly && it.mediaListEntry != null -> true + else -> false + } + if (shouldAdd) { idArr.add(it.id) Media(it) } else null - else null + } else null } }?.toCollection(ArrayList()) ?: arrayListOf() - - list["trendingMovies"] = - trendingMovies?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - list["topRated"] = - topRated?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - list["mostFav"] = - mostFav?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - - list["recentUpdates"]?.addAll(recentUpdates2?.airingSchedules?.mapNotNull { i -> - i.media?.let { - if (!idArr.contains(it.id)) - if (!listOnly && it.countryOfOrigin == "JP" && Anilist.adult && adultOnly && it.isAdult == true) { - idArr.add(it.id) - Media(it) - } else if (!listOnly && !adultOnly && (it.countryOfOrigin == "JP" && it.isAdult == false)) { - idArr.add(it.id) - Media(it) - } else if ((listOnly && it.mediaListEntry != null)) { - idArr.add(it.id) - Media(it) - } else null - else null - } - }?.toCollection(ArrayList()) ?: arrayListOf()) - list["trendingMovies"]?.addAll(trendingMovies2?.media?.map { Media(it) } - ?.toCollection(ArrayList()) ?: arrayListOf()) - list["topRated"]?.addAll( - topRated2?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - ) - list["mostFav"]?.addAll( - mostFav2?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - ) + } + executeQuery(queryAnimeList(), force = true)?.data?.apply { + list["recentUpdates"] = filterRecentUpdates(recentUpdates) + list["trendingMovies"] = mediaList(trendingMovies) + list["topRated"] = mediaList(topRated) + list["mostFav"] = mediaList(mostFav) } return list } - - private val onListManga = - (if (PrefManager.getVal(PrefName.IncludeMangaList)) "" else "onList:false").replace( - "\"", - "" - ) - - private fun trendingManga(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA,countryOfOrigin:JP, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - private fun trendingManhwa(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA, countryOfOrigin:KR, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - private fun trendingNovel(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA, format: NOVEL, countryOfOrigin:JP, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - private fun topRatedManga(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort: SCORE_DESC, type: MANGA, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - private fun mostFavManga(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:FAVOURITES_DESC,type: MANGA, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - suspend fun loadMangaList(): Map> { val list = mutableMapOf>() - fun query(): String { - return """{ - trendingManga:${trendingManga(1)} - trendingManga2:${trendingManga(2)} - trendingManhwa:${trendingManhwa(1)} - trendingManhwa2:${trendingManhwa(2)} - trendingNovel:${trendingNovel(1)} - trendingNovel2:${trendingNovel(2)} - topRated:${topRatedManga(1)} - topRated2:${topRatedManga(2)} - mostFav:${mostFavManga(1)} - mostFav2:${mostFavManga(2)} - }""".trimIndent() + executeQuery(queryMangaList(), force = true)?.data?.apply { + list["trendingManga"] = mediaList(trendingManga) + list["trendingManhwa"] = mediaList(trendingManhwa) + list["trendingNovel"] = mediaList(trendingNovel) + list["topRated"] = mediaList(topRated) + list["mostFav"] = mediaList(mostFav) } - - executeQuery(query(), force = true)?.data?.apply { - list["trendingManga"] = - trendingManga?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - list["trendingManhwa"] = - trendingManhwa?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - list["trendingNovel"] = - trendingNovel?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - list["topRated"] = - topRated?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - list["mostFav"] = - mostFav?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - - list["trendingManga"]?.addAll( - trendingManga2?.media?.map { Media(it) }?.toList() ?: arrayListOf() - ) - list["trendingManhwa"]?.addAll( - trendingManhwa2?.media?.map { Media(it) }?.toList() ?: arrayListOf() - ) - list["trendingNovel"]?.addAll( - trendingNovel2?.media?.map { Media(it) }?.toList() ?: arrayListOf() - ) - list["topRated"]?.addAll(topRated2?.media?.map { Media(it) }?.toList() ?: arrayListOf()) - list["mostFav"]?.addAll(mostFav2?.media?.map { Media(it) }?.toList() ?: arrayListOf()) - } - - return list } + suspend fun recentlyUpdated( greater: Long = 0, lesser: Long = System.currentTimeMillis() / 1000 - 10000 diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt b/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt index e6635b9a..e909a3bc 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt @@ -163,13 +163,9 @@ class Query { @Serializable data class Data( @SerialName("recentUpdates") val recentUpdates: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("recentUpdates2") val recentUpdates2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("trendingMovies") val trendingMovies: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("trendingMovies2") val trendingMovies2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("topRated") val topRated: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("topRated2") val topRated2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("mostFav") val mostFav: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("mostFav2") val mostFav2: ani.dantotsu.connections.anilist.api.Page?, ) } @@ -181,15 +177,10 @@ class Query { @Serializable data class Data( @SerialName("trendingManga") val trendingManga: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("trendingManga2") val trendingManga2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("trendingManhwa") val trendingManhwa: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("trendingManhwa2") val trendingManhwa2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("trendingNovel") val trendingNovel: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("trendingNovel2") val trendingNovel2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("topRated") val topRated: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("topRated2") val topRated2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("mostFav") val mostFav: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("mostFav2") val mostFav2: ani.dantotsu.connections.anilist.api.Page?, ) } From 903423b842e4615eebbe5535e121accca05ba471 Mon Sep 17 00:00:00 2001 From: aayush262 Date: Tue, 11 Jun 2024 20:56:59 +0530 Subject: [PATCH 04/26] fix: random things --- .../profile/activity/ActivityFragment.kt | 25 +++++++--- .../dantotsu/profile/activity/ActivityItem.kt | 7 ++- .../settings/UserInterfaceSettingsActivity.kt | 28 +++++------ .../ani/dantotsu/util/AlertDialogBuilder.kt | 46 ++++++++----------- .../main/res/layout/item_activity_reply.xml | 1 - app/src/main/res/xml/anime_preferences.xml | 5 +- 6 files changed, 56 insertions(+), 56 deletions(-) diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt index b56f947d..3b9b32d0 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt @@ -48,9 +48,11 @@ class ActivityFragment : Fragment() { userId = it.getInt("userId") activityId = it.getInt("activityId") } - binding.titleBar.visibility = if (type == ActivityType.OTHER_USER) View.VISIBLE else View.GONE - binding.titleText.text = if (userId == Anilist.userid) getString(R.string.create_new_activity) else getString(R.string.write_a_message) - binding.titleImage.setOnClickListener{handleTitleImageClick() } + binding.titleBar.visibility = + if (type == ActivityType.OTHER_USER) View.VISIBLE else View.GONE + binding.titleText.text = + if (userId == Anilist.userid) getString(R.string.create_new_activity) else getString(R.string.write_a_message) + binding.titleImage.setOnClickListener { handleTitleImageClick() } binding.listRecyclerView.adapter = adapter binding.listRecyclerView.layoutManager = LinearLayoutManager(context) binding.listProgressBar.isVisible = true @@ -88,6 +90,7 @@ class ActivityFragment : Fragment() { } }) } + private fun handleTitleImageClick() { val intent = Intent(context, ActivityMarkdownCreator::class.java).apply { putExtra("type", if (userId == Anilist.userid) "activity" else "message") @@ -103,14 +106,14 @@ class ActivityFragment : Fragment() { ActivityType.OTHER_USER -> getActivities(userId = userId) ActivityType.ONE -> getActivities(activityId = activityId) } - adapter.addAll(list.map { ActivityItem(it, ::onActivityClick, adapter ,requireActivity()) }) + adapter.addAll(list.map { ActivityItem(it, adapter, ::onActivityClick) }) } private suspend fun getActivities( global: Boolean = false, userId: Int? = null, activityId: Int? = null, - filter:Boolean = false + filter: Boolean = false ): List { val res = Anilist.query.getFeed(userId, global, page, activityId)?.data?.page?.activities page += 1 @@ -133,7 +136,11 @@ class ActivityFragment : Fragment() { private fun onActivityClick(id: Int, type: String) { val intent = when (type) { "USER" -> Intent(requireContext(), ProfileActivity::class.java).putExtra("userId", id) - "MEDIA" -> Intent(requireContext(), MediaDetailsActivity::class.java).putExtra("mediaId", id) + "MEDIA" -> Intent( + requireContext(), + MediaDetailsActivity::class.java + ).putExtra("mediaId", id) + else -> return } ContextCompat.startActivity(requireContext(), intent, null) @@ -149,7 +156,11 @@ class ActivityFragment : Fragment() { companion object { enum class ActivityType { GLOBAL, USER, OTHER_USER, ONE } - fun newInstance(type: ActivityType, userId: Int? = null, activityId: Int? = null): ActivityFragment { + fun newInstance( + type: ActivityType, + userId: Int? = null, + activityId: Int? = null + ): ActivityFragment { return ActivityFragment().apply { arguments = Bundle().apply { putSerializable("type", type) diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt index 22cf432b..26c886c9 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt @@ -29,9 +29,8 @@ import kotlinx.coroutines.withContext class ActivityItem( private val activity: Activity, - val clickCallback: (Int, type: String) -> Unit, private val parentAdapter: GroupieAdapter, - private val fragActivity: FragmentActivity + val clickCallback: (Int, type: String) -> Unit, ) : BindableItem() { private lateinit var binding: ItemActivityBinding @@ -54,14 +53,14 @@ class ActivityItem( } binding.activityRepliesContainer.setOnClickListener { RepliesBottomDialog.newInstance(activity.id) - .show(fragActivity.supportFragmentManager, "replies") + .show((context as FragmentActivity).supportFragmentManager, "replies") } binding.replyCount.text = activity.replyCount.toString() binding.activityReplies.setColorFilter(ContextCompat.getColor(binding.root.context, R.color.bg_opp)) binding.activityLikeContainer.setOnLongClickListener { UsersDialogFragment().apply { userList(userList) - show(fragActivity.supportFragmentManager, "dialog") + show((context as FragmentActivity).supportFragmentManager, "dialog") } true } diff --git a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt index 8d1a9582..fd6bfde6 100644 --- a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt @@ -14,6 +14,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager +import ani.dantotsu.util.customAlertDialog class UserInterfaceSettingsActivity : AppCompatActivity() { lateinit var binding: ActivityUserInterfaceSettingsBinding @@ -38,20 +39,21 @@ class UserInterfaceSettingsActivity : AppCompatActivity() { binding.uiSettingsHomeLayout.setOnClickListener { val set = PrefManager.getVal>(PrefName.HomeLayout).toMutableList() val views = resources.getStringArray(R.array.home_layouts) - val dialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.home_layout_show)).apply { - setMultiChoiceItems( - views, - PrefManager.getVal>(PrefName.HomeLayout).toBooleanArray() - ) { _, i, value -> - set[i] = value + customAlertDialog() + .setTitle(getString(R.string.home_layout_show)) + .multiChoiceItems( + items = views, + checkedItems = PrefManager.getVal>(PrefName.HomeLayout).toBooleanArray() + ) { selectedItems -> + for (i in selectedItems.indices) { + set[i] = selectedItems[i] } - setPositiveButton("Done") { _, _ -> - PrefManager.setVal(PrefName.HomeLayout, set) - restartApp() - } - }.show() - dialog.window?.setDimAmount(0.8f) + } + .setPosButton("Done") { + PrefManager.setVal(PrefName.HomeLayout, set) + restartApp() + } + .show() } binding.uiSettingsSmallView.isChecked = PrefManager.getVal(PrefName.SmallView) diff --git a/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt b/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt index 14e71432..5bda5a18 100644 --- a/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt +++ b/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt @@ -1,5 +1,6 @@ package ani.dantotsu.util +import android.app.Activity import android.app.AlertDialog import android.content.Context import android.view.View @@ -21,6 +22,8 @@ class AlertDialogBuilder(private val context: Context) { private var onItemSelected: ((Int) -> Unit)? = null private var customView: View? = null private var attach: ((dialog: AlertDialog) -> Unit)? = null + private var onDismiss: (() -> Unit)? = null + fun setTitle(title: String?): AlertDialogBuilder { this.title = title return this @@ -52,11 +55,7 @@ class AlertDialogBuilder(private val context: Context) { return this } - fun setPosButton( - int: Int, - formatArgs: Int? = null, - onClick: (() -> Unit)? = null - ): AlertDialogBuilder { + fun setPosButton(int: Int, formatArgs: Int? = null, onClick: (() -> Unit)? = null): AlertDialogBuilder { this.posButtonTitle = context.getString(int, formatArgs) this.onPositiveButtonClick = onClick return this @@ -68,11 +67,7 @@ class AlertDialogBuilder(private val context: Context) { return this } - fun setNegButton( - int: Int, - formatArgs: Int? = null, - onClick: (() -> Unit)? = null - ): AlertDialogBuilder { + fun setNegButton(int: Int, formatArgs: Int? = null, onClick: (() -> Unit)? = null): AlertDialogBuilder { this.negButtonTitle = context.getString(int, formatArgs) this.onNegativeButtonClick = onClick return this @@ -84,11 +79,7 @@ class AlertDialogBuilder(private val context: Context) { return this } - fun setNeutralButton( - int: Int, - formatArgs: Int? = null, - onClick: (() -> Unit)? = null - ): AlertDialogBuilder { + fun setNeutralButton(int: Int, formatArgs: Int? = null, onClick: (() -> Unit)? = null): AlertDialogBuilder { this.neutralButtonTitle = context.getString(int, formatArgs) this.onNeutralButtonClick = onClick return this @@ -99,22 +90,19 @@ class AlertDialogBuilder(private val context: Context) { return this } - fun singleChoiceItems( - items: Array, - selectedItemIndex: Int = -1, - onItemSelected: (Int) -> Unit - ): AlertDialogBuilder { + fun onDismiss(onDismiss: (() -> Unit)? = null): AlertDialogBuilder { + this.onDismiss = onDismiss + return this + } + + fun singleChoiceItems(items: Array, selectedItemIndex: Int = -1, onItemSelected: (Int) -> Unit): AlertDialogBuilder { this.items = items this.selectedItemIndex = selectedItemIndex this.onItemSelected = onItemSelected return this } - fun multiChoiceItems( - items: Array, - checkedItems: BooleanArray? = null, - onItemsSelected: (BooleanArray) -> Unit - ): AlertDialogBuilder { + fun multiChoiceItems(items: Array, checkedItems: BooleanArray? = null, onItemsSelected: (BooleanArray) -> Unit): AlertDialogBuilder { this.items = items this.checkedItems = checkedItems ?: BooleanArray(items.size) { false } this.onItemsSelected = onItemsSelected @@ -122,6 +110,8 @@ class AlertDialogBuilder(private val context: Context) { } fun show() { + if (context is Activity && context.isFinishing) return // Ensure context is valid + val builder = AlertDialog.Builder(context, R.style.MyPopup) if (title != null) builder.setTitle(title) if (message != null) builder.setMessage(message) @@ -160,12 +150,14 @@ class AlertDialogBuilder(private val context: Context) { builder.setCancelable(false) val dialog = builder.create() attach?.invoke(dialog) + dialog.setOnDismissListener { + onDismiss?.invoke() + } dialog.window?.setDimAmount(0.8f) dialog.show() } - } fun Context.customAlertDialog(): AlertDialogBuilder { return AlertDialogBuilder(this) -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/item_activity_reply.xml b/app/src/main/res/layout/item_activity_reply.xml index 0b851914..de87b96a 100644 --- a/app/src/main/res/layout/item_activity_reply.xml +++ b/app/src/main/res/layout/item_activity_reply.xml @@ -129,7 +129,6 @@ android:textSize="12sp" tools:visibility="visible" tools:ignore="HardcodedText" /> - - + z From 1670383619fdebf9be6c4c6ffea97ba93b639dc6 Mon Sep 17 00:00:00 2001 From: aayush262 Date: Thu, 13 Jun 2024 17:53:40 +0530 Subject: [PATCH 05/26] feat(home): hive private media --- .../dantotsu/connections/anilist/AnilistQueries.kt | 9 +++++---- .../ani/dantotsu/settings/SettingsCommonActivity.kt | 11 +++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt index a53b2c41..438dc7ad 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -479,6 +479,7 @@ class AnilistQueries { suspend fun initHomePage(): Map> { val removeList = PrefManager.getCustomVal("removeList", setOf()) + val hidePrivate = PrefManager.getVal(PrefName.HidePrivate) val removedMedia = ArrayList() val toShow: List = PrefManager.getVal(PrefName.HomeLayout) // anime continue, anime fav, anime planned, manga continue, manga fav, manga planned, recommendations @@ -528,7 +529,7 @@ class AnilistQueries { current?.lists?.forEach { li -> li.entries?.reversed()?.forEach { val m = Media(it) - if (m.id !in removeList) { + if (m.id !in removeList && if (hidePrivate) !m.isListPrivate else true) { m.cameFromContinue = true subMap[m.id] = m } else { @@ -540,7 +541,7 @@ class AnilistQueries { repeating?.lists?.forEach { li -> li.entries?.reversed()?.forEach { val m = Media(it) - if (m.id !in removeList) { + if (m.id !in removeList && if (hidePrivate) !m.isListPrivate else true) { m.cameFromContinue = true subMap[m.id] = m } else { @@ -579,7 +580,7 @@ class AnilistQueries { current?.lists?.forEach { li -> li.entries?.reversed()?.forEach { val m = Media(it) - if (m.id !in removeList) { + if (m.id !in removeList && if (hidePrivate) !m.isListPrivate else true) { m.cameFromContinue = true subMap[m.id] = m } else { @@ -612,7 +613,7 @@ class AnilistQueries { apiMediaList?.edges?.forEach { it.node?.let { i -> val m = Media(i).apply { isFav = true } - if (m.id !in removeList) { + if (m.id !in removeList && if (hidePrivate) !m.isListPrivate else true) { returnArray.add(m) } else { removedMedia.add(m) diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt index c8dc29d5..0bcf356d 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt @@ -372,6 +372,17 @@ class SettingsCommonActivity : AppCompatActivity() { PrefManager.setVal(PrefName.ContinueMedia, isChecked) } ), + Settings( + type = 2, + name = getString(R.string.hide_private), + desc = getString(R.string.hide_private_desc), + icon = R.drawable.ic_round_remove_red_eye_24, + isChecked = PrefManager.getVal(PrefName.HidePrivate), + switch = { isChecked, _ -> + PrefManager.setVal(PrefName.HidePrivate, isChecked) + restartApp() + } + ), Settings( type = 2, name = getString(R.string.search_source_list), From 124c8f5ed74de017c0dd85da63114c9776847521 Mon Sep 17 00:00:00 2001 From: aayush262 Date: Thu, 13 Jun 2024 19:15:55 +0530 Subject: [PATCH 06/26] feat: optimize Alert Dialogs --- .../main/java/ani/dantotsu/MainActivity.kt | 46 ++- .../download/anime/OfflineAnimeFragment.kt | 32 +- .../download/manga/OfflineMangaFragment.kt | 23 +- .../ani/dantotsu/download/video/Helper.kt | 22 +- .../java/ani/dantotsu/home/LoginFragment.kt | 47 ++- .../java/ani/dantotsu/home/status/Stories.kt | 3 +- .../dantotsu/media/anime/AnimeWatchAdapter.kt | 188 +++++------ .../media/anime/AnimeWatchFragment.kt | 38 +-- .../dantotsu/media/anime/EpisodeAdapters.kt | 19 +- .../media/anime/SelectorDialogFragment.kt | 85 +++-- .../dantotsu/media/comments/CommentItem.kt | 22 +- .../media/comments/CommentsFragment.kt | 76 ++--- .../dantotsu/media/manga/MangaReadAdapter.kt | 315 +++++++++--------- .../dantotsu/media/manga/MangaReadFragment.kt | 25 +- .../manga/mangareader/MangaReaderActivity.kt | 22 +- .../media/novel/NovelResponseAdapter.kt | 34 +- .../dantotsu/profile/activity/ActivityItem.kt | 1 - .../activity}/RepliesBottomDialog.kt | 15 +- .../dantotsu/settings/ExtensionsActivity.kt | 65 ++-- .../InstalledAnimeExtensionsFragment.kt | 21 +- .../InstalledMangaExtensionsFragment.kt | 14 +- .../settings/PlayerSettingsActivity.kt | 168 +++++----- .../settings/SettingsCommonActivity.kt | 105 +++--- .../settings/SettingsExtensionsActivity.kt | 76 ++--- .../settings/SettingsNotificationActivity.kt | 29 +- .../settings/UserInterfaceSettingsActivity.kt | 12 +- .../dantotsu/settings/saving/Preferences.kt | 1 + .../dantotsu/util/ActivityMarkdownCreator.kt | 13 +- .../ani/dantotsu/util/AlertDialogBuilder.kt | 32 +- .../ani/dantotsu/util/StoragePermissions.kt | 25 +- app/src/main/res/values/strings.xml | 2 + 31 files changed, 739 insertions(+), 837 deletions(-) rename app/src/main/java/ani/dantotsu/{home/status => profile/activity}/RepliesBottomDialog.kt (90%) diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt index 2562ce93..68f42922 100644 --- a/app/src/main/java/ani/dantotsu/MainActivity.kt +++ b/app/src/main/java/ani/dantotsu/MainActivity.kt @@ -61,6 +61,7 @@ import ani.dantotsu.settings.saving.internal.PreferenceKeystore import ani.dantotsu.settings.saving.internal.PreferencePackager import ani.dantotsu.themes.ThemeManager import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar import com.google.android.material.textfield.TextInputEditText @@ -494,35 +495,28 @@ class MainActivity : AppCompatActivity() { val password = CharArray(16).apply { fill('0') } // Inflate the dialog layout - val dialogView = DialogUserAgentBinding.inflate(layoutInflater) - dialogView.userAgentTextBox.hint = "Password" - dialogView.subtitle.visibility = View.VISIBLE - dialogView.subtitle.text = getString(R.string.enter_password_to_decrypt_file) - - val dialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle("Enter Password") - .setView(dialogView.root) - .setPositiveButton("OK", null) - .setNegativeButton("Cancel") { dialog, _ -> + val dialogView = DialogUserAgentBinding.inflate(layoutInflater).apply { + userAgentTextBox.hint = "Password" + subtitle.visibility = View.VISIBLE + subtitle.text = getString(R.string.enter_password_to_decrypt_file) + } + customAlertDialog().apply { + setTitle("Enter Password") + setCustomView(dialogView.root) + setPosButton(R.string.yes) { + val editText = dialogView.userAgentTextBox + if (editText.text?.isNotBlank() == true) { + editText.text?.toString()?.trim()?.toCharArray(password) + callback(password) + } else { + toast("Password cannot be empty") + } + } + setNegButton(R.string.cancel) { password.fill('0') - dialog.dismiss() callback(null) } - .create() - - dialog.window?.setDimAmount(0.8f) - dialog.show() - - // Override the positive button here - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { - val editText = dialog.findViewById(R.id.userAgentTextBox) - if (editText?.text?.isNotBlank() == true) { - editText.text?.toString()?.trim()?.toCharArray(password) - dialog.dismiss() - callback(password) - } else { - toast("Password cannot be empty") - } + show() } } diff --git a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt index 3bcdffe1..4f3876fe 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt @@ -49,6 +49,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.anggrayudi.storage.file.openInputStream import com.google.android.material.card.MaterialCardView import com.google.android.material.imageview.ShapeableImageView @@ -203,25 +204,22 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { val type: MediaType = MediaType.ANIME // Alert dialog to confirm deletion - val builder = - androidx.appcompat.app.AlertDialog.Builder(requireContext(), R.style.MyPopup) - builder.setTitle("Delete ${item.title}?") - builder.setMessage("Are you sure you want to delete ${item.title}?") - builder.setPositiveButton("Yes") { _, _ -> - downloadManager.removeMedia(item.title, type) - val mediaIds = - PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values - ?: emptySet() - if (mediaIds.isEmpty()) { - snackString("No media found") // if this happens, terrible things have happened + requireContext().customAlertDialog().apply { + setTitle("Delete ${item.title}?") + setMessage("Are you sure you want to delete ${item.title}?") + setPosButton(R.string.yes) { + downloadManager.removeMedia(item.title, type) + val mediaIds = PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values ?: emptySet() + if (mediaIds.isEmpty()) { + snackString("No media found") // if this happens, terrible things have happened + } + getDownloads() } - getDownloads() + setNegButton(R.string.no) { + // Do nothing + } + show() } - builder.setNegativeButton("No") { _, _ -> - // Do nothing - } - val dialog = builder.show() - dialog.window?.setDimAmount(0.8f) true } } diff --git a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt index 6d88918e..dcb462e8 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt @@ -46,6 +46,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.anggrayudi.storage.file.openInputStream import com.google.android.material.card.MaterialCardView import com.google.android.material.imageview.ShapeableImageView @@ -201,19 +202,15 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { MediaType.NOVEL } // Alert dialog to confirm deletion - val builder = - androidx.appcompat.app.AlertDialog.Builder(requireContext(), R.style.MyPopup) - builder.setTitle("Delete ${item.title}?") - builder.setMessage("Are you sure you want to delete ${item.title}?") - builder.setPositiveButton("Yes") { _, _ -> - downloadManager.removeMedia(item.title, type) - getDownloads() - } - builder.setNegativeButton("No") { _, _ -> - // Do nothing - } - val dialog = builder.show() - dialog.window?.setDimAmount(0.8f) + requireContext().customAlertDialog().apply { + setTitle("Delete ${item.title}?") + setMessage("Are you sure you want to delete ${item.title}?") + setPosButton(R.string.yes) { + downloadManager.removeMedia(item.title, type) + getDownloads() + } + setNegButton(R.string.no) + }.show() true } } diff --git a/app/src/main/java/ani/dantotsu/download/video/Helper.kt b/app/src/main/java/ani/dantotsu/download/video/Helper.kt index b207d634..ab8dd73d 100644 --- a/app/src/main/java/ani/dantotsu/download/video/Helper.kt +++ b/app/src/main/java/ani/dantotsu/download/video/Helper.kt @@ -3,7 +3,6 @@ package ani.dantotsu.download.video import android.Manifest import android.annotation.SuppressLint import android.app.Activity -import android.app.AlertDialog import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -29,10 +28,10 @@ import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.anime.AnimeServiceDataSingleton import ani.dantotsu.media.Media import ani.dantotsu.media.MediaType -import ani.dantotsu.parsers.Subtitle import ani.dantotsu.parsers.Video import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import eu.kanade.tachiyomi.network.NetworkHelper import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -72,19 +71,19 @@ object Helper { episodeImage ) - val downloadsManger = Injekt.get() - val downloadCheck = downloadsManger + val downloadsManager = Injekt.get() + val downloadCheck = downloadsManager .queryDownload(title, episode, MediaType.ANIME) if (downloadCheck) { - AlertDialog.Builder(context, R.style.MyPopup) - .setTitle("Download Exists") - .setMessage("A download for this episode already exists. Do you want to overwrite it?") - .setPositiveButton("Yes") { _, _ -> + context.customAlertDialog().apply { + setTitle("Download Exists") + setMessage("A download for this episode already exists. Do you want to overwrite it?") + setPosButton(R.string.yes) { PrefManager.getAnimeDownloadPreferences().edit() .remove(animeDownloadTask.getTaskName()) .apply() - downloadsManger.removeDownload( + downloadsManager.removeDownload( DownloadedType( title, episode, @@ -99,8 +98,9 @@ object Helper { } } } - .setNegativeButton("No") { _, _ -> } - .show() + setNegButton(R.string.no) + show() + } } else { AnimeServiceDataSingleton.downloadQueue.offer(animeDownloadTask) if (!AnimeServiceDataSingleton.isServiceRunning) { diff --git a/app/src/main/java/ani/dantotsu/home/LoginFragment.kt b/app/src/main/java/ani/dantotsu/home/LoginFragment.kt index 5f89464e..13fde1e5 100644 --- a/app/src/main/java/ani/dantotsu/home/LoginFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/LoginFragment.kt @@ -12,12 +12,14 @@ import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist +import ani.dantotsu.databinding.DialogUserAgentBinding import ani.dantotsu.databinding.FragmentLoginBinding import ani.dantotsu.openLinkInBrowser import ani.dantotsu.settings.saving.internal.PreferenceKeystore import ani.dantotsu.settings.saving.internal.PreferencePackager import ani.dantotsu.toast import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.google.android.material.textfield.TextInputEditText class LoginFragment : Fragment() { @@ -94,38 +96,31 @@ class LoginFragment : Fragment() { val password = CharArray(16).apply { fill('0') } // Inflate the dialog layout - val dialogView = - LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_user_agent, null) - dialogView.findViewById(R.id.userAgentTextBox)?.hint = "Password" - val subtitleTextView = dialogView.findViewById(R.id.subtitle) - subtitleTextView?.visibility = View.VISIBLE - subtitleTextView?.text = "Enter your password to decrypt the file" + val dialogView = DialogUserAgentBinding.inflate(layoutInflater).apply { + userAgentTextBox.hint = "Password" + subtitle.visibility = View.VISIBLE + subtitle.text = getString(R.string.enter_password_to_decrypt_file) + } - val dialog = AlertDialog.Builder(requireActivity(), R.style.MyPopup) - .setTitle("Enter Password") - .setView(dialogView) - .setPositiveButton("OK", null) - .setNegativeButton("Cancel") { dialog, _ -> + requireActivity().customAlertDialog().apply { + setTitle("Enter Password") + setCustomView(dialogView.root) + setPosButton(R.string.ok){ + val editText = dialogView.userAgentTextBox + if (editText.text?.isNotBlank() == true) { + editText.text?.toString()?.trim()?.toCharArray(password) + callback(password) + } else { + toast("Password cannot be empty") + } + } + setNegButton(R.string.cancel) { password.fill('0') - dialog.dismiss() callback(null) } - .create() + }.show() - dialog.window?.setDimAmount(0.8f) - dialog.show() - // Override the positive button here - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { - val editText = dialog.findViewById(R.id.userAgentTextBox) - if (editText?.text?.isNotBlank() == true) { - editText.text?.toString()?.trim()?.toCharArray(password) - dialog.dismiss() - callback(password) - } else { - toast("Password cannot be empty") - } - } } private fun restartApp() { diff --git a/app/src/main/java/ani/dantotsu/home/status/Stories.kt b/app/src/main/java/ani/dantotsu/home/status/Stories.kt index d6522844..a017b7ab 100644 --- a/app/src/main/java/ani/dantotsu/home/status/Stories.kt +++ b/app/src/main/java/ani/dantotsu/home/status/Stories.kt @@ -5,7 +5,6 @@ import android.content.Context import android.content.Intent import android.content.res.ColorStateList import android.util.AttributeSet -import android.view.Gravity import android.view.LayoutInflater import android.view.MotionEvent import android.view.View @@ -16,7 +15,6 @@ import androidx.core.app.ActivityOptionsCompat import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.isVisible -import androidx.core.widget.NestedScrollView import androidx.fragment.app.FragmentActivity import ani.dantotsu.R import ani.dantotsu.blurImage @@ -32,6 +30,7 @@ import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.User import ani.dantotsu.profile.UsersDialogFragment import ani.dantotsu.profile.activity.ActivityItemBuilder +import ani.dantotsu.profile.activity.RepliesBottomDialog import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt index a2406a21..4f710896 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt @@ -39,6 +39,7 @@ import ani.dantotsu.settings.FAQActivity import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.toast +import ani.dantotsu.util.customAlertDialog import com.google.android.material.chip.Chip import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK @@ -61,9 +62,6 @@ class AnimeWatchAdapter( return ViewHolder(bind) } - private var nestedDialog: AlertDialog? = null - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { val binding = holder.binding _binding = binding @@ -201,104 +199,102 @@ class AnimeWatchAdapter( //Nested Button binding.animeNestedButton.setOnClickListener { - val dialogView = - LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null) - val dialogBinding = DialogLayoutBinding.bind(dialogView) - var refresh = false - var run = false - var reversed = media.selected!!.recyclerReversed - var style = - media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.AnimeDefaultView) - dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f - dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down" - dialogBinding.animeSourceTop.setOnClickListener { - reversed = !reversed - dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f - dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down" - run = true - } - //Grids - var selected = when (style) { - 0 -> dialogBinding.animeSourceList - 1 -> dialogBinding.animeSourceGrid - 2 -> dialogBinding.animeSourceCompact - else -> dialogBinding.animeSourceList - } - when (style) { - 0 -> dialogBinding.layoutText.setText(R.string.list) - 1 -> dialogBinding.layoutText.setText(R.string.grid) - 2 -> dialogBinding.layoutText.setText(R.string.compact) - else -> dialogBinding.animeSourceList - } - selected.alpha = 1f - fun selected(it: ImageButton) { - selected.alpha = 0.33f - selected = it - selected.alpha = 1f - } - dialogBinding.animeSourceList.setOnClickListener { - selected(it as ImageButton) - style = 0 - dialogBinding.layoutText.setText(R.string.list) - run = true - } - dialogBinding.animeSourceGrid.setOnClickListener { - selected(it as ImageButton) - style = 1 - dialogBinding.layoutText.setText(R.string.grid) - run = true - } - dialogBinding.animeSourceCompact.setOnClickListener { - selected(it as ImageButton) - style = 2 - dialogBinding.layoutText.setText(R.string.compact) - run = true - } - dialogBinding.animeWebviewContainer.setOnClickListener { - if (!WebViewUtil.supportsWebView(fragment.requireContext())) { - toast(R.string.webview_not_installed) + val dialogBinding = DialogLayoutBinding.inflate(fragment.layoutInflater) + dialogBinding.apply { + var refresh = false + var run = false + var reversed = media.selected!!.recyclerReversed + var style = + media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.AnimeDefaultView) + + animeSourceTop.rotation = if (reversed) -90f else 90f + sortText.text = if (reversed) "Down to Up" else "Up to Down" + animeSourceTop.setOnClickListener { + reversed = !reversed + animeSourceTop.rotation = if (reversed) -90f else 90f + sortText.text = if (reversed) "Down to Up" else "Up to Down" + run = true } - //start CookieCatcher activity - if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) { - val sourceAHH = watchSources[source] as? DynamicAnimeParser - val sourceHttp = - sourceAHH?.extension?.sources?.firstOrNull() as? AnimeHttpSource - val url = sourceHttp?.baseUrl - url?.let { - refresh = true - val headersMap = try { - sourceHttp.headers.toMultimap() - .mapValues { it.value.getOrNull(0) ?: "" } - } catch (e: Exception) { - emptyMap() + //Grids + var selected = when (style) { + 0 -> animeSourceList + 1 -> animeSourceGrid + 2 -> animeSourceCompact + else -> animeSourceList + } + when (style) { + 0 -> layoutText.setText(R.string.list) + 1 -> layoutText.setText(R.string.grid) + 2 -> layoutText.setText(R.string.compact) + else -> animeSourceList + } + selected.alpha = 1f + fun selected(it: ImageButton) { + selected.alpha = 0.33f + selected = it + selected.alpha = 1f + } + animeSourceList.setOnClickListener { + selected(it as ImageButton) + style = 0 + layoutText.setText(R.string.list) + run = true + } + animeSourceGrid.setOnClickListener { + selected(it as ImageButton) + style = 1 + layoutText.setText(R.string.grid) + run = true + } + animeSourceCompact.setOnClickListener { + selected(it as ImageButton) + style = 2 + layoutText.setText(R.string.compact) + run = true + } + animeWebviewContainer.setOnClickListener { + if (!WebViewUtil.supportsWebView(fragment.requireContext())) { + toast(R.string.webview_not_installed) + } + //start CookieCatcher activity + if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) { + val sourceAHH = watchSources[source] as? DynamicAnimeParser + val sourceHttp = + sourceAHH?.extension?.sources?.firstOrNull() as? AnimeHttpSource + val url = sourceHttp?.baseUrl + url?.let { + refresh = true + val headersMap = try { + sourceHttp.headers.toMultimap() + .mapValues { it.value.getOrNull(0) ?: "" } + } catch (e: Exception) { + emptyMap() + } + val intent = + Intent(fragment.requireContext(), CookieCatcher::class.java) + .putExtra("url", url) + .putExtra("headers", headersMap as HashMap) + startActivity(fragment.requireContext(), intent, null) } - val intent = Intent(fragment.requireContext(), CookieCatcher::class.java) - .putExtra("url", url) - .putExtra("headers", headersMap as HashMap) - startActivity(fragment.requireContext(), intent, null) } } + + //hidden + animeScanlatorContainer.visibility = View.GONE + animeDownloadContainer.visibility = View.GONE + fragment.requireContext().customAlertDialog().apply { + setTitle("Options") + setCustomView(dialogBinding.root) + setPosButton("OK") { + if (run) fragment.onIconPressed(style, reversed) + if (refresh) fragment.loadEpisodes(source, true) + } + setNegButton("Cancel") { + if (refresh) fragment.loadEpisodes(source, true) + } + show() + } } - - //hidden - dialogBinding.animeScanlatorContainer.visibility = View.GONE - dialogBinding.animeDownloadContainer.visibility = View.GONE - - nestedDialog = AlertDialog.Builder(fragment.requireContext(), R.style.MyPopup) - .setTitle("Options") - .setView(dialogView) - .setPositiveButton("OK") { _, _ -> - if (run) fragment.onIconPressed(style, reversed) - if (refresh) fragment.loadEpisodes(source, true) - } - .setNegativeButton("Cancel") { _, _ -> - if (refresh) fragment.loadEpisodes(source, true) - } - .setOnCancelListener { - if (refresh) fragment.loadEpisodes(source, true) - } - .create() - nestedDialog?.show() } //Episode Handling handleEpisodes() diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt index ecd8ee73..a9dc9b7e 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt @@ -63,6 +63,7 @@ import ani.dantotsu.toast import ani.dantotsu.util.Logger import ani.dantotsu.util.StoragePermissions.Companion.accessAlertDialog import ani.dantotsu.util.StoragePermissions.Companion.hasDirAccess +import ani.dantotsu.util.customAlertDialog import com.anggrayudi.storage.file.extension import com.google.android.material.appbar.AppBarLayout import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource @@ -396,20 +397,18 @@ class AnimeWatchFragment : Fragment() { if (allSettings.size > 1) { val names = allSettings.map { LanguageMapper.getLanguageName(it.lang) }.toTypedArray() - val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup) - .setTitle("Select a Source") - .setSingleChoiceItems(names, -1) { dialog, which -> + requireContext() + .customAlertDialog() + .apply { + setTitle("Select a Source") + singleChoiceItems(names) { which -> selectedSetting = allSettings[which] itemSelected = true - dialog.dismiss() - - // Move the fragment transaction here requireActivity().runOnUiThread { - val fragment = - AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) { - changeUIVisibility(true) - loadEpisodes(media.selected!!.sourceIndex, true) - } + val fragment = AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) { + changeUIVisibility(true) + loadEpisodes(media.selected!!.sourceIndex, true) + } parentFragmentManager.beginTransaction() .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) .replace(R.id.fragmentExtensionsContainer, fragment) @@ -417,13 +416,13 @@ class AnimeWatchFragment : Fragment() { .commit() } } - .setOnDismissListener { + onDismiss { if (!itemSelected) { changeUIVisibility(true) } } - .show() - dialog.window?.setDimAmount(0.8f) + show() + } } else { // If there's only one setting, proceed with the fragment transaction requireActivity().runOnUiThread { @@ -432,11 +431,12 @@ class AnimeWatchFragment : Fragment() { changeUIVisibility(true) loadEpisodes(media.selected!!.sourceIndex, true) } - parentFragmentManager.beginTransaction() - .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) - .replace(R.id.fragmentExtensionsContainer, fragment) - .addToBackStack(null) - .commit() + parentFragmentManager.beginTransaction().apply { + setCustomAnimations(R.anim.slide_up, R.anim.slide_down) + replace(R.id.fragmentExtensionsContainer, fragment) + addToBackStack(null) + commit() + } } } diff --git a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt index 14be291a..1ee5c881 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt @@ -23,6 +23,7 @@ import ani.dantotsu.media.MediaNameAdapter import ani.dantotsu.media.MediaType import ani.dantotsu.setAnimation import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.util.customAlertDialog import com.bumptech.glide.Glide import com.bumptech.glide.load.model.GlideUrl import kotlinx.coroutines.delay @@ -318,16 +319,14 @@ class EpisodeAdapter( fragment.onAnimeEpisodeStopDownloadClick(episodeNumber) return@setOnClickListener } else if (downloadedEpisodes.contains(episodeNumber)) { - val builder = AlertDialog.Builder(currContext(), R.style.MyPopup) - builder.setTitle("Delete Episode") - builder.setMessage("Are you sure you want to delete Episode ${episodeNumber}?") - builder.setPositiveButton("Yes") { _, _ -> - fragment.onAnimeEpisodeRemoveDownloadClick(episodeNumber) - } - builder.setNegativeButton("No") { _, _ -> - } - val dialog = builder.show() - dialog.window?.setDimAmount(0.8f) + binding.root.context.customAlertDialog().apply { + setTitle("Delete Episode") + setMessage("Are you sure you want to delete Episode $episodeNumber?") + setPosButton(R.string.yes) { + fragment.onAnimeEpisodeRemoveDownloadClick(episodeNumber) + } + setNegButton(R.string.no) + }.show() return@setOnClickListener } else { fragment.onAnimeEpisodeDownloadClick(episodeNumber) diff --git a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt index e93267e2..febd93ec 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt @@ -444,15 +444,12 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { if (subtitles.isNotEmpty()) { val subtitleNames = subtitles.map { it.language } var subtitleToDownload: Subtitle? = null - val alertDialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.download_subtitle) - .setSingleChoiceItems( - subtitleNames.toTypedArray(), - -1 - ) { _, which -> + requireActivity().customAlertDialog().apply { + setTitle(R.string.download_subtitle) + singleChoiceItems(subtitleNames.toTypedArray()) {which -> subtitleToDownload = subtitles[which] } - .setPositiveButton(R.string.download) { dialog, _ -> + setPosButton(R.string.download) { scope.launch { if (subtitleToDownload != null) { SubtitleDownloader.downloadSubtitle( @@ -466,13 +463,9 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { ) } } - dialog.dismiss() } - .setNegativeButton(R.string.cancel) { dialog, _ -> - dialog.dismiss() - } - .show() - alertDialog.window?.setDimAmount(0.8f) + setNegButton(R.string.cancel) {} + }.show() } else { snackString(R.string.no_subtitles_available) } @@ -576,65 +569,63 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { if (audioTracks.isNotEmpty()) { val audioNamesArray = audioTracks.toTypedArray() val checkedItems = BooleanArray(audioNamesArray.size) { false } - val alertDialog = AlertDialog.Builder(currContext, R.style.MyPopup) - .setTitle(R.string.download_audio_tracks) - .setMultiChoiceItems(audioNamesArray, checkedItems) { _, which, isChecked -> - val audioPair = Pair(extractor.audioTracks[which].url, extractor.audioTracks[which].lang) - if (isChecked) { - selectedAudioTracks.add(audioPair) - } else { - selectedAudioTracks.remove(audioPair) + + currContext.customAlertDialog().apply{ // ToTest + setTitle(R.string.download_audio_tracks) + multiChoiceItems(audioNamesArray, checkedItems) { + it.forEachIndexed { index, isChecked -> + val audioPair = Pair(extractor.audioTracks[index].url, extractor.audioTracks[index].lang) + if (isChecked) { + selectedAudioTracks.add(audioPair) + } else { + selectedAudioTracks.remove(audioPair) + } } } - .setPositiveButton(R.string.download) { _, _ -> - dialog?.dismiss() + setPosButton(R.string.download) { go() } - .setNegativeButton(R.string.skip) { dialog, _ -> + setNegButton(R.string.skip) { selectedAudioTracks = mutableListOf() go() - dialog.dismiss() } - .setNeutralButton(R.string.cancel) { dialog, _ -> + setNeutralButton(R.string.cancel) { selectedAudioTracks = mutableListOf() - dialog.dismiss() } - .show() - alertDialog.window?.setDimAmount(0.8f) + show() + } } else { go() } } - if (subtitles.isNotEmpty()) { + if (subtitles.isNotEmpty()) { // ToTest val subtitleNamesArray = subtitleNames.toTypedArray() val checkedItems = BooleanArray(subtitleNamesArray.size) { false } - val alertDialog = AlertDialog.Builder(currContext, R.style.MyPopup) - .setTitle(R.string.download_subtitle) - .setMultiChoiceItems(subtitleNamesArray, checkedItems) { _, which, isChecked -> - val subtitlePair = Pair(subtitles[which].file.url, subtitles[which].language) - if (isChecked) { - selectedSubtitles.add(subtitlePair) - } else { - selectedSubtitles.remove(subtitlePair) + currContext.customAlertDialog().apply { + setTitle(R.string.download_subtitle) + multiChoiceItems(subtitleNamesArray, checkedItems) { + it.forEachIndexed { index, isChecked -> + val subtitlePair = Pair(subtitles[index].file.url, subtitles[index].language) + if (isChecked) { + selectedSubtitles.add(subtitlePair) + } else { + selectedSubtitles.remove(subtitlePair) + } } } - .setPositiveButton(R.string.download) { _, _ -> - dialog?.dismiss() + setPosButton(R.string.download) { checkAudioTracks() } - .setNegativeButton(R.string.skip) { dialog, _ -> + setNegButton(R.string.skip) { selectedSubtitles = mutableListOf() checkAudioTracks() - dialog.dismiss() } - .setNeutralButton(R.string.cancel) { dialog, _ -> + setNeutralButton(R.string.cancel) { selectedSubtitles = mutableListOf() - dialog.dismiss() } - .show() - alertDialog.window?.setDimAmount(0.8f) - + show() + } } else { checkAudioTracks() } diff --git a/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt b/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt index 6c552090..0a4bed33 100644 --- a/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt +++ b/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt @@ -21,6 +21,7 @@ import ani.dantotsu.setAnimation import ani.dantotsu.snackString import ani.dantotsu.util.ColorEditor.Companion.adjustColorForContrast import ani.dantotsu.util.ColorEditor.Companion.getContrastRatio +import ani.dantotsu.util.customAlertDialog import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.Section import com.xwray.groupie.viewbinding.BindableItem @@ -385,19 +386,14 @@ class CommentItem( * @param callback the callback to call when the user clicks yes */ private fun dialogBuilder(title: String, message: String, callback: () -> Unit) { - val alertDialog = - android.app.AlertDialog.Builder(commentsFragment.activity, R.style.MyPopup) - .setTitle(title) - .setMessage(message) - .setPositiveButton("Yes") { dialog, _ -> - callback() - dialog.dismiss() - } - .setNegativeButton("No") { dialog, _ -> - dialog.dismiss() - } - val dialog = alertDialog.show() - dialog?.window?.setDimAmount(0.8f) + commentsFragment.activity.customAlertDialog().apply { + setTitle(title) + setMessage(message) + setPosButton("Yes") { + callback() + } + setNegButton("No") {} + }.show() } private val usernameColors: Array = arrayOf( diff --git a/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt b/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt index ff861dbe..fc21805e 100644 --- a/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt @@ -25,6 +25,7 @@ import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.comments.Comment import ani.dantotsu.connections.comments.CommentResponse import ani.dantotsu.connections.comments.CommentsAPI +import ani.dantotsu.databinding.DialogEdittextBinding import ani.dantotsu.databinding.FragmentCommentsBinding import ani.dantotsu.loadImage import ani.dantotsu.media.MediaDetailsActivity @@ -34,6 +35,7 @@ import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.toast import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.Section import io.noties.markwon.editor.MarkwonEditor @@ -162,33 +164,26 @@ class CommentsFragment : Fragment() { } binding.commentFilter.setOnClickListener { - val alertDialog = AlertDialog.Builder(activity, R.style.MyPopup) - .setTitle("Enter a chapter/episode number tag") - .setView(R.layout.dialog_edittext) - .setPositiveButton("OK") { dialog, _ -> - val editText = - (dialog as AlertDialog).findViewById(R.id.dialogEditText) - val text = editText?.text.toString() + activity.customAlertDialog().apply { + val customView = DialogEdittextBinding.inflate(layoutInflater) + setTitle("Enter a chapter/episode number tag") + setCustomView(customView.root) + setPosButton("OK") { + val text = customView.dialogEditText.text.toString() filterTag = text.toIntOrNull() lifecycleScope.launch { loadAndDisplayComments() } - - dialog.dismiss() } - .setNeutralButton("Clear") { dialog, _ -> + setNeutralButton("Clear") { filterTag = null lifecycleScope.launch { loadAndDisplayComments() } - dialog.dismiss() } - .setNegativeButton("Cancel") { dialog, _ -> - filterTag = null - dialog.dismiss() - } - val dialog = alertDialog.show() - dialog?.window?.setDimAmount(0.8f) + setNegButton("Cancel") { filterTag = null } + show() + } } var isFetching = false @@ -303,13 +298,12 @@ class CommentsFragment : Fragment() { activity.binding.commentLabel.setOnClickListener { //alert dialog to enter a number, with a cancel and ok button - val alertDialog = AlertDialog.Builder(activity, R.style.MyPopup) - .setTitle("Enter a chapter/episode number tag") - .setView(R.layout.dialog_edittext) - .setPositiveButton("OK") { dialog, _ -> - val editText = - (dialog as AlertDialog).findViewById(R.id.dialogEditText) - val text = editText?.text.toString() + activity.customAlertDialog().apply { + val customView = DialogEdittextBinding.inflate(layoutInflater) + setTitle("Enter a chapter/episode number tag") + setCustomView(customView.root) + setPosButton("OK") { + val text = customView.dialogEditText.text.toString() tag = text.toIntOrNull() if (tag == null) { activity.binding.commentLabel.background = ResourcesCompat.getDrawable( @@ -324,28 +318,25 @@ class CommentsFragment : Fragment() { null ) } - dialog.dismiss() } - .setNeutralButton("Clear") { dialog, _ -> + setNeutralButton("Clear") { tag = null activity.binding.commentLabel.background = ResourcesCompat.getDrawable( resources, R.drawable.ic_label_off_24, null ) - dialog.dismiss() } - .setNegativeButton("Cancel") { dialog, _ -> + setNegButton("Cancel") { tag = null activity.binding.commentLabel.background = ResourcesCompat.getDrawable( resources, R.drawable.ic_label_off_24, null ) - dialog.dismiss() } - val dialog = alertDialog.show() - dialog?.window?.setDimAmount(0.8f) + show() + } } } @@ -362,12 +353,6 @@ class CommentsFragment : Fragment() { } } } - - @SuppressLint("NotifyDataSetChanged") - override fun onStart() { - super.onStart() - } - @SuppressLint("NotifyDataSetChanged") override fun onResume() { super.onResume() @@ -579,9 +564,9 @@ class CommentsFragment : Fragment() { * Called when the user tries to comment for the first time */ private fun showCommentRulesDialog() { - val alertDialog = AlertDialog.Builder(activity, R.style.MyPopup) - .setTitle("Commenting Rules") - .setMessage( + activity.customAlertDialog().apply{ + setTitle("Commenting Rules") + setMessage( "I WILL BAN YOU WITHOUT HESITATION\n" + "1. No racism\n" + "2. No hate speech\n" + @@ -594,16 +579,13 @@ class CommentsFragment : Fragment() { "10. No illegal content\n" + "11. Anything you know you shouldn't comment\n" ) - .setPositiveButton("I Understand") { dialog, _ -> - dialog.dismiss() + setPosButton("I Understand") { PrefManager.setVal(PrefName.FirstComment, false) processComment() } - .setNegativeButton("Cancel") { dialog, _ -> - dialog.dismiss() - } - val dialog = alertDialog.show() - dialog?.window?.setDimAmount(0.8f) + setNegButton(R.string.cancel) + show() + } } private fun processComment() { diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt index 177d3684..4cd856a1 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt @@ -19,6 +19,7 @@ import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.R import ani.dantotsu.currActivity import ani.dantotsu.currContext +import ani.dantotsu.databinding.CustomDialogLayoutBinding import ani.dantotsu.databinding.DialogLayoutBinding import ani.dantotsu.databinding.ItemAnimeWatchBinding import ani.dantotsu.databinding.ItemChipBinding @@ -40,6 +41,7 @@ import ani.dantotsu.settings.FAQActivity import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.toast +import ani.dantotsu.util.customAlertDialog import com.google.android.material.chip.Chip import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK import eu.kanade.tachiyomi.source.online.HttpSource @@ -65,8 +67,6 @@ class MangaReadAdapter( return ViewHolder(bind) } - private var nestedDialog: AlertDialog? = null - override fun onBindViewHolder(holder: ViewHolder, position: Int) { val binding = holder.binding _binding = binding @@ -171,194 +171,179 @@ class MangaReadAdapter( } binding.animeNestedButton.setOnClickListener { - - val dialogView = - LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null) - val dialogBinding = DialogLayoutBinding.bind(dialogView) + val dialogBinding = DialogLayoutBinding.inflate(fragment.layoutInflater) var refresh = false var run = false var reversed = media.selected!!.recyclerReversed var style = media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.MangaDefaultView) - dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f - dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down" - dialogBinding.animeSourceTop.setOnClickListener { - reversed = !reversed - dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f - dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down" - run = true - } + dialogBinding.apply { + animeSourceTop.rotation = if (reversed) -90f else 90f + sortText.text = if (reversed) "Down to Up" else "Up to Down" + animeSourceTop.setOnClickListener { + reversed = !reversed + animeSourceTop.rotation = if (reversed) -90f else 90f + sortText.text = if (reversed) "Down to Up" else "Up to Down" + run = true + } - //Grids - dialogBinding.animeSourceGrid.visibility = View.GONE - var selected = when (style) { - 0 -> dialogBinding.animeSourceList - 1 -> dialogBinding.animeSourceCompact - else -> dialogBinding.animeSourceList - } - when (style) { - 0 -> dialogBinding.layoutText.setText(R.string.list) - 1 -> dialogBinding.layoutText.setText(R.string.compact) - else -> dialogBinding.animeSourceList - } - selected.alpha = 1f - fun selected(it: ImageButton) { - selected.alpha = 0.33f - selected = it + //Grids + animeSourceGrid.visibility = View.GONE + var selected = when (style) { + 0 -> animeSourceList + 1 -> animeSourceCompact + else -> animeSourceList + } + when (style) { + 0 -> layoutText.setText(R.string.list) + 1 -> layoutText.setText(R.string.compact) + else -> animeSourceList + } selected.alpha = 1f - } - dialogBinding.animeSourceList.setOnClickListener { - selected(it as ImageButton) - style = 0 - dialogBinding.layoutText.setText(R.string.list) - run = true - } - dialogBinding.animeSourceCompact.setOnClickListener { - selected(it as ImageButton) - style = 1 - dialogBinding.layoutText.setText(R.string.compact) - run = true - } - dialogBinding.animeWebviewContainer.setOnClickListener { - if (!WebViewUtil.supportsWebView(fragment.requireContext())) { - toast(R.string.webview_not_installed) + fun selected(it: ImageButton) { + selected.alpha = 0.33f + selected = it + selected.alpha = 1f } - //start CookieCatcher activity - if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) { - val sourceAHH = mangaReadSources[source] as? DynamicMangaParser - val sourceHttp = sourceAHH?.extension?.sources?.firstOrNull() as? HttpSource - val url = sourceHttp?.baseUrl - url?.let { - refresh = true - val intent = Intent(fragment.requireContext(), CookieCatcher::class.java) - .putExtra("url", url) - startActivity(fragment.requireContext(), intent, null) + animeSourceList.setOnClickListener { + selected(it as ImageButton) + style = 0 + layoutText.setText(R.string.list) + run = true + } + animeSourceCompact.setOnClickListener { + selected(it as ImageButton) + style = 1 + layoutText.setText(R.string.compact) + run = true + } + animeWebviewContainer.setOnClickListener { + if (!WebViewUtil.supportsWebView(fragment.requireContext())) { + toast(R.string.webview_not_installed) } - } - } - - //Multi download - dialogBinding.downloadNo.text = "0" - dialogBinding.animeDownloadTop.setOnClickListener { - //Alert dialog asking for the number of chapters to download - val alertDialog = AlertDialog.Builder(currContext(), R.style.MyPopup) - alertDialog.setTitle("Multi Chapter Downloader") - alertDialog.setMessage("Enter the number of chapters to download") - val input = NumberPicker(currContext()) - input.minValue = 1 - input.maxValue = 20 - input.value = 1 - alertDialog.setView(input) - alertDialog.setPositiveButton("OK") { _, _ -> - dialogBinding.downloadNo.text = "${input.value}" - } - alertDialog.setNegativeButton("Cancel") { dialog, _ -> dialog.cancel() } - val dialog = alertDialog.show() - dialog.window?.setDimAmount(0.8f) - } - - //Scanlator - dialogBinding.animeScanlatorContainer.isVisible = options.count() > 1 - dialogBinding.scanlatorNo.text = "${options.count()}" - dialogBinding.animeScanlatorTop.setOnClickListener { - val dialogView2 = - LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null) - val checkboxContainer = - dialogView2.findViewById(R.id.checkboxContainer) - val tickAllButton = dialogView2.findViewById(R.id.toggleButton) - - // Function to get the right image resource for the toggle button - fun getToggleImageResource(container: ViewGroup): Int { - var allChecked = true - var allUnchecked = true - - for (i in 0 until container.childCount) { - val checkBox = container.getChildAt(i) as CheckBox - if (!checkBox.isChecked) { - allChecked = false - } else { - allUnchecked = false + //start CookieCatcher activity + if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) { + val sourceAHH = mangaReadSources[source] as? DynamicMangaParser + val sourceHttp = sourceAHH?.extension?.sources?.firstOrNull() as? HttpSource + val url = sourceHttp?.baseUrl + url?.let { + refresh = true + val intent = + Intent(fragment.requireContext(), CookieCatcher::class.java) + .putExtra("url", url) + startActivity(fragment.requireContext(), intent, null) } } - return when { - allChecked -> R.drawable.untick_all_boxes - allUnchecked -> R.drawable.tick_all_boxes - else -> R.drawable.invert_all_boxes - } } - // Dynamically add checkboxes - options.forEach { option -> - val checkBox = CheckBox(currContext()).apply { - text = option - setOnCheckedChangeListener { _, _ -> - // Update image resource when you change a checkbox - tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) + //Multi download + downloadNo.text = "0" + animeDownloadTop.setOnClickListener { + //Alert dialog asking for the number of chapters to download + fragment.requireContext().customAlertDialog().apply { + setTitle("Multi Chapter Downloader") + setMessage("Enter the number of chapters to download") + val input = NumberPicker(currContext()) + input.minValue = 1 + input.maxValue = 20 + input.value = 1 + setCustomView(input) + setPosButton(R.string.ok) { + downloadNo.text = "${input.value}" } + setNegButton(R.string.cancel) + show() } - - // Set checked if its already selected - if (media.selected!!.scanlators != null) { - checkBox.isChecked = media.selected!!.scanlators?.contains(option) != true - scanlatorSelectionListener?.onScanlatorsSelected() - } else { - checkBox.isChecked = true - } - checkboxContainer.addView(checkBox) } - // Create AlertDialog - val dialog = AlertDialog.Builder(currContext(), R.style.MyPopup) - .setView(dialogView2) - .setPositiveButton("OK") { _, _ -> - hiddenScanlators.clear() - for (i in 0 until checkboxContainer.childCount) { - val checkBox = checkboxContainer.getChildAt(i) as CheckBox + //Scanlator + animeScanlatorContainer.isVisible = options.count() > 1 + scanlatorNo.text = "${options.count()}" + animeScanlatorTop.setOnClickListener { + CustomDialogLayoutBinding.inflate(fragment.layoutInflater) + val dialogView = CustomDialogLayoutBinding.inflate(fragment.layoutInflater) + val checkboxContainer = dialogView.checkboxContainer + val tickAllButton = dialogView.toggleButton + + fun getToggleImageResource(container: ViewGroup): Int { + var allChecked = true + var allUnchecked = true + + for (i in 0 until container.childCount) { + val checkBox = container.getChildAt(i) as CheckBox if (!checkBox.isChecked) { - hiddenScanlators.add(checkBox.text.toString()) + allChecked = false + } else { + allUnchecked = false } } - fragment.onScanlatorChange(hiddenScanlators) - scanlatorSelectionListener?.onScanlatorsSelected() - } - .setNegativeButton("Cancel", null) - .show() - dialog.window?.setDimAmount(0.8f) - - // Standard image resource - tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) - - // Listens to ticked checkboxes and changes image resource accordingly - tickAllButton.setOnClickListener { - // Toggle checkboxes - for (i in 0 until checkboxContainer.childCount) { - val checkBox = checkboxContainer.getChildAt(i) as CheckBox - checkBox.isChecked = !checkBox.isChecked + return when { + allChecked -> R.drawable.untick_all_boxes + allUnchecked -> R.drawable.tick_all_boxes + else -> R.drawable.invert_all_boxes + } } - // Update image resource + options.forEach { option -> + val checkBox = CheckBox(currContext()).apply { + text = option + setOnCheckedChangeListener { _, _ -> + tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) + } + } + + if (media.selected!!.scanlators != null) { + checkBox.isChecked = media.selected!!.scanlators?.contains(option) != true + scanlatorSelectionListener?.onScanlatorsSelected() + } else { + checkBox.isChecked = true + } + checkboxContainer.addView(checkBox) + } + + fragment.requireContext().customAlertDialog().apply { + setCustomView(dialogView.root) + setPosButton("OK") { + hiddenScanlators.clear() + for (i in 0 until checkboxContainer.childCount) { + val checkBox = checkboxContainer.getChildAt(i) as CheckBox + if (!checkBox.isChecked) { + hiddenScanlators.add(checkBox.text.toString()) + } + } + fragment.onScanlatorChange(hiddenScanlators) + scanlatorSelectionListener?.onScanlatorsSelected() + } + setNegButton("Cancel") + }.show() + tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) + + tickAllButton.setOnClickListener { + for (i in 0 until checkboxContainer.childCount) { + val checkBox = checkboxContainer.getChildAt(i) as CheckBox + checkBox.isChecked = !checkBox.isChecked + } + tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) + } + } + + fragment.requireContext().customAlertDialog().apply { + setTitle("Options") + setCustomView(root) + setPosButton("OK") { + if (run) fragment.onIconPressed(style, reversed) + if (downloadNo.text != "0") { + fragment.multiDownload(downloadNo.text.toString().toInt()) + } + if (refresh) fragment.loadChapters(source, true) + } + setNegButton("Cancel") { + if (refresh) fragment.loadChapters(source, true) + } + show() } } - - nestedDialog = AlertDialog.Builder(fragment.requireContext(), R.style.MyPopup) - .setTitle("Options") - .setView(dialogView) - .setPositiveButton("OK") { _, _ -> - if (run) fragment.onIconPressed(style, reversed) - if (dialogBinding.downloadNo.text != "0") { - fragment.multiDownload(dialogBinding.downloadNo.text.toString().toInt()) - } - if (refresh) fragment.loadChapters(source, true) - } - .setNegativeButton("Cancel") { _, _ -> - if (refresh) fragment.loadChapters(source, true) - } - .setOnCancelListener { - if (refresh) fragment.loadChapters(source, true) - } - .create() - nestedDialog?.show() } //Chapter Handling handleChapters() diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt index bce5f312..7430bafa 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt @@ -60,6 +60,7 @@ import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.util.StoragePermissions.Companion.accessAlertDialog import ani.dantotsu.util.StoragePermissions.Companion.hasDirAccess +import ani.dantotsu.util.customAlertDialog import com.google.android.material.appbar.AppBarLayout import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import eu.kanade.tachiyomi.source.ConfigurableSource @@ -386,32 +387,30 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { if (allSettings.size > 1) { val names = allSettings.map { LanguageMapper.getLanguageName(it.lang) }.toTypedArray() - val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup) - .setTitle("Select a Source") - .setSingleChoiceItems(names, -1) { dialog, which -> + requireContext().customAlertDialog().apply { + setTitle("Select a Source") + singleChoiceItems(names) { which -> selectedSetting = allSettings[which] itemSelected = true - dialog.dismiss() - // Move the fragment transaction here - val fragment = - MangaSourcePreferencesFragment().getInstance(selectedSetting.id) { - changeUIVisibility(true) - loadChapters(media.selected!!.sourceIndex, true) - } + val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id) { + changeUIVisibility(true) + loadChapters(media.selected!!.sourceIndex, true) + } parentFragmentManager.beginTransaction() .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) .replace(R.id.fragmentExtensionsContainer, fragment) .addToBackStack(null) .commit() } - .setOnDismissListener { + onDismiss{ if (!itemSelected) { changeUIVisibility(true) } } - .show() - dialog.window?.setDimAmount(0.8f) + show() + + } } else { // If there's only one setting, proceed with the fragment transaction val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id) { diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt index 86085f58..bb28d7aa 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt @@ -83,6 +83,7 @@ import ani.dantotsu.showSystemBarsRetractView import ani.dantotsu.snackString import ani.dantotsu.themes.ThemeManager import ani.dantotsu.tryWith +import ani.dantotsu.util.customAlertDialog import com.alexvasilkov.gestures.views.GestureFrameLayout import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView @@ -1013,28 +1014,27 @@ class MangaReaderActivity : AppCompatActivity() { PrefManager.setCustomVal("${media.id}_progressDialog", !isChecked) showProgressDialog = !isChecked } - AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.title_update_progress)) - .setView(dialogView) - .setCancelable(false) - .setPositiveButton(getString(R.string.yes)) { dialog, _ -> + customAlertDialog().apply { + setTitle(R.string.title_update_progress) + setCustomView(dialogView) + setCancelable(false) + setPosButton(R.string.yes) { PrefManager.setCustomVal("${media.id}_save_progress", true) updateProgress( media, MediaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!) .toString() ) - dialog.dismiss() runnable.run() } - .setNegativeButton(getString(R.string.no)) { dialog, _ -> + setNegButton(R.string.no) { PrefManager.setCustomVal("${media.id}_save_progress", false) - dialog.dismiss() runnable.run() } - .setOnCancelListener { hideSystemBars() } - .create() - .show() + setOnCancelListener { hideSystemBars() } + show() + + } } else { if (!incognito && PrefManager.getCustomVal( "${media.id}_save_progress", diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt index 92ddc739..cade2abd 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt @@ -13,6 +13,7 @@ import ani.dantotsu.parsers.ShowResponse import ani.dantotsu.setAnimation import ani.dantotsu.snackString import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.bumptech.glide.Glide import com.bumptech.glide.load.model.GlideUrl @@ -93,27 +94,22 @@ class NovelResponseAdapter( } binding.root.setOnLongClickListener { - val builder = androidx.appcompat.app.AlertDialog.Builder( - fragment.requireContext(), - R.style.MyPopup - ) - builder.setTitle("Delete ${novel.name}?") - builder.setMessage("Are you sure you want to delete ${novel.name}?") - builder.setPositiveButton("Yes") { _, _ -> - downloadedCheckCallback.deleteDownload(novel) - deleteDownload(novel.link) - snackString("Deleted ${novel.name}") - if (binding.itemEpisodeFiller.text.toString() - .contains("Download", ignoreCase = true) - ) { - binding.itemEpisodeFiller.text = "" + it.context.customAlertDialog().apply { + setTitle("Delete ${novel.name}?") + setMessage("Are you sure you want to delete ${novel.name}?") + setPosButton(R.string.yes) { + downloadedCheckCallback.deleteDownload(novel) + deleteDownload(novel.link) + snackString("Deleted ${novel.name}") + if (binding.itemEpisodeFiller.text.toString() + .contains("Download", ignoreCase = true) + ) { + binding.itemEpisodeFiller.text = "" + } } + setNegButton(R.string.no) + show() } - builder.setNegativeButton("No") { _, _ -> - // Do nothing - } - val dialog = builder.show() - dialog.window?.setDimAmount(0.8f) true } } diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt index 26c886c9..bca86098 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt @@ -11,7 +11,6 @@ import ani.dantotsu.buildMarkwon import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.api.Activity import ani.dantotsu.databinding.ItemActivityBinding -import ani.dantotsu.home.status.RepliesBottomDialog import ani.dantotsu.loadImage import ani.dantotsu.profile.User import ani.dantotsu.profile.UsersDialogFragment diff --git a/app/src/main/java/ani/dantotsu/home/status/RepliesBottomDialog.kt b/app/src/main/java/ani/dantotsu/profile/activity/RepliesBottomDialog.kt similarity index 90% rename from app/src/main/java/ani/dantotsu/home/status/RepliesBottomDialog.kt rename to app/src/main/java/ani/dantotsu/profile/activity/RepliesBottomDialog.kt index 44a11baf..184c7af9 100644 --- a/app/src/main/java/ani/dantotsu/home/status/RepliesBottomDialog.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/RepliesBottomDialog.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.home.status +package ani.dantotsu.profile.activity import android.content.Intent import android.os.Bundle @@ -14,7 +14,6 @@ import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.api.ActivityReply import ani.dantotsu.databinding.BottomSheetRecyclerBinding import ani.dantotsu.profile.ProfileActivity -import ani.dantotsu.profile.activity.ActivityReplyItem import ani.dantotsu.snackString import ani.dantotsu.util.ActivityMarkdownCreator import com.xwray.groupie.GroupieAdapter @@ -72,14 +71,10 @@ class RepliesBottomDialog : BottomSheetDialogFragment() { adapter.update( replies.map { ActivityReplyItem( - it, - activityId, - requireActivity(), - adapter, - clickCallback = { int, _ -> - onClick(int) - } - ) + it, activityId, requireActivity(), adapter, + ) { i, _ -> + onClick(i) + } } ) } else { diff --git a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt index 748e476e..46d42eb9 100644 --- a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt @@ -35,6 +35,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager +import ani.dantotsu.util.customAlertDialog import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager @@ -178,25 +179,20 @@ class ExtensionsActivity : AppCompatActivity() { entry.name.lowercase().replace("_", " ") .replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() } }.toTypedArray() - val builder = AlertDialog.Builder(this, R.style.MyPopup) val listOrder: String = PrefManager.getVal(PrefName.LangSort) val index = LanguageMapper.Companion.Language.entries.toTypedArray() .indexOfFirst { it.code == listOrder } - builder.setTitle("Language") - builder.setSingleChoiceItems(languageOptions, index) { dialog, i -> - PrefManager.setVal( - PrefName.LangSort, - LanguageMapper.Companion.Language.entries[i].code - ) - val currentFragment = - supportFragmentManager.findFragmentByTag("f${viewPager.currentItem}") - if (currentFragment is SearchQueryHandler) { - currentFragment.notifyDataChanged() + customAlertDialog().apply { + setTitle("Language") + singleChoiceItems(languageOptions, index) { selected -> + PrefManager.setVal(PrefName.LangSort, LanguageMapper.Companion.Language.entries[selected].code) + val currentFragment = supportFragmentManager.findFragmentByTag("f${viewPager.currentItem}") + if (currentFragment is SearchQueryHandler) { + currentFragment.notifyDataChanged() + } } - dialog.dismiss() + show() } - val dialog = builder.show() - dialog.window?.setDimAmount(0.8f) } binding.settingsContainer.updateLayoutParams { topMargin = statusBarHeight @@ -247,10 +243,10 @@ class ExtensionsActivity : AppCompatActivity() { ) view.repositoryItem.text = item.removePrefix("https://raw.githubusercontent.com") view.repositoryItem.setOnClickListener { - AlertDialog.Builder(this@ExtensionsActivity, R.style.MyPopup) - .setTitle(R.string.rem_repository) - .setMessage(item) - .setPositiveButton(getString(R.string.ok)) { dialog, _ -> + customAlertDialog().apply { + setTitle(R.string.rem_repository) + setMessage(item) + setPosButton(R.string.ok) { val repos = PrefManager.getVal>(prefName).minus(item) PrefManager.setVal(prefName, repos) repoInventory.removeView(view.root) @@ -267,13 +263,10 @@ class ExtensionsActivity : AppCompatActivity() { else -> {} } } - dialog.dismiss() } - .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - } - .create() - .show() + setNegButton(R.string.cancel) + show() + } } view.repositoryItem.setOnLongClickListener { it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) @@ -324,20 +317,18 @@ class ExtensionsActivity : AppCompatActivity() { dialogView.repoInventory.apply { getSavedRepositories(this, type) } - val alertDialog = AlertDialog.Builder(this@ExtensionsActivity, R.style.MyPopup) - .setTitle(R.string.edit_repositories) - .setView(dialogView.root) - .setPositiveButton(getString(R.string.add)) { _, _ -> - if (!dialogView.repositoryTextBox.text.isNullOrBlank()) - processUserInput(dialogView.repositoryTextBox.text.toString(), type) - } - .setNegativeButton(getString(R.string.close)) { dialog, _ -> - dialog.dismiss() - } - .create() processEditorAction(dialogView.repositoryTextBox, type) - alertDialog.show() - alertDialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(R.string.edit_repositories) + setCustomView(dialogView.root) + setPosButton(R.string.add) { + if (!dialogView.repositoryTextBox.text.isNullOrBlank()) { + processUserInput(dialogView.repositoryTextBox.text.toString(), type) + } + } + setNegButton(R.string.close) + show() + } } } } diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt index 2e6d650c..4d3350e3 100644 --- a/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt @@ -25,13 +25,15 @@ import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.R import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.databinding.FragmentExtensionsBinding -import ani.dantotsu.others.LanguageMapper +import ani.dantotsu.others.LanguageMapper.Companion.getLanguageName import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog + import com.google.android.material.tabs.TabLayout import com.google.android.material.textfield.TextInputLayout import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource @@ -72,16 +74,15 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { if (allSettings.isNotEmpty()) { var selectedSetting = allSettings[0] if (allSettings.size > 1) { - val names = allSettings.map { LanguageMapper.getLanguageName(it.lang) } + val names = allSettings.map { getLanguageName(it.lang) } .toTypedArray() var selectedIndex = 0 - val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup) - .setTitle("Select a Source") - .setSingleChoiceItems(names, selectedIndex) { dialog, which -> + requireContext().customAlertDialog().apply { + setTitle("Select a Source") + singleChoiceItems(names, selectedIndex) { which -> itemSelected = true selectedIndex = which selectedSetting = allSettings[selectedIndex] - dialog.dismiss() val fragment = AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) { @@ -93,13 +94,13 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { .addToBackStack(null) .commit() } - .setOnDismissListener { + onDismiss { if (!itemSelected) { changeUIVisibility(true) } } - .show() - dialog.window?.setDimAmount(0.8f) + show() + } } else { // If there's only one setting, proceed with the fragment transaction val fragment = @@ -295,7 +296,7 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { override fun onBindViewHolder(holder: ViewHolder, position: Int) { val extension = getItem(position) val nsfw = if (extension.isNsfw) "(18+)" else "" - val lang = LanguageMapper.getLanguageName(extension.lang) + val lang = getLanguageName(extension.lang) holder.extensionNameTextView.text = extension.name val versionText = "$lang ${extension.versionName} $nsfw" holder.extensionVersionTextView.text = versionText diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt index 0a66c648..c11ec223 100644 --- a/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt @@ -34,6 +34,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.google.android.material.tabs.TabLayout import com.google.android.material.textfield.TextInputLayout import eu.kanade.tachiyomi.data.notification.Notifications @@ -74,13 +75,12 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { val names = allSettings.map { LanguageMapper.getLanguageName(it.lang) } .toTypedArray() var selectedIndex = 0 - val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup) - .setTitle("Select a Source") - .setSingleChoiceItems(names, selectedIndex) { dialog, which -> + requireContext().customAlertDialog().apply { + setTitle("Select a Source") + singleChoiceItems(names, selectedIndex) { which -> itemSelected = true selectedIndex = which selectedSetting = allSettings[selectedIndex] - dialog.dismiss() // Move the fragment transaction here val fragment = @@ -93,13 +93,13 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { .addToBackStack(null) .commit() } - .setOnDismissListener { + onDismiss { if (!itemSelected) { changeUIVisibility(true) } } - .show() - dialog.window?.setDimAmount(0.8f) + show() + } } else { // If there's only one setting, proceed with the fragment transaction val fragment = diff --git a/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt index 9006228a..e3999509 100644 --- a/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt @@ -28,6 +28,7 @@ import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager import ani.dantotsu.toast +import ani.dantotsu.util.customAlertDialog import com.google.android.material.slider.Slider.OnChangeListener import kotlin.math.roundToInt @@ -100,20 +101,19 @@ class PlayerSettingsActivity : AppCompatActivity() { R.string.default_playback_speed, speedsName[PrefManager.getVal(PrefName.DefaultSpeed)] ) - val speedDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.default_speed)) binding.playerSettingsSpeed.setOnClickListener { - val dialog = - speedDialog.setSingleChoiceItems( + customAlertDialog().apply { + setTitle(getString(R.string.default_speed)) + singleChoiceItems( speedsName, PrefManager.getVal(PrefName.DefaultSpeed) - ) { dialog, i -> + ) { i -> PrefManager.setVal(PrefName.DefaultSpeed, i) binding.playerSettingsSpeed.text = getString(R.string.default_playback_speed, speedsName[i]) - dialog.dismiss() - }.show() - dialog.window?.setDimAmount(0.8f) + } + show() + } } binding.playerSettingsCursedSpeeds.isChecked = PrefManager.getVal(PrefName.CursedSpeeds) @@ -278,17 +278,17 @@ class PlayerSettingsActivity : AppCompatActivity() { } val resizeModes = arrayOf("Original", "Zoom", "Stretch") - val resizeDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.default_resize_mode)) binding.playerResizeMode.setOnClickListener { - val dialog = resizeDialog.setSingleChoiceItems( - resizeModes, - PrefManager.getVal(PrefName.Resize) - ) { dialog, count -> - PrefManager.setVal(PrefName.Resize, count) - dialog.dismiss() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.default_resize_mode)) + singleChoiceItems( + resizeModes, + PrefManager.getVal(PrefName.Resize) + ) { count -> + PrefManager.setVal(PrefName.Resize, count) + } + show() + } } fun toggleSubOptions(isChecked: Boolean) { @@ -332,18 +332,18 @@ class PlayerSettingsActivity : AppCompatActivity() { "Blue", "Magenta" ) - val primaryColorDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.primary_sub_color)) binding.videoSubColorPrimary.setOnClickListener { - val dialog = primaryColorDialog.setSingleChoiceItems( - colorsPrimary, - PrefManager.getVal(PrefName.PrimaryColor) - ) { dialog, count -> - PrefManager.setVal(PrefName.PrimaryColor, count) - dialog.dismiss() - updateSubPreview() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.primary_sub_color)) + singleChoiceItems( + colorsPrimary, + PrefManager.getVal(PrefName.PrimaryColor) + ) { count -> + PrefManager.setVal(PrefName.PrimaryColor, count) + updateSubPreview() + } + show() + } } val colorsSecondary = arrayOf( "Black", @@ -359,32 +359,32 @@ class PlayerSettingsActivity : AppCompatActivity() { "Magenta", "Transparent" ) - val secondaryColorDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.outline_sub_color)) binding.videoSubColorSecondary.setOnClickListener { - val dialog = secondaryColorDialog.setSingleChoiceItems( - colorsSecondary, - PrefManager.getVal(PrefName.SecondaryColor) - ) { dialog, count -> - PrefManager.setVal(PrefName.SecondaryColor, count) - dialog.dismiss() - updateSubPreview() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.outline_sub_color)) + singleChoiceItems( + colorsSecondary, + PrefManager.getVal(PrefName.SecondaryColor) + ) { count -> + PrefManager.setVal(PrefName.SecondaryColor, count) + updateSubPreview() + } + show() + } } val typesOutline = arrayOf("Outline", "Shine", "Drop Shadow", "None") - val outlineDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.outline_type)) binding.videoSubOutline.setOnClickListener { - val dialog = outlineDialog.setSingleChoiceItems( - typesOutline, - PrefManager.getVal(PrefName.Outline) - ) { dialog, count -> - PrefManager.setVal(PrefName.Outline, count) - dialog.dismiss() - updateSubPreview() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.outline_type)) + singleChoiceItems( + typesOutline, + PrefManager.getVal(PrefName.Outline) + ) { count -> + PrefManager.setVal(PrefName.Outline, count) + updateSubPreview() + } + show() + } } val colorsSubBackground = arrayOf( "Transparent", @@ -400,18 +400,18 @@ class PlayerSettingsActivity : AppCompatActivity() { "Blue", "Magenta" ) - val subBackgroundDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.sub_background_color_select)) binding.videoSubColorBackground.setOnClickListener { - val dialog = subBackgroundDialog.setSingleChoiceItems( - colorsSubBackground, - PrefManager.getVal(PrefName.SubBackground) - ) { dialog, count -> - PrefManager.setVal(PrefName.SubBackground, count) - dialog.dismiss() - updateSubPreview() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.sub_background_color_select)) + singleChoiceItems( + colorsSubBackground, + PrefManager.getVal(PrefName.SubBackground) + ) { count -> + PrefManager.setVal(PrefName.SubBackground, count) + updateSubPreview() + } + show() + } } val colorsSubWindow = arrayOf( @@ -428,18 +428,18 @@ class PlayerSettingsActivity : AppCompatActivity() { "Blue", "Magenta" ) - val subWindowDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.sub_window_color_select)) binding.videoSubColorWindow.setOnClickListener { - val dialog = subWindowDialog.setSingleChoiceItems( - colorsSubWindow, - PrefManager.getVal(PrefName.SubWindow) - ) { dialog, count -> - PrefManager.setVal(PrefName.SubWindow, count) - dialog.dismiss() - updateSubPreview() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.sub_window_color_select)) + singleChoiceItems( + colorsSubWindow, + PrefManager.getVal(PrefName.SubWindow) + ) { count -> + PrefManager.setVal(PrefName.SubWindow, count) + updateSubPreview() + } + show() + } } binding.videoSubAlpha.value = PrefManager.getVal(PrefName.SubAlpha) @@ -459,18 +459,18 @@ class PlayerSettingsActivity : AppCompatActivity() { "Levenim MT Bold", "Blocky" ) - val fontDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.subtitle_font)) binding.videoSubFont.setOnClickListener { - val dialog = fontDialog.setSingleChoiceItems( - fonts, - PrefManager.getVal(PrefName.Font) - ) { dialog, count -> - PrefManager.setVal(PrefName.Font, count) - dialog.dismiss() - updateSubPreview() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.subtitle_font)) + singleChoiceItems( + fonts, + PrefManager.getVal(PrefName.Font) + ) { count -> + PrefManager.setVal(PrefName.Font, count) + updateSubPreview() + } + show() + } } binding.subtitleFontSize.setText(PrefManager.getVal(PrefName.FontSize).toString()) binding.subtitleFontSize.setOnEditorActionListener { _, actionId, _ -> diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt index 0bcf356d..a9c839e7 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt @@ -20,6 +20,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.databinding.ActivitySettingsCommonBinding +import ani.dantotsu.databinding.DialogSetPasswordBinding import ani.dantotsu.databinding.DialogUserAgentBinding import ani.dantotsu.download.DownloadsManager import ani.dantotsu.initActivity @@ -37,6 +38,7 @@ import ani.dantotsu.themes.ThemeManager import ani.dantotsu.toast import ani.dantotsu.util.LauncherWrapper import ani.dantotsu.util.StoragePermissions +import ani.dantotsu.util.customAlertDialog import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -160,18 +162,16 @@ class SettingsCommonActivity : AppCompatActivity() { icon = R.drawable.ic_download_24, onClick = { val managers = arrayOf("Default", "1DM", "ADM") - val downloadManagerDialog = - AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.download_manager) - var downloadManager: Int = PrefManager.getVal(PrefName.DownloadManager) - val dialog = downloadManagerDialog.setSingleChoiceItems( - managers, downloadManager - ) { dialog, count -> - downloadManager = count - PrefManager.setVal(PrefName.DownloadManager, downloadManager) - dialog.dismiss() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.download_manager)) + singleChoiceItems( + managers, + PrefManager.getVal(PrefName.DownloadManager) + ) { count -> + PrefManager.setVal(PrefName.DownloadManager, count) + } + show() + } } ), Settings( @@ -180,90 +180,67 @@ class SettingsCommonActivity : AppCompatActivity() { desc = getString(R.string.app_lock_desc), icon = R.drawable.ic_round_lock_open_24, onClick = { - val passwordDialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.app_lock) - .setView(R.layout.dialog_set_password) - .setPositiveButton(R.string.ok) { dialog, _ -> - val passwordInput = - (dialog as AlertDialog).findViewById(R.id.passwordInput) - val confirmPasswordInput = - dialog.findViewById(R.id.confirmPasswordInput) - val forgotPasswordCheckbox = - dialog.findViewById(R.id.forgotPasswordCheckbox) - if (forgotPasswordCheckbox?.isChecked == true) { + customAlertDialog().apply { + val view = DialogSetPasswordBinding.inflate(layoutInflater) + setTitle(R.string.app_lock) + setCustomView(view.root) + setPosButton(R.string.ok) { + if (view.forgotPasswordCheckbox.isChecked) { PrefManager.setVal(PrefName.OverridePassword, true) } - - val password = passwordInput?.text.toString() - val confirmPassword = confirmPasswordInput?.text.toString() - + val password = view.passwordInput.text.toString() + val confirmPassword = view.confirmPasswordInput.text.toString() if (password == confirmPassword && password.isNotEmpty()) { PrefManager.setVal(PrefName.AppPassword, password) - if (dialog.findViewById(R.id.biometricCheckbox)?.isChecked == true) { + if (view.biometricCheckbox.isChecked) { val canBiometricPrompt = BiometricManager.from(applicationContext) - .canAuthenticate( - BiometricManager.Authenticators.BIOMETRIC_WEAK - ) == BiometricManager.BIOMETRIC_SUCCESS + .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS + if (canBiometricPrompt) { val biometricPrompt = - BiometricPromptUtils.createBiometricPrompt( - this@SettingsCommonActivity - ) { _ -> + BiometricPromptUtils.createBiometricPrompt(this@SettingsCommonActivity) { _ -> val token = UUID.randomUUID().toString() PrefManager.setVal( PrefName.BiometricToken, token ) toast(R.string.success) - dialog.dismiss() } val promptInfo = - BiometricPromptUtils.createPromptInfo( - this@SettingsCommonActivity - ) + BiometricPromptUtils.createPromptInfo(this@SettingsCommonActivity) biometricPrompt.authenticate(promptInfo) } + } else { PrefManager.setVal(PrefName.BiometricToken, "") toast(R.string.success) - dialog.dismiss() } } else { toast(R.string.password_mismatch) } } - .setNegativeButton(R.string.cancel) { dialog, _ -> - dialog.dismiss() - } - .setNeutralButton(R.string.remove) { dialog, _ -> + setNegButton(R.string.cancel) + setNeutralButton(R.string.remove){ PrefManager.setVal(PrefName.AppPassword, "") PrefManager.setVal(PrefName.BiometricToken, "") PrefManager.setVal(PrefName.OverridePassword, false) toast(R.string.success) - dialog.dismiss() } - .create() - - passwordDialog.window?.setDimAmount(0.8f) - passwordDialog.setOnShowListener { - passwordDialog.findViewById(R.id.passwordInput) - ?.requestFocus() - val biometricCheckbox = - passwordDialog.findViewById(R.id.biometricCheckbox) - val forgotPasswordCheckbox = - passwordDialog.findViewById(R.id.forgotPasswordCheckbox) - val canAuthenticate = - BiometricManager.from(applicationContext).canAuthenticate( - BiometricManager.Authenticators.BIOMETRIC_WEAK - ) == BiometricManager.BIOMETRIC_SUCCESS - biometricCheckbox?.isVisible = canAuthenticate - biometricCheckbox?.isChecked = - PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty() - forgotPasswordCheckbox?.isChecked = - PrefManager.getVal(PrefName.OverridePassword) + setOnShowListener { + view.passwordInput.requestFocus() + val canAuthenticate = + BiometricManager.from(applicationContext).canAuthenticate( + BiometricManager.Authenticators.BIOMETRIC_WEAK + ) == BiometricManager.BIOMETRIC_SUCCESS + view.biometricCheckbox.isVisible = canAuthenticate + view.biometricCheckbox.isChecked = + PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty() + view.forgotPasswordCheckbox.isChecked = + PrefManager.getVal(PrefName.OverridePassword) + } + show() } - passwordDialog.show() } ), diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt index abf742ba..a054b89a 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt @@ -81,11 +81,11 @@ class SettingsExtensionsActivity : AppCompatActivity() { view.repositoryItem.text = item.removePrefix("https://raw.githubusercontent.com/") view.repositoryItem.setOnClickListener { - AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.rem_repository).setMessage(item) - .setPositiveButton(getString(R.string.ok)) { dialog, _ -> - val repos = - PrefManager.getVal>(repoList).minus(item) + context.customAlertDialog().apply { + setTitle(R.string.rem_repository) + setMessage(item) + setPosButton(R.string.ok) { + val repos = PrefManager.getVal>(repoList).minus(item) PrefManager.setVal(repoList, repos) setExtensionOutput(repoInventory, type) CoroutineScope(Dispatchers.IO).launch { @@ -93,18 +93,16 @@ class SettingsExtensionsActivity : AppCompatActivity() { MediaType.ANIME -> { animeExtensionManager.findAvailableExtensions() } - MediaType.MANGA -> { mangaExtensionManager.findAvailableExtensions() } - else -> {} } } - dialog.dismiss() - }.setNegativeButton(getString(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - }.create().show() + } + setNegButton(R.string.cancel) + show() + } } view.repositoryItem.setOnLongClickListener { it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) @@ -209,27 +207,27 @@ class SettingsExtensionsActivity : AppCompatActivity() { val editText = dialogView.userAgentTextBox.apply { hint = getString(R.string.manga_add_repository) } - val alertDialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.manga_add_repository).setView(dialogView.root) - .setPositiveButton(getString(R.string.ok)) { dialog, _ -> + context.customAlertDialog().apply { + setTitle(R.string.manga_add_repository) + setCustomView(dialogView.root) + setPosButton(R.string.ok) { if (!editText.text.isNullOrBlank()) processUserInput( editText.text.toString(), MediaType.MANGA, it.attachView ) - dialog.dismiss() - }.setNegativeButton(getString(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - }.create() + } + setNegButton(R.string.cancel) + attach { dialog -> + processEditorAction( + dialog, + editText, + MediaType.MANGA, + it.attachView + ) + } + }.show() - processEditorAction( - alertDialog, - editText, - MediaType.MANGA, - it.attachView - ) - alertDialog.show() - alertDialog.window?.setDimAmount(0.8f) }, attach = { setExtensionOutput(it.attachView, MediaType.MANGA) @@ -258,24 +256,18 @@ class SettingsExtensionsActivity : AppCompatActivity() { val dialogView = DialogUserAgentBinding.inflate(layoutInflater) val editText = dialogView.userAgentTextBox editText.setText(PrefManager.getVal(PrefName.DefaultUserAgent)) - val alertDialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.user_agent).setView(dialogView.root) - .setPositiveButton(getString(R.string.ok)) { dialog, _ -> - PrefManager.setVal( - PrefName.DefaultUserAgent, - editText.text.toString() - ) - dialog.dismiss() - }.setNeutralButton(getString(R.string.reset)) { dialog, _ -> + context.customAlertDialog().apply { + setTitle(R.string.user_agent) + setCustomView(dialogView.root) + setPosButton(R.string.ok) { + PrefManager.setVal(PrefName.DefaultUserAgent, editText.text.toString()) + } + setNeutralButton(R.string.reset) { PrefManager.removeVal(PrefName.DefaultUserAgent) editText.setText("") - dialog.dismiss() - }.setNegativeButton(getString(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - }.create() - - alertDialog.show() - alertDialog.window?.setDimAmount(0.8f) + } + setNegButton(R.string.cancel) + }.show() } ), Settings( diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt index b97dfa4c..c7a9b1e6 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt @@ -25,6 +25,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager +import ani.dantotsu.util.customAlertDialog import java.util.Locale class SettingsNotificationActivity : AppCompatActivity() { @@ -77,26 +78,16 @@ class SettingsNotificationActivity : AppCompatActivity() { desc = getString(R.string.subscriptions_info), icon = R.drawable.ic_round_notifications_none_24, onClick = { - val speedDialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.subscriptions_checking_time) - val dialog = - speedDialog.setSingleChoiceItems(timeNames, curTime) { dialog, i -> + context.customAlertDialog().apply { + setTitle(R.string.subscriptions_checking_time) + singleChoiceItems(timeNames, curTime) { i -> curTime = i - it.settingsTitle.text = - getString( - R.string.subscriptions_checking_time_s, - timeNames[i] - ) - PrefManager.setVal( - PrefName.SubscriptionNotificationInterval, - curTime - ) - dialog.dismiss() - TaskScheduler.create( - context, PrefManager.getVal(PrefName.UseAlarmManager) - ).scheduleAllTasks(context) - }.show() - dialog.window?.setDimAmount(0.8f) + it.settingsTitle.text = getString(R.string.subscriptions_checking_time_s, timeNames[i]) + PrefManager.setVal(PrefName.SubscriptionNotificationInterval, curTime) + TaskScheduler.create(context, PrefManager.getVal(PrefName.UseAlarmManager)).scheduleAllTasks(context) + } + show() + } }, onLongClick = { TaskScheduler.create( diff --git a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt index fd6bfde6..08b6345e 100644 --- a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt @@ -39,9 +39,9 @@ class UserInterfaceSettingsActivity : AppCompatActivity() { binding.uiSettingsHomeLayout.setOnClickListener { val set = PrefManager.getVal>(PrefName.HomeLayout).toMutableList() val views = resources.getStringArray(R.array.home_layouts) - customAlertDialog() - .setTitle(getString(R.string.home_layout_show)) - .multiChoiceItems( + customAlertDialog().apply { + setTitle(getString(R.string.home_layout_show)) + multiChoiceItems( items = views, checkedItems = PrefManager.getVal>(PrefName.HomeLayout).toBooleanArray() ) { selectedItems -> @@ -49,11 +49,13 @@ class UserInterfaceSettingsActivity : AppCompatActivity() { set[i] = selectedItems[i] } } - .setPosButton("Done") { + setPosButton(R.string.ok) { PrefManager.setVal(PrefName.HomeLayout, set) restartApp() } - .show() + show() + } + } binding.uiSettingsSmallView.isChecked = PrefManager.getVal(PrefName.SmallView) diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt index b4e43ac8..6f188b85 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt @@ -22,6 +22,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files CheckUpdate(Pref(Location.General, Boolean::class, true)), VerboseLogging(Pref(Location.General, Boolean::class, false)), DohProvider(Pref(Location.General, Int::class, 0)), + HidePrivate(Pref(Location.General, Boolean::class, false)), DefaultUserAgent( Pref( Location.General, diff --git a/app/src/main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt b/app/src/main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt index 1a3e9f18..ccddfbab 100644 --- a/app/src/main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt +++ b/app/src/main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt @@ -120,12 +120,11 @@ class ActivityMarkdownCreator : AppCompatActivity() { toast(getString(R.string.cannot_be_empty)) return@setOnClickListener } - AlertDialog.Builder(this, R.style.MyPopup).apply { + customAlertDialog().apply { setTitle(R.string.warning) setMessage(R.string.post_to_anilist_warning) - setPositiveButton(R.string.ok) { _, _ -> + setPosButton(R.string.ok) { launchIO { - val isEdit = editId != -1 val success = when (type) { "activity" -> if (isEdit) { @@ -139,7 +138,6 @@ class ActivityMarkdownCreator : AppCompatActivity() { } else { Anilist.mutation.postReply(parentId, text) } - "message" -> if (isEdit) { Anilist.mutation.postMessage(userId, text, editId) } else { @@ -152,11 +150,12 @@ class ActivityMarkdownCreator : AppCompatActivity() { finish() } } - setNeutralButton(R.string.open_rules) { _, _ -> + setNeutralButton(R.string.open_rules) { openLinkInBrowser("https://anilist.co/forum/thread/14") } - setNegativeButton(R.string.cancel, null) - }.show() + setNegButton(R.string.cancel) + show() + } } binding.previewCheckbox.setOnClickListener { diff --git a/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt b/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt index 5bda5a18..374eb3d1 100644 --- a/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt +++ b/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt @@ -21,8 +21,23 @@ class AlertDialogBuilder(private val context: Context) { private var selectedItemIndex: Int = -1 private var onItemSelected: ((Int) -> Unit)? = null private var customView: View? = null + private var onShow: (() -> Unit)? = null private var attach: ((dialog: AlertDialog) -> Unit)? = null private var onDismiss: (() -> Unit)? = null + private var onCancel: (() -> Unit)? = null + private var cancelable: Boolean = true + fun setCancelable(cancelable: Boolean): AlertDialogBuilder { + this.cancelable = cancelable + return this + } + fun setOnShowListener(onShow: () -> Unit): AlertDialogBuilder { + this.onShow = onShow + return this + } + fun setOnCancelListener(onCancel: () -> Unit): AlertDialogBuilder { + this.onCancel = onCancel + return this + } fun setTitle(title: String?): AlertDialogBuilder { this.title = title @@ -48,6 +63,10 @@ class AlertDialogBuilder(private val context: Context) { this.customView = view return this } + fun setCustomView(layoutResId: Int): AlertDialogBuilder { + this.customView = View.inflate(context, layoutResId, null) + return this + } fun setPosButton(title: String?, onClick: (() -> Unit)? = null): AlertDialogBuilder { this.posButtonTitle = title @@ -118,9 +137,10 @@ class AlertDialogBuilder(private val context: Context) { if (customView != null) builder.setView(customView) if (items != null) { if (onItemSelected != null) { - builder.setSingleChoiceItems(items, selectedItemIndex) { _, which -> + builder.setSingleChoiceItems(items, selectedItemIndex) { dialog, which -> selectedItemIndex = which onItemSelected?.invoke(which) + dialog.dismiss() } } else if (checkedItems != null && onItemsSelected != null) { builder.setMultiChoiceItems(items, checkedItems) { _, which, isChecked -> @@ -147,12 +167,20 @@ class AlertDialogBuilder(private val context: Context) { dialog.dismiss() } } - builder.setCancelable(false) + if (onCancel != null) { + builder.setOnCancelListener { + onCancel?.invoke() + } + } + builder.setCancelable(cancelable) val dialog = builder.create() attach?.invoke(dialog) dialog.setOnDismissListener { onDismiss?.invoke() } + dialog.setOnShowListener { + onShow?.invoke() + } dialog.window?.setDimAmount(0.8f) dialog.show() } diff --git a/app/src/main/java/ani/dantotsu/util/StoragePermissions.kt b/app/src/main/java/ani/dantotsu/util/StoragePermissions.kt index 8291c567..ee2a9aa8 100644 --- a/app/src/main/java/ani/dantotsu/util/StoragePermissions.kt +++ b/app/src/main/java/ani/dantotsu/util/StoragePermissions.kt @@ -71,20 +71,17 @@ class StoragePermissions { complete(true) return } - val builder = AlertDialog.Builder(this, R.style.MyPopup) - builder.setTitle(getString(R.string.dir_access)) - builder.setMessage(getString(R.string.dir_access_msg)) - builder.setPositiveButton(getString(R.string.ok)) { dialog, _ -> - launcher.registerForCallback(complete) - launcher.launch() - dialog.dismiss() - } - builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - complete(false) - } - val dialog = builder.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.dir_access)) + setMessage(getString(R.string.dir_access_msg)) + setPosButton(getString(R.string.ok)) { + launcher.registerForCallback(complete) + launcher.launch() + } + setNegButton(getString(R.string.cancel)) { + complete(false) + } + }.show() } private fun pathToUri(path: String): Uri { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3660dae9..c6cb3844 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -948,6 +948,8 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc Use color from media\'s banner Use your own color for the theme Choose a color + Hide private Media from home screen + Long click "Continue Watching" to access Torrent Add-on Enable Torrent Anime Downloader Add-on From 899af3ee1aa73ac4ec523e6dc95f3af54e6eacdf Mon Sep 17 00:00:00 2001 From: ibo <41344259+sneazy-ibo@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:07:38 +0200 Subject: [PATCH 07/26] feat: added clearhistory button (#416) --- .../java/ani/dantotsu/media/SearchAdapter.kt | 13 +++++++++- .../dantotsu/media/SearchHistoryAdapter.kt | 6 +++++ .../main/res/drawable/ic_round_history_24.xml | 10 +++++++ .../main/res/layout/item_search_header.xml | 26 +++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_round_history_24.xml diff --git a/app/src/main/java/ani/dantotsu/media/SearchAdapter.kt b/app/src/main/java/ani/dantotsu/media/SearchAdapter.kt index ecf3692c..21f0b683 100644 --- a/app/src/main/java/ani/dantotsu/media/SearchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/SearchAdapter.kt @@ -183,6 +183,12 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri binding.searchByImage.setOnClickListener { activity.startActivity(Intent(activity, ImageSearchActivity::class.java)) } + binding.clearHistory.setOnClickListener { + it.startAnimation(fadeOutAnimation()) + it.visibility = View.GONE + searchHistoryAdapter.clearHistory() + } + updateClearHistoryVisibility() fun searchTitle() { activity.result.apply { search = @@ -300,11 +306,17 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri } binding.searchResultLayout.visibility = View.VISIBLE + binding.clearHistory.visibility = View.GONE binding.searchHistoryList.visibility = View.GONE binding.searchByImage.visibility = View.GONE } } + private fun updateClearHistoryVisibility() { + binding.clearHistory.visibility = + if (searchHistoryAdapter.itemCount > 0) View.VISIBLE else View.GONE + } + private fun fadeInAnimation(): Animation { return AlphaAnimation(0f, 1f).apply { duration = 150 @@ -375,4 +387,3 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri override fun getItemCount(): Int = chips.size } } - diff --git a/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt b/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt index f1519e2b..4e2988e3 100644 --- a/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt @@ -49,6 +49,12 @@ class SearchHistoryAdapter(private val type: String, private val searchClicked: PrefManager.setVal(historyType, searchHistory) } + fun clearHistory() { + searchHistory?.clear() + PrefManager.setVal(historyType, searchHistory) + submitList(searchHistory?.toList()) + } + override fun onCreateViewHolder( parent: ViewGroup, viewType: Int diff --git a/app/src/main/res/drawable/ic_round_history_24.xml b/app/src/main/res/drawable/ic_round_history_24.xml new file mode 100644 index 00000000..1d5164d3 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_history_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/item_search_header.xml b/app/src/main/res/layout/item_search_header.xml index dfd6083c..bd30343c 100644 --- a/app/src/main/res/layout/item_search_header.xml +++ b/app/src/main/res/layout/item_search_header.xml @@ -117,6 +117,32 @@ app:drawableTint="?attr/colorPrimary" /> + + + + + Install Add-on Add-on not found Image + Clear History Failed to install extension due to conflict READING WATCHING From eda213a7650de4f5aff70e2015ffb881d05f4e5e Mon Sep 17 00:00:00 2001 From: ibo <41344259+sneazy-ibo@users.noreply.github.com> Date: Sun, 16 Jun 2024 07:11:11 +0200 Subject: [PATCH 08/26] [skip ci] feat: better empty source dialog + bruh (#428) * feat: better empty source dialog + bruh * fix: itemMedia bindings --- .../dantotsu/media/anime/AnimeWatchAdapter.kt | 184 +++++++++-------- .../media/anime/AnimeWatchFragment.kt | 28 +-- .../dantotsu/media/anime/EpisodeAdapters.kt | 26 +-- .../dantotsu/media/manga/MangaReadAdapter.kt | 188 ++++++++++-------- .../dantotsu/media/manga/MangaReadFragment.kt | 37 ++-- .../dantotsu/media/novel/NovelReadAdapter.kt | 6 +- .../dantotsu/media/novel/NovelReadFragment.kt | 18 +- .../media/novel/NovelResponseAdapter.kt | 2 +- .../java/ani/dantotsu/parsers/AnimeSources.kt | 2 +- app/src/main/res/layout/dialog_layout.xml | 18 +- ...me_watch.xml => fragment_media_source.xml} | 6 +- .../main/res/layout/item_episode_compact.xml | 6 +- app/src/main/res/layout/item_episode_grid.xml | 8 +- app/src/main/res/layout/item_episode_list.xml | 8 +- ..._anime_watch.xml => item_media_source.xml} | 39 ++-- app/src/main/res/layout/item_novel_header.xml | 2 +- .../main/res/layout/item_novel_response.xml | 2 +- app/src/main/res/values/strings.xml | 8 +- 18 files changed, 310 insertions(+), 278 deletions(-) rename app/src/main/res/layout/{fragment_anime_watch.xml => fragment_media_source.xml} (93%) rename app/src/main/res/layout/{item_anime_watch.xml => item_media_source.xml} (93%) diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt index 4f710896..e1c77312 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt @@ -8,7 +8,6 @@ import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.ImageButton import android.widget.LinearLayout -import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.startActivity import androidx.core.view.isGone @@ -19,7 +18,7 @@ import ani.dantotsu.FileUrl import ani.dantotsu.R import ani.dantotsu.currActivity import ani.dantotsu.databinding.DialogLayoutBinding -import ani.dantotsu.databinding.ItemAnimeWatchBinding +import ani.dantotsu.databinding.ItemMediaSourceBinding import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.displayTimer import ani.dantotsu.isOnline @@ -33,6 +32,7 @@ import ani.dantotsu.others.LanguageMapper import ani.dantotsu.others.webview.CookieCatcher import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.DynamicAnimeParser +import ani.dantotsu.parsers.OfflineAnimeParser import ani.dantotsu.parsers.WatchSources import ani.dantotsu.px import ani.dantotsu.settings.FAQActivity @@ -55,10 +55,10 @@ class AnimeWatchAdapter( ) : RecyclerView.Adapter() { private var autoSelect = true var subscribe: MediaDetailsActivity.PopImageButton? = null - private var _binding: ItemAnimeWatchBinding? = null + private var _binding: ItemMediaSourceBinding? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val bind = ItemAnimeWatchBinding.inflate(LayoutInflater.from(parent.context), parent, false) + val bind = ItemMediaSourceBinding.inflate(LayoutInflater.from(parent.context), parent, false) return ViewHolder(bind) } @@ -73,7 +73,7 @@ class AnimeWatchAdapter( null ) } - //Youtube + // Youtube if (media.anime?.youtube != null && PrefManager.getVal(PrefName.ShowYtButton)) { binding.animeSourceYT.visibility = View.VISIBLE binding.animeSourceYT.setOnClickListener { @@ -87,7 +87,7 @@ class AnimeWatchAdapter( R.string.subbed ) - //PreferDub + // PreferDub var changing = false binding.animeSourceDubbed.setOnCheckedChangeListener { _, isChecked -> binding.animeSourceDubbedText.text = @@ -97,8 +97,8 @@ class AnimeWatchAdapter( if (!changing) fragment.onDubClicked(isChecked) } - //Wrong Title - binding.animeSourceSearch.setOnClickListener { + // Wrong Title + binding.mediaSourceSearch.setOnClickListener { SourceSearchDialogFragment().show( fragment.requireActivity().supportFragmentManager, null @@ -106,37 +106,37 @@ class AnimeWatchAdapter( } val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode) - binding.animeSourceNameContainer.isGone = offline - binding.animeSourceSettings.isGone = offline - binding.animeSourceSearch.isGone = offline - binding.animeSourceTitle.isGone = offline + binding.mediaSourceNameContainer.isGone = offline + binding.mediaSourceSettings.isGone = offline + binding.mediaSourceSearch.isGone = offline + binding.mediaSourceTitle.isGone = offline - //Source Selection + // Source Selection var source = media.selected!!.sourceIndex.let { if (it >= watchSources.names.size) 0 else it } setLanguageList(media.selected!!.langIndex, source) if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) { - binding.animeSource.setText(watchSources.names[source]) + binding.mediaSource.setText(watchSources.names[source]) watchSources[source].apply { this.selectDub = media.selected!!.preferDub - binding.animeSourceTitle.text = showUserText - showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } + binding.mediaSourceTitle.text = showUserText + showUserTextListener = { MainScope().launch { binding.mediaSourceTitle.text = it } } binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately() } } - binding.animeSource.setAdapter( + binding.mediaSource.setAdapter( ArrayAdapter( fragment.requireContext(), R.layout.item_dropdown, watchSources.names ) ) - binding.animeSourceTitle.isSelected = true - binding.animeSource.setOnItemClickListener { _, _, i, _ -> + binding.mediaSourceTitle.isSelected = true + binding.mediaSource.setOnItemClickListener { _, _, i, _ -> fragment.onSourceChange(i).apply { - binding.animeSourceTitle.text = showUserText - showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } + binding.mediaSourceTitle.text = showUserText + showUserTextListener = { MainScope().launch { binding.mediaSourceTitle.text = it } } changing = true binding.animeSourceDubbed.isChecked = selectDub changing = false @@ -148,15 +148,15 @@ class AnimeWatchAdapter( fragment.loadEpisodes(i, false) } - binding.animeSourceLanguage.setOnItemClickListener { _, _, i, _ -> + binding.mediaSourceLanguage.setOnItemClickListener { _, _, i, _ -> // Check if 'extension' and 'selected' properties exist and are accessible (watchSources[source] as? DynamicAnimeParser)?.let { ext -> ext.sourceLanguage = i fragment.onLangChange(i) fragment.onSourceChange(media.selected!!.sourceIndex).apply { - binding.animeSourceTitle.text = showUserText + binding.mediaSourceTitle.text = showUserText showUserTextListener = - { MainScope().launch { binding.animeSourceTitle.text = it } } + { MainScope().launch { binding.mediaSourceTitle.text = it } } changing = true binding.animeSourceDubbed.isChecked = selectDub changing = false @@ -168,19 +168,19 @@ class AnimeWatchAdapter( } ?: run { } } - //settings - binding.animeSourceSettings.setOnClickListener { + // Settings + binding.mediaSourceSettings.setOnClickListener { (watchSources[source] as? DynamicAnimeParser)?.let { ext -> fragment.openSettings(ext.extension) } } - //Icons + // Icons - //subscribe + // Subscribe subscribe = MediaDetailsActivity.PopImageButton( fragment.lifecycleScope, - binding.animeSourceSubscribe, + binding.mediaSourceSubscribe, R.drawable.ic_round_notifications_active_24, R.drawable.ic_round_notifications_none_24, R.color.bg_opp, @@ -188,17 +188,17 @@ class AnimeWatchAdapter( fragment.subscribed, true ) { - fragment.onNotificationPressed(it, binding.animeSource.text.toString()) + fragment.onNotificationPressed(it, binding.mediaSource.text.toString()) } subscribeButton(false) - binding.animeSourceSubscribe.setOnLongClickListener { + binding.mediaSourceSubscribe.setOnLongClickListener { openSettings(fragment.requireContext(), CHANNEL_SUBSCRIPTION_CHECK) } - //Nested Button - binding.animeNestedButton.setOnClickListener { + // Nested Button + binding.mediaNestedButton.setOnClickListener { val dialogBinding = DialogLayoutBinding.inflate(fragment.layoutInflater) dialogBinding.apply { var refresh = false @@ -207,26 +207,26 @@ class AnimeWatchAdapter( var style = media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.AnimeDefaultView) - animeSourceTop.rotation = if (reversed) -90f else 90f + mediaSourceTop.rotation = if (reversed) -90f else 90f sortText.text = if (reversed) "Down to Up" else "Up to Down" - animeSourceTop.setOnClickListener { + mediaSourceTop.setOnClickListener { reversed = !reversed - animeSourceTop.rotation = if (reversed) -90f else 90f + mediaSourceTop.rotation = if (reversed) -90f else 90f sortText.text = if (reversed) "Down to Up" else "Up to Down" run = true } - //Grids + // Grids var selected = when (style) { - 0 -> animeSourceList - 1 -> animeSourceGrid - 2 -> animeSourceCompact - else -> animeSourceList + 0 -> mediaSourceList + 1 -> mediaSourceGrid + 2 -> mediaSourceCompact + else -> mediaSourceList } when (style) { 0 -> layoutText.setText(R.string.list) 1 -> layoutText.setText(R.string.grid) 2 -> layoutText.setText(R.string.compact) - else -> animeSourceList + else -> mediaSourceList } selected.alpha = 1f fun selected(it: ImageButton) { @@ -234,29 +234,29 @@ class AnimeWatchAdapter( selected = it selected.alpha = 1f } - animeSourceList.setOnClickListener { + mediaSourceList.setOnClickListener { selected(it as ImageButton) style = 0 layoutText.setText(R.string.list) run = true } - animeSourceGrid.setOnClickListener { + mediaSourceGrid.setOnClickListener { selected(it as ImageButton) style = 1 layoutText.setText(R.string.grid) run = true } - animeSourceCompact.setOnClickListener { + mediaSourceCompact.setOnClickListener { selected(it as ImageButton) style = 2 layoutText.setText(R.string.compact) run = true } - animeWebviewContainer.setOnClickListener { + mediaWebviewContainer.setOnClickListener { if (!WebViewUtil.supportsWebView(fragment.requireContext())) { toast(R.string.webview_not_installed) } - //start CookieCatcher activity + // Start CookieCatcher activity if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) { val sourceAHH = watchSources[source] as? DynamicAnimeParser val sourceHttp = @@ -279,8 +279,8 @@ class AnimeWatchAdapter( } } - //hidden - animeScanlatorContainer.visibility = View.GONE + // Hidden + mangaScanlatorContainer.visibility = View.GONE animeDownloadContainer.visibility = View.GONE fragment.requireContext().customAlertDialog().apply { setTitle("Options") @@ -296,7 +296,7 @@ class AnimeWatchAdapter( } } } - //Episode Handling + // Episode Handling handleEpisodes() } @@ -304,7 +304,7 @@ class AnimeWatchAdapter( subscribe?.enabled(enabled) } - //Chips + // Chips fun updateChips(limit: Int, names: Array, arr: Array, selected: Int = 0) { val binding = _binding if (binding != null) { @@ -315,13 +315,13 @@ class AnimeWatchAdapter( val chip = ItemChipBinding.inflate( LayoutInflater.from(fragment.context), - binding.animeSourceChipGroup, + binding.mediaSourceChipGroup, false ).root chip.isCheckable = true fun selected() { chip.isChecked = true - binding.animeWatchChipScroll.smoothScrollTo( + binding.mediaWatchChipScroll.smoothScrollTo( (chip.left - screenWidth / 2) + (chip.width / 2), 0 ) @@ -340,14 +340,14 @@ class AnimeWatchAdapter( selected() fragment.onChipClicked(position, limit * (position), last - 1) } - binding.animeSourceChipGroup.addView(chip) + binding.mediaSourceChipGroup.addView(chip) if (selected == position) { selected() select = chip } } if (select != null) - binding.animeWatchChipScroll.apply { + binding.mediaWatchChipScroll.apply { post { scrollTo( (select.left - screenWidth / 2) + (select.width / 2), @@ -359,7 +359,7 @@ class AnimeWatchAdapter( } fun clearChips() { - _binding?.animeSourceChipGroup?.removeAllViews() + _binding?.mediaSourceChipGroup?.removeAllViews() } fun handleEpisodes() { @@ -375,15 +375,15 @@ class AnimeWatchAdapter( var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString() if (episodes.contains(continueEp)) { - binding.animeSourceContinue.visibility = View.VISIBLE + binding.sourceContinue.visibility = View.VISIBLE handleProgress( - binding.itemEpisodeProgressCont, - binding.itemEpisodeProgress, - binding.itemEpisodeProgressEmpty, + binding.itemMediaProgressCont, + binding.itemMediaProgress, + binding.itemMediaProgressEmpty, media.id, continueEp ) - if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight > PrefManager.getVal( + if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight > PrefManager.getVal( PrefName.WatchPercentage ) ) { @@ -391,9 +391,9 @@ class AnimeWatchAdapter( if (e != -1 && e + 1 < episodes.size) { continueEp = episodes[e + 1] handleProgress( - binding.itemEpisodeProgressCont, - binding.itemEpisodeProgress, - binding.itemEpisodeProgressEmpty, + binding.itemMediaProgressCont, + binding.itemMediaProgress, + binding.itemMediaProgressEmpty, media.id, continueEp ) @@ -403,51 +403,63 @@ class AnimeWatchAdapter( val cleanedTitle = ep.title?.let { MediaNameAdapter.removeEpisodeNumber(it) } - binding.itemEpisodeImage.loadImage( + binding.itemMediaImage.loadImage( ep.thumb ?: FileUrl[media.banner ?: media.cover], 0 ) if (ep.filler) binding.itemEpisodeFillerView.visibility = View.VISIBLE - binding.animeSourceContinueText.text = + binding.mediaSourceContinueText.text = currActivity()!!.getString( R.string.continue_episode, ep.number, if (ep.filler) currActivity()!!.getString(R.string.filler_tag) else "", cleanedTitle ) - binding.animeSourceContinue.setOnClickListener { + binding.sourceContinue.setOnClickListener { fragment.onEpisodeClick(continueEp) } if (fragment.continueEp) { if ( - (binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams) + (binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams) .weight < PrefManager.getVal(PrefName.WatchPercentage) ) { - binding.animeSourceContinue.performClick() + binding.sourceContinue.performClick() fragment.continueEp = false } } } else { - binding.animeSourceContinue.visibility = View.GONE + binding.sourceContinue.visibility = View.GONE } - binding.animeSourceProgressBar.visibility = View.GONE + binding.sourceProgressBar.visibility = View.GONE val sourceFound = media.anime.episodes!!.isNotEmpty() - binding.animeSourceNotFound.isGone = sourceFound + val isDownloadedSource = watchSources[media.selected!!.sourceIndex] is OfflineAnimeParser + + if (isDownloadedSource) { + binding.sourceNotFound.text = if (sourceFound) { + currActivity()!!.getString(R.string.source_not_found) + } else { + currActivity()!!.getString(R.string.download_not_found) + } + } else { + binding.sourceNotFound.text = currActivity()!!.getString(R.string.source_not_found) + } + + binding.sourceNotFound.isGone = sourceFound binding.faqbutton.isGone = sourceFound if (!sourceFound && PrefManager.getVal(PrefName.SearchSources) && autoSelect) { - if (binding.animeSource.adapter.count > media.selected!!.sourceIndex + 1) { + if (binding.mediaSource.adapter.count > media.selected!!.sourceIndex + 1) { val nextIndex = media.selected!!.sourceIndex + 1 - binding.animeSource.setText( - binding.animeSource.adapter + binding.mediaSource.setText( + binding.mediaSource.adapter .getItem(nextIndex).toString(), false ) fragment.onSourceChange(nextIndex).apply { - binding.animeSourceTitle.text = showUserText + binding.mediaSourceTitle.text = showUserText showUserTextListener = - { MainScope().launch { binding.animeSourceTitle.text = it } } + { MainScope().launch { binding.mediaSourceTitle.text = it } } binding.animeSourceDubbed.isChecked = selectDub binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately() setLanguageList(0, nextIndex) @@ -456,13 +468,13 @@ class AnimeWatchAdapter( fragment.loadEpisodes(nextIndex, false) } } - binding.animeSource.setOnClickListener { autoSelect = false } + binding.mediaSource.setOnClickListener { autoSelect = false } } else { - binding.animeSourceContinue.visibility = View.GONE - binding.animeSourceNotFound.visibility = View.GONE + binding.sourceContinue.visibility = View.GONE + binding.sourceNotFound.visibility = View.GONE binding.faqbutton.visibility = View.GONE clearChips() - binding.animeSourceProgressBar.visibility = View.VISIBLE + binding.sourceProgressBar.visibility = View.VISIBLE } } } @@ -476,9 +488,9 @@ class AnimeWatchAdapter( ext.sourceLanguage = lang } try { - binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang) + binding?.mediaSourceLanguage?.setText(parser.extension.sources[lang].lang) } catch (e: IndexOutOfBoundsException) { - binding?.animeSourceLanguage?.setText( + binding?.mediaSourceLanguage?.setText( parser.extension.sources.firstOrNull()?.lang ?: "Unknown" ) } @@ -489,9 +501,9 @@ class AnimeWatchAdapter( ) val items = adapter.count - binding?.animeSourceLanguageContainer?.visibility = + binding?.mediaSourceLanguageContainer?.visibility = if (items > 1) View.VISIBLE else View.GONE - binding?.animeSourceLanguage?.setAdapter(adapter) + binding?.mediaSourceLanguage?.setAdapter(adapter) } } @@ -499,7 +511,7 @@ class AnimeWatchAdapter( override fun getItemCount(): Int = 1 - inner class ViewHolder(val binding: ItemAnimeWatchBinding) : + inner class ViewHolder(val binding: ItemMediaSourceBinding) : RecyclerView.ViewHolder(binding.root) { init { displayTimer(media, binding.animeSourceContainer) diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt index a9dc9b7e..847d8755 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt @@ -32,7 +32,7 @@ import ani.dantotsu.FileUrl import ani.dantotsu.R import ani.dantotsu.addons.download.DownloadAddonManager import ani.dantotsu.connections.anilist.api.MediaStreamingEpisode -import ani.dantotsu.databinding.FragmentAnimeWatchBinding +import ani.dantotsu.databinding.FragmentMediaSourceBinding import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager.Companion.compareName @@ -81,7 +81,7 @@ import kotlin.math.max import kotlin.math.roundToInt class AnimeWatchFragment : Fragment() { - private var _binding: FragmentAnimeWatchBinding? = null + private var _binding: FragmentMediaSourceBinding? = null private val binding get() = _binding!! private val model: MediaDetailsViewModel by activityViewModels() @@ -108,7 +108,7 @@ class AnimeWatchFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - _binding = FragmentAnimeWatchBinding.inflate(inflater, container, false) + _binding = FragmentMediaSourceBinding.inflate(inflater, container, false) return _binding?.root } @@ -129,7 +129,7 @@ class AnimeWatchFragment : Fragment() { ) - binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight) + binding.mediaSourceRecycler.updatePadding(bottom = binding.mediaSourceRecycler.paddingBottom + navBarHeight) screenWidth = resources.displayMetrics.widthPixels.dp var maxGridSize = (screenWidth / 100f).roundToInt() @@ -153,13 +153,13 @@ class AnimeWatchFragment : Fragment() { } } - binding.animeSourceRecycler.layoutManager = gridLayoutManager + binding.mediaSourceRecycler.layoutManager = gridLayoutManager binding.ScrollTop.setOnClickListener { - binding.animeSourceRecycler.scrollToPosition(10) - binding.animeSourceRecycler.smoothScrollToPosition(0) + binding.mediaSourceRecycler.scrollToPosition(10) + binding.mediaSourceRecycler.smoothScrollToPosition(0) } - binding.animeSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { + binding.mediaSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) @@ -173,7 +173,7 @@ class AnimeWatchFragment : Fragment() { } }) model.scrolledToTop.observe(viewLifecycleOwner) { - if (it) binding.animeSourceRecycler.scrollToPosition(0) + if (it) binding.mediaSourceRecycler.scrollToPosition(0) } continueEp = model.continueMedia ?: false @@ -206,7 +206,7 @@ class AnimeWatchFragment : Fragment() { offlineMode = offlineMode ) - binding.animeSourceRecycler.adapter = + binding.mediaSourceRecycler.adapter = ConcatAdapter(headerAdapter, episodeAdapter) lifecycleScope.launch(Dispatchers.IO) { @@ -267,7 +267,7 @@ class AnimeWatchFragment : Fragment() { } media.anime?.episodes = episodes - //CHIP GROUP + // CHIP GROUP val total = episodes.size val divisions = total.toDouble() / 10 start = 0 @@ -635,7 +635,7 @@ class AnimeWatchFragment : Fragment() { private fun reload() { val selected = model.loadSelected(media) - //Find latest episode for subscription + // Find latest episode for subscription selected.latest = media.anime?.episodes?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f selected.latest = @@ -679,14 +679,14 @@ class AnimeWatchFragment : Fragment() { override fun onResume() { super.onResume() binding.mediaInfoProgressBar.visibility = progress - binding.animeSourceRecycler.layoutManager?.onRestoreInstanceState(state) + binding.mediaSourceRecycler.layoutManager?.onRestoreInstanceState(state) requireActivity().setNavigationTheme() } override fun onPause() { super.onPause() - state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState() + state = binding.mediaSourceRecycler.layoutManager?.onSaveInstanceState() } companion object { diff --git a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt index 1ee5c881..c0ac4352 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt @@ -107,8 +107,8 @@ class EpisodeAdapter( val thumb = ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null } - Glide.with(binding.itemEpisodeImage).load(thumb ?: media.cover).override(400, 0) - .into(binding.itemEpisodeImage) + Glide.with(binding.itemMediaImage).load(thumb ?: media.cover).override(400, 0) + .into(binding.itemMediaImage) binding.itemEpisodeNumber.text = ep.number binding.itemEpisodeTitle.text = if (ep.number == title) "Episode $title" else title @@ -141,9 +141,9 @@ class EpisodeAdapter( } handleProgress( - binding.itemEpisodeProgressCont, - binding.itemEpisodeProgress, - binding.itemEpisodeProgressEmpty, + binding.itemMediaProgressCont, + binding.itemMediaProgress, + binding.itemMediaProgressEmpty, media.id, ep.number ) @@ -155,8 +155,8 @@ class EpisodeAdapter( val thumb = ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null } - Glide.with(binding.itemEpisodeImage).load(thumb ?: media.cover).override(400, 0) - .into(binding.itemEpisodeImage) + Glide.with(binding.itemMediaImage).load(thumb ?: media.cover).override(400, 0) + .into(binding.itemMediaImage) binding.itemEpisodeNumber.text = ep.number binding.itemEpisodeTitle.text = title @@ -184,9 +184,9 @@ class EpisodeAdapter( binding.itemEpisodeViewed.visibility = View.GONE } handleProgress( - binding.itemEpisodeProgressCont, - binding.itemEpisodeProgress, - binding.itemEpisodeProgressEmpty, + binding.itemMediaProgressCont, + binding.itemMediaProgress, + binding.itemMediaProgressEmpty, media.id, ep.number ) @@ -209,9 +209,9 @@ class EpisodeAdapter( } } handleProgress( - binding.itemEpisodeProgressCont, - binding.itemEpisodeProgress, - binding.itemEpisodeProgressEmpty, + binding.itemMediaProgressCont, + binding.itemMediaProgress, + binding.itemMediaProgressEmpty, media.id, ep.number ) diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt index 4cd856a1..a98f0570 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt @@ -1,6 +1,5 @@ package ani.dantotsu.media.manga -import android.app.AlertDialog import android.content.Intent import android.view.LayoutInflater import android.view.View @@ -21,7 +20,7 @@ import ani.dantotsu.currActivity import ani.dantotsu.currContext import ani.dantotsu.databinding.CustomDialogLayoutBinding import ani.dantotsu.databinding.DialogLayoutBinding -import ani.dantotsu.databinding.ItemAnimeWatchBinding +import ani.dantotsu.databinding.ItemMediaSourceBinding import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.isOnline import ani.dantotsu.loadImage @@ -36,6 +35,7 @@ import ani.dantotsu.others.webview.CookieCatcher import ani.dantotsu.parsers.DynamicMangaParser import ani.dantotsu.parsers.MangaReadSources import ani.dantotsu.parsers.MangaSources +import ani.dantotsu.parsers.OfflineMangaParser import ani.dantotsu.px import ani.dantotsu.settings.FAQActivity import ani.dantotsu.settings.saving.PrefManager @@ -57,13 +57,13 @@ class MangaReadAdapter( ) : RecyclerView.Adapter() { var subscribe: MediaDetailsActivity.PopImageButton? = null - private var _binding: ItemAnimeWatchBinding? = null + private var _binding: ItemMediaSourceBinding? = null val hiddenScanlators = mutableListOf() var scanlatorSelectionListener: ScanlatorSelectionListener? = null var options = listOf() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val bind = ItemAnimeWatchBinding.inflate(LayoutInflater.from(parent.context), parent, false) + val bind = ItemMediaSourceBinding.inflate(LayoutInflater.from(parent.context), parent, false) return ViewHolder(bind) } @@ -72,14 +72,14 @@ class MangaReadAdapter( _binding = binding binding.sourceTitle.setText(R.string.chaps) - //Fuck u launch + // Fuck u launch binding.faqbutton.setOnClickListener { val intent = Intent(fragment.requireContext(), FAQActivity::class.java) startActivity(fragment.requireContext(), intent, null) } - //Wrong Title - binding.animeSourceSearch.setOnClickListener { + // Wrong Title + binding.mediaSourceSearch.setOnClickListener { SourceSearchDialogFragment().show( fragment.requireActivity().supportFragmentManager, null @@ -87,54 +87,54 @@ class MangaReadAdapter( } val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode) - binding.animeSourceNameContainer.isGone = offline - binding.animeSourceSettings.isGone = offline - binding.animeSourceSearch.isGone = offline - binding.animeSourceTitle.isGone = offline - //Source Selection + binding.mediaSourceNameContainer.isGone = offline + binding.mediaSourceSettings.isGone = offline + binding.mediaSourceSearch.isGone = offline + binding.mediaSourceTitle.isGone = offline + // Source Selection var source = media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it } setLanguageList(media.selected!!.langIndex, source) if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) { - binding.animeSource.setText(mangaReadSources.names[source]) + binding.mediaSource.setText(mangaReadSources.names[source]) mangaReadSources[source].apply { - binding.animeSourceTitle.text = showUserText - showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } + binding.mediaSourceTitle.text = showUserText + showUserTextListener = { MainScope().launch { binding.mediaSourceTitle.text = it } } } } media.selected?.scanlators?.let { hiddenScanlators.addAll(it) } - binding.animeSource.setAdapter( + binding.mediaSource.setAdapter( ArrayAdapter( fragment.requireContext(), R.layout.item_dropdown, mangaReadSources.names ) ) - binding.animeSourceTitle.isSelected = true - binding.animeSource.setOnItemClickListener { _, _, i, _ -> + binding.mediaSourceTitle.isSelected = true + binding.mediaSource.setOnItemClickListener { _, _, i, _ -> fragment.onSourceChange(i).apply { - binding.animeSourceTitle.text = showUserText - showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } + binding.mediaSourceTitle.text = showUserText + showUserTextListener = { MainScope().launch { binding.mediaSourceTitle.text = it } } source = i setLanguageList(0, i) } subscribeButton(false) - //invalidate if it's the last source + // Invalidate if it's the last source val invalidate = i == mangaReadSources.names.size - 1 fragment.loadChapters(i, invalidate) } - binding.animeSourceLanguage.setOnItemClickListener { _, _, i, _ -> + binding.mediaSourceLanguage.setOnItemClickListener { _, _, i, _ -> // Check if 'extension' and 'selected' properties exist and are accessible (mangaReadSources[source] as? DynamicMangaParser)?.let { ext -> ext.sourceLanguage = i fragment.onLangChange(i, ext.saveName) fragment.onSourceChange(media.selected!!.sourceIndex).apply { - binding.animeSourceTitle.text = showUserText + binding.mediaSourceTitle.text = showUserText showUserTextListener = - { MainScope().launch { binding.animeSourceTitle.text = it } } + { MainScope().launch { binding.mediaSourceTitle.text = it } } setLanguageList(i, source) } subscribeButton(false) @@ -143,17 +143,17 @@ class MangaReadAdapter( } } - //settings - binding.animeSourceSettings.setOnClickListener { + // Settings + binding.mediaSourceSettings.setOnClickListener { (mangaReadSources[source] as? DynamicMangaParser)?.let { ext -> fragment.openSettings(ext.extension) } } - //Grids + // Grids subscribe = MediaDetailsActivity.PopImageButton( fragment.lifecycleScope, - binding.animeSourceSubscribe, + binding.mediaSourceSubscribe, R.drawable.ic_round_notifications_active_24, R.drawable.ic_round_notifications_none_24, R.color.bg_opp, @@ -161,16 +161,16 @@ class MangaReadAdapter( fragment.subscribed, true ) { - fragment.onNotificationPressed(it, binding.animeSource.text.toString()) + fragment.onNotificationPressed(it, binding.mediaSource.text.toString()) } subscribeButton(false) - binding.animeSourceSubscribe.setOnLongClickListener { + binding.mediaSourceSubscribe.setOnLongClickListener { openSettings(fragment.requireContext(), CHANNEL_SUBSCRIPTION_CHECK) } - binding.animeNestedButton.setOnClickListener { + binding.mediaNestedButton.setOnClickListener { val dialogBinding = DialogLayoutBinding.inflate(fragment.layoutInflater) var refresh = false var run = false @@ -178,26 +178,26 @@ class MangaReadAdapter( var style = media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.MangaDefaultView) dialogBinding.apply { - animeSourceTop.rotation = if (reversed) -90f else 90f + mediaSourceTop.rotation = if (reversed) -90f else 90f sortText.text = if (reversed) "Down to Up" else "Up to Down" - animeSourceTop.setOnClickListener { + mediaSourceTop.setOnClickListener { reversed = !reversed - animeSourceTop.rotation = if (reversed) -90f else 90f + mediaSourceTop.rotation = if (reversed) -90f else 90f sortText.text = if (reversed) "Down to Up" else "Up to Down" run = true } - //Grids - animeSourceGrid.visibility = View.GONE + // Grids + mediaSourceGrid.visibility = View.GONE var selected = when (style) { - 0 -> animeSourceList - 1 -> animeSourceCompact - else -> animeSourceList + 0 -> mediaSourceList + 1 -> mediaSourceCompact + else -> mediaSourceList } when (style) { 0 -> layoutText.setText(R.string.list) 1 -> layoutText.setText(R.string.compact) - else -> animeSourceList + else -> mediaSourceList } selected.alpha = 1f fun selected(it: ImageButton) { @@ -205,23 +205,23 @@ class MangaReadAdapter( selected = it selected.alpha = 1f } - animeSourceList.setOnClickListener { + mediaSourceList.setOnClickListener { selected(it as ImageButton) style = 0 layoutText.setText(R.string.list) run = true } - animeSourceCompact.setOnClickListener { + mediaSourceCompact.setOnClickListener { selected(it as ImageButton) style = 1 layoutText.setText(R.string.compact) run = true } - animeWebviewContainer.setOnClickListener { + mediaWebviewContainer.setOnClickListener { if (!WebViewUtil.supportsWebView(fragment.requireContext())) { toast(R.string.webview_not_installed) } - //start CookieCatcher activity + // Start CookieCatcher activity if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) { val sourceAHH = mangaReadSources[source] as? DynamicMangaParser val sourceHttp = sourceAHH?.extension?.sources?.firstOrNull() as? HttpSource @@ -236,10 +236,10 @@ class MangaReadAdapter( } } - //Multi download + // Multi download downloadNo.text = "0" - animeDownloadTop.setOnClickListener { - //Alert dialog asking for the number of chapters to download + mediaDownloadTop.setOnClickListener { + // Alert dialog asking for the number of chapters to download fragment.requireContext().customAlertDialog().apply { setTitle("Multi Chapter Downloader") setMessage("Enter the number of chapters to download") @@ -256,10 +256,10 @@ class MangaReadAdapter( } } - //Scanlator - animeScanlatorContainer.isVisible = options.count() > 1 + // Scanlator + mangaScanlatorContainer.isVisible = options.count() > 1 scanlatorNo.text = "${options.count()}" - animeScanlatorTop.setOnClickListener { + mangaScanlatorTop.setOnClickListener { CustomDialogLayoutBinding.inflate(fragment.layoutInflater) val dialogView = CustomDialogLayoutBinding.inflate(fragment.layoutInflater) val checkboxContainer = dialogView.checkboxContainer @@ -345,7 +345,7 @@ class MangaReadAdapter( } } } - //Chapter Handling + // Chapter Handling handleChapters() } @@ -353,7 +353,7 @@ class MangaReadAdapter( subscribe?.enabled(enabled) } - //Chips + // Chips fun updateChips(limit: Int, names: Array, arr: Array, selected: Int = 0) { val binding = _binding if (binding != null) { @@ -364,13 +364,13 @@ class MangaReadAdapter( val chip = ItemChipBinding.inflate( LayoutInflater.from(fragment.context), - binding.animeSourceChipGroup, + binding.mediaSourceChipGroup, false ).root chip.isCheckable = true fun selected() { chip.isChecked = true - binding.animeWatchChipScroll.smoothScrollTo( + binding.mediaWatchChipScroll.smoothScrollTo( (chip.left - screenWidth / 2) + (chip.width / 2), 0 ) @@ -388,7 +388,7 @@ class MangaReadAdapter( } else { names[last - 1] } - //chip.text = "${names[limit * (position)]} - ${names[last - 1]}" + // chip.text = "${names[limit * (position)]} - ${names[last - 1]}" val chipText = "$startChapterString - $endChapterString" chip.text = chipText chip.setTextColor( @@ -402,14 +402,14 @@ class MangaReadAdapter( selected() fragment.onChipClicked(position, limit * (position), last - 1) } - binding.animeSourceChipGroup.addView(chip) + binding.mediaSourceChipGroup.addView(chip) if (selected == position) { selected() select = chip } } if (select != null) - binding.animeWatchChipScroll.apply { + binding.mediaWatchChipScroll.apply { post { scrollTo( (select.left - screenWidth / 2) + (select.width / 2), @@ -421,7 +421,7 @@ class MangaReadAdapter( } fun clearChips() { - _binding?.animeSourceChipGroup?.removeAllViews() + _binding?.mediaSourceChipGroup?.removeAllViews() } fun handleChapters() { @@ -447,70 +447,86 @@ class MangaReadAdapter( } if (formattedChapters.contains(continueEp)) { continueEp = chapters[formattedChapters.indexOf(continueEp)] - binding.animeSourceContinue.visibility = View.VISIBLE + binding.sourceContinue.visibility = View.VISIBLE handleProgress( - binding.itemEpisodeProgressCont, - binding.itemEpisodeProgress, - binding.itemEpisodeProgressEmpty, + binding.itemMediaProgressCont, + binding.itemMediaProgress, + binding.itemMediaProgressEmpty, media.id, continueEp ) - if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight > 0.8f) { + if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight > 0.8f) { val e = chapters.indexOf(continueEp) if (e != -1 && e + 1 < chapters.size) { continueEp = chapters[e + 1] } } val ep = media.manga.chapters!![continueEp]!! - binding.itemEpisodeImage.loadImage(media.banner ?: media.cover) - binding.animeSourceContinueText.text = + binding.itemMediaImage.loadImage(media.banner ?: media.cover) + binding.mediaSourceContinueText.text = currActivity()!!.getString( R.string.continue_chapter, ep.number, if (!ep.title.isNullOrEmpty()) ep.title else "" ) - binding.animeSourceContinue.setOnClickListener { + binding.sourceContinue.setOnClickListener { fragment.onMangaChapterClick(continueEp) } if (fragment.continueEp) { - if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight < 0.8f) { - binding.animeSourceContinue.performClick() + if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight < 0.8f) { + binding.sourceContinue.performClick() fragment.continueEp = false } } } else { - binding.animeSourceContinue.visibility = View.GONE + binding.sourceContinue.visibility = View.GONE } - binding.animeSourceProgressBar.visibility = View.GONE - val sourceFound = media.manga.chapters!!.isNotEmpty() - binding.animeSourceNotFound.isGone = sourceFound + + binding.sourceProgressBar.visibility = View.GONE + + val sourceFound = filteredChapters.isNotEmpty() + val isDownloadedSource = mangaReadSources[media.selected!!.sourceIndex] is OfflineMangaParser + + if (isDownloadedSource) { + binding.sourceNotFound.text = if (sourceFound) { + currActivity()!!.getString(R.string.source_not_found) + } else { + currActivity()!!.getString(R.string.download_not_found) + } + } else { + binding.sourceNotFound.text = currActivity()!!.getString(R.string.source_not_found) + } + + binding.sourceNotFound.isGone = sourceFound binding.faqbutton.isGone = sourceFound + + if (!sourceFound && PrefManager.getVal(PrefName.SearchSources)) { - if (binding.animeSource.adapter.count > media.selected!!.sourceIndex + 1) { + if (binding.mediaSource.adapter.count > media.selected!!.sourceIndex + 1) { val nextIndex = media.selected!!.sourceIndex + 1 - binding.animeSource.setText( - binding.animeSource.adapter + binding.mediaSource.setText( + binding.mediaSource.adapter .getItem(nextIndex).toString(), false ) fragment.onSourceChange(nextIndex).apply { - binding.animeSourceTitle.text = showUserText + binding.mediaSourceTitle.text = showUserText showUserTextListener = - { MainScope().launch { binding.animeSourceTitle.text = it } } + { MainScope().launch { binding.mediaSourceTitle.text = it } } setLanguageList(0, nextIndex) } subscribeButton(false) - // invalidate if it's the last source + // Invalidate if it's the last source val invalidate = nextIndex == mangaReadSources.names.size - 1 fragment.loadChapters(nextIndex, invalidate) } } } else { - binding.animeSourceContinue.visibility = View.GONE - binding.animeSourceNotFound.visibility = View.GONE + binding.sourceContinue.visibility = View.GONE + binding.sourceNotFound.visibility = View.GONE binding.faqbutton.visibility = View.GONE clearChips() - binding.animeSourceProgressBar.visibility = View.VISIBLE + binding.sourceProgressBar.visibility = View.VISIBLE } } } @@ -524,9 +540,9 @@ class MangaReadAdapter( ext.sourceLanguage = lang } try { - binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang) + binding?.mediaSourceLanguage?.setText(parser.extension.sources[lang].lang) } catch (e: IndexOutOfBoundsException) { - binding?.animeSourceLanguage?.setText( + binding?.mediaSourceLanguage?.setText( parser.extension.sources.firstOrNull()?.lang ?: "Unknown" ) } @@ -536,9 +552,9 @@ class MangaReadAdapter( parser.extension.sources.map { LanguageMapper.getLanguageName(it.lang) } ) val items = adapter.count - binding?.animeSourceLanguageContainer?.isVisible = items > 1 + binding?.mediaSourceLanguageContainer?.isVisible = items > 1 - binding?.animeSourceLanguage?.setAdapter(adapter) + binding?.mediaSourceLanguage?.setAdapter(adapter) } } @@ -546,7 +562,7 @@ class MangaReadAdapter( override fun getItemCount(): Int = 1 - inner class ViewHolder(val binding: ItemAnimeWatchBinding) : + inner class ViewHolder(val binding: ItemMediaSourceBinding) : RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt index 7430bafa..be836ad5 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt @@ -2,7 +2,6 @@ package ani.dantotsu.media.manga import android.Manifest import android.annotation.SuppressLint -import android.app.AlertDialog import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -31,7 +30,7 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.R -import ani.dantotsu.databinding.FragmentAnimeWatchBinding +import ani.dantotsu.databinding.FragmentMediaSourceBinding import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager.Companion.compareName @@ -75,7 +74,7 @@ import kotlin.math.max import kotlin.math.roundToInt open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { - private var _binding: FragmentAnimeWatchBinding? = null + private var _binding: FragmentMediaSourceBinding? = null private val binding get() = _binding!! private val model: MediaDetailsViewModel by activityViewModels() @@ -102,7 +101,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - _binding = FragmentAnimeWatchBinding.inflate(inflater, container, false) + _binding = FragmentMediaSourceBinding.inflate(inflater, container, false) return _binding?.root } @@ -122,7 +121,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { ContextCompat.RECEIVER_EXPORTED ) - binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight) + binding.mediaSourceRecycler.updatePadding(bottom = binding.mediaSourceRecycler.paddingBottom + navBarHeight) screenWidth = resources.displayMetrics.widthPixels.dp var maxGridSize = (screenWidth / 100f).roundToInt() @@ -145,13 +144,13 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { } } - binding.animeSourceRecycler.layoutManager = gridLayoutManager + binding.mediaSourceRecycler.layoutManager = gridLayoutManager binding.ScrollTop.setOnClickListener { - binding.animeSourceRecycler.scrollToPosition(10) - binding.animeSourceRecycler.smoothScrollToPosition(0) + binding.mediaSourceRecycler.scrollToPosition(10) + binding.mediaSourceRecycler.smoothScrollToPosition(0) } - binding.animeSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { + binding.mediaSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) @@ -165,7 +164,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { } }) model.scrolledToTop.observe(viewLifecycleOwner) { - if (it) binding.animeSourceRecycler.scrollToPosition(0) + if (it) binding.mediaSourceRecycler.scrollToPosition(0) } continueEp = model.continueMedia ?: false @@ -200,7 +199,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { } } - binding.animeSourceRecycler.adapter = + binding.mediaSourceRecycler.adapter = ConcatAdapter(headerAdapter, chapterAdapter) lifecycleScope.launch(Dispatchers.IO) { @@ -215,8 +214,8 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { reload() } } else { - binding.animeNotSupported.visibility = View.VISIBLE - binding.animeNotSupported.text = + binding.mediaNotSupported.visibility = View.VISIBLE + binding.mediaNotSupported.text = getString(R.string.not_supported, media.format ?: "") } } @@ -232,10 +231,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { } fun multiDownload(n: Int) { - //get last viewed chapter + // Get last viewed chapter val selected = media.userProgress val chapters = media.manga?.chapters?.values?.toList() - //filter by selected language + // Filter by selected language val progressChapterIndex = (chapters?.indexOfFirst { MediaNameAdapter.findChapterNumber(it.number)?.toInt() == selected } ?: 0) + 1 @@ -245,7 +244,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { // Calculate the end index val endIndex = minOf(progressChapterIndex + n, chapters.size) - //make sure there are enough chapters + // Make sure there are enough chapters val chaptersToDownload = chapters.subList(progressChapterIndex, endIndex) @@ -583,7 +582,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { private fun reload() { val selected = model.loadSelected(media) - //Find latest chapter for subscription + // Find latest chapter for subscription selected.latest = media.manga?.chapters?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f selected.latest = @@ -617,14 +616,14 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { override fun onResume() { super.onResume() binding.mediaInfoProgressBar.visibility = progress - binding.animeSourceRecycler.layoutManager?.onRestoreInstanceState(state) + binding.mediaSourceRecycler.layoutManager?.onRestoreInstanceState(state) requireActivity().setNavigationTheme() } override fun onPause() { super.onPause() - state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState() + state = binding.mediaSourceRecycler.layoutManager?.onSaveInstanceState() } companion object { diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelReadAdapter.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelReadAdapter.kt index 7f9d34f4..cfaf298f 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelReadAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelReadAdapter.kt @@ -50,16 +50,16 @@ class NovelReadAdapter( val source = media.selected!!.sourceIndex.let { if (it >= novelReadSources.names.size) 0 else it } if (novelReadSources.names.isNotEmpty() && source in 0 until novelReadSources.names.size) { - binding.animeSource.setText(novelReadSources.names[source], false) + binding.mediaSource.setText(novelReadSources.names[source], false) } - binding.animeSource.setAdapter( + binding.mediaSource.setAdapter( ArrayAdapter( fragment.requireContext(), R.layout.item_dropdown, novelReadSources.names ) ) - binding.animeSource.setOnItemClickListener { _, _, i, _ -> + binding.mediaSource.setOnItemClickListener { _, _, i, _ -> fragment.onSourceChange(i) search() } diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt index c95328f6..5cd1f8ad 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt @@ -20,7 +20,7 @@ import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager import ani.dantotsu.R import ani.dantotsu.currContext -import ani.dantotsu.databinding.FragmentAnimeWatchBinding +import ani.dantotsu.databinding.FragmentMediaSourceBinding import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.novel.NovelDownloaderService @@ -47,7 +47,7 @@ class NovelReadFragment : Fragment(), DownloadTriggerCallback, DownloadedCheckCallback { - private var _binding: FragmentAnimeWatchBinding? = null + private var _binding: FragmentMediaSourceBinding? = null private val binding get() = _binding!! private val model: MediaDetailsViewModel by activityViewModels() @@ -214,11 +214,11 @@ class NovelReadFragment : Fragment(), ContextCompat.RECEIVER_EXPORTED ) - binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight) + binding.mediaSourceRecycler.updatePadding(bottom = binding.mediaSourceRecycler.paddingBottom + navBarHeight) - binding.animeSourceRecycler.layoutManager = LinearLayoutManager(requireContext()) + binding.mediaSourceRecycler.layoutManager = LinearLayoutManager(requireContext()) model.scrolledToTop.observe(viewLifecycleOwner) { - if (it) binding.animeSourceRecycler.scrollToPosition(0) + if (it) binding.mediaSourceRecycler.scrollToPosition(0) } continueEp = model.continueMedia ?: false @@ -237,7 +237,7 @@ class NovelReadFragment : Fragment(), this, this ) // probably a better way to do this but it works - binding.animeSourceRecycler.adapter = + binding.mediaSourceRecycler.adapter = ConcatAdapter(headerAdapter, novelResponseAdapter) loaded = true Handler(Looper.getMainLooper()).postDelayed({ @@ -290,7 +290,7 @@ class NovelReadFragment : Fragment(), container: ViewGroup?, savedInstanceState: Bundle? ): View? { - _binding = FragmentAnimeWatchBinding.inflate(inflater, container, false) + _binding = FragmentMediaSourceBinding.inflate(inflater, container, false) return _binding?.root } @@ -304,12 +304,12 @@ class NovelReadFragment : Fragment(), override fun onResume() { super.onResume() binding.mediaInfoProgressBar.visibility = progress - binding.animeSourceRecycler.layoutManager?.onRestoreInstanceState(state) + binding.mediaSourceRecycler.layoutManager?.onRestoreInstanceState(state) } override fun onPause() { super.onPause() - state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState() + state = binding.mediaSourceRecycler.layoutManager?.onSaveInstanceState() } companion object { diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt index cade2abd..7bc304cd 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt @@ -39,7 +39,7 @@ class NovelResponseAdapter( val binding = holder.binding val novel = list[position] setAnimation(fragment.requireContext(), holder.binding.root) - binding.itemEpisodeImage.loadImage(novel.coverUrl, 400, 0) + binding.itemMediaImage.loadImage(novel.coverUrl, 400, 0) val color =fragment.requireContext().getThemeColor(com.google.android.material.R.attr.colorOnBackground) binding.itemEpisodeTitle.text = novel.name diff --git a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt index 119f4d2f..f343d70b 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt @@ -39,7 +39,7 @@ object AnimeSources : WatchSources() { } fun performReorderAnimeSources() { - //remove the downloaded source from the list to avoid duplicates + // Remove the downloaded source from the list to avoid duplicates list = list.filter { it.name != "Downloaded" } list = sortPinnedAnimeSources(list, pinnedAnimeSources) + Lazier( { OfflineAnimeParser() }, diff --git a/app/src/main/res/layout/dialog_layout.xml b/app/src/main/res/layout/dialog_layout.xml index 4eae05d3..eab3f479 100644 --- a/app/src/main/res/layout/dialog_layout.xml +++ b/app/src/main/res/layout/dialog_layout.xml @@ -54,7 +54,7 @@ app:cardElevation="0dp"> @@ -235,7 +235,7 @@ app:cardElevation="0dp"> + tools:listitem="@layout/item_media_source" /> diff --git a/app/src/main/res/layout/item_episode_grid.xml b/app/src/main/res/layout/item_episode_grid.xml index d793687d..12dd8636 100644 --- a/app/src/main/res/layout/item_episode_grid.xml +++ b/app/src/main/res/layout/item_episode_grid.xml @@ -13,7 +13,7 @@ app:cardElevation="4dp"> diff --git a/app/src/main/res/layout/item_episode_list.xml b/app/src/main/res/layout/item_episode_list.xml index 3e33c882..a4214537 100644 --- a/app/src/main/res/layout/item_episode_list.xml +++ b/app/src/main/res/layout/item_episode_list.xml @@ -47,7 +47,7 @@ android:indeterminate="true" /> @@ -318,7 +318,7 @@ Chapter Wrong Title? - Couldn\'t find anything X( \n - Try another source. + Hmm, nothing came up from this source.\n + Let\'s look elsewhere :) + + + Your downloads are feeling a bit lonely…\n + Nothing here yet :( %1$s is not supported! Select Server From 0052eba828dc42a67c4be82e402ea4a14c05c48e Mon Sep 17 00:00:00 2001 From: aayush262 Date: Sun, 16 Jun 2024 11:01:32 +0530 Subject: [PATCH 09/26] feat: remove predefined repo links --- .../java/ani/dantotsu/home/HomeFragment.kt | 5 ++--- .../settings/SettingsCommonActivity.kt | 18 ++++++++---------- .../extension/api/ExtensionGithubApi.kt | 8 -------- .../main/res/layout/activity_notification.xml | 6 ++++-- 4 files changed, 14 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt index a66506d9..43bf8bea 100644 --- a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt @@ -482,13 +482,12 @@ class HomeFragment : Fragment() { CoroutineScope(Dispatchers.IO).launch { model.setListImages() } - CoroutineScope(Dispatchers.IO).launch { - model.initUserStatus() - } + var empty = true val homeLayoutShow: List = PrefManager.getVal(PrefName.HomeLayout) model.initHomePage() + model.initUserStatus() (array.indices).forEach { i -> if (homeLayoutShow.elementAt(i)) { empty = false diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt index a9c839e7..dea8666c 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt @@ -305,10 +305,10 @@ class SettingsCommonActivity : AppCompatActivity() { desc = getString(R.string.change_download_location_desc), icon = R.drawable.ic_round_source_24, onClick = { - val dialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.change_download_location) - .setMessage(R.string.download_location_msg) - .setPositiveButton(R.string.ok) { dialog, _ -> + context.customAlertDialog().apply{ + setTitle(R.string.change_download_location) + setMessage(R.string.download_location_msg) + setPosButton(R.string.ok){ val oldUri = PrefManager.getVal(PrefName.DownloadsDir) launcher.registerForCallback { success -> if (success) { @@ -331,12 +331,10 @@ class SettingsCommonActivity : AppCompatActivity() { } } launcher.launch() - dialog.dismiss() - }.setNeutralButton(R.string.cancel) { dialog, _ -> - dialog.dismiss() - }.create() - dialog.window?.setDimAmount(0.8f) - dialog.show() + } + setNegButton(R.string.cancel) + show() + } } ), Settings( diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt index c67eefea..03b6d137 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt @@ -64,10 +64,6 @@ internal class ExtensionGithubApi { val repos = PrefManager.getVal>(PrefName.AnimeExtensionRepos).toMutableList() - if (repos.isEmpty()) { - repos.add("https://raw.githubusercontent.com/aniyomiorg/aniyomi-extensions/repo") - PrefManager.setVal(PrefName.AnimeExtensionRepos, repos.toSet()) - } repos.forEach { try { @@ -157,10 +153,6 @@ internal class ExtensionGithubApi { val repos = PrefManager.getVal>(PrefName.MangaExtensionRepos).toMutableList() - if (repos.isEmpty()) { - repos.add("https://raw.githubusercontent.com/keiyoushi/extensions/main") - PrefManager.setVal(PrefName.MangaExtensionRepos, repos.toSet()) - } repos.forEach { try { diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml index bfdda606..f4d30071 100644 --- a/app/src/main/res/layout/activity_notification.xml +++ b/app/src/main/res/layout/activity_notification.xml @@ -42,6 +42,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" + android:layout_marginBottom="67dp" app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + + \ No newline at end of file From 563a96cf9855f90065e51b2d923ed5ef08593e34 Mon Sep 17 00:00:00 2001 From: Sadwhy <99601717+Sadwhy@users.noreply.github.com> Date: Thu, 20 Jun 2024 21:31:51 +0600 Subject: [PATCH 10/26] [skip ci] Updated faq + Force LTR layout (#435) * Updated faq + Force LTR layout * Ibo merge issue --- .../java/ani/dantotsu/settings/FAQActivity.kt | 10 ++++++++++ app/src/main/res/values/strings.xml | 19 +++++++++---------- app/src/main/res/values/themes.xml | 1 + 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt b/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt index 49656f55..98a248e2 100644 --- a/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt @@ -50,6 +50,11 @@ class FAQActivity : AppCompatActivity() { currContext()?.getString(R.string.question_5) ?: "", currContext()?.getString(R.string.answer_5) ?: "" ), + Triple( + R.drawable.ic_anilist, + currContext()?.getString(R.string.question_18) ?: "", + currContext()?.getString(R.string.answer_18) ?: "" + ), Triple( R.drawable.ic_anilist, currContext()?.getString(R.string.question_6) ?: "", @@ -60,6 +65,11 @@ class FAQActivity : AppCompatActivity() { currContext()?.getString(R.string.question_7) ?: "", currContext()?.getString(R.string.answer_7) ?: "" ), + Triple( + R.drawable.ic_round_magnet_24, + currContext()?.getString(R.string.question_19) ?: "", + currContext()?.getString(R.string.answer_19) ?: "" + ), Triple( R.drawable.ic_round_lock_open_24, currContext()?.getString(R.string.question_9) ?: "", diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 383298d2..c1a78c8d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -156,12 +156,12 @@ Chapter Wrong Title? - Hmm, nothing came up from this source.\n - Let\'s look elsewhere :) + Nothing came up from this source.\n + Let\'s look elsewhere. Your downloads are feeling a bit lonely…\n - Nothing here yet :( + Try downloading something. %1$s is not supported! Select Server @@ -633,13 +633,7 @@ This is because it updates every 48 hours automatically (by Anilist). If you really need to update your stats, you can force update your stats after going to this [link](https://anilist.co/settings/lists). How to download Anime? - There are two methods of downloading currently. One is internal and the other is external. If you download internally, then it can be viewed within the app but only the app can open that episode, you cannot move it or share it. The other option is to use external downloading. It requires a download manager to download and a separate video player to watch. External downloads can be shared but you cannot view it within the Dantotsu app.\n\n•To download internally:\n\n1. Tap the download button.\n2. Pick the server and the video quality.\n3. Profit.\n\n•To download externally:\n\n 1. Download 1DM or ADM from Google Play Store. - \n2. Enter the app, give storage access and set your preferences (downloading speed, downloading path etc(optional)) - \n3. Now go to \`Dantotsu > Settings > Common > Download Managers\` and choose the download manager you just set up. - \n4. Go to your desired episode and press on the download icon of any server. There may be a popup to set your preferences again, just press on "Download" and it will be saved in the directed path. - - \n\nNote: Direct downloads are also possible without a manager but it\'s not recommended. -\n\nNerd Note: Internally downloaded episodes are stored in \`Android/data/ani.dantotsu.*/files/Anime_Downloads\`\nYou cannot play those files as they are in \`.exo\` format, split into hundreds of pieces and are encrypted. + There are two methods of downloading. Internal and external. If you download internally, then it can be viewed within the app and tracking will work normally for it. The other option is to use external downloader. It requires a download manager to download and a separate video player to watch. External downloads cannot be viewed within the Dantotsu app.\n\n•To download internally:\n\n1. Tap the download button.\n2. For the first time, it will ask you to set a download location. All your downloads will be stored there.\n3. Pick the server and the video quality.\n4. Profit.\n\n•To download externally:\n\n 1. Download 1DM or ADM from Google Play Store.\n2. Enter the app, give storage access and set your preferences (downloading speed, downloading path etc(optional))\n3. Now go to \`Dantotsu > Settings > Common > Download Managers\` and choose the download manager you just set up.\n4. Go to your desired episode and press on the download icon of any server. There may be a popup to set your preferences again, just press on "Download" and it will be saved in the directed path.\n\nNote: External downloads are also possible without a manager but it\'s not recommended.\n\nNerd Note: Internally downloaded episodes are stored in \`{your set location}/Dantotsu/Anime/*`\nYou can change your download location in settings but your previous downloaded episodes will not show up in the app anymore. How to enable NSFW content? You can enable NSFW content by enabling 18+ contents from this [link](https://anilist.co/settings/media). You also have to enable NSFW extensions in \`Settings > Extensions > NSFW extensions\` @@ -668,6 +662,11 @@ Some useful tips and tricks The following presents some tips and tricks you may or may not know about - \n \n \n - By hold pressing the Dantotsu logo in settings, you can check if there are any new updates manually. \n \n - Hold pressing an error message/tag/synonym or title will copy it. \n \n - You can open an episode with other apps by hold pressing any server for that episode. This helps in streaming the episode using other video players or download the episode using download managers. \n \n - You can set up custom lists using this [link](https://anilist.co/settings/lists). (you need to be signed in) \n \n - If your episode/chapter is not being progressed automatically after you finish watching/reading it, then hold press the status bar(planning/repeating/watching button) of that anime/manga. The next time you start a chapter/finish an episode, you will stumble upon a popup. Press yes there. + I can\'t login to Anilist. + The reason this happens is probably because you\'re not using the default browser.\n\n>You have to set Chrome as your default browser to login to Anilist.\n\n>It takes a few seconds for the login button to display changes.\n\nIf it doesn\'t work then you could possibly be IP banned from Anilist or your ISP banned Anilist themselves. We believe that this is highly unlikely so open [Anilist](https://anilist.co) in your browser to see if that\`s the case. + + What is torrent? How do I use it? + Torrent or formally known as BitTorrent is an internet communication protocol for peer\-to\-peer file sharing. A torrent network doesn\'t use a centralised server to host files. It shares files in a decentralised manner. A torrent network has two types of peers. Seeders & Leachers.\n\n• Seeders : These are peers who completed downloading and has the full file. And now they are sharing this file to other peers which is called seeding. A torrent cannot work without at least one seeder. The more seeder a torrent has, the better.\n\n• Leachers : These are peers who are currently downloading the file. If they currently have 40% downloaded then they will share the 40% to any other peers who requests it.\n\nUnlike a centralised server, torrents have no bandwidth limit. You can get your files from hundreds of seeders at the highest possible speed your internet can offer. But many ISP throttle torrent to slow them down because it\'s demanding on their infrastructure. Use a VPN to circumvent this.\n\n• How to use torrents :\n\n1. Install the Torrent Add-on from \`Dantotsu > Settings > Add-ons > Torrent Add-on.\` \n2. Get a source that provides torrents.\n3. USE A VPN. Using VPN while torrenting only has upsides. Without a VPN your IP address will be exposed, your ISP will throttle your network and you could possibly be fined if you live in a country first world country. DO NOT USE IT WITHOUT A VPN IF YOU DON\'T KNOW WHAT YOU\'RE DOING.\n4. Now use that source to start torrenting. Subscribed! Receiving notifications, when new episodes are released on %1$s. diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 72ea4803..46c85c9e 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -9,6 +9,7 @@ false false true + ltr true @font/poppins @style/ShapeAppearanceOverlay.Demo From 83e7e4591d2503687d1e360625f9382b293c4250 Mon Sep 17 00:00:00 2001 From: ibo <41344259+sneazy-ibo@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:47:57 +0200 Subject: [PATCH 11/26] feat(discord): send embeds through webhook for pretesters --- .github/workflows/beta.yml | 122 +++++++++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 11 deletions(-) diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 68444cd8..a4488b89 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -26,7 +26,6 @@ jobs: workflow: beta.yml name: last-sha path: . - continue-on-error: true - name: Get Commits Since Last Run @@ -40,6 +39,8 @@ jobs: echo "Commits since $LAST_SHA:" # Accumulate commit logs in a shell variable COMMIT_LOGS=$(git log $LAST_SHA..HEAD --pretty=format:"● %s ~%an") + # Replace commit messages with pull request links + COMMIT_LOGS=$(echo "$COMMIT_LOGS" | sed -E 's/#([0-9]+)/[#\1](https:\/\/github.com\/rebelonion\/Dantotsu\/pull\/\1)/g') # URL-encode the newline characters for GitHub Actions COMMIT_LOGS="${COMMIT_LOGS//'%'/'%25'}" COMMIT_LOGS="${COMMIT_LOGS//$'\n'/'%0A'}" @@ -74,13 +75,13 @@ jobs: - name: Decode Keystore File run: echo "${{ secrets.KEYSTORE_FILE }}" | base64 -d > $GITHUB_WORKSPACE/key.keystore - + - name: List files in the directory run: ls -l - + - name: Make gradlew executable run: chmod +x ./gradlew - + - name: Build with Gradle run: ./gradlew assembleGoogleAlpha -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }} @@ -96,22 +97,121 @@ jobs: if: ${{ github.repository == 'rebelonion/Dantotsu' }} shell: bash run: | - #Discord + # Prepare Discord embed + fetch_user_details() { + local login=$1 + user_details=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/users/$login") + name=$(echo "$user_details" | jq -r '.name // .login') + avatar_url=$(echo "$user_details" | jq -r '.avatar_url') + echo "$name|$login|$avatar_url" + } + # Additional information for the goats + declare -A additional_info + additional_info["ibo"]=" Discord: <@951737931159187457>\n AniList: [takarealist112]()" + additional_info["aayush262"]=" Discord: <@918825160654598224>\n AniList: [aayush262]()" + additional_info["rebelonion"]=" Discord: <@714249925248024617>\n AniList: [rebelonion]()\n PornHub: [rebelonion]()" + # Extract contributor names from commit log and make unique list + committers=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g' | grep -oP '(?<=~)[^%]*') + committers=$(echo "$committers" | sort | uniq) + # Fetch contributors from GitHub + contributors=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/rebelonion/Dantotsu/contributors") + # Initialize needed variables + developers="" + committers_count=0 + thumbnail_url="https://i.imgur.com/5o3Y9Jb.gif" + # Process contributors and filter by committers + while read -r login commits; do + user_info=$(fetch_user_details "$login") + name=$(echo "$user_info" | cut -d'|' -f1) + login=$(echo "$user_info" | cut -d'|' -f2) + avatar_url=$(echo "$user_info" | cut -d'|' -f3) + if echo "$committers" | grep -qw "$name"; then + extra_info="${additional_info[$name]}" + if [ -n "$extra_info" ]; then + extra_info=$(echo -e "$extra_info" | sed 's/^/- /') + fi + developers="${developers}◗ **${name}** + ${extra_info} + - Github: [${login}](https://github.com/${login}) + - Commits: ${commits} + " + committers_count=$((committers_count + 1)) + if [ $committers_count -eq 1 ]; then + thumbnail_url="$avatar_url" + else + thumbnail_url="https://i.imgur.com/5o3Y9Jb.gif" + fi + fi + done < <(echo "$contributors" | jq -r '.[] | "\(.login) \(.contributions)"') + + + # Remove trailing newline + developers=$(echo "$developers" | sed '$ s/$//') commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g; s/^/\n/') - # Truncate commit messages if they are too long - max_length=1900 # Adjust this value as needed + + # Truncate field values + max_length=1000 + if [ ${#developers} -gt $max_length ]; then + developers="${developers:0:$max_length}... (truncated)" + fi if [ ${#commit_messages} -gt $max_length ]; then commit_messages="${commit_messages:0:$max_length}... (truncated)" fi - contentbody=$( jq -nc --arg msg "Alpha-Build: <@&1225347048321191996> **$VERSION**:" --arg commits "$commit_messages" '{"content": ($msg + "\n" + $commits)}' ) - curl -F "payload_json=${contentbody}" -F "dantotsu_debug=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" ${{ secrets.DISCORD_WEBHOOK }} + + # Construct Discord payload + discord_data=$(jq -nc \ + --arg field_value "$commit_messages" \ + --arg author_value "$developers" \ + --arg footer_text "Version $VERSION" \ + --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \ + --arg thumbnail_url "$thumbnail_url" \ + '{ + "content": "@here", + "embeds": [ + { + "title": "New Alpha-Build dropped", + "color": 15532323, + "fields": [ + { + "name": "Commits:", + "value": $field_value, + "inline": true + }, + { + "name": "Developers:", + "value": $author_value, + "inline": false + } + ], + "footer": { + "text": $footer_text + }, + "timestamp": $timestamp, + "thumbnail": { + "url": $thumbnail_url + } + } + ], + "attachments": [] + }') + # Send Discord message + curl -H "Content-Type: application/json" \ + -d "$discord_data" \ + ${{ secrets.DISCORD_WEBHOOK }} - #Telegram + # Upload APK to Discord + curl -F "payload_json=${contentbody}" \ + -F "dantotsu_debug=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" \ + ${{ secrets.DISCORD_WEBHOOK }} + + # Send Telegram message and upload APK curl -F "chat_id=${{ secrets.TELEGRAM_CHANNEL_ID }}" \ -F "document=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" \ -F "caption=Alpha-Build: ${VERSION}: ${commit_messages}" \ https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument - + env: COMMIT_LOG: ${{ env.COMMIT_LOG }} VERSION: ${{ env.VERSION }} From 43fc9c17f5981e5f4afb6199eae13a587ab8bbf9 Mon Sep 17 00:00:00 2001 From: aayush262 <99584765+aayush2622@users.noreply.github.com> Date: Fri, 21 Jun 2024 23:43:04 +0530 Subject: [PATCH 12/26] feat(discord): no more here ping --- .github/workflows/beta.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index a4488b89..32fbfa9e 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -168,7 +168,7 @@ jobs: --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \ --arg thumbnail_url "$thumbnail_url" \ '{ - "content": "@here", + "content": "@&1225347048321191996", "embeds": [ { "title": "New Alpha-Build dropped", From ed24e64b7825fd4249fd8d74b04a789fb8afdae3 Mon Sep 17 00:00:00 2001 From: aayush262 <99584765+aayush2622@users.noreply.github.com> Date: Fri, 21 Jun 2024 23:51:24 +0530 Subject: [PATCH 13/26] feat(discord): no more here ping pt2 --- .github/workflows/beta.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 32fbfa9e..fe94f80c 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -168,7 +168,7 @@ jobs: --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \ --arg thumbnail_url "$thumbnail_url" \ '{ - "content": "@&1225347048321191996", + "content": "<@&1225347048321191996>", "embeds": [ { "title": "New Alpha-Build dropped", From 2f06ac60715febb840b763f7d4f7fb1462082a7e Mon Sep 17 00:00:00 2001 From: ibo <41344259+sneazy-ibo@users.noreply.github.com> Date: Fri, 21 Jun 2024 20:29:38 +0200 Subject: [PATCH 14/26] [skip ci] feat: showOnlyLibrary button in Calendar --- .../ani/dantotsu/media/CalendarActivity.kt | 17 ++++-- .../dantotsu/media/OtherDetailsViewModel.kt | 57 +++++++++++++------ .../ani/dantotsu/media/user/ListActivity.kt | 1 + .../drawable/ic_round_library_books_24.xml | 11 ++++ app/src/main/res/layout/activity_list.xml | 9 +++ app/src/main/res/values/strings.xml | 1 + 6 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 app/src/main/res/drawable/ic_round_library_books_24.xml diff --git a/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt b/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt index 66cec7e4..230f3a4e 100644 --- a/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt @@ -30,6 +30,7 @@ class CalendarActivity : AppCompatActivity() { private lateinit var binding: ActivityListBinding private val scope = lifecycleScope private var selectedTabIdx = 1 + private var showOnlyLibrary = false private val model: OtherDetailsViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { @@ -38,8 +39,6 @@ class CalendarActivity : AppCompatActivity() { ThemeManager(this).applyTheme() binding = ActivityListBinding.inflate(layoutInflater) - - val primaryColor = getThemeColor(com.google.android.material.R.attr.colorSurface) val primaryTextColor = getThemeColor(com.google.android.material.R.attr.colorPrimary) val secondaryTextColor = getThemeColor(com.google.android.material.R.attr.colorOutline) @@ -79,6 +78,17 @@ class CalendarActivity : AppCompatActivity() { override fun onTabReselected(tab: TabLayout.Tab?) {} }) + binding.listed.setOnClickListener { + showOnlyLibrary = !showOnlyLibrary + binding.listed.setImageResource( + if (showOnlyLibrary) R.drawable.ic_round_collections_bookmark_24 + else R.drawable.ic_round_library_books_24 + ) + scope.launch { + model.loadCalendar(showOnlyLibrary) + } + } + model.getCalendar().observe(this) { if (it != null) { binding.listProgressBar.visibility = View.GONE @@ -97,11 +107,10 @@ class CalendarActivity : AppCompatActivity() { live.observe(this) { if (it) { scope.launch { - withContext(Dispatchers.IO) { model.loadCalendar() } + withContext(Dispatchers.IO) { model.loadCalendar(showOnlyLibrary) } live.postValue(false) } } } - } } diff --git a/app/src/main/java/ani/dantotsu/media/OtherDetailsViewModel.kt b/app/src/main/java/ani/dantotsu/media/OtherDetailsViewModel.kt index 0be0fc22..a086a765 100644 --- a/app/src/main/java/ani/dantotsu/media/OtherDetailsViewModel.kt +++ b/app/src/main/java/ani/dantotsu/media/OtherDetailsViewModel.kt @@ -26,25 +26,50 @@ class OtherDetailsViewModel : ViewModel() { if (author.value == null) author.postValue(Anilist.query.getAuthorDetails(m)) } + private var cachedAllCalendarData: Map>? = null + private var cachedLibraryCalendarData: Map>? = null private val calendar: MutableLiveData>> = MutableLiveData(null) fun getCalendar(): LiveData>> = calendar - suspend fun loadCalendar() { - val curr = System.currentTimeMillis() / 1000 - val res = Anilist.query.recentlyUpdated(curr - 86400, curr + (86400 * 6)) - val df = DateFormat.getDateInstance(DateFormat.FULL) - val map = mutableMapOf>() - val idMap = mutableMapOf>() - res?.forEach { - val v = it.relation?.split(",")?.map { i -> i.toLong() }!! - val dateInfo = df.format(Date(v[1] * 1000)) - val list = map.getOrPut(dateInfo) { mutableListOf() } - val idList = idMap.getOrPut(dateInfo) { mutableListOf() } - it.relation = "Episode ${v[0]}" - if (!idList.contains(it.id)) { - idList.add(it.id) - list.add(it) + suspend fun loadCalendar(showOnlyLibrary: Boolean = false) { + if (cachedAllCalendarData == null || cachedLibraryCalendarData == null) { + val curr = System.currentTimeMillis() / 1000 + val res = Anilist.query.recentlyUpdated(curr - 86400, curr + (86400 * 6)) + val df = DateFormat.getDateInstance(DateFormat.FULL) + val allMap = mutableMapOf>() + val libraryMap = mutableMapOf>() + val idMap = mutableMapOf>() + + val userId = Anilist.userid ?: 0 + val userLibrary = Anilist.query.getMediaLists(true, userId) + val libraryMediaIds = userLibrary.flatMap { it.value }.map { it.id } + + res.forEach { + val v = it.relation?.split(",")?.map { i -> i.toLong() }!! + val dateInfo = df.format(Date(v[1] * 1000)) + val list = allMap.getOrPut(dateInfo) { mutableListOf() } + val libraryList = if (libraryMediaIds.contains(it.id)) { + libraryMap.getOrPut(dateInfo) { mutableListOf() } + } else { + null + } + val idList = idMap.getOrPut(dateInfo) { mutableListOf() } + it.relation = "Episode ${v[0]}" + if (!idList.contains(it.id)) { + idList.add(it.id) + list.add(it) + libraryList?.add(it) + } } + + cachedAllCalendarData = allMap + cachedLibraryCalendarData = libraryMap } - calendar.postValue(map) + + val cacheToUse: Map> = if (showOnlyLibrary) { + cachedLibraryCalendarData ?: emptyMap() + } else { + cachedAllCalendarData ?: emptyMap() + } + calendar.postValue(cacheToUse) } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt b/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt index 994a0c78..08109594 100644 --- a/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt @@ -47,6 +47,7 @@ class ListActivity : AppCompatActivity() { window.statusBarColor = primaryColor window.navigationBarColor = primaryColor + binding.listed.visibility = View.GONE binding.listTabLayout.setBackgroundColor(primaryColor) binding.listAppBar.setBackgroundColor(primaryColor) binding.listTitle.setTextColor(primaryTextColor) diff --git a/app/src/main/res/drawable/ic_round_library_books_24.xml b/app/src/main/res/drawable/ic_round_library_books_24.xml new file mode 100644 index 00000000..ee9f6511 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_library_books_24.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml index 63aa5d81..d70668fc 100644 --- a/app/src/main/res/layout/activity_list.xml +++ b/app/src/main/res/layout/activity_list.xml @@ -55,6 +55,15 @@ app:srcCompat="@drawable/ic_round_search_24" app:tint="?attr/colorOnBackground" /> + + Installed Manga Color Picker Random Selection + Listed in Library Incognito Mode EXAMPLE Configure From a8ccf8d246ab844dbfa45139a1379a84a3cb54d0 Mon Sep 17 00:00:00 2001 From: ibo <41344259+sneazy-ibo@users.noreply.github.com> Date: Fri, 21 Jun 2024 20:30:34 +0200 Subject: [PATCH 15/26] [skip ci] fix: story buttons hitbox --- app/src/main/res/layout/fragment_status.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/fragment_status.xml b/app/src/main/res/layout/fragment_status.xml index 2aaf4850..72f488e0 100644 --- a/app/src/main/res/layout/fragment_status.xml +++ b/app/src/main/res/layout/fragment_status.xml @@ -202,7 +202,7 @@ android:id="@+id/activityRepliesContainer" android:layout_width="wrap_content" android:layout_height="wrap_content" - + android:padding="12dp" android:orientation="vertical" tools:ignore="UseCompoundDrawables"> @@ -230,7 +230,7 @@ android:id="@+id/activityLikeContainer" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="12dp" + android:padding="12dp" android:orientation="vertical" tools:ignore="UseCompoundDrawables"> From feb765448b8c5cbae343a5eb52f4287a9b84f102 Mon Sep 17 00:00:00 2001 From: Sadwhy <99601717+Sadwhy@users.noreply.github.com> Date: Sat, 22 Jun 2024 10:13:34 +0600 Subject: [PATCH 16/26] [skip ci] Fix: Removed extension lower limit --- .../tachiyomi/extension/api/ExtensionGithubApi.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt index 03b6d137..d42ece54 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt @@ -91,9 +91,10 @@ internal class ExtensionGithubApi { // Sanity check - a small number of extensions probably means something broke // with the repo generator - if (repoExtensions.size < 10) { - throw Exception() - } + //if (repoExtensions.size < 10) { + // throw Exception() + //} + // No official repo now so this won't be needed anymore. User-made repo can have less than 10 extensions extensions.addAll(repoExtensions) } catch (e: Throwable) { @@ -180,9 +181,10 @@ internal class ExtensionGithubApi { // Sanity check - a small number of extensions probably means something broke // with the repo generator - if (repoExtensions.size < 10) { - throw Exception() - } + //if (repoExtensions.size < 10) { + // throw Exception() + //} + // No official repo now so this won't be needed anymore. User made repo can have less than 10 extensions. extensions.addAll(repoExtensions) } catch (e: Throwable) { From 37ba9341cc70dc680c59f046b4693d8cd0a31ac2 Mon Sep 17 00:00:00 2001 From: ibo <41344259+sneazy-ibo@users.noreply.github.com> Date: Sat, 22 Jun 2024 06:16:19 +0200 Subject: [PATCH 17/26] feat(discord): added hash links + fix trailing --- .github/workflows/beta.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index fe94f80c..1cc4f6bd 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -38,7 +38,7 @@ jobs: fi echo "Commits since $LAST_SHA:" # Accumulate commit logs in a shell variable - COMMIT_LOGS=$(git log $LAST_SHA..HEAD --pretty=format:"● %s ~%an") + COMMIT_LOGS=$(git log $LAST_SHA..HEAD --pretty=format:"[[%h](https://github.com/${{ github.repository }}/commit/%H)] %s ~%an") # Replace commit messages with pull request links COMMIT_LOGS=$(echo "$COMMIT_LOGS" | sed -E 's/#([0-9]+)/[#\1](https:\/\/github.com\/rebelonion\/Dantotsu\/pull\/\1)/g') # URL-encode the newline characters for GitHub Actions @@ -108,9 +108,9 @@ jobs: } # Additional information for the goats declare -A additional_info - additional_info["ibo"]=" Discord: <@951737931159187457>\n AniList: [takarealist112]()" - additional_info["aayush262"]=" Discord: <@918825160654598224>\n AniList: [aayush262]()" - additional_info["rebelonion"]=" Discord: <@714249925248024617>\n AniList: [rebelonion]()\n PornHub: [rebelonion]()" + additional_info["ibo"]=" Discord: <@951737931159187457>\n AniList: [takarealist112]()\n" + additional_info["aayush262"]=" Discord: <@918825160654598224>\n AniList: [aayush262]()\n" + additional_info["rebelonion"]=" Discord: <@714249925248024617>\n AniList: [rebelonion]()\n PornHub: [rebelonion]()\n" # Extract contributor names from commit log and make unique list committers=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g' | grep -oP '(?<=~)[^%]*') committers=$(echo "$committers" | sort | uniq) @@ -133,8 +133,7 @@ jobs: extra_info=$(echo -e "$extra_info" | sed 's/^/- /') fi developers="${developers}◗ **${name}** - ${extra_info} - - Github: [${login}](https://github.com/${login}) + ${extra_info} Github: [${login}](https://github.com/${login}) - Commits: ${commits} " committers_count=$((committers_count + 1)) From 665b558b1f52c9290dac70bc692d53185f319f45 Mon Sep 17 00:00:00 2001 From: ibo <41344259+sneazy-ibo@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:59:32 +0200 Subject: [PATCH 18/26] feat(discord,telegram): dev info and thumbnail ranking logic + hyperlink on tg --- .github/workflows/beta.yml | 169 +++++++++++++++++++++++++++---------- 1 file changed, 124 insertions(+), 45 deletions(-) diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 1cc4f6bd..8a20867d 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -12,6 +12,7 @@ jobs: runs-on: ubuntu-latest env: CI: true + SKIP_BUILD: false steps: - name: Checkout repo @@ -19,7 +20,6 @@ jobs: with: fetch-depth: 0 - - name: Download last SHA artifact uses: dawidd6/action-download-artifact@v3 with: @@ -38,7 +38,7 @@ jobs: fi echo "Commits since $LAST_SHA:" # Accumulate commit logs in a shell variable - COMMIT_LOGS=$(git log $LAST_SHA..HEAD --pretty=format:"[[%h](https://github.com/${{ github.repository }}/commit/%H)] %s ~%an") + COMMIT_LOGS=$(git log $LAST_SHA..HEAD --pretty=format:"● %s ~%an [֍](https://github.com/${{ github.repository }}/commit/%H)") # Replace commit messages with pull request links COMMIT_LOGS=$(echo "$COMMIT_LOGS" | sed -E 's/#([0-9]+)/[#\1](https:\/\/github.com\/rebelonion\/Dantotsu\/pull\/\1)/g') # URL-encode the newline characters for GitHub Actions @@ -66,26 +66,40 @@ jobs: echo "Version $VERSION" echo "VERSION=$VERSION" >> $GITHUB_ENV + - name: List files in the directory + run: ls -l + - name: Setup JDK 17 + if: ${{ env.SKIP_BUILD != 'true' }} uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: 17 cache: gradle - + - name: Decode Keystore File + if: ${{ github.repository == 'rebelonion/Dantotsu' }} run: echo "${{ secrets.KEYSTORE_FILE }}" | base64 -d > $GITHUB_WORKSPACE/key.keystore - - name: List files in the directory - run: ls -l - - name: Make gradlew executable + if: ${{ env.SKIP_BUILD != 'true' }} run: chmod +x ./gradlew - name: Build with Gradle - run: ./gradlew assembleGoogleAlpha -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }} - + if: ${{ env.SKIP_BUILD != 'true' }} + run: | + if [ "${{ github.repository }}" == "rebelonion/Dantotsu" ]; then + ./gradlew assembleGoogleAlpha \ + -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore \ + -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} \ + -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} \ + -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}; + else + ./gradlew assembleGoogleAlpha; + fi + - name: Upload a Build Artifact + if: ${{ env.SKIP_BUILD != 'true' }} uses: actions/upload-artifact@v4 with: name: Dantotsu @@ -94,7 +108,6 @@ jobs: path: "app/build/outputs/apk/google/alpha/app-google-alpha.apk" - name: Upload APK to Discord and Telegram - if: ${{ github.repository == 'rebelonion/Dantotsu' }} shell: bash run: | # Prepare Discord embed @@ -103,55 +116,96 @@ jobs: user_details=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ "https://api.github.com/users/$login") name=$(echo "$user_details" | jq -r '.name // .login') + login=$(echo "$user_details" | jq -r '.login') avatar_url=$(echo "$user_details" | jq -r '.avatar_url') echo "$name|$login|$avatar_url" } # Additional information for the goats declare -A additional_info - additional_info["ibo"]=" Discord: <@951737931159187457>\n AniList: [takarealist112]()\n" - additional_info["aayush262"]=" Discord: <@918825160654598224>\n AniList: [aayush262]()\n" - additional_info["rebelonion"]=" Discord: <@714249925248024617>\n AniList: [rebelonion]()\n PornHub: [rebelonion]()\n" - # Extract contributor names from commit log and make unique list - committers=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g' | grep -oP '(?<=~)[^%]*') - committers=$(echo "$committers" | sort | uniq) + additional_info["ibo"]="\n Discord: <@951737931159187457>\n AniList: [takarealist112]()" + additional_info["aayush262"]="\n Discord: <@918825160654598224>\n AniList: [aayush262]()" + additional_info["rebelonion"]="\n Discord: <@714249925248024617>\n AniList: [rebelonion]()\n PornHub: [rebelonion]()" + + # Count recent commits and create an associative array + declare -A recent_commit_counts + while read -r count name; do + recent_commit_counts["$name"]=$count + done < <(echo "$COMMIT_LOG" | sed 's/%0A/\n/g' | grep -oP '(?<=~)[^[]*' | sort | uniq -c | sort -rn) # Fetch contributors from GitHub contributors=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "https://api.github.com/repos/rebelonion/Dantotsu/contributors") + "https://api.github.com/repos/${{ github.repository }}/contributors") + + # Create a sorted list of contributors based on recent commit counts + sorted_contributors=$(for login in $(echo "$contributors" | jq -r '.[].login'); do + user_info=$(fetch_user_details "$login") + name=$(echo "$user_info" | cut -d'|' -f1) + count=${recent_commit_counts["$name"]:-0} + echo "$count|$login" + done | sort -rn | cut -d'|' -f2) + # Initialize needed variables developers="" committers_count=0 - thumbnail_url="https://i.imgur.com/5o3Y9Jb.gif" - # Process contributors and filter by committers - while read -r login commits; do + max_commits=0 + top_contributor="" + top_contributor_count=0 + top_contributor_avatar="" + + # Process contributors in the new order + while read -r login; do user_info=$(fetch_user_details "$login") name=$(echo "$user_info" | cut -d'|' -f1) login=$(echo "$user_info" | cut -d'|' -f2) avatar_url=$(echo "$user_info" | cut -d'|' -f3) - if echo "$committers" | grep -qw "$name"; then + + # Only process if they have recent commits + commit_count=${recent_commit_counts["$name"]:-0} + if [ $commit_count -gt 0 ]; then + # Update top contributor information + if [ $commit_count -gt $max_commits ]; then + max_commits=$commit_count + top_contributor="$login" + top_contributor_count=1 + top_contributor_avatar="$avatar_url" + elif [ $commit_count -eq $max_commits ]; then + top_contributor_count=$((top_contributor_count + 1)) + fi + + # Get commit count for this contributor on the dev branch + branch_commit_count=$(git rev-list --count dev --author="$login") + extra_info="${additional_info[$name]}" if [ -n "$extra_info" ]; then - extra_info=$(echo -e "$extra_info" | sed 's/^/- /') + extra_info=$(echo "$extra_info" | sed 's/\\n/\n- /g') fi - developers="${developers}◗ **${name}** - ${extra_info} Github: [${login}](https://github.com/${login}) - - Commits: ${commits} - " - committers_count=$((committers_count + 1)) - if [ $committers_count -eq 1 ]; then - thumbnail_url="$avatar_url" + + # Construct the developer entry + developer_entry="◗ **${name}** ${extra_info} + - Github: [${login}](https://github.com/${login}) + - Commits: ${branch_commit_count}" + + # Add the entry to developers, with a newline if it's not the first entry + if [ -n "$developers" ]; then + developers="${developers} + ${developer_entry}" else - thumbnail_url="https://i.imgur.com/5o3Y9Jb.gif" + developers="${developer_entry}" fi + + committers_count=$((committers_count + 1)) fi - done < <(echo "$contributors" | jq -r '.[] | "\(.login) \(.contributions)"') - - - # Remove trailing newline - developers=$(echo "$developers" | sed '$ s/$//') - commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g; s/^/\n/') - + done <<< "$sorted_contributors" + + # Set the thumbnail URL based on top contributor(s) + if [ $top_contributor_count -eq 1 ]; then + thumbnail_url="$top_contributor_avatar" + else + thumbnail_url="https://i.imgur.com/5o3Y9Jb.gif" + fi + # Truncate field values max_length=1000 + commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g; s/^/\n/') if [ ${#developers} -gt $max_length ]; then developers="${developers:0:$max_length}... (truncated)" fi @@ -195,22 +249,47 @@ jobs: ], "attachments": [] }') + # Send Discord message curl -H "Content-Type: application/json" \ -d "$discord_data" \ ${{ secrets.DISCORD_WEBHOOK }} + echo "You have only send an embed to discord due to SKIP_BUILD being set to true" # Upload APK to Discord - curl -F "payload_json=${contentbody}" \ - -F "dantotsu_debug=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" \ - ${{ secrets.DISCORD_WEBHOOK }} + if [ "$SKIP_BUILD" != "true" ]; then + curl -F "payload_json=${contentbody}" \ + -F "dantotsu_debug=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" \ + ${{ secrets.DISCORD_WEBHOOK }} + else + echo "Skipping APK upload to Discord due to SKIP_BUILD being set to true" + fi + + # Format commit messages for Telegram + telegram_commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g' | while read -r line; do + message=$(echo "$line" | sed -E 's/● (.*) ~(.*) \[֍\]\((.*)\)/● \1 ~\2 ֍<\/a>/') + message=$(echo "$message" | sed -E 's/\[#([0-9]+)\]\((https:\/\/github\.com\/[^)]+)\)/#\1<\/a>/g') + echo "$message" + done) + telegram_commit_messages="
${telegram_commit_messages}
" + + # Upload APK to Telegram + if [ "$SKIP_BUILD" != "true" ]; then + APK_PATH="app/build/outputs/apk/google/alpha/app-google-alpha.apk" + response=$(curl -sS -f -X POST \ + "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument" \ + -F "chat_id=${{ secrets.TELEGRAM_CHANNEL_ID }}" \ + -F "document=@$APK_PATH" \ + -F "caption=New Alpha-Build dropped 🔥 + + Commits: + ${telegram_commit_messages} + version: ${VERSION}" \ + -F "parse_mode=HTML") + else + echo "Skipping Telegram message and APK upload due to SKIP_BUILD being set to true" + fi - # Send Telegram message and upload APK - curl -F "chat_id=${{ secrets.TELEGRAM_CHANNEL_ID }}" \ - -F "document=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" \ - -F "caption=Alpha-Build: ${VERSION}: ${commit_messages}" \ - https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument - env: COMMIT_LOG: ${{ env.COMMIT_LOG }} VERSION: ${{ env.VERSION }} From 2180086573880e9c78d27334af4dd57578e68c2c Mon Sep 17 00:00:00 2001 From: ibo <41344259+sneazy-ibo@users.noreply.github.com> Date: Tue, 25 Jun 2024 06:29:10 +0200 Subject: [PATCH 19/26] feat(discord): dynamic embed color --- .github/workflows/beta.yml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 8a20867d..313a0be5 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -120,11 +120,21 @@ jobs: avatar_url=$(echo "$user_details" | jq -r '.avatar_url') echo "$name|$login|$avatar_url" } + # Additional information for the goats declare -A additional_info additional_info["ibo"]="\n Discord: <@951737931159187457>\n AniList: [takarealist112]()" additional_info["aayush262"]="\n Discord: <@918825160654598224>\n AniList: [aayush262]()" - additional_info["rebelonion"]="\n Discord: <@714249925248024617>\n AniList: [rebelonion]()\n PornHub: [rebelonion]()" + additional_info["rebelonion"]="\n Discord: <@714249925248024617>\n AniList: [rebelonion]()\n PornHub: [rebelonion]()" + + # Decimal color codes for contributors + declare -A contributor_colors + default_color="#ff25f9" + contributor_colors["ibo"]="#ff7500" + contributor_colors["aayush262"]="#5d689d" + contributor_colors["Sadwhy"]="#ff7e95" + contributor_colors["rebelonion"]="#d4e5ed" + hex_to_decimal() { printf '%d' "0x${1#"#"}"; } # Count recent commits and create an associative array declare -A recent_commit_counts @@ -150,6 +160,7 @@ jobs: top_contributor="" top_contributor_count=0 top_contributor_avatar="" + embed_color=$default_color # Process contributors in the new order while read -r login; do @@ -164,11 +175,14 @@ jobs: # Update top contributor information if [ $commit_count -gt $max_commits ]; then max_commits=$commit_count - top_contributor="$login" + top_contributors=("$login") top_contributor_count=1 top_contributor_avatar="$avatar_url" + embed_color=$(hex_to_decimal "${contributor_colors[$name]:-$default_color}") elif [ $commit_count -eq $max_commits ]; then + top_contributors+=("$login") top_contributor_count=$((top_contributor_count + 1)) + embed_color=$default_color fi # Get commit count for this contributor on the dev branch @@ -191,16 +205,16 @@ jobs: else developers="${developer_entry}" fi - committers_count=$((committers_count + 1)) fi done <<< "$sorted_contributors" - # Set the thumbnail URL based on top contributor(s) + # Set the thumbnail URL and color based on top contributor(s) if [ $top_contributor_count -eq 1 ]; then thumbnail_url="$top_contributor_avatar" else thumbnail_url="https://i.imgur.com/5o3Y9Jb.gif" + embed_color=$default_color fi # Truncate field values @@ -220,12 +234,13 @@ jobs: --arg footer_text "Version $VERSION" \ --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \ --arg thumbnail_url "$thumbnail_url" \ + --argjson embed_color "$embed_color" \ '{ "content": "<@&1225347048321191996>", "embeds": [ { "title": "New Alpha-Build dropped", - "color": 15532323, + "color": $embed_color, "fields": [ { "name": "Commits:", From ae95b61298fa0ad93fc6ee598c713559507cb9de Mon Sep 17 00:00:00 2001 From: ibo <41344259+sneazy-ibo@users.noreply.github.com> Date: Tue, 25 Jun 2024 07:01:04 +0200 Subject: [PATCH 20/26] feat: sort subscriptions in groups (#443) --- .../ani/dantotsu/settings/SubscriptionItem.kt | 33 ++--- .../dantotsu/settings/SubscriptionSource.kt | 113 ++++++++++++++++++ .../settings/SubscriptionsBottomDialog.kt | 38 +++++- app/src/main/res/layout/item_extension.xml | 22 +++- app/src/main/res/layout/item_subscription.xml | 1 - app/src/main/res/values/strings.xml | 2 + 6 files changed, 184 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/ani/dantotsu/settings/SubscriptionSource.kt diff --git a/app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt b/app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt index bb8c0396..bb60cd4e 100644 --- a/app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt +++ b/app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt @@ -9,31 +9,25 @@ import ani.dantotsu.loadImage import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.notifications.subscription.SubscriptionHelper import com.xwray.groupie.GroupieAdapter -import com.xwray.groupie.Item import com.xwray.groupie.viewbinding.BindableItem class SubscriptionItem( val id: Int, private val media: SubscriptionHelper.Companion.SubscribeMedia, - private val adapter: GroupieAdapter + private val adapter: GroupieAdapter, + private val onItemRemoved: (Int) -> Unit ) : BindableItem() { private lateinit var binding: ItemSubscriptionBinding - override fun bind(p0: ItemSubscriptionBinding, p1: Int) { - val context = p0.root.context - binding = p0 - val parserName = if (media.isAnime) - SubscriptionHelper.getAnimeParser(media.id).name - else - SubscriptionHelper.getMangaParser(media.id).name - val mediaName = media.name - val showName = "$mediaName ($parserName)" - binding.subscriptionName.text = showName + + override fun bind(viewBinding: ItemSubscriptionBinding, position: Int) { + binding = viewBinding + val context = binding.root.context + + binding.subscriptionName.text = media.name binding.root.setOnClickListener { ContextCompat.startActivity( context, - Intent(context, MediaDetailsActivity::class.java).putExtra( - "mediaId", media.id - ), + Intent(context, MediaDetailsActivity::class.java).putExtra("mediaId", media.id), null ) } @@ -41,14 +35,11 @@ class SubscriptionItem( binding.deleteSubscription.setOnClickListener { SubscriptionHelper.deleteSubscription(id, true) adapter.remove(this) + onItemRemoved(id) } } - override fun getLayout(): Int { - return R.layout.item_subscription - } + override fun getLayout(): Int = R.layout.item_subscription - override fun initializeViewBinding(p0: View): ItemSubscriptionBinding { - return ItemSubscriptionBinding.bind(p0) - } + override fun initializeViewBinding(view: View): ItemSubscriptionBinding = ItemSubscriptionBinding.bind(view) } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/SubscriptionSource.kt b/app/src/main/java/ani/dantotsu/settings/SubscriptionSource.kt new file mode 100644 index 00000000..80975159 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/settings/SubscriptionSource.kt @@ -0,0 +1,113 @@ +package ani.dantotsu.settings + +import android.app.AlertDialog +import android.content.Context +import android.graphics.drawable.Drawable +import android.view.View +import android.view.ViewGroup +import ani.dantotsu.R +import ani.dantotsu.databinding.ItemExtensionBinding +import ani.dantotsu.notifications.subscription.SubscriptionHelper +import com.xwray.groupie.GroupieAdapter +import com.xwray.groupie.viewbinding.BindableItem + +class SubscriptionSource( + private val parserName: String, + private val subscriptions: MutableList, + private val adapter: GroupieAdapter, + private var parserIcon: Drawable? = null, + private val onGroupRemoved: (SubscriptionSource) -> Unit +) : BindableItem() { + private lateinit var binding: ItemExtensionBinding + private var isExpanded = false + + override fun bind(viewBinding: ItemExtensionBinding, position: Int) { + binding = viewBinding + binding.extensionNameTextView.text = parserName + updateSubscriptionCount() + binding.extensionSubscriptions.visibility = View.VISIBLE + + binding.extensionSubscriptions.setOnClickListener(null) + binding.root.setOnClickListener { + isExpanded = !isExpanded + toggleSubscriptions() + } + binding.subscriptionCount.setOnClickListener { + showRemoveAllSubscriptionsDialog(it.context) + } + binding.extensionIconImageView.visibility = View.VISIBLE + val layoutParams = binding.extensionIconImageView.layoutParams as ViewGroup.MarginLayoutParams + layoutParams.leftMargin = 28 + binding.extensionIconImageView.layoutParams = layoutParams + + parserIcon?.let { + binding.extensionIconImageView.setImageDrawable(it) + } ?: run { + binding.extensionIconImageView.setImageResource(R.drawable.control_background_40dp) + } + + binding.extensionPinImageView.visibility = View.GONE + binding.extensionVersionTextView.visibility = View.GONE + binding.closeTextView.visibility = View.GONE + binding.settingsImageView.visibility = View.GONE + } + + private fun updateSubscriptionCount() { + binding.subscriptionCount.text = subscriptions.size.toString() + binding.subscriptionCount.visibility = if (subscriptions.isEmpty()) View.GONE else View.VISIBLE + } + + private fun showRemoveAllSubscriptionsDialog(context: Context) { + AlertDialog.Builder(context, R.style.MyPopup) + .setTitle(R.string.remove_all_subscriptions) + .setMessage(context.getString(R.string.remove_all_subscriptions_desc, parserName)) + .setPositiveButton(R.string.apply) { _, _ -> + removeAllSubscriptions() + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + private fun removeAllSubscriptions() { + subscriptions.forEach { subscription -> + SubscriptionHelper.deleteSubscription(subscription.id, false) + } + if (isExpanded) { + val startPosition = adapter.getAdapterPosition(this) + 1 + repeat(subscriptions.size) { + adapter.removeGroupAtAdapterPosition(startPosition) + } + } + subscriptions.clear() + onGroupRemoved(this) + } + + private fun removeSubscription(id: Any?) { + subscriptions.removeAll { it.id == id } + updateSubscriptionCount() + if (subscriptions.isEmpty()) { + onGroupRemoved(this) + } else { + adapter.notifyItemChanged(adapter.getAdapterPosition(this)) + } + } + + private fun toggleSubscriptions() { + val startPosition = adapter.getAdapterPosition(this) + 1 + if (isExpanded) { + subscriptions.forEachIndexed { index, subscribeMedia -> + adapter.add(startPosition + index, SubscriptionItem(subscribeMedia.id, subscribeMedia, adapter) { removedId -> + removeSubscription(removedId) + }) + } + } else { + repeat(subscriptions.size) { + adapter.removeGroupAtAdapterPosition(startPosition) + } + } + } + + override fun getLayout(): Int = R.layout.item_extension + + override fun initializeViewBinding(view: View): ItemExtensionBinding = ItemExtensionBinding.bind(view) +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt b/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt index 2b833114..93fd58db 100644 --- a/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt +++ b/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt @@ -1,5 +1,6 @@ package ani.dantotsu.settings +import android.graphics.drawable.Drawable import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -9,13 +10,21 @@ import ani.dantotsu.BottomSheetDialogFragment import ani.dantotsu.R import ani.dantotsu.databinding.BottomSheetRecyclerBinding import ani.dantotsu.notifications.subscription.SubscriptionHelper +import ani.dantotsu.parsers.novel.NovelExtensionManager import com.xwray.groupie.GroupieAdapter +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager +import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get class SubscriptionsBottomDialog : BottomSheetDialogFragment() { private var _binding: BottomSheetRecyclerBinding? = null private val binding get() = _binding!! private val adapter: GroupieAdapter = GroupieAdapter() private var subscriptions: Map = mapOf() + private val animeExtension: AnimeExtensionManager = Injekt.get() + private val mangaExtensions: MangaExtensionManager = Injekt.get() + private val novelExtensions: NovelExtensionManager = Injekt.get() override fun onCreateView( inflater: LayoutInflater, @@ -36,8 +45,33 @@ class SubscriptionsBottomDialog : BottomSheetDialogFragment() { val context = requireContext() binding.title.text = context.getString(R.string.subscriptions) binding.replyButton.visibility = View.GONE - subscriptions.forEach { (id, media) -> - adapter.add(SubscriptionItem(id, media, adapter)) + + val groupedSubscriptions = subscriptions.values.groupBy { + if (it.isAnime) SubscriptionHelper.getAnimeParser(it.id).name + else SubscriptionHelper.getMangaParser(it.id).name + } + + groupedSubscriptions.forEach { (parserName, mediaList) -> + adapter.add(SubscriptionSource( + parserName, + mediaList.toMutableList(), + adapter, + getParserIcon(parserName) + ) { group -> + adapter.remove(group) + }) + } + } + + private fun getParserIcon(parserName: String): Drawable? { + return when { + animeExtension.installedExtensionsFlow.value.any { it.name == parserName } -> + animeExtension.installedExtensionsFlow.value.find { it.name == parserName }?.icon + mangaExtensions.installedExtensionsFlow.value.any { it.name == parserName } -> + mangaExtensions.installedExtensionsFlow.value.find { it.name == parserName }?.icon + novelExtensions.installedExtensionsFlow.value.any { it.name == parserName } -> + novelExtensions.installedExtensionsFlow.value.find { it.name == parserName }?.icon + else -> null } } diff --git a/app/src/main/res/layout/item_extension.xml b/app/src/main/res/layout/item_extension.xml index b32b6ef5..6fd255b0 100644 --- a/app/src/main/res/layout/item_extension.xml +++ b/app/src/main/res/layout/item_extension.xml @@ -25,7 +25,7 @@ android:layout_width="40dp" android:layout_height="40dp" android:layout_gravity="center_vertical" - android:layout_marginEnd="3dp" + android:layout_marginEnd="9dp" tools:ignore="ContentDescription" /> + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index befeed04..d64f5f37 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -417,6 +417,8 @@ Use Alarm Manager for reliable Notifications Using Alarm Manger can help fight against battery optimization, but may consume more battery. It also requires the Alarm Manager permission. Use + Remove All Subscriptions + Are you sure you want to remove all subscriptions for %1$s? Notification for Checking Subscriptions Subscriptions Update Frequency : %1$s Subscriptions Update Frequency From 46d16be835e8d69b215f350c8f55b5b4cb12e838 Mon Sep 17 00:00:00 2001 From: aayush262 Date: Wed, 26 Jun 2024 19:27:26 +0530 Subject: [PATCH 21/26] feat: delete comment and subscription notification --- .../dantotsu/home/status/StatusActivity.kt | 9 ++- .../java/ani/dantotsu/home/status/Stories.kt | 15 ++-- .../profile/activity/ActivityFragment.kt | 2 +- .../dantotsu/profile/activity/FeedActivity.kt | 9 +-- .../notification/NotificationActivity.kt | 22 +++--- .../notification/NotificationFragment.kt | 59 ++++++++++----- .../profile/notification/NotificationItem.kt | 73 ++++++++++++++++++- app/src/main/res/layout/fragment_status.xml | 2 +- app/src/main/res/values/strings.xml | 3 +- 9 files changed, 136 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/ani/dantotsu/home/status/StatusActivity.kt b/app/src/main/java/ani/dantotsu/home/status/StatusActivity.kt index 7fc0e78b..ced8b2d8 100644 --- a/app/src/main/java/ani/dantotsu/home/status/StatusActivity.kt +++ b/app/src/main/java/ani/dantotsu/home/status/StatusActivity.kt @@ -49,7 +49,10 @@ class StatusActivity : AppCompatActivity(), StoriesCallback { if (activity.getOrNull(position) != null) { val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity ) val startIndex = if ( startFrom > 0) startFrom else 0 - binding.stories.setStoriesList(activity[position].activity, this, startIndex + 1) + binding.stories.setStoriesList( + activityList = activity[position].activity, + startIndex = startIndex + 1 + ) } else { Logger.log("index out of bounds for position $position of size ${activity.size}") finish() @@ -92,7 +95,7 @@ class StatusActivity : AppCompatActivity(), StoriesCallback { val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity ) val startIndex= if ( startFrom > 0) startFrom else 0 binding.stories.startAnimation(slideOutLeft) - binding.stories.setStoriesList(activity[position].activity, this, startIndex + 1) + binding.stories.setStoriesList(activity[position].activity, startIndex + 1) binding.stories.startAnimation(slideInRight) } else { finish() @@ -107,7 +110,7 @@ class StatusActivity : AppCompatActivity(), StoriesCallback { val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity ) val startIndex = if ( startFrom > 0) startFrom else 0 binding.stories.startAnimation(slideOutRight) - binding.stories.setStoriesList(activity[position].activity, this, startIndex + 1) + binding.stories.setStoriesList(activity[position].activity,startIndex + 1) binding.stories.startAnimation(slideInLeft) } else { finish() diff --git a/app/src/main/java/ani/dantotsu/home/status/Stories.kt b/app/src/main/java/ani/dantotsu/home/status/Stories.kt index a017b7ab..a141a5d6 100644 --- a/app/src/main/java/ani/dantotsu/home/status/Stories.kt +++ b/app/src/main/java/ani/dantotsu/home/status/Stories.kt @@ -49,7 +49,6 @@ import kotlin.math.abs class Stories @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr), View.OnTouchListener { - private lateinit var activity: FragmentActivity private lateinit var binding: FragmentStatusBinding private lateinit var activityList: List private lateinit var storiesListener: StoriesCallback @@ -80,10 +79,9 @@ class Stories @JvmOverloads constructor( fun setStoriesList( - activityList: List, activity: FragmentActivity, startIndex: Int = 1 + activityList: List, startIndex: Int = 1 ) { this.activityList = activityList - this.activity = activity this.storyIndex = startIndex addLoadingViews(activityList) } @@ -368,7 +366,9 @@ class Stories @JvmOverloads constructor( if ( story.status?.contains("completed") == false && !story.status.contains("plans") && - !story.status.contains("repeating") + !story.status.contains("repeating")&& + !story.status.contains("paused")&& + !story.status.contains("dropped") ) { "of ${story.media?.title?.userPreferred}" } else { @@ -389,7 +389,7 @@ class Stories @JvmOverloads constructor( story.media?.id ), ActivityOptionsCompat.makeSceneTransitionAnimation( - activity, + (it.context as FragmentActivity), binding.coverImage, ViewCompat.getTransitionName(binding.coverImage)!! ).toBundle() @@ -427,7 +427,7 @@ class Stories @JvmOverloads constructor( binding.activityReplies.setColorFilter(ContextCompat.getColor(context, R.color.bg_opp)) binding.activityRepliesContainer.setOnClickListener { RepliesBottomDialog.newInstance(story.id) - .show(activity.supportFragmentManager, "replies") + .show((it.context as FragmentActivity).supportFragmentManager, "replies") } binding.activityLike.setColorFilter(if (story.isLiked == true) likeColor else notLikeColor) binding.activityLikeCount.text = story.likeCount.toString() @@ -435,10 +435,9 @@ class Stories @JvmOverloads constructor( like() } binding.activityLikeContainer.setOnLongClickListener { - val context = activity UsersDialogFragment().apply { userList(userList) - show(context.supportFragmentManager, "dialog") + show((it.context as FragmentActivity).supportFragmentManager, "dialog") } true } diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt index 3b9b32d0..8b1500bd 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt @@ -60,7 +60,7 @@ class ActivityFragment : Fragment() { binding.feedRefresh.updateLayoutParams { bottomMargin = navBarHeight } - binding.emptyTextView.text = getString(R.string.no_activities) + binding.emptyTextView.text = getString(R.string.nothing_here) lifecycleScope.launch { getList() if (adapter.itemCount == 0) { diff --git a/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt b/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt index 45122fec..40cb8f96 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt @@ -51,6 +51,7 @@ class FeedActivity : AppCompatActivity() { binding.notificationBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() } val getOne = intent.getIntExtra("activityId", -1) if (getOne != -1) { navBar.visibility = View.GONE } + binding.notificationViewPager.isUserInputEnabled = false binding.notificationViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, getOne) binding.notificationViewPager.setOffscreenPageLimit(4) binding.notificationViewPager.setCurrentItem(selected, false) @@ -63,13 +64,7 @@ class FeedActivity : AppCompatActivity() { newTab: AnimatedBottomBar.Tab ) { selected = newIndex - binding.notificationViewPager.setCurrentItem(selected, true) - } - }) - binding.notificationViewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - navBar.selectTabAt(position) + binding.notificationViewPager.setCurrentItem(selected, false) } }) } diff --git a/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt b/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt index 75089031..bd5fad0b 100644 --- a/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt @@ -16,7 +16,8 @@ import ani.dantotsu.initActivity import ani.dantotsu.navBarHeight import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager -import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.* +import ani.dantotsu.profile.notification.NotificationFragment.Companion.newInstance import nl.joery.animatedbottombar.AnimatedBottomBar class NotificationActivity : AppCompatActivity() { @@ -48,6 +49,7 @@ class NotificationActivity : AppCompatActivity() { binding.notificationBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() } val getOne = intent.getIntExtra("activityId", -1) if (getOne != -1) navBar.isVisible = false + binding.notificationViewPager.isUserInputEnabled = false binding.notificationViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, getOne) binding.notificationViewPager.setCurrentItem(selected, false) navBar.selectTabAt(selected) @@ -59,13 +61,7 @@ class NotificationActivity : AppCompatActivity() { newTab: AnimatedBottomBar.Tab ) { selected = newIndex - binding.notificationViewPager.setCurrentItem(selected, true) - } - }) - binding.notificationViewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - navBar.selectTabAt(position) + binding.notificationViewPager.setCurrentItem(selected, false) } }) } @@ -83,11 +79,11 @@ class NotificationActivity : AppCompatActivity() { override fun getItemCount(): Int = if (id != -1) 1 else 4 override fun createFragment(position: Int): Fragment = when (position) { - 0 -> NotificationFragment.newInstance(NotificationType.USER) - 1 -> NotificationFragment.newInstance(if (id != -1) NotificationType.ONE else NotificationType.MEDIA, id) - 2 -> NotificationFragment.newInstance(NotificationType.SUBSCRIPTION) - 3 -> NotificationFragment.newInstance(NotificationType.COMMENT) - else -> NotificationFragment.newInstance(NotificationType.MEDIA) + 0 -> newInstance(USER) + 1 -> newInstance(if (id != -1) ONE else MEDIA, id) + 2 -> newInstance(SUBSCRIPTION) + 3 -> newInstance(COMMENT) + else -> newInstance(MEDIA) } } } diff --git a/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt b/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt index 6360763d..c38ec0fb 100644 --- a/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt @@ -20,7 +20,11 @@ import ani.dantotsu.notifications.comment.CommentStore import ani.dantotsu.notifications.subscription.SubscriptionStore import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.activity.FeedActivity -import ani.dantotsu.setBaseline +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.COMMENT +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.MEDIA +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.ONE +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.SUBSCRIPTION +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.USER import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import com.xwray.groupie.GroupieAdapter @@ -29,8 +33,8 @@ import kotlinx.coroutines.launch class NotificationFragment : Fragment() { - private lateinit var type : NotificationType - private var getID : Int = -1 + private lateinit var type: NotificationType + private var getID: Int = -1 private lateinit var binding: FragmentNotificationsBinding private var adapter: GroupieAdapter = GroupieAdapter() private var currentPage = 1 @@ -53,12 +57,10 @@ class NotificationFragment : Fragment() { binding.notificationRecyclerView.adapter = adapter binding.notificationRecyclerView.layoutManager = LinearLayoutManager(context) binding.notificationProgressBar.isVisible = true - binding.emptyTextView.text = getString(R.string.no_notifications) + binding.emptyTextView.text = getString(R.string.nothing_here) lifecycleScope.launch { getList() - if (adapter.itemCount == 0) { - binding.emptyTextView.isVisible = true - } + binding.notificationProgressBar.isVisible = false } binding.notificationSwipeRefresh.setOnRefreshListener { @@ -87,13 +89,16 @@ class NotificationFragment : Fragment() { private suspend fun getList() { val list = when (type) { - NotificationType.ONE -> getNotificationsFiltered(false) { it.id == getID } - NotificationType.MEDIA -> getNotificationsFiltered(type = true) { it.media != null } - NotificationType.USER -> getNotificationsFiltered { it.media == null } - NotificationType.SUBSCRIPTION -> getSubscriptions() - NotificationType.COMMENT -> getComments() + ONE -> getNotificationsFiltered(false) { it.id == getID } + MEDIA -> getNotificationsFiltered(type = true) { it.media != null } + USER -> getNotificationsFiltered { it.media == null } + SUBSCRIPTION -> getSubscriptions() + COMMENT -> getComments() + } + adapter.addAll(list.map { NotificationItem(it, type, adapter, ::onClick) }) + if (adapter.itemCount == 0) { + binding.emptyTextView.isVisible = true } - adapter.addAll(list.map { NotificationItem(it, ::onClick) }) } private suspend fun getNotificationsFiltered( @@ -114,8 +119,11 @@ class NotificationFragment : Fragment() { PrefName.SubscriptionNotificationStore, null ) ?: listOf() - return list.sortedByDescending { (it.time / 1000L).toInt() } - .filter { it.image != null }.map { + + return list + .sortedByDescending { (it.time / 1000L).toInt() } + .filter { it.image != null } // to remove old data + .map { Notification( it.type, System.currentTimeMillis().toInt(), @@ -162,19 +170,31 @@ class NotificationFragment : Fragment() { fun onClick(id: Int, optional: Int?, type: NotificationClickType) { val intent = when (type) { - NotificationClickType.USER -> Intent(requireContext(), ProfileActivity::class.java).apply { + NotificationClickType.USER -> Intent( + requireContext(), + ProfileActivity::class.java + ).apply { putExtra("userId", id) } - NotificationClickType.MEDIA -> Intent(requireContext(), MediaDetailsActivity::class.java).apply { + NotificationClickType.MEDIA -> Intent( + requireContext(), + MediaDetailsActivity::class.java + ).apply { putExtra("mediaId", id) } - NotificationClickType.ACTIVITY -> Intent(requireContext(), FeedActivity::class.java).apply { + NotificationClickType.ACTIVITY -> Intent( + requireContext(), + FeedActivity::class.java + ).apply { putExtra("activityId", id) } - NotificationClickType.COMMENT -> Intent(requireContext(), MediaDetailsActivity::class.java).apply { + NotificationClickType.COMMENT -> Intent( + requireContext(), + MediaDetailsActivity::class.java + ).apply { putExtra("FRAGMENT_TO_LOAD", "COMMENTS") putExtra("mediaId", id) putExtra("commentId", optional ?: -1) @@ -188,6 +208,7 @@ class NotificationFragment : Fragment() { } } + override fun onResume() { super.onResume() if (this::binding.isInitialized) { diff --git a/app/src/main/java/ani/dantotsu/profile/notification/NotificationItem.kt b/app/src/main/java/ani/dantotsu/profile/notification/NotificationItem.kt index f51225ea..61273374 100644 --- a/app/src/main/java/ani/dantotsu/profile/notification/NotificationItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/notification/NotificationItem.kt @@ -8,16 +8,27 @@ import ani.dantotsu.connections.anilist.api.Notification import ani.dantotsu.connections.anilist.api.NotificationType import ani.dantotsu.databinding.ItemNotificationBinding import ani.dantotsu.loadImage -import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationClickType +import ani.dantotsu.notifications.comment.CommentStore +import ani.dantotsu.notifications.subscription.SubscriptionStore import ani.dantotsu.profile.activity.ActivityItemBuilder +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationClickType +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.COMMENT +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.SUBSCRIPTION import ani.dantotsu.setAnimation +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.toPx +import ani.dantotsu.util.customAlertDialog +import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.viewbinding.BindableItem class NotificationItem( private val notification: Notification, - val clickCallback: (Int, Int?, NotificationClickType) -> Unit -) : BindableItem() { + val type: NotificationFragment.Companion.NotificationType, + val parentAdapter: GroupieAdapter, + val clickCallback: (Int, Int?, NotificationClickType) -> Unit, + + ) : BindableItem() { private lateinit var binding: ItemNotificationBinding override fun bind(viewBinding: ItemNotificationBinding, position: Int) { binding = viewBinding @@ -25,6 +36,48 @@ class NotificationItem( setBinding() } + fun dialog() { + when (type) { + COMMENT, SUBSCRIPTION -> { + binding.root.context.customAlertDialog().apply { + setTitle(R.string.delete) + setMessage(ActivityItemBuilder.getContent(notification)) + setPosButton(R.string.yes) { + when (type) { + COMMENT -> { + val list = PrefManager.getNullableVal>( + PrefName.CommentNotificationStore, + null + ) ?: listOf() + val newList = list.filter { it.commentId != notification.commentId } + PrefManager.setVal(PrefName.CommentNotificationStore, newList) + parentAdapter.remove(this@NotificationItem) + + } + + SUBSCRIPTION -> { + val list = PrefManager.getNullableVal>( + PrefName.SubscriptionNotificationStore, + null + ) ?: listOf() + val newList = list.filter { (it.time / 1000L).toInt() != notification.createdAt} + PrefManager.setVal(PrefName.SubscriptionNotificationStore, newList) + parentAdapter.remove(this@NotificationItem) + } + + else -> {} + } + } + setNegButton(R.string.no) + show() + } + } + + else -> {} + } + + } + override fun getLayout(): Int { return R.layout.item_notification } @@ -33,7 +86,11 @@ class NotificationItem( return ItemNotificationBinding.bind(view) } - private fun image(user: Boolean = false, commentNotification: Boolean = false, newRelease: Boolean = false) { + private fun image( + user: Boolean = false, + commentNotification: Boolean = false, + newRelease: Boolean = false + ) { val cover = if (user) notification.user?.bannerImage ?: notification.user?.avatar?.medium else notification.media?.bannerImage @@ -348,6 +405,14 @@ class NotificationItem( } } } + binding.notificationCoverUser.setOnLongClickListener { + dialog() + true + } + binding.notificationBannerImage.setOnLongClickListener { + dialog() + true + } } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_status.xml b/app/src/main/res/layout/fragment_status.xml index 72f488e0..63664c60 100644 --- a/app/src/main/res/layout/fragment_status.xml +++ b/app/src/main/res/layout/fragment_status.xml @@ -177,7 +177,7 @@ android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginHorizontal="16dp" - android:layout_marginVertical="32dp" + android:layout_marginVertical="6dp" android:orientation="horizontal"> All - No more notifications - No more activities + Nothing here Followers Write a Message STATUS From f62bdf9360d15143d83eb3787da584df238ed17f Mon Sep 17 00:00:00 2001 From: aayush262 Date: Thu, 27 Jun 2024 00:23:31 +0530 Subject: [PATCH 22/26] feat: lil faster home screen? idk tbh --- .../main/java/ani/dantotsu/MainActivity.kt | 1 - .../connections/anilist/AnilistQueries.kt | 447 ++++++++---------- .../connections/anilist/AnilistViewModel.kt | 17 +- .../java/ani/dantotsu/home/HomeFragment.kt | 54 ++- 4 files changed, 243 insertions(+), 276 deletions(-) diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt index 68f42922..7b2e0b1d 100644 --- a/app/src/main/java/ani/dantotsu/MainActivity.kt +++ b/app/src/main/java/ani/dantotsu/MainActivity.kt @@ -313,7 +313,6 @@ class MainActivity : AppCompatActivity() { mainViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle) mainViewPager.setPageTransformer(ZoomOutPageTransformer()) - mainViewPager.offscreenPageLimit = 1 navbar.selectTabAt(selectedOption) navbar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt index 438dc7ad..a834acc3 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -6,14 +6,14 @@ import ani.dantotsu.checkGenreTime import ani.dantotsu.checkId import ani.dantotsu.connections.anilist.Anilist.authorRoles import ani.dantotsu.connections.anilist.Anilist.executeQuery -import ani.dantotsu.connections.anilist.api.Activity import ani.dantotsu.connections.anilist.api.FeedResponse import ani.dantotsu.connections.anilist.api.FuzzyDate +import ani.dantotsu.connections.anilist.api.MediaEdge +import ani.dantotsu.connections.anilist.api.MediaList import ani.dantotsu.connections.anilist.api.NotificationResponse import ani.dantotsu.connections.anilist.api.Page import ani.dantotsu.connections.anilist.api.Query import ani.dantotsu.connections.anilist.api.ReplyResponse -import ani.dantotsu.connections.anilist.api.ToggleLike import ani.dantotsu.currContext import ani.dantotsu.isOnline import ani.dantotsu.logError @@ -28,6 +28,7 @@ import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -422,9 +423,7 @@ class AnilistQueries { val toShow: List = PrefManager.getVal(PrefName.HomeLayout) if (toShow.getOrNull(7) != true) return null - var query = """{""" - query += "Page1:${status(1)}Page2:${status(2)}" - query += """}""".trimEnd(',') + val query = """{Page1:${status(1)}Page2:${status(2)}}""" val response = executeQuery(query, show = true) val list = mutableListOf() val threeDaysAgo = Calendar.getInstance().apply { @@ -461,8 +460,9 @@ class AnilistQueries { } } - if (anilistActivities.isEmpty() && Anilist.token != null){ - anilistActivities.add(0, + if (anilistActivities.isEmpty() && Anilist.token != null) { + anilistActivities.add( + 0, User( Anilist.userid!!, Anilist.username!!, @@ -477,206 +477,176 @@ class AnilistQueries { } else return null } - suspend fun initHomePage(): Map> { + suspend fun initHomePage(): Map> { val removeList = PrefManager.getCustomVal("removeList", setOf()) val hidePrivate = PrefManager.getVal(PrefName.HidePrivate) val removedMedia = ArrayList() val toShow: List = - PrefManager.getVal(PrefName.HomeLayout) // anime continue, anime fav, anime planned, manga continue, manga fav, manga planned, recommendations - var query = """{""" - if (toShow.getOrNull(0) == true) query += """currentAnime: ${ - continueMediaQuery( - "ANIME", - "CURRENT" - ) - }, repeatingAnime: ${continueMediaQuery("ANIME", "REPEATING")}""" - if (toShow.getOrNull(1) == true) query += """favoriteAnime: ${favMediaQuery(true, 1)}""" - if (toShow.getOrNull(2) == true) query += """plannedAnime: ${ - continueMediaQuery( - "ANIME", - "PLANNING" - ) - }""" - if (toShow.getOrNull(3) == true) query += """currentManga: ${ - continueMediaQuery( - "MANGA", - "CURRENT" - ) - }, repeatingManga: ${continueMediaQuery("MANGA", "REPEATING")}""" - if (toShow.getOrNull(4) == true) query += """favoriteManga: ${favMediaQuery(false, 1)}""" - if (toShow.getOrNull(5) == true) query += """plannedManga: ${ - continueMediaQuery( - "MANGA", - "PLANNING" - ) - }""" - if (toShow.getOrNull(6) == true) query += """recommendationQuery: ${recommendationQuery()}, recommendationPlannedQueryAnime: ${ - recommendationPlannedQuery( - "ANIME" - ) - }, recommendationPlannedQueryManga: ${recommendationPlannedQuery("MANGA")}""" - query += """}""".trimEnd(',') + PrefManager.getVal(PrefName.HomeLayout) // list of booleans for what to show + val queries = mutableListOf() + if (toShow.getOrNull(0) == true) { + queries.add("""currentAnime: ${continueMediaQuery("ANIME", "CURRENT")}""") + queries.add("""repeatingAnime: ${continueMediaQuery("ANIME", "REPEATING")}""") + } + if (toShow.getOrNull(1) == true) queries.add("""favoriteAnime: ${favMediaQuery(true, 1)}""") + if (toShow.getOrNull(2) == true) queries.add( + """plannedAnime: ${ + continueMediaQuery( + "ANIME", + "PLANNING" + ) + }""" + ) + if (toShow.getOrNull(3) == true) { + queries.add("""currentManga: ${continueMediaQuery("MANGA", "CURRENT")}""") + queries.add("""repeatingManga: ${continueMediaQuery("MANGA", "REPEATING")}""") + } + if (toShow.getOrNull(4) == true) queries.add( + """favoriteManga: ${ + favMediaQuery( + false, + 1 + ) + }""" + ) + if (toShow.getOrNull(5) == true) queries.add( + """plannedManga: ${ + continueMediaQuery( + "MANGA", + "PLANNING" + ) + }""" + ) + if (toShow.getOrNull(6) == true) { + queries.add("""recommendationQuery: ${recommendationQuery()}""") + queries.add("""recommendationPlannedQueryAnime: ${recommendationPlannedQuery("ANIME")}""") + queries.add("""recommendationPlannedQueryManga: ${recommendationPlannedQuery("MANGA")}""") + } + + val query = "{${queries.joinToString(",")}}" val response = executeQuery(query, show = true) - val returnMap = mutableMapOf>() - fun current(type: String) { + val returnMap = mutableMapOf>() + + fun processMedia( + type: String, + currentMedia: List?, + repeatingMedia: List? + ) { val subMap = mutableMapOf() val returnArray = arrayListOf() - val current = - if (type == "Anime") response?.data?.currentAnime else response?.data?.currentManga - val repeating = - if (type == "Anime") response?.data?.repeatingAnime else response?.data?.repeatingManga - current?.lists?.forEach { li -> - li.entries?.reversed()?.forEach { - val m = Media(it) - if (m.id !in removeList && if (hidePrivate) !m.isListPrivate else true) { - m.cameFromContinue = true - subMap[m.id] = m - } else { - removedMedia.add(m) - } + + (currentMedia ?: emptyList()).forEach { entry -> + val media = Media(entry) + if (media.id !in removeList && (!hidePrivate || !media.isListPrivate)) { + media.cameFromContinue = true + subMap[media.id] = media + } else { + removedMedia.add(media) } } - repeating?.lists?.forEach { li -> - li.entries?.reversed()?.forEach { - val m = Media(it) - if (m.id !in removeList && if (hidePrivate) !m.isListPrivate else true) { - m.cameFromContinue = true - subMap[m.id] = m - } else { - removedMedia.add(m) - } + (repeatingMedia ?: emptyList()).forEach { entry -> + val media = Media(entry) + if (media.id !in removeList && (!hidePrivate || !media.isListPrivate)) { + media.cameFromContinue = true + subMap[media.id] = media + } else { + removedMedia.add(media) } } - if (type != "Anime") { + @Suppress("UNCHECKED_CAST") + val list = PrefManager.getNullableCustomVal( + "continue${type}List", + listOf(), + List::class.java + ) as List + if (list.isNotEmpty()) { + list.reversed().forEach { id -> + subMap[id]?.let { returnArray.add(it) } + } + subMap.values.forEach { + if (!returnArray.contains(it)) returnArray.add(it) + } + } else { returnArray.addAll(subMap.values) - returnMap["current$type"] = returnArray - return } - @Suppress("UNCHECKED_CAST") - val list = PrefManager.getNullableCustomVal( - "continueAnimeList", - listOf(), - List::class.java - ) as List - if (list.isNotEmpty()) { - list.reversed().forEach { - if (subMap.containsKey(it)) returnArray.add(subMap[it]!!) - } - for (i in subMap) { - if (i.value !in returnArray) returnArray.add(i.value) - } - } else returnArray.addAll(subMap.values) returnMap["current$type"] = returnArray - } - fun planned(type: String) { - val subMap = mutableMapOf() - val returnArray = arrayListOf() - val current = - if (type == "Anime") response?.data?.plannedAnime else response?.data?.plannedManga - current?.lists?.forEach { li -> - li.entries?.reversed()?.forEach { - val m = Media(it) - if (m.id !in removeList && if (hidePrivate) !m.isListPrivate else true) { - m.cameFromContinue = true - subMap[m.id] = m - } else { - removedMedia.add(m) - } - } - } - @Suppress("UNCHECKED_CAST") - val list = PrefManager.getNullableCustomVal( - "continueAnimeList", - listOf(), - List::class.java - ) as List - if (list.isNotEmpty()) { - list.reversed().forEach { - if (subMap.containsKey(it)) returnArray.add(subMap[it]!!) - } - for (i in subMap) { - if (i.value !in returnArray) returnArray.add(i.value) - } - } else returnArray.addAll(subMap.values) - returnMap["planned$type"] = returnArray - } + if (toShow.getOrNull(0) == true) processMedia( + "Anime", + response?.data?.currentAnime?.lists?.flatMap { it.entries ?: emptyList() }?.reversed(), + response?.data?.repeatingAnime?.lists?.flatMap { it.entries ?: emptyList() }?.reversed() + ) + if (toShow.getOrNull(2) == true) processMedia( + "AnimePlanned", + response?.data?.plannedAnime?.lists?.flatMap { it.entries ?: emptyList() }?.reversed(), + null + ) + if (toShow.getOrNull(3) == true) processMedia( + "Manga", + response?.data?.currentManga?.lists?.flatMap { it.entries ?: emptyList() }?.reversed(), + response?.data?.repeatingManga?.lists?.flatMap { it.entries ?: emptyList() }?.reversed() + ) + if (toShow.getOrNull(5) == true) processMedia( + "MangaPlanned", + response?.data?.plannedManga?.lists?.flatMap { it.entries ?: emptyList() }?.reversed(), + null + ) - fun favorite(type: String) { - val favourites = - if (type == "Anime") response?.data?.favoriteAnime?.favourites else response?.data?.favoriteManga?.favourites - val apiMediaList = if (type == "Anime") favourites?.anime else favourites?.manga + fun processFavorites(type: String, favorites: List?) { val returnArray = arrayListOf() - apiMediaList?.edges?.forEach { - it.node?.let { i -> - val m = Media(i).apply { isFav = true } - if (m.id !in removeList && if (hidePrivate) !m.isListPrivate else true) { - returnArray.add(m) + favorites?.forEach { edge -> + edge.node?.let { + val media = Media(it).apply { isFav = true } + if (media.id !in removeList && (!hidePrivate || !media.isListPrivate)) { + returnArray.add(media) } else { - removedMedia.add(m) + removedMedia.add(media) } } } returnMap["favorite$type"] = returnArray } - if (toShow.getOrNull(0) == true) { - current("Anime") - } - if (toShow.getOrNull(1) == true) { - favorite("Anime") - } - if (toShow.getOrNull(2) == true) { - planned("Anime") - } - if (toShow.getOrNull(3) == true) { - current("Manga") - } - if (toShow.getOrNull(4) == true) { - favorite("Manga") - } - if (toShow.getOrNull(5) == true) { - planned("Manga") - } + if (toShow.getOrNull(1) == true) processFavorites( + "Anime", + response?.data?.favoriteAnime?.favourites?.anime?.edges + ) + if (toShow.getOrNull(4) == true) processFavorites( + "Manga", + response?.data?.favoriteManga?.favourites?.manga?.edges + ) + if (toShow.getOrNull(6) == true) { val subMap = mutableMapOf() - response?.data?.recommendationQuery?.apply { - recommendations?.onEach { - val json = it.mediaRecommendation - if (json != null) { - val m = Media(json) - m.relation = json.type?.toString() - subMap[m.id] = m - } + response?.data?.recommendationQuery?.recommendations?.forEach { + it.mediaRecommendation?.let { json -> + val media = Media(json) + media.relation = json.type?.toString() + subMap[media.id] = media } } - response?.data?.recommendationPlannedQueryAnime?.apply { - lists?.forEach { li -> - li.entries?.forEach { - val m = Media(it) - if (m.status == "RELEASING" || m.status == "FINISHED") { - m.relation = it.media?.type?.toString() - subMap[m.id] = m - } - } + response?.data?.recommendationPlannedQueryAnime?.lists?.flatMap { + it.entries ?: emptyList() + }?.forEach { + val media = Media(it) + if (media.status in listOf("RELEASING", "FINISHED")) { + media.relation = it.media?.type?.toString() + subMap[media.id] = media } } - response?.data?.recommendationPlannedQueryManga?.apply { - lists?.forEach { li -> - li.entries?.forEach { - val m = Media(it) - if (m.status == "RELEASING" || m.status == "FINISHED") { - m.relation = it.media?.type?.toString() - subMap[m.id] = m - } - } + response?.data?.recommendationPlannedQueryManga?.lists?.flatMap { + it.entries ?: emptyList() + }?.forEach { + val media = Media(it) + if (media.status in listOf("RELEASING", "FINISHED")) { + media.relation = it.media?.type?.toString() + subMap[media.id] = media } } - val list = ArrayList(subMap.values.toList()) - list.sortByDescending { it.meanScore } + val list = ArrayList(subMap.values).apply { sortByDescending { it.meanScore } } returnMap["recommendations"] = list } @@ -1061,102 +1031,97 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult: media1?.media?.mapTo(combinedList) { Media(it) } return combinedList } - private fun getPreference(pref: PrefName): Boolean = PrefManager.getVal(pref) - private fun buildQueryString(sort: String, type: String, format: String? = null, country: String? = null): String { - val includeList = if (type == "ANIME" && !getPreference(PrefName.IncludeAnimeList)) "onList:false" else if (type == "MANGA" && !getPreference(PrefName.IncludeMangaList)) "onList:false" else "" - val isAdult = if (getPreference(PrefName.AdultOnly)) "isAdult:true" else "" - val formatFilter = format?.let { "format: $it, " } ?: "" - val countryFilter = country?.let { "countryOfOrigin: $it, " } ?: "" - return """Page(page:1,perPage:50){ - pageInfo{hasNextPage total} - media(sort:$sort, type:$type, $formatFilter $countryFilter $includeList $isAdult){ - id idMal status chapters episodes nextAiringEpisode{episode} - isAdult type meanScore isFavourite format bannerImage countryOfOrigin - coverImage{large} title{english romaji userPreferred} - mediaListEntry{progress private score(format:POINT_100) status} + private fun getPreference(pref: PrefName): Boolean = PrefManager.getVal(pref) + + private fun buildQueryString( + sort: String, + type: String, + format: String? = null, + country: String? = null + ): String { + val includeList = when { + type == "ANIME" && !getPreference(PrefName.IncludeAnimeList) -> "onList:false" + type == "MANGA" && !getPreference(PrefName.IncludeMangaList) -> "onList:false" + else -> "" + } + val isAdult = if (getPreference(PrefName.AdultOnly)) "isAdult:true" else "" + val formatFilter = format?.let { "format:$it, " } ?: "" + val countryFilter = country?.let { "countryOfOrigin:$it, " } ?: "" + + return buildString { + append("""Page(page:1,perPage:50){pageInfo{hasNextPage total}media(sort:$sort, type:$type, $formatFilter $countryFilter $includeList $isAdult){id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}""") } - }""" } private fun recentAnimeUpdates(page: Int): String { val currentTime = System.currentTimeMillis() / 1000 - return """Page(page:$page,perPage:50){ - pageInfo{hasNextPage total} - airingSchedules(airingAt_greater:0 airingAt_lesser:${currentTime - 10000} sort:TIME_DESC){ - episode airingAt media{ - id idMal status chapters episodes nextAiringEpisode{episode} - isAdult type meanScore isFavourite format bannerImage countryOfOrigin - coverImage{large} title{english romaji userPreferred} - mediaListEntry{progress private score(format:POINT_100) status} - } + return buildString { + append("""Page(page:$page,perPage:50){pageInfo{hasNextPage total}airingSchedules(airingAt_greater:0 airingAt_lesser:${currentTime - 10000} sort:TIME_DESC){episode airingAt media{id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}}""") } - }""" - } - private fun queryAnimeList(): String { - return """{ - recentUpdates:${recentAnimeUpdates(1)} - recentUpdates2:${recentAnimeUpdates(2)} - trendingMovies:${buildQueryString("POPULARITY_DESC", "ANIME", "MOVIE")} - topRated:${buildQueryString("SCORE_DESC", "ANIME")} - mostFav:${buildQueryString("FAVOURITES_DESC", "ANIME")} - }""" - } - private fun queryMangaList(): String { - return """{ - trendingManga:${buildQueryString("POPULARITY_DESC", "MANGA", country = "JP")} - trendingManhwa:${buildQueryString("POPULARITY_DESC", "MANGA", country = "KR")} - trendingNovel:${buildQueryString("POPULARITY_DESC", "MANGA", format = "NOVEL", country = "JP")} - topRated:${buildQueryString("SCORE_DESC", "MANGA")} - mostFav:${buildQueryString("FAVOURITES_DESC", "MANGA")} - - }""" } - suspend fun loadAnimeList(): Map> { + private fun queryAnimeList(): String { + return buildString { + append("""{recentUpdates:${recentAnimeUpdates(1)} recentUpdates2:${recentAnimeUpdates(2)} trendingMovies:${buildQueryString("POPULARITY_DESC", "ANIME", "MOVIE")} topRated:${buildQueryString("SCORE_DESC", "ANIME")} mostFav:${buildQueryString("FAVOURITES_DESC", "ANIME")}}""") + } + } + + private fun queryMangaList(): String { + return buildString { + append("""{trendingManga:${buildQueryString("POPULARITY_DESC", "MANGA", country = "JP")} trendingManhwa:${buildQueryString("POPULARITY_DESC", "MANGA", country = "KR")} trendingNovel:${buildQueryString("POPULARITY_DESC", "MANGA", format = "NOVEL", country = "JP")} topRated:${buildQueryString("SCORE_DESC", "MANGA")} mostFav:${buildQueryString("FAVOURITES_DESC", "MANGA")}}""") + } + } + + suspend fun loadAnimeList(): Map> = coroutineScope { val list = mutableMapOf>() - fun filterRecentUpdates( - page: Page?, - ): ArrayList { - val listOnly: Boolean = PrefManager.getVal(PrefName.RecentlyListOnly) - val adultOnly: Boolean = PrefManager.getVal(PrefName.AdultOnly) - val idArr = mutableListOf() + fun filterRecentUpdates(page: Page?): ArrayList { + val listOnly = getPreference(PrefName.RecentlyListOnly) + val adultOnly = getPreference(PrefName.AdultOnly) + val idArr = mutableSetOf() return page?.airingSchedules?.mapNotNull { i -> - i.media?.let { - if (!idArr.contains(it.id)) { - val shouldAdd = when { - !listOnly && it.countryOfOrigin == "JP" && Anilist.adult && adultOnly && it.isAdult == true -> true - !listOnly && !adultOnly && it.countryOfOrigin == "JP" && it.isAdult == false -> true - listOnly && it.mediaListEntry != null -> true - else -> false - } - if (shouldAdd) { - idArr.add(it.id) - Media(it) - } else null + i.media?.takeIf { !idArr.contains(it.id) }?.let { + val shouldAdd = when { + !listOnly && it.countryOfOrigin == "JP" && adultOnly && it.isAdult == true -> true + !listOnly && !adultOnly && it.countryOfOrigin == "JP" && it.isAdult == false -> true + listOnly && it.mediaListEntry != null -> true + else -> false + } + if (shouldAdd) { + idArr.add(it.id) + Media(it) } else null } }?.toCollection(ArrayList()) ?: arrayListOf() } - executeQuery(queryAnimeList(), force = true)?.data?.apply { + + val animeList = async { executeQuery(queryAnimeList(), force = true) } + + animeList.await()?.data?.apply { list["recentUpdates"] = filterRecentUpdates(recentUpdates) list["trendingMovies"] = mediaList(trendingMovies) list["topRated"] = mediaList(topRated) list["mostFav"] = mediaList(mostFav) } - return list + + list } - suspend fun loadMangaList(): Map> { + + suspend fun loadMangaList(): Map> = coroutineScope { val list = mutableMapOf>() - executeQuery(queryMangaList(), force = true)?.data?.apply { + + val mangaList = async { executeQuery(queryMangaList(), force = true) } + + mangaList.await()?.data?.apply { list["trendingManga"] = mediaList(trendingManga) list["trendingManhwa"] = mediaList(trendingManhwa) list["trendingNovel"] = mediaList(trendingNovel) list["topRated"] = mediaList(topRated) list["mostFav"] = mediaList(mostFav) } - return list + + list } @@ -1560,10 +1525,10 @@ Page(page:$page,perPage:50) { resetNotification: Boolean = true, type: Boolean? = null ): NotificationResponse? { - val type_in = "type_in:[AIRING,MEDIA_MERGE,MEDIA_DELETION,MEDIA_DATA_CHANGE]" + val typeIn = "type_in:[AIRING,MEDIA_MERGE,MEDIA_DELETION,MEDIA_DATA_CHANGE]" val reset = if (resetNotification) "true" else "false" val res = executeQuery( - """{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){pageInfo{currentPage,hasNextPage}notifications(resetNotificationCount:$reset , ${if (type == true) type_in else ""}){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""", + """{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){pageInfo{currentPage,hasNextPage}notifications(resetNotificationCount:$reset , ${if (type == true) typeIn else ""}){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""", force = true ) if (res != null && resetNotification) { diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt index e75383a1..f1db7ff6 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt @@ -91,17 +91,16 @@ class AnilistHomeViewModel : ViewModel() { fun getHidden(): LiveData> = hidden - @Suppress("UNCHECKED_CAST") suspend fun initHomePage() { val res = Anilist.query.initHomePage() - res["currentAnime"]?.let { animeContinue.postValue(it as ArrayList?) } - res["favoriteAnime"]?.let { animeFav.postValue(it as ArrayList?) } - res["plannedAnime"]?.let { animePlanned.postValue(it as ArrayList?) } - res["currentManga"]?.let { mangaContinue.postValue(it as ArrayList?) } - res["favoriteManga"]?.let { mangaFav.postValue(it as ArrayList?) } - res["plannedManga"]?.let { mangaPlanned.postValue(it as ArrayList?) } - res["recommendations"]?.let { recommendation.postValue(it as ArrayList?) } - res["hidden"]?.let { hidden.postValue(it as ArrayList?) } + res["currentAnime"]?.let { animeContinue.postValue(it) } + res["favoriteAnime"]?.let { animeFav.postValue(it) } + res["currentAnimePlanned"]?.let { animePlanned.postValue(it) } + res["currentManga"]?.let { mangaContinue.postValue(it) } + res["favoriteManga"]?.let { mangaFav.postValue(it) } + res["currentMangaPlanned"]?.let { mangaPlanned.postValue(it) } + res["recommendations"]?.let { recommendation.postValue(it) } + res["hidden"]?.let { hidden.postValue(it) } } suspend fun loadMain(context: FragmentActivity) { diff --git a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt index 43bf8bea..dfd39e6a 100644 --- a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt @@ -50,6 +50,7 @@ import ani.dantotsu.statusBarHeight import ani.dantotsu.util.Logger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.math.max @@ -457,53 +458,56 @@ class HomeFragment : Fragment() { var running = false val live = Refresh.activity.getOrPut(1) { MutableLiveData(true) } - live.observe(viewLifecycleOwner) - { - if (!running && it) { + live.observe(viewLifecycleOwner) { shouldRefresh -> + if (!running && shouldRefresh) { running = true scope.launch { withContext(Dispatchers.IO) { - //Get userData First - Anilist.userid = - PrefManager.getNullableVal(PrefName.AnilistUserId, null) - ?.toIntOrNull() + // Get user data first + Anilist.userid = PrefManager.getNullableVal(PrefName.AnilistUserId, null)?.toIntOrNull() if (Anilist.userid == null) { - getUserId(requireContext()) { - load() - } - } else { - CoroutineScope(Dispatchers.IO).launch { + withContext(Dispatchers.Main) { getUserId(requireContext()) { load() } } + } else { + getUserId(requireContext()) { + load() + } } model.loaded = true - CoroutineScope(Dispatchers.IO).launch { - model.setListImages() - } + model.setListImages() + } - var empty = true - val homeLayoutShow: List = - PrefManager.getVal(PrefName.HomeLayout) - model.initHomePage() - model.initUserStatus() - (array.indices).forEach { i -> + var empty = true + val homeLayoutShow: List = PrefManager.getVal(PrefName.HomeLayout) + + withContext(Dispatchers.Main) { + homeLayoutShow.indices.forEach { i -> if (homeLayoutShow.elementAt(i)) { empty = false - } else withContext(Dispatchers.Main) { + } else { containers[i].visibility = View.GONE } } - model.empty.postValue(empty) } + + val initHomePage = async(Dispatchers.IO) { model.initHomePage() } + val initUserStatus = async(Dispatchers.IO) { model.initUserStatus() } + initHomePage.await() + initUserStatus.await() + + withContext(Dispatchers.Main) { + model.empty.postValue(empty) + binding.homeHiddenItemsContainer.visibility = View.GONE + } + live.postValue(false) _binding?.homeRefresh?.isRefreshing = false running = false } - binding.homeHiddenItemsContainer.visibility = View.GONE } - } } From b109d50d89c11f9058625157000d749bdd295308 Mon Sep 17 00:00:00 2001 From: rebelonion <87634197+rebelonion@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:55:37 -0500 Subject: [PATCH 23/26] fix: markdown options hidden behind keyboard --- app/src/main/AndroidManifest.xml | 2 +- .../main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4c267042..15e5b0a2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -202,7 +202,7 @@ android:parentActivityName=".MainActivity" /> + android:windowSoftInputMode="adjustResize|stateHidden" /> Date: Wed, 26 Jun 2024 15:02:12 -0500 Subject: [PATCH 24/26] fix start keyboard expanded smh --- app/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 15e5b0a2..1a6ab597 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -202,7 +202,7 @@ android:parentActivityName=".MainActivity" /> + android:windowSoftInputMode="adjustResize|stateVisible" /> Date: Sat, 29 Jun 2024 10:59:18 -0500 Subject: [PATCH 25/26] fix: some markdown fixes --- app/src/main/AndroidManifest.xml | 1 + app/src/main/java/ani/dantotsu/Functions.kt | 15 +++- .../main/java/ani/dantotsu/MainActivity.kt | 8 ++- .../media/comments/CommentsFragment.kt | 1 + .../ani/dantotsu/others/AlignTagHandler.kt | 32 +++++++++ .../ani/dantotsu/settings/SettingsActivity.kt | 14 ++-- .../dantotsu/settings/saving/Preferences.kt | 1 + .../dantotsu/util/ActivityMarkdownCreator.kt | 9 ++- .../java/ani/dantotsu/util/AniMarkdown.kt | 65 ++++++++++++++---- .../java/ani/dantotsu/util/AudioHelper.kt | 58 ++++++++++++++++ .../res/layout/activity_markdown_creator.xml | 15 ++++ app/src/main/res/raw/audio.mp3 | Bin 0 -> 34560 bytes app/src/main/res/values/strings.xml | 2 + 13 files changed, 194 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/ani/dantotsu/others/AlignTagHandler.kt create mode 100644 app/src/main/java/ani/dantotsu/util/AudioHelper.kt create mode 100644 app/src/main/res/raw/audio.mp3 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1a6ab597..9b071c2b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ + diff --git a/app/src/main/java/ani/dantotsu/Functions.kt b/app/src/main/java/ani/dantotsu/Functions.kt index abfebb3a..c41d22ce 100644 --- a/app/src/main/java/ani/dantotsu/Functions.kt +++ b/app/src/main/java/ani/dantotsu/Functions.kt @@ -68,7 +68,6 @@ import android.widget.ImageView import android.widget.TextView import android.widget.Toast import androidx.annotation.AttrRes -import androidx.annotation.ColorInt import androidx.appcompat.app.AppCompatDelegate import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -98,6 +97,7 @@ import ani.dantotsu.databinding.ItemCountDownBinding import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.notifications.IncognitoNotificationClickReceiver +import ani.dantotsu.others.AlignTagHandler import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.others.SpoilerPlugin import ani.dantotsu.parsers.ShowResponse @@ -119,8 +119,8 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withC import com.bumptech.glide.load.resource.gif.GifDrawable import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.target.Target -import com.bumptech.glide.request.target.ViewTarget import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -1448,6 +1448,8 @@ fun openOrCopyAnilistLink(link: String) { } else { copyToClipboard(link, true) } + } else if (getYoutubeId(link).isNotEmpty()) { + openLinkInYouTube(link) } else { copyToClipboard(link, true) } @@ -1484,6 +1486,7 @@ fun buildMarkwon( TagHandlerNoOp.create("h1", "h2", "h3", "h4", "h5", "h6", "hr", "pre", "a") ) } + plugin.addHandler(AlignTagHandler()) }) .usePlugin(GlideImagesPlugin.create(object : GlideImagesPlugin.GlideStore { @@ -1527,3 +1530,11 @@ fun buildMarkwon( .build() return markwon } + + + +fun getYoutubeId(url: String): String { + val regex = """(?:youtube\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|(?:youtu\.be|youtube\.com)/)([^"&?/\s]{11})|youtube\.com/""".toRegex() + val matchResult = regex.find(url) + return matchResult?.groupValues?.getOrNull(1) ?: "" +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt index 7b2e0b1d..412ed676 100644 --- a/app/src/main/java/ani/dantotsu/MainActivity.kt +++ b/app/src/main/java/ani/dantotsu/MainActivity.kt @@ -2,7 +2,6 @@ package ani.dantotsu import android.animation.ObjectAnimator import android.annotation.SuppressLint -import android.app.AlertDialog import android.content.Intent import android.content.res.Configuration import android.graphics.drawable.Animatable @@ -60,11 +59,11 @@ import ani.dantotsu.settings.saving.SharedPreferenceBooleanLiveData import ani.dantotsu.settings.saving.internal.PreferenceKeystore import ani.dantotsu.settings.saving.internal.PreferencePackager import ani.dantotsu.themes.ThemeManager +import ani.dantotsu.util.AudioHelper import ani.dantotsu.util.Logger import ani.dantotsu.util.customAlertDialog import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar -import com.google.android.material.textfield.TextInputEditText import eu.kanade.domain.source.service.SourcePreferences import io.noties.markwon.Markwon import io.noties.markwon.SoftBreakAddsNewLinePlugin @@ -455,7 +454,10 @@ class MainActivity : AppCompatActivity() { } } } - + if (PrefManager.getVal(PrefName.OC)) { + AudioHelper.run(this, R.raw.audio) + PrefManager.setVal(PrefName.OC, false) + } val torrentManager = Injekt.get() fun startTorrent() { if (torrentManager.isAvailable() && PrefManager.getVal(PrefName.TorrentEnabled)) { diff --git a/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt b/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt index fc21805e..de1c76b8 100644 --- a/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt @@ -353,6 +353,7 @@ class CommentsFragment : Fragment() { } } } + @SuppressLint("NotifyDataSetChanged") override fun onResume() { super.onResume() diff --git a/app/src/main/java/ani/dantotsu/others/AlignTagHandler.kt b/app/src/main/java/ani/dantotsu/others/AlignTagHandler.kt new file mode 100644 index 00000000..1708ed05 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/others/AlignTagHandler.kt @@ -0,0 +1,32 @@ +package ani.dantotsu.others + +import android.text.Layout +import android.text.style.AlignmentSpan +import io.noties.markwon.MarkwonConfiguration +import io.noties.markwon.RenderProps +import io.noties.markwon.html.HtmlTag +import io.noties.markwon.html.tag.SimpleTagHandler + + +class AlignTagHandler : SimpleTagHandler() { + + override fun getSpans( + configuration: MarkwonConfiguration, + renderProps: RenderProps, + tag: HtmlTag + ): Any { + val alignment: Layout.Alignment = if (tag.attributes().containsKey("center")) { + Layout.Alignment.ALIGN_CENTER + } else if (tag.attributes().containsKey("end")) { + Layout.Alignment.ALIGN_OPPOSITE + } else { + Layout.Alignment.ALIGN_NORMAL + } + + return AlignmentSpan.Standard(alignment) + } + + override fun supportedTags(): Collection { + return setOf("align") + } +} diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt index 2022fac1..7e58e870 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt @@ -23,12 +23,12 @@ import ani.dantotsu.databinding.ActivitySettingsBinding import ani.dantotsu.initActivity import ani.dantotsu.navBarHeight import ani.dantotsu.openLinkInBrowser -import ani.dantotsu.openLinkInYouTube import ani.dantotsu.others.AppUpdater import ani.dantotsu.others.CustomBottomDialog import ani.dantotsu.pop import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.startMainActivity import ani.dantotsu.statusBarHeight @@ -217,10 +217,14 @@ class SettingsActivity : AppCompatActivity() { settingsLogo.setSafeOnClickListener { cursedCounter++ (settingsLogo.drawable as Animatable).start() - if (cursedCounter % 7 == 0) { - toast(R.string.you_cursed) - openLinkInYouTube(getString(R.string.cursed_yt)) - //PrefManager.setVal(PrefName.ImageUrl, !PrefManager.getVal(PrefName.ImageUrl, false)) + if (cursedCounter % 16 == 0) { + val oldVal: Boolean = PrefManager.getVal(PrefName.OC) + if (!oldVal) { + toast(R.string.omega_cursed) + } else { + toast(R.string.omega_freed) + } + PrefManager.setVal(PrefName.OC, !oldVal) } else { snackString(array[(Math.random() * array.size).toInt()], context) } diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt index 6f188b85..c71a6a9b 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt @@ -191,6 +191,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files SubscriptionNotificationStore(Pref(Location.Irrelevant, List::class, listOf())), UnreadCommentNotifications(Pref(Location.Irrelevant, Int::class, 0)), DownloadsDir(Pref(Location.Irrelevant, String::class, "")), + OC(Pref(Location.Irrelevant, Boolean::class, false)), RefreshStatus(Pref(Location.Irrelevant, Boolean::class, false)), //Protected diff --git a/app/src/main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt b/app/src/main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt index fdc24937..5548401e 100644 --- a/app/src/main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt +++ b/app/src/main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt @@ -6,6 +6,7 @@ import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.widget.addTextChangedListener import ani.dantotsu.R @@ -42,7 +43,7 @@ class ActivityMarkdownCreator : AppCompatActivity() { BOLD("****", 2, R.id.formatBold), ITALIC("**", 1, R.id.formatItalic), STRIKETHROUGH("~~~~", 2, R.id.formatStrikethrough), - SPOILER("~||~", 2, R.id.formatSpoiler), + SPOILER("~!!~", 2, R.id.formatSpoiler), LINK("[Placeholder](%s)", 0, R.id.formatLink), IMAGE("img(%s)", 0, R.id.formatImage), YOUTUBE("youtube(%s)", 0, R.id.formatYoutube), @@ -267,9 +268,13 @@ class ActivityMarkdownCreator : AppCompatActivity() { private fun previewMarkdown(preview: Boolean) { val markwon = buildMarkwon(this, false, anilist = true) if (preview) { + binding.editText.isVisible = false binding.editText.isEnabled = false - markwon.setMarkdown(binding.editText, text) + binding.markdownPreview.isVisible = true + markwon.setMarkdown(binding.markdownPreview, AniMarkdown.getBasicAniHTML(text)) } else { + binding.editText.isVisible = true + binding.markdownPreview.isVisible = false binding.editText.setText(text) binding.editText.isEnabled = true val markwonEditor = MarkwonEditor.create(markwon) diff --git a/app/src/main/java/ani/dantotsu/util/AniMarkdown.kt b/app/src/main/java/ani/dantotsu/util/AniMarkdown.kt index 3f8c04d7..200b3883 100644 --- a/app/src/main/java/ani/dantotsu/util/AniMarkdown.kt +++ b/app/src/main/java/ani/dantotsu/util/AniMarkdown.kt @@ -1,12 +1,13 @@ package ani.dantotsu.util +import ani.dantotsu.getYoutubeId import ani.dantotsu.util.ColorEditor.Companion.toCssColor class AniMarkdown { //istg anilist has the worst api companion object { - private fun convertNestedImageToHtml(markdown: String): String { + private fun String.convertNestedImageToHtml(): String { val regex = """\[!\[(.*?)]\((.*?)\)]\((.*?)\)""".toRegex() - return regex.replace(markdown) { matchResult -> + return regex.replace(this) { matchResult -> val altText = matchResult.groupValues[1] val imageUrl = matchResult.groupValues[2] val linkUrl = matchResult.groupValues[3] @@ -14,26 +15,49 @@ class AniMarkdown { //istg anilist has the worst api } } - private fun convertImageToHtml(markdown: String): String { + private fun String.convertImageToHtml(): String { val regex = """!\[(.*?)]\((.*?)\)""".toRegex() - return regex.replace(markdown) { matchResult -> + val anilistRegex = """img\(.*?\)""".toRegex() + val markdownImage = regex.replace(this) { matchResult -> val altText = matchResult.groupValues[1] val imageUrl = matchResult.groupValues[2] """$altText""" } + return anilistRegex.replace(markdownImage) { matchResult -> + val imageUrl = matchResult.groupValues[1] + """Image""" + } } - private fun convertLinkToHtml(markdown: String): String { + private fun String.convertLinkToHtml(): String { val regex = """\[(.*?)]\((.*?)\)""".toRegex() - return regex.replace(markdown) { matchResult -> + return regex.replace(this) { matchResult -> val linkText = matchResult.groupValues[1] val linkUrl = matchResult.groupValues[2] """
$linkText""" } } - private fun replaceLeftovers(html: String): String { - return html.replace(" ", " ") + private fun String.convertYoutubeToHtml(): String { + val regex = """
""".toRegex() + return regex.replace(this) { matchResult -> + val url = matchResult.groupValues[1] + val id = getYoutubeId(url) + if (id.isNotEmpty()) { + """
+ $url + + Youtube Link + +
""".trimIndent() + } else { + """Youtube Video""" + } + } + } + + private fun String.replaceLeftovers(): String { + return this.replace(" ", " ") .replace("&", "&") .replace("<", "<") .replace(">", ">") @@ -46,18 +70,29 @@ class AniMarkdown { //istg anilist has the worst api .replace("\n", "
") } - private fun underlineToHtml(html: String): String { - return html.replace("(?s)___(.*?)___".toRegex(), "
$1
") + private fun String.underlineToHtml(): String { + return this.replace("(?s)___(.*?)___".toRegex(), "
$1
") .replace("(?s)__(.*?)__".toRegex(), "
$1
") .replace("(?s)\\s+_([^_]+)_\\s+".toRegex(), "$1") } + private fun String.convertCenterToHtml(): String { + val regex = """~~~(.*?)~~~""".toRegex() + return regex.replace(this) { matchResult -> + val centerText = matchResult.groupValues[1] + """$centerText""" + } + } + fun getBasicAniHTML(html: String): String { - val step0 = convertNestedImageToHtml(html) - val step1 = convertImageToHtml(step0) - val step2 = convertLinkToHtml(step1) - val step3 = replaceLeftovers(step2) - return underlineToHtml(step3) + return html + .convertNestedImageToHtml() + .convertImageToHtml() + .convertLinkToHtml() + .convertYoutubeToHtml() + .convertCenterToHtml() + .replaceLeftovers() + .underlineToHtml() } fun getFullAniHTML(html: String, textColor: Int): String { diff --git a/app/src/main/java/ani/dantotsu/util/AudioHelper.kt b/app/src/main/java/ani/dantotsu/util/AudioHelper.kt new file mode 100644 index 00000000..bfd38be0 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/util/AudioHelper.kt @@ -0,0 +1,58 @@ +package ani.dantotsu.util + +import android.content.Context +import android.media.AudioManager +import android.media.MediaPlayer + +class AudioHelper(private val context: Context) { + + private val audioManager: AudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + private var mediaPlayer: MediaPlayer? = null + + fun routeAudioToSpeaker() { + audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) + audioManager.mode = AudioManager.MODE_IN_COMMUNICATION + audioManager.isSpeakerphoneOn = true + } + + private val maxVolume: Int + get() = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + private var oldVolume: Int = 0 + fun setVolume(percentage: Int) { + oldVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + val volume = (maxVolume * percentage) / 100 + audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0) + } + + fun playAudio(audio: Int) { + mediaPlayer?.release() + mediaPlayer = MediaPlayer.create(context, audio) + mediaPlayer?.setOnCompletionListener { + setVolume(oldVolume) + audioManager.abandonAudioFocus(null) + it.release() + } + mediaPlayer?.setOnPreparedListener { + it.start() + } + } + + fun stopAudio() { + mediaPlayer?.let { + if (it.isPlaying) { + it.stop() + } + it.release() + mediaPlayer = null + } + } + + companion object { + fun run(context: Context, audio: Int) { + val audioHelper = AudioHelper(context) + audioHelper.routeAudioToSpeaker() + audioHelper.setVolume(90) + audioHelper.playAudio(audio) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_markdown_creator.xml b/app/src/main/res/layout/activity_markdown_creator.xml index fb66d6f0..548ad177 100644 --- a/app/src/main/res/layout/activity_markdown_creator.xml +++ b/app/src/main/res/layout/activity_markdown_creator.xml @@ -92,6 +92,21 @@ android:textIsSelectable="true" android:textSize="18sp" tools:ignore="LabelFor" /> + + diff --git a/app/src/main/res/raw/audio.mp3 b/app/src/main/res/raw/audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..49e1e430f00161703b3ef817055fd833ed1c4eed GIT binary patch literal 34560 zcmezWdkPB!{|5$!Oa=x94h9BZ1qKF21_s6@3=9kk3=Ad>3=Awjj=rwU44fPcEDWQ7 z${{cs{!|W75{(-Te-ZpUMGBqH&|)Phx;kSvffTIVYgjze0B#%#{r;Hbrk> zdy*z)z?479W69D5vpDbNlop0tpElJkr$6P-`lmJ$u@*i7F-qiH|uaXaxmbnZ+Fil1NOO zB*k)HWum9IqlxgIJ9oC3bSf@MYLwZKR?u?#RN#uRg^?RmZd_%#u+2lUPsyOlNSgcb zL79UpX7AsZK7M@FD{zIqfwk98uO(g=d^zsixl!CHC}U!A@CoO1kG0;8rkW~YOD@iAREhF8H#%H#gVqRr1JqaQK+8LdcS(D^0|<1O;92IdoRV)R5Iw zgoi;zfkA+nr~-#SM^lX7zKM+T2Hu_5FDWQL<8F*(SCOdVEYMbX(#-ETk%1v1kuCh} z>HVkozdWS=|3%0Avxk)IIvNk?@Uya>|KT;=^}k{NKYPO+PZ$p*D=;uWxa#Tqq2BQC zxm`-?v3CU+9FEugXgu;c}?)5W+m;YL|>dT%*7W%wd%%7{fwVuqOrP(OWNpScxFs(Ir z$yg{66uRt1uZS|2SIuMb#VSRb{&SMPr1ZHmvgX6<~(y?=dRC= zjozn!&a_>>{{P$mXKt`4%;~%KIG5q%tWVcG-bZFI8b9&cFpI;eaEZRvjEN`KuPYEe zSTfP)YDnuU(e%yl!WL{wUXvGQ)Xx*~Xx_!Qn&-={y7d#H2;lEJjL}sh3Qb@{7UW3I;yiOdJUZ#_IGFVAD z<7lsC0QceshGp9xWJj%i^r~);f9*Ezs0*83v3xD9GTbgc#UsLEikjC{hk%@gm;dct zdm^$~WDj?GlbF>WiB(VkeB-@5+?v^Y3mb6TfttqpeV8k_PL=0}R2@1*U7m zK9<_YsqeeZx;7)Sl&NahUWwb?r&M%gyo6P~7HF6Sy!;c(G5nk&ELhywtm@kNU=wGl0!qf00Zak6ox{$my4(iXjX<5R@HYuU^_GtBc={W$T4 zN#~kzmdnmdBA4uzuAV=2*t@erZ8+MpBts@LPVM>2*hegaExym1?C;*af0k$edQF!NJ3RMaZ++3xu|Cy^ z?_|Il^(NbgdH=tBZ{XxO?I+G*vEhFQLnDiVP0i_$5pAQL{F z^~;{W^nIk15q(CXuX^|Hp9`mdeX21{t!4K7IPpb_s@0m4XLPkDS}3x{iI)FgzQ?I0 zbvgHkM~D9@ur!O9D4IPjv_5iUcFw&2skSG6=WJfP>1K-$ALAyDFD}J@h33jcXFTc8 z5pelAZRUqX!fK%xQi@fUmfL^6=vS70e`UzucTV&34?j`p5@_H%6(-igSzYTocY}wa zvy*UMxbPm|&TS{(e+$0TzwNZuZQU6z2OdZ(^t-s*U(~#KbX%Xtw}UMHGc)INDH^55 zvbh_1g%|&~6?vXk`)jKIy(gOA{@6KjEaYH#votZoVfywfk+*#ueWBsM(f{hjXVM8_ zn!k409M0j&)lFU6lECS^bj_9xJ&Z=O31*w?vy!m4zWi7`in4So5a zeVt#E7T>XAQURM?v!Uquv!@@upT(Q~?!@fpPd^FYD_3x>7o1h4UUc#092J{;J5Rjh z`KJDO{|?Dt9y9qGI5@X!T)V6JQr>J!yvyYip~ka3)+KSySl0fz>ij-q{Uafs7R>UF zGrFEXd-}2bEZf%aDZZaS{p5UaJwa)I$5~t9of%)w3CZQhJSk_Z7XEnu4$ohe;H@`3 zINQF>VSTtFW!WNSF4LXQo@gW{$iI@ga!2Nw>#x5&3fu|~UmT6+Pqp8q$<8Dev8dGl z|DvrnhWVTy-gm88{#@w0rSO@T-$iT_XPj|ZELB&~RJL1_(cs?YhCQY{Q?9rqx^1_) z)5@d6{MUKb?B+Qq_Qx-5VNBtW>rgBHwEUZ^!ULz%B7Q6Xe+gUOru)!N_G9RjKab*` zD^C={L_{Geon)~fDey$&b7Pl$h57yg`vS$gDE~}?vtQv8|$rh zrWT3zI=s9(`98;+?`8Ark1gM`#FTN;9F`Q{?d}&-1=3hl8+c?nPW}J?eZ{7EceZ!> z+E4WP`+0(lmYhtu@$y?Q6pnc4FfgbxtIfUq^up<>!flqducbMxDwhl2+81l`?bp}! zOEp>_COqhvx~3~{^)#(=rJbwl+&usP|8b*r!ZKwhMOCY_)>@r04-U)Sc;`K*B}m}8 zm11F!OVa@crU0YNyLPcWI|D6}r{CMSp?UeNLfh3(FZ<`OkNOqV5hupS(HV7O*{hvN z%Zs#UEq{_S|NsBqj!3Q~10|v0HJ3N7n0k)K@NM6dohMus`HB~bOCs7x;PlU=w8`gs zBAf7oFwa?U6b{vJH9E#Ob5t`1XlQ>(;EL~-l<+&Qa^nPAnE1->u&S-ZgQTFO2^G&uMDo&Yt9T8;&@y(%m;$V~@MgucNKotN30|Pkx8JOn! zM`kukFa)oBt!tpnz|Cr%zF4GMFkyMlm!$rrM_cYD2r&0vF!U8*e%Ke4c&;P9z)L~1 zS!(8ClYW1rhCI%MEz23c*7Ed+GH&y)`n{Cv^R?;!|NsB1BPBHBh+1a7&6zVbYp%^Z z+FC55GNa+R;EfsnqRU=0G|1#Nc4aik^Aug?D{=bW6v*f!apj!klB&un%mv=8OD)(} zzeuTC!MmyY`dy9id0Y1X|NsAG!i6cv6pg)qK1)k}nzrSej`oa)Do5BX`i>k*@il2+ zXo~V{x6qM0?NxTc^|)E`zg*3bnakh5jl6YGUtjW)(bP;UJ(k4cORv|5WbL@)(WE0- z*q8dt)c#agz+a!8bMK|!I`h)#&8r1Yx1(Yi*%x&m@NFsjq&CZe!BJP;d5#!=>Qd_r zrN^@k|C>!&<#YM_w>57QrcdvADe1Lkt~isy&X=XvSDD7-sW^&p>^N@pS8D!KSB<)5 zG3VYJzfCKdSymdLn7dZ5q4}cg1GPhzC7>vPhW~$>ZoxI`l4 zVD!p2Ce2(k)Up^DxIC|z^4!=O;mPqTx_(;O^7BrApU={{`=js2$~o-MPu4gJZ25BG zyxFvAUwd8VtYnn`ub9sNziI8hq_x&7Km6ZyXK~5Gm0JI&B;Pq@eVFCO3;|l0se9sy z3j_D{92V&7lcwiS)@F+K+TFXV;*6$3@~oCcp%YEI(x|-2Ifuo+>3fHZDM}mMoxl6%za@cY!PlxP=Oi!baaDTru3clA zuh*JmZPhEiQq!;fepgYwxh~nOr%d$T_KT-hb-Ys1JN|EP;F*jwXa2QYEMFoXI)#aW z?{uYX&tr?dx-k(gIfu*Y-ar2}(dDLpZVtQ|N71tDv6B1DqJRKJ}>P^4pn#Lk2uGoSvCG+tPqb z>V4>5Rxgnh9$gQW1s4>id%soM%*2ry*>&$!|E7W`UYi0|Nv;q2=jHDbkXo8;Jpca% zjg73m6R$4?#Q-$@FANBL%`CwXqQzyKkj%h!P}_gmOh(2C)yyxK6%-f*wN;v!@(O~k zR|K~%xy`?S&zTcsE{rcQEXG`G;q-lKWwQf~7F_TaP)3(PhO zyxX!?;msPYwXeesD+LzpUT@^@ZO)_pEx7WjRQ;Na)m8zEAoyfgt!~pYu2$9XB?e)g0ZUeWY2&)+Z6M9Tz@U3Tp!mh( z{>5H1ubjzUpV+zbs@*rsnwXDg7sbE5d}oQb!la)k?(li}SvGO_U*4~GqHN`zJ*xA0l8xyj#YFF($_ z+f^$P|Eusqt|7PEnPP@GyUj{3GG^3V%h}iLUUKo2a`};4UoWy|X?x}rXk-TJx_tWi z@n7oKqo*Z1?6=Bv=_;g#1&OpOp4)G(8n}1LoRUT6tHO!{l;^p9Xp(V~o6)*B&9XsX zK5}A-iH}{jS-j)oT^UaWcR$LldeO91bjdag5t9|u7FE{#_;2*<(bJwI^0)e2#U>c7 z*5q=U@H~F@q!ss0`B?=@U)8o;A$VT7!jVsr7nJT{BSg^j-{u|hVvX*SfRov>qODuH zHFcApcHHYsRb5lFQTk%c9EIgMi>wR&0xK^*DJ%z3Vv zIWTNzD2Zx6XExpOgVN4um5LWDtu$9(=FLCRRvZ!tXkhvO|L-MtYyEd_ z+k*_0OdejlvibTafdyw8=d6&i$vmQ-c5(s(+YjTH&(}}=mCQG<<>lr-rfb`lo?c{M zs9>TveV-an#>{d?ltD?J>&7NuKt zzW!;^Fjh#{V&{i-0HEPN(<{0%Rn93`(>q^h@d>Yu_W4glY7T~}zx~tHKO=fd=9`mM z2ejU?{Mozas?6Eba{H>%<{a@n9(hDPcYYQx%Z8km?dO)%F6Us{KgZWK_U@alK^K}1 z&p-YD-}Mli(_1#pXzHJJCuUdl#=S0wdF&?MUm?8ql*zZ0y$Pb_Om%VBvij1V_Q%^A z`z5J7Uh_zJ+xe|*O%Y~J(a(eIF1IkopHo}3?%lVjl^KqS=b!%npS?;h^;V>hqx;!B zy;$8F_ZB6#$$PxN!g*OnamCmXm)QEZDH4hD9W4!I$^{zpR`ZcBj(kK~GD$^O^OlFP!k4*~)U5*ZyM0 zRl|d`HZ5^vu&iLqW&gV1^u$+30>#UknYFC{)z5v9*&qL5OQ(-sLbYwklfL4a_M5r5 zx!Vsox@qzI{r~SZGc@=3lye*>+)Hdu{o2dZGIK_NkHO`?V!gqj!sh^!69Xfx{kh)b zc4eC0gwU7L`8r20ajn%&Z_(M-7`k-Lht_Eqq5?$9-2}W?&#*;Ldu$e+YBp)dtjFrn z`>&SIuZ=9OT<5<_E9M}No6I@a4-EUY&vVxQQQ)YWAj;_K%DgWB|Nn|t%B7m;5~EKg zZ`~haFro9NLt=tAf7F%*8t-}(rf|>T*{*vmXWOY<)k4!_e%tF;y|??d>DlL)`6VGc znd$ zMAk*O%--~6y{A|cuT^0FwY5>%?@qZSI7*y6zv%grjD6{KA2t2t_%t%O7-tmx|NnpC zt+a0(3)`LB6Tj5`PI6c7D>UJEEj{=I)ck~n|75S*Kbg!Nf-LR-SgEc=2P@AbKbpj z|7)WoP0AgCY;XSmE1#BXUA^qnxkG-(-9D^e`@33k$x$T-snjO3trt3&)ZCZ^7%dxA zmqb=v-uyYQ~xBa_*U4r>mV&yBu)a%o@U5lyxe<)!9-(;_Cg6mluto&W#br!&j* zOYTM9@VxQjp6J*8`6(tdmUuAxY;e1_i-W;|g@u8!p#ILaxRo2fbYHM(T-_JDb;GS1 zp;`J7%gRf2UOkFUo1>9%(#UN4?Umv6W|!aHD_q9H{7u3qdTQMK|25}=C8LwJ$h7TV z*>0F~>r}4Fwp&bR%_kYMFeorFF)-|~f0wNva^sil3pt0Y$MtUA$hG726<-r<8Fd6}&9 zIOqNDXIi(nOZrECa?*>Meo@*5-yfr8!$Q`+aMxwpDB)066o1&X$&D{iU3TV#e!UB+tqu$f z1}qJnJDAPAUj3V>J~=_c&|&@;O=gF)tnv5u&oOb)NY!*IwqE%AOpEabbD={_6F3f? zx&QuXPHAM&_JuNAxQ-+;-aJ{)bB$BN(q&Frnv>0L$El!d7?%DQ``@l~GA#B~SCg)ds#~j~S8R(bl zW^qpExYJ%fugQ~F?TqRQ%n_ab|LgQ!o3BpmpP4=5o4%IjnH9$+kIr}zzVKvFQE9k# zpqFmbrvLvoeGjYJbaA2R905lYWBI=kGoNX&q;0z_QvOu^)Me>;OI#Qk|FGDyFG}v0 z?K)6b$@OW{J3ZyunFh0h&+AS3Sn%k&zYFiSuDD0~+oo5`OB}mkka~=JZutLO_1r<* z4j+!vkvNjD;F99!-(FwOF{>-CXb5w3o#saqQ+>gq5AT1i@QEp8HJ=g6+1;nG>@!!b zVY(Wph^PNDohJvCIz2*+7##GHa@Z`CPKRmr>|83n^HbeNEq$(jGy9CMGk5+jv2T8< zHD742zDn7?Pyft*78-JS_w9W(bNX3+FDs79Vthjr zLqiN07`%DYbuEtG468dfbLIX$e`NI&>{dLoy{Y{EZMe7>yJm-O-o&5wH?4L}c(h~j z4ExWwLXwaF|NsB||08#5d!%<6oi5ie*}7)-BAc0cVJAznTs&4JFfdG$Iu}*gy=l$g zUgO~U${$v72WkV4RBv*>e`}rI6uw}AsJqIa>Nk~^DjqF#H>iKUWySIS|NsBb|KGXe zw}g2~>a=%pFS8@fT`G<4tey1YO2@oGE;TI zX27 zeeqjYr<<+@4PA-)|Nqxd%sX{W?WdnjSrE{WykPq=pXT*W%cB;yF!C`ldokbD z_?0$A$T)sm<%N^mJ#9QR_22Qn+OYaxrRbq43E@?%JZ%lp$^oWIC*ZP!%)lJfc427!I1 zclo*(tm59y^J4-tgLkt(--9W6k3+)FdiHphS8BYd<>RjEpZ$9F&!1X`NmnwQHOfzg zZ8T5uH9IaBuya?R_`=BBfBrAt9-=sZk?7h|t$8or+g(doER(%UR@^Z}yi5|-@PsaA zgNFYqj~j_keG5W8rFm>tuH%}k7`b#ouw(ENZIdlr88J+a3=c${&Yy{SDA4Mg_4?cO z$n`H8Q$yU>y^W1H7S{WZsc-2GHnAI9_{B9gy13gcRp8>(TEybqv_jYamBxRAU3%Bn z&%Stg&9`SE(di7E|8DuTRQ6nL+)+KQ>(3XQC{oLgs&>)-wSRuo>(0Gd$$LY}>rWY% zKW^H%t6AZoL*)K|#QmFPpLLn9%}kaKy|PR{P$|+;!Dw;O!sSQ#KIJyNa1cA7v)pA} zj(0-7%HFOR(e>vYCp_`Fw&hz#_}6-S*6YGmSB_V$c=vxw+WX_I$)#+LO%0pt796fm zvpy@5A9d-N+3J-(af@6wH94fZJ$3f)l6`U+)C7g~e;Jr+Os@v!>oEDZ?OLV#w86X6 zHU4SOo|lChtN(1AK3C%cmx5Csga59X)464ulT?r2JaR_R*7uGLZwbTQ?Mb`77|%|8 z*SEWqy?y%heNPyt%q&#i_kS%{YV1<}yUbsc!o|()@BjGPvHqX^DH)~-s*8FV7O|*3 zVEMgrX2Hf2W=b)&b9@d8U%7kuz;y@S+hyA8oh`S?y?DK2LPcd|^*aYi&*?(e?|)rb z7J4h-?*;oX)xzT9zyJFqy8b`@v+#f>Qxsc(o3gPs?rKbt7^wux7$M40FO z+P~X&wTY}bT_Uw(`-7zHdjCh}RldZm)M`QN7-$ z%XRG1wngTlYH-Y(kRtZ?=VAx~+5d`!-dD-7jaK-Tg~LQEyLSMwr>ut%AE&7?*Te-}==h zIkk^xrKk1v%l}skG^A?%YE58}U=VqCL-fAXY^_3j&gkwl_X~t){XX{Ln)T5$oWH-l z|3AUwe{snF|I_=0 zPmx2kfgyn*cy~g0O;*mzBY#x$^o?r|_+`JBJ{<90+(7;P?fw58o^P+>dA>p>z{3Cg zDwYoSl9a%DiITY-`8Qr^xGmnq;(T=C#svyZbNOE=)c^lve!-$e5ncg6!@t}2`XzUf z1A&>letA5a!R>3Exokn;!p!BlCReyj3>X*~xVIPX&zhDckP&-iWAKV>QM-*%eF|J* zS48>iEhew*51O>}OF)fsXZruzcQTB-R7=)$+;5V2y!rF1z}5-B7B%jgesTX_kq0y0 zeq@>w{OK730|R5(JM*_eXPtPtcTCeN4dyQErcRXp;4D*CSL%lz z8Osj2cZQv@_h!hmxgA%N5Ne$CTb=R3Be^vR2bfgKxs@_&qt@hpHrr6~#FuB8?BD+v z(so;iyjm!C=wHBstGndguD@_#)rjjAYI-Df_v9P~&f6j?J9U+>^eu31H)Oth%usvx zW#$JbZag{66krse^1w>Q`D%{@gG=AT6kq+-p}qUkuJOxsE}U|B|NdV**VD7+hA7{N zKP4iy-TB3>zZ?upxi2mXh;feIX#(niLBoGd;O&>sMUI4aS=ZV;n$b1aJ@t)*XJhb+ zH+!2o4SX1+dZq~GMKoRA`t_nxkZFy$t*LH(^ebjAft%0e76);9YpiMCqoZP$eAF#8 z?EAZ&95c>2^1gpkx7NjdSH#w(yN-p$J5P*xb!*OpY5Dytryf7Z*kEaLfN{l1N3o|J zy3z5mLaNH)2a7KUUH`gofx$xFd4;b`6{c+JI#GS-kaL<%NNQ+)-RlXRJx>Jg{d0eH z#ie>_cU1J6)YLDQqUYAGJja>#E+H*5o zMsmq}weI=bH8Yl6_$Rupt)(#S>|=prDcW;$f~KrE^;|RjpZ4dbg0|F%({*!1w$>~1 zG0xq=S^sji=4GC8Yn2HM3=S(87*h5fxN&{suiGW2VpA(OU9&Luyyq)!pBHq|bKxKD zTwabxXJ;Q%>^Tu`bz`Y!;M8+L>;Hs5XL-nTYU8xO7MfZAoMf1-3xxh(4hvo;@vh7h zlmigu|E9pmSIok{!o0L?9g^z3mP&`J_hcUoS)8@xV{b(qgT$IPsoo=gM(XP}u3>MP z`K=^WZxK)Qbc2mou6n%jdA>4ctBOcY&*5pCni4Csc7%u~TRJtF*Yz#9f8rrS(>aS& z@AKgoS985h-m9{WQ?~x}iq>s>p3@uHv=!SH7Yg@uzFw2f_)x8OuBi8lX0fMdbV^Nw ztc5LKh3k5hUFakf0PPM-&g4!Vg15!l8al(G3}A>p&5!NPcAL(J1%|hHP7{!AJ;Z4 zS4~^8@~H53`~4rTu={;D&L)}~uYBV6iBt1=es$$M7Jt~v_V$W_nzoRrbwopOi}*i5 zi&@|IiYGDsVo7Obov5(6Q0<|Qz>_DJb{$XdeO}s@y{;xZB6!l~ppZwMx98vgk;S~c zBDtAs)B1@|a-XF7x7E6u6?T6()LiyTLRhquYwlWb3_#QW>ad86qjI}Kvrh9$EWYl| z=@`DGBTF$+L+i^#UKfUj#S>5Ke!FMAX_8ZN-0N$3&dc>@muGC-YwO=H`uu!Z@}t!< zF{^K_4_oIw^HpS|M8>?Q-gD=LmiKc%QI__)SFd;qLPZryHYb)~# z-&t9_`P90P4{KVIGo#N><&Cm*)7F=q)BjxYr%cY1lUw{hxF|3<1~kl`WUkn9WT~WM zp3u%4#$kcpPg+(>_h|WgY{;GYYEhb7qbrN7`GqNYL9R-!ZNEjd`Ck1zlvl{Na&nmd zyrV}?S-vaSJ2g;o?dcOeNAvvdDD!Mo-Xg#t5Tc;=r1wOkp{83vbj!Ljo~u)Weky7m zd(1UCNJl!YYQ@Zn3j!V4eZQzmPnqa3F>&8fuEV?b70jOFyej2t_w$W~MSazC^ixAT zvWq?`8%9sw&T&}B<1GuU|GU)rs;7q3uaFg0D^;1}y*W)IT_kcF6V+C0H1lp?5nR5% z^*~#|YpG7Rlfg-IQf-X%wx%l|JF(up=2i9Wr8_nRy*FLc81eFB&GLw(MzzW8#Vsu{ z{(m`*Ep2`%+V*suS=Kjw?y*O9{fd|W8&v+W{q_Hag+j0GA3YYM_J}K9W?r*J9&0|F zSi0I$&djxP_w(R;cXf|8O|M*=!gbug`>EAo5l)jC)^k*p9@W3_oO$y3LC!ZRZmA|E zd)}D~eyQY}|Fil0!}r(!J09Se=Xz{H3DdOdl8zyD~~1Sczwj^7vaPK3n${C)9~;=6Zp%FS8z|Gx^%J-5X|fg$^lV$mr< z4u);Kd+se@QlD73M3!Ocyq9+*lg_?aRsC-A)`L4|%FZ=2R@|I8>$`%yB$Jz>c>ebv z%a#cAH7VJBiQXhCzOU{}Nk{eW_KpX)=KufeqF%g~kAp$#Lr2V}7AFSX=6v&&hAgo3 z?;39TLh4g!#;WJG%yC||_Ss8kgmU^Xe_OJdYhi*zn+IFF_YdLgCCBC&WbS^qd-6Xs zXV;^ry*e6CY|s6Eli4xy-6Pf+?`jl&9{S9t{MhHzlsBa}H~)XTuJoGnVd42dmuG)l zF?n;LQ=i5E$v<4!1J3uU=Wqy|WVrCMo~i2HjLXM(&1Nv14aupn#>=+kL+k zGPG^)IBb?Z-y&}jmvE#eb7s)ozTIZ^f4837b)+Jx_`k{9x~oEEc^ZMoGrMGTg;@YiVIQ{*fUL)5( z$G=?*xzLm`|Nprs$?Zm~B{O{ImQGloJon^PK`z~Q#b56J;&ykqJ9Va$fQ)DB+jS*- zxqRLp*(P~^wtJjKg$E_!?QWA;2^Ze!RFJgTypsE+N{EYz`{!9GhDwRc&LONfcS4rye(sj>2 zsuI!Yy5jARCEWtqN8H*ssxjA{V&3}cVWz;l|2uZ%NUB*(f3eVSrhubR{h$92438X} zrLZZ{N0x#4Yy}s4R#M*;pa1_)1voaaaD=67je8?_^W3zuUoI1;%G~SPeo)LXY>!Ju zwNQfjn+4YKdSVm#-v91yJ2)x1v#v|zf93uEzb!K+FWw$leye@^ zpQMXCiWlQnJj+r!&hff|!DC%BgEvFWigX3p{Q+maLs#jkeha*yYbrgV{sRY-Q|_8C z9@p=`>EV!{viz^!CiT0o*;T(<|Noz^D^ybUO7C~TxBop+iY->!S7pw+%y=jZD)b>E z50KT-(D0uWc>Ci@y$L~H)^(<;m$?4g7d~DS${D}>olR-iLVpJ4wkeD}Ij(2>ixytZ z+rFYg;9^j_Y5;E^d*mpy&%k>-W zS3BrGEzb^8-1W0i%jwJ9;ukH~Z=RgY!Ti7e{~?{#Z|a2vMf&qPzx|Ay8+%GtJGU(Q zl2F+P2Fr*57ly|R1SZC*u}nR)(V=6?x#r-nl@AxF9aq2CdhMTK<28-!pIeW}?pK%F zKF#>m`&36}nFi&F_4&8@H5uBJ&z@S9d^ojdCN&Cb&ShKv>hoa--#-7n+-v_NGp`A_{yDqDy3WrwUnl+Qy;DtWmJIGn z|L$*<3u5GPpFK5{RdH%Y{J(O^G>xg;`e|VT7Aq&C_J0@nNB?v(I~2nCx?aQWhSyu$ z!Y3ltoUzN-*);VhW-&0fH!?7=o?;HZ(BA6kb)hI@qRpIh{XbGP-NP#HtX{>j&{4Aa ztwUABAx{mn7j9zii4Ckrx=U`R>N+|GTAZ7FJDl*_2t>t{$~|!P>4j z5_~B}+V#KF3QK!aN>vs;TGV1F>uOZ@e%}eU=;j?^9lA4S=&3WXZCUq-K{2}dD9=xh z<`+Cd*&J*?=51Vc-2bo0_X4Z`ACuju=m)Ks=BsD$1|xo zw`598NI;wNie>(Hzu#myT~N+-k#(~$>rqfvfR#UU{bMRsWs8DWzV269yv{4sJgJ4F z&eLnzn}A0Cljen~%crF^*ne^6KWD5=xFjwpaH5f9tnCq*v4DWuKGE|J>%bMX{O+lgxrOjGB2L?(WvL*{mR- zm2lZo<5dRFrT!apI1Y6rKWN%oBCql+;Pr)Dg^IHy`7F;beOGq#+}gP(4hnHhe7E%f z|E$ugC7E97K1G7+zjI_vgoRHHerxpEEnNzt#VTxXR0?PJxX-yO?+yQuqJ=-?T=iW4nxjmvMoTP|8pF zqk9#7X8(vi`089L>q!aObX}{a%Ndb-o5UV{tB$=dzfWxh3-cV%2ed!8`O?9aRZ|4fQuSDRy;&%v29XEoXH ziClJYM!DpUD+cC5UZDv)bPhAJa29Vrqmpsm{AJna;%#&9xL8@7HB4DtJi$h}Bv80$ z%az)_7mqkboO95e@$K#ZKgXOUR-}kNIU_l9<_hKCM`z_owinLsb!IM9S!y64z`zh7 zW+|)?p~Qb{x6Agd>0&iy+cxRgln8h&zZP)Ak;knicn3%QYKf1W0#OEEyEP|XVM}vQ zzqtQ@d#<>i8;_UKq>T4$hb57Vc;Tt2F;lR>2pWa1Z6{KL}!V*lWi8d9G^GGA4yGRJup>Uyg$$UW(& zx-#Vp?@k*QR)=gR){mX0E8oaZ^*yWX&(UXbc`cK}oi|r@Uo&Y=-hKVSVkN6HrWze3 zeUl*(sVi@qnwyuPj&N12-5ZOk+q=(=j>T(6E9x!C{}F z+E&}AL{%ZQQ z_?>`O^mL`QMiv~4=KFdqtB__85<0-+a~^`x#AEkf+vWY`jfg6Id2E$X)Tj9Zlg(m{SSI}`oo&qqGB`Iqu0Zb_j}<3Vm+#mJUYP{9MM zgQ4|rm4C#G!y=dbJzvX8B+c?xw2x#HDfdiKf30w+@1>otkbpY)JhyJ*v9*do<=5z7x(e9cry63K}XsYOl zwQs%txuc`w`PZZ8icAHkzx;o$NT5aiUGd)6uGS|`cnP?)Sihgc!?;2)&|w0j%KX)f zx2)S5{X1Ll8e0+bqSEQCUe}$fwy#cK)9-R^>4uF{*FO~%bZIz{sBGC2wQ9n}b#sOP zueMyIc;R4bnx@Cy^e5MugqEzn9?X>JTWsMXvPaVXjankD3{HW!J{@4`e|x~Smu*5H z!d^+;H8>Q)V>r1OvtgvloKRIW9^+m?^;Pc0(Da<}=_svM(zUb}^ zL8*8rL3bX05eEb7^>51a|9{(a%2{-3YSl5lHRsxlQj!-NFxz%nI7>!N$Yoo=E^(nl zh=oIf!R>**#ai=^8OpBCrt9Z_n30|O`ntxkw-rzI?wamjuT;{#Xutl}ty9&udE9mr z*O{$(ck#S`r#}7v|D&0oWsTix=A9me1tRB^i?5j2adx-(ax^M9G&Zp0F@5g3TrbvS z5@HHZc5z0ZN9MN9&JSkna5ZE#fu{eGz^Iq%T3`ISv}0AC#&!KQPh37{{!VOm z`T5GntGp*k8;-+XKdSY=IQ@KPlGEaRy?p$nIBM zUiN$`{3(=kSEj8}oj0equa1QG$wbY}rOlj`G7^jozTB<4)2FmbxTRbx-s{wLWaaEH zS4I8SKd}+|cz(9p;_C)~c1+ai)O;4U`eJ>No~nkg#^zT+JeB+Z|1I0V!Rd9|$nxFw zGFAU8!QmUqDVpc!9uy^La zi|y^vKQ~8CS{1&PMUX)qd+q$YPPdv|C|4T1qf!Q@^dVu!38*|(o_WgU{^8ax} zg0rL`8{hPw&+4B&ZEtqg6=-zXx5d!>GT#9*2Yp8L}N|I(hl!=dnAZ~V(&SA4#x z9pasD|L?l6<*UR(R;GpqhKyA~uUKnu3wN9lzki2g{-10fj$|c;gO~o>od0uY!-tI2 zMFJYrueGWDO1!~v%t|6PmAIrywEuKsHu)j6-_hU4bv|8G4?xAoCecyd61 z;XnX`$vwWv%5!lFExcAcEAOXVQ0F>uL!M=7ZAwi1w->yPzk-gwNxpyHXBP+46#16( zAsyet(o5t2-@Q`XxZ%XN+t(Af^R4Jy>As-PXa0AdXp269b3Tj=Jgf}Nz7L*GpY!w) zhr{L7)8~HkWhz+1+xWpV{F>|0|9K5Jr+gocv#KbaTf;@G4Mv`?Du7rZP>_A+SrkU+1U>x29|UGKnoW zQ#ICpIV?82f>Bk9frWuB{>Y}@iY-1NzReGXHZA6TqoDJ(bN2fyi;^4`opV#1J#o%b z(Zr57h9?xxU9SIstwdQd;*@F5{MBn$SMjDVIkRoU?$G@cvaEUxZcc9y3S7Y8z>xaK zTjP4S%Tbpbc7obk2KNpzosXEg@2{7d3YX54B*~qUrvk$y`VPx9@w_$v|2N&qNVI2h z*vVhJZf#$1t;*+gHuu)(pKOa)9Oj)F%gUvq#Gz^RCUtwd(ps&sQjJs9b*#H5yG>zE zudCRQvUdKt^Y1U3bWMwRk{bC`J?nXot!&>lPf6yx|NjP@)@WE6d#5$!uY6*q??U%T zz0ehz@&Co7ceZ%iU3YZqZ=K}k@V)+jh|wtpt?+WiNPG7ialsjZ*Sx1@NrnGE+V(C{ z$bSo{5{8HWg23pPTq0p1T-vpYPF<&#n&&<<3FHh}_IA&)F2$K_hgAePJa=)obj!$J zaXiBMRqI3VbsM?j^Q*e${Z9s5a}`)znag5nm7<{hm21|;tASVN|G)2fq-!lt6JQRe+6w?T*Yf@$z|fL79pB|5p2je&IZ* zEXSW_5fJ>l^5D6tB0inR^)4vNGIHnn=2=!b7v(l?VCrCIV9w*q=H7kYUejd5{eOFxYyDz*FtML~vy6u3-#rhWdv&R~ zJf5B*(AUsd3x*OcD>|GsFPX`0h8RlsOpP)X^P6+ufl-}UqU-hS|`tXT%U9{_3p zFey#;yIrZJeJLpOcD|}>mDgPR)TK){b_Oqhvt`RP!w4rAQ3eJE{&&n1zHTu&=C;-F zaLKu+lADisDD>WUQZ0LR+1O>_kGoPrGPS49T6Vp8l9M#4JiPw@XN3u$cDhS=@Ya6U z-<6S4wK}hV`Tw0RM)nD-IXs#cFfcIwJy0aKt!>7MYs)P1X5LwBR?*2Kxci06y1P+n z8B^?~7dtuk?~Gh-7(1u8C&=&B`~Uw^gyh0C9&<9T-}m&pFHe7H?!_`zHWKvdIbqy&X$EcH;QPjnf>{f zvWQ{Hwl!s5RfYNg|Fz9LV`Vbs^S&p4h2N{c`2T~$AW-bp1u+H&{{xr)#d`6lzAt^r z_~_KP9U+!npG2A5FQh)%wR_%Qf8jU>11Vun=i(%d&`9&=U)(g3Ugk!;neye){r`V? zjc1f(YCf+$`Ahwt|Hc0w1QHhMUI8V1cn=s_{&xr7dGT0pN??}tT$$8M-KOsC%Vvap zOk45c%Vn;4l@l5m7H|d!2+y%oz7`Nysn;%$FQ=ly$@5yIJ2v#|ze52Yj@Di^KZN!r zG&6H@)&{)&|No4RDVM3sIjwwy9L>h5Kbd~NYO+81@|uop@Qi|s?~Mc)L>RTQn2Otv zRGs2JuX^DT@8csajf!)VCT6eNxBrG0Cx?2b|9qzKGXe>Tiu$W$_y7Od+P2Vb#mu9= z&v;u6jP@(U*9N%%w6!|gysDT_%0AY$&r)ffXF=M^X{UW2&tA#xGp+W|39*lV&3`?+ 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

n*I~A2M{vu;iCh0p&ySuMmYkQLp(@n&0* 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 Forgot Password (hold clear button for 10 seconds) Hide Notification Dot Private + you have been Ǫ̴̺̙͎̤̫͓̮̰̿͝M̴͇̤͗́̾̈́̑̍̿̈͌͝Ȅ̴̡̨̛͉̣̙̩̲̣̤̟̪̣̎͗̎̆̒̉͆̆̕ͅͅǴ̸̯̬̗̠̙͛͐̀̈͋̀̈̽́̎̿͘͘͝ͅĀ̶̧̲̀ͅ ̴̢̟͕̜̓̾̓C̶̬̜̰̘̝̱̫͓͙̭̈́͐͋̓̏̈̍̓̀̌̾̚Ư̸̛̤̱̈́͆̽͊͛̐̓́̑͘̕̕͝R̸̨̨͈̬̱̺͕̪̪̘͕͎̂͛́̅̆̓̀͝ͅS̴̨̨̛̩̭̗̹̰̭̥͉̮̝̠̓̔͆̂͊͆̀̈́̅̕͘̚͝È̴̢̛̝͈̳͉͈͒͒̒̄̏̈̈́D̸̢̡̨̜̞̩̼̫̹̗̮͛̀̈̋̾̇̕̕͜ͅ + you have been freed From 79742f415bec098776b7b6c40fee7dd355aa6518 Mon Sep 17 00:00:00 2001 From: aayush262 Date: Sun, 30 Jun 2024 22:29:30 +0530 Subject: [PATCH 26/26] feat: something --- .../dantotsu/connections/anilist/Anilist.kt | 1 + .../java/ani/dantotsu/home/AnimeFragment.kt | 18 ++++++++++------ .../java/ani/dantotsu/home/MangaFragment.kt | 20 ++++++++++++------ .../notification/NotificationActivity.kt | 4 ++-- .../dantotsu/settings/SubscriptionSource.kt | 21 +++++++++---------- app/src/main/res/layout/fragment_home.xml | 3 +-- app/src/main/res/layout/item_extension.xml | 1 - .../main/res/layout/item_extension_select.xml | 1 - 8 files changed, 40 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt b/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt index 51f3bc9d..a712aba3 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt @@ -191,6 +191,7 @@ object Anilist { ) val remaining = json.headers["X-RateLimit-Remaining"]?.toIntOrNull() ?: -1 Logger.log("Remaining requests: $remaining") + println("Remaining requests: $remaining") if (json.code == 429) { val retry = json.headers["Retry-After"]?.toIntOrNull() ?: -1 val passedLimitReset = json.headers["X-RateLimit-Reset"]?.toLongOrNull() ?: 0 diff --git a/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt b/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt index f9a9f596..87713885 100644 --- a/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt @@ -38,6 +38,7 @@ import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -289,15 +290,20 @@ class AnimeFragment : Fragment() { } } } - model.loaded = true - model.loadTrending(1) - model.loadAll() + } + model.loaded = true + val loadTrending = async(Dispatchers.IO) { model.loadTrending(1) } + val loadAll = async(Dispatchers.IO) { model.loadAll() } + val loadPopular = async(Dispatchers.IO) { model.loadPopular( - "ANIME", sort = Anilist.sortBy[1], onList = PrefManager.getVal( - PrefName.PopularAnimeList - ) + "ANIME", + sort = Anilist.sortBy[1], + onList = PrefManager.getVal(PrefName.PopularAnimeList) ) } + loadTrending.await() + loadAll.await() + loadPopular.await() live.postValue(false) _binding?.animeRefresh?.isRefreshing = false running = false diff --git a/app/src/main/java/ani/dantotsu/home/MangaFragment.kt b/app/src/main/java/ani/dantotsu/home/MangaFragment.kt index c878c584..0ed19510 100644 --- a/app/src/main/java/ani/dantotsu/home/MangaFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/MangaFragment.kt @@ -35,6 +35,7 @@ import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -274,15 +275,22 @@ class MangaFragment : Fragment() { } } } - model.loaded = true - model.loadTrending() - model.loadAll() + } + model.loaded = true + val loadTrending = async(Dispatchers.IO) { model.loadTrending() } + val loadAll = async(Dispatchers.IO) { model.loadAll() } + val loadPopular = async(Dispatchers.IO) { model.loadPopular( - "MANGA", sort = Anilist.sortBy[1], onList = PrefManager.getVal( - PrefName.PopularMangaList - ) + "MANGA", + sort = Anilist.sortBy[1], + onList = PrefManager.getVal(PrefName.PopularAnimeList) ) } + + loadTrending.await() + loadAll.await() + loadPopular.await() + live.postValue(false) _binding?.mangaRefresh?.isRefreshing = false running = false diff --git a/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt b/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt index bd5fad0b..05412474 100644 --- a/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt @@ -79,8 +79,8 @@ class NotificationActivity : AppCompatActivity() { override fun getItemCount(): Int = if (id != -1) 1 else 4 override fun createFragment(position: Int): Fragment = when (position) { - 0 -> newInstance(USER) - 1 -> newInstance(if (id != -1) ONE else MEDIA, id) + 0 -> newInstance(if (id != -1) ONE else USER, id) + 1 -> newInstance(MEDIA) 2 -> newInstance(SUBSCRIPTION) 3 -> newInstance(COMMENT) else -> newInstance(MEDIA) diff --git a/app/src/main/java/ani/dantotsu/settings/SubscriptionSource.kt b/app/src/main/java/ani/dantotsu/settings/SubscriptionSource.kt index 80975159..6740b148 100644 --- a/app/src/main/java/ani/dantotsu/settings/SubscriptionSource.kt +++ b/app/src/main/java/ani/dantotsu/settings/SubscriptionSource.kt @@ -8,6 +8,7 @@ import android.view.ViewGroup import ani.dantotsu.R import ani.dantotsu.databinding.ItemExtensionBinding import ani.dantotsu.notifications.subscription.SubscriptionHelper +import ani.dantotsu.util.customAlertDialog import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.viewbinding.BindableItem @@ -26,14 +27,13 @@ class SubscriptionSource( binding.extensionNameTextView.text = parserName updateSubscriptionCount() binding.extensionSubscriptions.visibility = View.VISIBLE - - binding.extensionSubscriptions.setOnClickListener(null) binding.root.setOnClickListener { isExpanded = !isExpanded toggleSubscriptions() } - binding.subscriptionCount.setOnClickListener { + binding.root.setOnLongClickListener { showRemoveAllSubscriptionsDialog(it.context) + true } binding.extensionIconImageView.visibility = View.VISIBLE val layoutParams = binding.extensionIconImageView.layoutParams as ViewGroup.MarginLayoutParams @@ -58,14 +58,13 @@ class SubscriptionSource( } private fun showRemoveAllSubscriptionsDialog(context: Context) { - AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.remove_all_subscriptions) - .setMessage(context.getString(R.string.remove_all_subscriptions_desc, parserName)) - .setPositiveButton(R.string.apply) { _, _ -> - removeAllSubscriptions() - } - .setNegativeButton(R.string.cancel, null) - .show() + context.customAlertDialog().apply{ + setTitle(R.string.remove_all_subscriptions) + setMessage(R.string.remove_all_subscriptions_desc, parserName) + setPosButton(R.string.ok) { removeAllSubscriptions() } + setNegButton(R.string.cancel) + show() + } } private fun removeAllSubscriptions() { diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 48159e9c..d68cd3a9 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -314,10 +314,9 @@ android:id="@+id/homeUserStatusContainer" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="100dp" + android:minHeight="120dp" android:layout_marginVertical="8dp" android:visibility="gone"> - diff --git a/app/src/main/res/layout/item_extension_select.xml b/app/src/main/res/layout/item_extension_select.xml index 98af9cad..cb272972 100644 --- a/app/src/main/res/layout/item_extension_select.xml +++ b/app/src/main/res/layout/item_extension_select.xml @@ -4,7 +4,6 @@ android:id="@+id/extensionCardView" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/colorSurface" android:orientation="horizontal" android:paddingTop="10dp" android:paddingBottom="10dp">