Merge pull request #186 from rebelonion/dev

Dev
This commit is contained in:
rebel onion 2024-02-08 07:35:44 -06:00 committed by GitHub
commit ab199a3502
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 438 additions and 112 deletions

View file

@ -98,7 +98,7 @@ jobs:
if [ ${#commit_messages} -gt $max_length ]; then if [ ${#commit_messages} -gt $max_length ]; then
commit_messages="${commit_messages:0:$max_length}... (truncated)" commit_messages="${commit_messages:0:$max_length}... (truncated)"
fi fi
contentbody=$( jq -nc --arg msg "Alpha-Build: <@719439449423085569> **$VERSION**:" --arg commits "$commit_messages" '{"content": ($msg + "\n" + $commits)}' ) contentbody=$( jq -nc --arg msg "Alpha-Build: <@714249925248024617> **$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 }} curl -F "payload_json=${contentbody}" -F "dantotsu_debug=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" ${{ secrets.DISCORD_WEBHOOK }}
#Telegram #Telegram

View file

@ -18,8 +18,8 @@ android {
minSdk 23 minSdk 23
targetSdk 34 targetSdk 34
versionCode((System.currentTimeMillis() / 60000).toInteger()) versionCode((System.currentTimeMillis() / 60000).toInteger())
versionName "2.1.0" versionName "2.2.0"
versionCode 210000000 versionCode 220000000
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
@ -138,10 +138,11 @@ dependencies {
implementation 'ca.gosyer:voyager-navigator:1.0.0-rc07' implementation 'ca.gosyer:voyager-navigator:1.0.0-rc07'
implementation 'com.squareup.logcat:logcat:0.1' implementation 'com.squareup.logcat:logcat:0.1'
implementation 'com.github.inorichi.injekt:injekt-core:65b0440' implementation 'com.github.inorichi.injekt:injekt-core:65b0440'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11' implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.11' implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
implementation 'com.squareup.okio:okio:3.7.0' implementation 'com.squareup.okio:okio:3.7.0'
implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.12'
implementation 'ch.acra:acra-http:5.11.3' implementation 'ch.acra:acra-http:5.11.3'
implementation 'org.jsoup:jsoup:1.15.4' implementation 'org.jsoup:jsoup:1.15.4'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.6.2' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.6.2'

View file

@ -52,7 +52,7 @@
android:icon="${icon_placeholder}" android:icon="${icon_placeholder}"
android:label="@string/app_name" android:label="@string/app_name"
android:largeHeap="true" android:largeHeap="true"
android:requestLegacyExternalStorage="false" android:requestLegacyExternalStorage="true"
android:roundIcon="${icon_placeholder_round}" android:roundIcon="${icon_placeholder_round}"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Dantotsu" android:theme="@style/Theme.Dantotsu"

View file

@ -651,7 +651,7 @@ fun savePrefs(
context: Context, context: Context,
password: CharArray password: CharArray
): File? { ): File? {
var file = File(path, "$title.ani") var file = File(path, "$title.sani")
var counter = 1 var counter = 1
while (file.exists()) { while (file.exists()) {
file = File(path, "${title}_${counter}.sani") file = File(path, "${title}_${counter}.sani")

View file

@ -3,6 +3,7 @@ package ani.dantotsu.connections.anilist
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.Log
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.client import ani.dantotsu.client
@ -128,7 +129,6 @@ object Anilist {
toast("Rate limited. Try after ${rateLimitReset - (System.currentTimeMillis() / 1000)} seconds") toast("Rate limited. Try after ${rateLimitReset - (System.currentTimeMillis() / 1000)} seconds")
throw Exception("Rate limited after ${rateLimitReset - (System.currentTimeMillis() / 1000)} seconds") throw Exception("Rate limited after ${rateLimitReset - (System.currentTimeMillis() / 1000)} seconds")
} }
val data = mapOf( val data = mapOf(
"query" to query, "query" to query,
"variables" to variables "variables" to variables
@ -147,6 +147,8 @@ object Anilist {
data = data, data = data,
cacheTime = cache ?: 10 cacheTime = cache ?: 10
) )
val remaining = json.headers["X-RateLimit-Remaining"]?.toIntOrNull() ?: -1
Log.d("AnilistQuery", "Remaining requests: $remaining")
if (json.code == 429) { if (json.code == 429) {
val retry = json.headers["Retry-After"]?.toIntOrNull() ?: -1 val retry = json.headers["Retry-After"]?.toIntOrNull() ?: -1
val passedLimitReset = json.headers["X-RateLimit-Reset"]?.toLongOrNull() ?: 0 val passedLimitReset = json.headers["X-RateLimit-Reset"]?.toLongOrNull() ?: 0

View file

@ -1,6 +1,5 @@
package ani.dantotsu.connections.anilist package ani.dantotsu.connections.anilist
import android.app.Activity
import android.util.Base64 import android.util.Base64
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.checkGenreTime import ani.dantotsu.checkGenreTime
@ -266,12 +265,33 @@ class AnilistQueries {
suspend fun continueMedia(type: String, planned: Boolean = false): ArrayList<Media> { suspend fun continueMedia(type: String, planned: Boolean = false): ArrayList<Media> {
val returnArray = arrayListOf<Media>() val returnArray = arrayListOf<Media>()
val map = mutableMapOf<Int, Media>() val map = mutableMapOf<Int, Media>()
val statuses = if (!planned) arrayOf("CURRENT", "REPEATING") else arrayOf("PLANNING") val query = if (planned) {
suspend fun repeat(status: String) { """{ planned: ${continueMediaQuery(type, "PLANNING")} }"""
val response = } else {
executeQuery<Query.MediaListCollection>(""" { MediaListCollection(userId: ${Anilist.userid}, type: $type, status: $status , sort: UPDATED_TIME ) { lists { entries { progress private score(format:POINT_100) status media { id idMal type isAdult status chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } } } """) """{
current: ${continueMediaQuery(type, "CURRENT")},
repeating: ${continueMediaQuery(type, "REPEATING")}
}"""
}
response?.data?.mediaListCollection?.lists?.forEach { li -> val response = executeQuery<Query.CombinedMediaListResponse>(query)
if (planned) {
response?.data?.planned?.lists?.forEach { li ->
li.entries?.reversed()?.forEach {
val m = Media(it)
m.cameFromContinue = true
map[m.id] = m
}
}
} else {
response?.data?.current?.lists?.forEach { li ->
li.entries?.reversed()?.forEach {
val m = Media(it)
m.cameFromContinue = true
map[m.id] = m
}
}
response?.data?.repeating?.lists?.forEach { li ->
li.entries?.reversed()?.forEach { li.entries?.reversed()?.forEach {
val m = Media(it) val m = Media(it)
m.cameFromContinue = true m.cameFromContinue = true
@ -279,8 +299,6 @@ class AnilistQueries {
} }
} }
} }
statuses.forEach { repeat(it) }
val set = PrefManager.getCustomVal<Set<Int>>("continue_$type", setOf()).toMutableSet() val set = PrefManager.getCustomVal<Set<Int>>("continue_$type", setOf()).toMutableSet()
if (set.isNotEmpty()) { if (set.isNotEmpty()) {
set.reversed().forEach { set.reversed().forEach {
@ -293,13 +311,16 @@ class AnilistQueries {
return returnArray return returnArray
} }
private fun continueMediaQuery(type: String, status: String): String {
return """ MediaListCollection(userId: ${Anilist.userid}, type: $type, status: $status , sort: UPDATED_TIME ) { lists { entries { progress private score(format:POINT_100) status media { id idMal type isAdult status chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } } """
}
suspend fun favMedia(anime: Boolean): ArrayList<Media> { suspend fun favMedia(anime: Boolean): ArrayList<Media> {
var hasNextPage = true var hasNextPage = true
var page = 0 var page = 0
suspend fun getNextPage(page: Int): List<Media> { suspend fun getNextPage(page: Int): List<Media> {
val response = val response = executeQuery<Query.User>("""{${favMediaQuery(anime, page)}}""")
executeQuery<Query.User>("""{User(id:${Anilist.userid}){id favourites{${if (anime) "anime" else "manga"}(page:$page){pageInfo{hasNextPage}edges{favouriteOrder node{id idMal isAdult mediaListEntry{ progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode{episode}meanScore isFavourite format startDate{year month day} title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}}}}""")
val favourites = response?.data?.user?.favourites val favourites = response?.data?.user?.favourites
val apiMediaList = if (anime) favourites?.anime else favourites?.manga val apiMediaList = if (anime) favourites?.anime else favourites?.manga
hasNextPage = apiMediaList?.pageInfo?.hasNextPage ?: false hasNextPage = apiMediaList?.pageInfo?.hasNextPage ?: false
@ -318,9 +339,12 @@ class AnilistQueries {
return responseArray return responseArray
} }
private fun favMediaQuery(anime: Boolean, page: Int): String {
return """User(id:${Anilist.userid}){id favourites{${if (anime) "anime" else "manga"}(page:$page){pageInfo{hasNextPage}edges{favouriteOrder node{id idMal isAdult mediaListEntry{ progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode{episode}meanScore isFavourite format startDate{year month day} title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}}}"""
}
suspend fun recommendations(): ArrayList<Media> { suspend fun recommendations(): ArrayList<Media> {
val response = val response = executeQuery<Query.Page>("""{${recommendationQuery()}}""")
executeQuery<Query.Page>(""" { Page(page: 1, perPage:30) { pageInfo { total currentPage hasNextPage } recommendations(sort: RATING_DESC, onList: true) { rating userRating mediaRecommendation { id idMal isAdult mediaListEntry { progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode {episode} popularity meanScore isFavourite format title {english romaji userPreferred } type status(version: 2) bannerImage coverImage { large } } } } } """)
val map = mutableMapOf<Int, Media>() val map = mutableMapOf<Int, Media>()
response?.data?.page?.apply { response?.data?.page?.apply {
recommendations?.onEach { recommendations?.onEach {
@ -336,7 +360,7 @@ class AnilistQueries {
val types = arrayOf("ANIME", "MANGA") val types = arrayOf("ANIME", "MANGA")
suspend fun repeat(type: String) { suspend fun repeat(type: String) {
val res = val res =
executeQuery<Query.MediaListCollection>(""" { MediaListCollection(userId: ${Anilist.userid}, type: $type, status: PLANNING , sort: MEDIA_POPULARITY_DESC ) { lists { entries { media { id mediaListEntry { progress private score(format:POINT_100) status } idMal type isAdult popularity status(version: 2) chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } } } """) executeQuery<Query.MediaListCollection>("""{${recommendationPlannedQuery(type)}}""")
res?.data?.mediaListCollection?.lists?.forEach { li -> res?.data?.mediaListCollection?.lists?.forEach { li ->
li.entries?.forEach { li.entries?.forEach {
val m = Media(it) val m = Media(it)
@ -354,6 +378,185 @@ class AnilistQueries {
return list return list
} }
private fun recommendationQuery(): String {
return """ Page(page: 1, perPage:30) { pageInfo { total currentPage hasNextPage } recommendations(sort: RATING_DESC, onList: true) { rating userRating mediaRecommendation { id idMal isAdult mediaListEntry { progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode {episode} popularity meanScore isFavourite format title {english romaji userPreferred } type status(version: 2) bannerImage coverImage { large } } } } """
}
private fun recommendationPlannedQuery(type: String): String {
return """ MediaListCollection(userId: ${Anilist.userid}, type: $type, status: PLANNING , sort: MEDIA_POPULARITY_DESC ) { lists { entries { media { id mediaListEntry { progress private score(format:POINT_100) status } idMal type isAdult popularity status(version: 2) chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } }"""
}
suspend fun initHomePage(): Map<String, ArrayList<Media>> {
val toShow: List<Boolean> =
PrefManager.getVal(PrefName.HomeLayoutShow) // 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(',')
val response = executeQuery<Query.HomePageMedia>(query)
val returnMap = mutableMapOf<String, ArrayList<Media>>()
fun current(type: String) {
val subMap = mutableMapOf<Int, Media>()
val returnArray = arrayListOf<Media>()
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)
m.cameFromContinue = true
subMap[m.id] = m
}
}
repeating?.lists?.forEach { li ->
li.entries?.reversed()?.forEach {
val m = Media(it)
m.cameFromContinue = true
subMap[m.id] = m
}
}
val set = PrefManager.getCustomVal<Set<Int>>("continue_${type.uppercase()}", setOf())
.toMutableSet()
if (set.isNotEmpty()) {
set.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<Int, Media>()
val returnArray = arrayListOf<Media>()
val current =
if (type == "Anime") response?.data?.plannedAnime else response?.data?.plannedManga
current?.lists?.forEach { li ->
li.entries?.reversed()?.forEach {
val m = Media(it)
m.cameFromContinue = true
subMap[m.id] = m
}
}
val set = PrefManager.getCustomVal<Set<Int>>("continue_$type", setOf()).toMutableSet()
if (set.isNotEmpty()) {
set.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
}
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
val returnArray = arrayListOf<Media>()
apiMediaList?.edges?.forEach {
it.node?.let { i ->
returnArray.add(Media(i).apply { isFav = true })
}
}
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(6) == true) {
val subMap = mutableMapOf<Int, Media>()
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?.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?.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
}
}
}
}
val list = ArrayList(subMap.values.toList())
list.sortByDescending { it.meanScore }
returnMap["recommendations"] = list
}
return returnMap
}
private suspend fun bannerImage(type: String): String? { private suspend fun bannerImage(type: String): String? {
//var image = loadData<BannerImage>("banner_$type") //var image = loadData<BannerImage>("banner_$type")
val image: BannerImage? = BannerImage( val image: BannerImage? = BannerImage(

View file

@ -97,6 +97,17 @@ class AnilistHomeViewModel : ViewModel() {
fun getRecommendation(): LiveData<ArrayList<Media>> = recommendation fun getRecommendation(): LiveData<ArrayList<Media>> = recommendation
suspend fun setRecommendation() = recommendation.postValue(Anilist.query.recommendations()) suspend fun setRecommendation() = recommendation.postValue(Anilist.query.recommendations())
suspend fun initHomePage() {
val res = Anilist.query.initHomePage()
res["currentAnime"]?.let { animeContinue.postValue(it) }
res["favoriteAnime"]?.let { animeFav.postValue(it) }
res["plannedAnime"]?.let { animePlanned.postValue(it) }
res["currentManga"]?.let { mangaContinue.postValue(it) }
res["favoriteManga"]?.let { mangaFav.postValue(it) }
res["plannedManga"]?.let { mangaPlanned.postValue(it) }
res["recommendations"]?.let { recommendation.postValue(it) }
}
suspend fun loadMain(context: FragmentActivity) { suspend fun loadMain(context: FragmentActivity) {
Anilist.getSavedToken() Anilist.getSavedToken()
MAL.getSavedToken(context) MAL.getSavedToken(context)

View file

@ -105,6 +105,40 @@ class Query {
) )
} }
@Serializable
data class CombinedMediaListResponse(
@SerialName("data")
val data: Data?
) {
@Serializable
data class Data(
@SerialName("current") val current: ani.dantotsu.connections.anilist.api.MediaListCollection?,
@SerialName("planned") val planned: ani.dantotsu.connections.anilist.api.MediaListCollection?,
@SerialName("repeating") val repeating: ani.dantotsu.connections.anilist.api.MediaListCollection?,
)
}
@Serializable
data class HomePageMedia(
@SerialName("data")
val data: Data?
) {
@Serializable
data class Data(
@SerialName("currentAnime") val currentAnime: ani.dantotsu.connections.anilist.api.MediaListCollection?,
@SerialName("repeatingAnime") val repeatingAnime: ani.dantotsu.connections.anilist.api.MediaListCollection?,
@SerialName("favoriteAnime") val favoriteAnime: ani.dantotsu.connections.anilist.api.User?,
@SerialName("plannedAnime") val plannedAnime: ani.dantotsu.connections.anilist.api.MediaListCollection?,
@SerialName("currentManga") val currentManga: ani.dantotsu.connections.anilist.api.MediaListCollection?,
@SerialName("repeatingManga") val repeatingManga: ani.dantotsu.connections.anilist.api.MediaListCollection?,
@SerialName("favoriteManga") val favoriteManga: ani.dantotsu.connections.anilist.api.User?,
@SerialName("plannedManga") val plannedManga: ani.dantotsu.connections.anilist.api.MediaListCollection?,
@SerialName("recommendationQuery") val recommendationQuery: ani.dantotsu.connections.anilist.api.Page?,
@SerialName("recommendationPlannedQueryAnime") val recommendationPlannedQueryAnime: ani.dantotsu.connections.anilist.api.MediaListCollection?,
@SerialName("recommendationPlannedQueryManga") val recommendationPlannedQueryManga: ani.dantotsu.connections.anilist.api.MediaListCollection?,
)
}
@Serializable @Serializable
data class GenreCollection( data class GenreCollection(
@SerialName("data") @SerialName("data")

View file

@ -306,13 +306,13 @@ class HomeFragment : Fragment() {
} }
val array = arrayOf( val array = arrayOf(
Runnable { runBlocking { model.setAnimeContinue() } }, "AnimeContinue",
Runnable { runBlocking { model.setAnimeFav() } }, "AnimeFav",
Runnable { runBlocking { model.setAnimePlanned() } }, "AnimePlanned",
Runnable { runBlocking { model.setMangaContinue() } }, "MangaContinue",
Runnable { runBlocking { model.setMangaFav() } }, "MangaFav",
Runnable { runBlocking { model.setMangaPlanned() } }, "MangaPlanned",
Runnable { runBlocking { model.setRecommendation() } } "Recommendation"
) )
val containers = arrayOf( val containers = arrayOf(
@ -339,9 +339,11 @@ class HomeFragment : Fragment() {
var empty = true var empty = true
val homeLayoutShow: List<Boolean> = val homeLayoutShow: List<Boolean> =
PrefManager.getVal(PrefName.HomeLayoutShow) PrefManager.getVal(PrefName.HomeLayoutShow)
runBlocking {
model.initHomePage()
}
(array.indices).forEach { i -> (array.indices).forEach { i ->
if (homeLayoutShow.elementAt(i)) { if (homeLayoutShow.elementAt(i)) {
array[i].run()
empty = false empty = false
} else withContext(Dispatchers.Main) { } else withContext(Dispatchers.Main) {
containers[i].visibility = View.GONE containers[i].visibility = View.GONE

View file

@ -22,6 +22,7 @@ import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.databinding.ItemChipBinding
import ani.dantotsu.databinding.ItemSearchHeaderBinding import ani.dantotsu.databinding.ItemSearchHeaderBinding
import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import com.google.android.material.checkbox.MaterialCheckBox.* import com.google.android.material.checkbox.MaterialCheckBox.*
@ -105,6 +106,9 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
onList = listOnly onList = listOnly
isAdult = adult isAdult = adult
} }
if (binding.searchBarText.text.toString().equals("hentai", true)) {
openLinkInBrowser("https://www.youtube.com/watch?v=GgJrEOo0QoA")
}
activity.search() activity.search()
} }

View file

@ -257,8 +257,15 @@ class AnimeWatchAdapter(
val url = sourceHttp?.baseUrl val url = sourceHttp?.baseUrl
url?.let { url?.let {
refresh = true 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) val intent = Intent(fragment.requireContext(), CookieCatcher::class.java)
.putExtra("url", url) .putExtra("url", url)
.putExtra("headers", headersMap as HashMap<String, String>)
startActivity(fragment.requireContext(), intent, null) startActivity(fragment.requireContext(), intent, null)
} }
} }

View file

@ -372,18 +372,18 @@ class EpisodeAdapter(
if (activeDownloads.contains(episodeNumber)) { if (activeDownloads.contains(episodeNumber)) {
// Show spinner // Show spinner
binding.itemDownload.setImageResource(R.drawable.ic_sync) binding.itemDownload.setImageResource(R.drawable.ic_sync)
startOrContinueRotation(episodeNumber) startOrContinueRotation(episodeNumber) {
binding.itemDownload.rotation = 0f
}
binding.itemEpisodeDesc.visibility = View.GONE binding.itemEpisodeDesc.visibility = View.GONE
} else if (downloadedEpisodes.contains(episodeNumber)) { } else if (downloadedEpisodes.contains(episodeNumber)) {
binding.itemEpisodeDesc.visibility = View.GONE binding.itemEpisodeDesc.visibility = View.GONE
binding.itemDownloadStatus.visibility = View.VISIBLE binding.itemDownloadStatus.visibility = View.VISIBLE
// Show checkmark // Show checkmark
binding.itemDownload.setImageResource(R.drawable.ic_circle_check) binding.itemDownload.setImageResource(R.drawable.ic_circle_check)
//binding.itemDownload.setColorFilter(typedValue2.data) //TODO: colors go to wrong places
binding.itemDownload.postDelayed({ binding.itemDownload.postDelayed({
binding.itemDownload.setImageResource(R.drawable.ic_round_delete_24) binding.itemDownload.setImageResource(R.drawable.ic_round_delete_24)
binding.itemDownload.rotation = 0f binding.itemDownload.rotation = 0f
//binding.itemDownload.setColorFilter(typedValue2.data)
}, 1000) }, 1000)
} else { } else {
binding.itemDownloadStatus.visibility = View.GONE binding.itemDownloadStatus.visibility = View.GONE
@ -396,7 +396,7 @@ class EpisodeAdapter(
} }
private fun startOrContinueRotation(episodeNumber: String) { private fun startOrContinueRotation(episodeNumber: String, resetRotation: () -> Unit) {
if (!isRotationCoroutineRunningFor(episodeNumber)) { if (!isRotationCoroutineRunningFor(episodeNumber)) {
val scope = fragment.lifecycle.coroutineScope val scope = fragment.lifecycle.coroutineScope
scope.launch { scope.launch {
@ -411,6 +411,7 @@ class EpisodeAdapter(
} }
// Remove chapter number from active coroutines set // Remove chapter number from active coroutines set
activeCoroutines.remove(episodeNumber) activeCoroutines.remove(episodeNumber)
resetRotation()
} }
} }
} }

View file

@ -97,7 +97,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.internal.immutableListOf
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.* import java.util.*
@ -1080,17 +1079,17 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
//Cast //Cast
if (PrefManager.getVal(PrefName.Cast)) { if (PrefManager.getVal(PrefName.Cast)) {
playerView.findViewById<MediaRouteButton>(R.id.exo_cast).apply { playerView.findViewById<CustomCastButton>(R.id.exo_cast).apply {
visibility = View.VISIBLE visibility = View.VISIBLE
if(PrefManager.getVal(PrefName.UseInternalCast)) {
try { try {
CastButtonFactory.setUpMediaRouteButton(context, this) CastButtonFactory.setUpMediaRouteButton(context, this)
dialogFactory = CustomCastThemeFactory() dialogFactory = CustomCastThemeFactory()
} catch (e: Exception) { } catch (e: Exception) {
isCastApiAvailable = false isCastApiAvailable = false
} }
setOnLongClickListener { } else {
cast() setCastCallback { cast() }
true
} }
} }
} }
@ -1375,16 +1374,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
logger("mimeType: $mimeType") logger("mimeType: $mimeType")
if (sub != null) { if (sub != null) {
val listofnotnullsubs = immutableListOf(sub).filterNotNull() val listofnotnullsubs = listOfNotNull(sub)
builder.setSubtitleConfigurations(listofnotnullsubs) builder.setSubtitleConfigurations(listofnotnullsubs)
} }
builder.build() builder.build()
} else { } else {
val addedSubsDownloadedMediaItem = downloadedMediaItem.buildUpon() val addedSubsDownloadedMediaItem = downloadedMediaItem.buildUpon()
if (sub != null) { if (sub != null) {
val listofnotnullsubs = immutableListOf(sub).filterNotNull() val listofnotnullsubs = listOfNotNull(sub)
val addLanguage = listofnotnullsubs[0].buildUpon().setLanguage("en").build() val addLanguage = listofnotnullsubs[0].buildUpon().setLanguage("en").build()
addedSubsDownloadedMediaItem.setSubtitleConfigurations(immutableListOf(addLanguage)) addedSubsDownloadedMediaItem.setSubtitleConfigurations(listOf(addLanguage))
episode.selectedSubtitle = 0 episode.selectedSubtitle = 0
} }
addedSubsDownloadedMediaItem.build() addedSubsDownloadedMediaItem.build()
@ -2005,3 +2004,31 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
} }
} }
class CustomCastButton : MediaRouteButton {
private var castCallback: (() -> Unit)? = null
fun setCastCallback(castCallback: () -> Unit) {
this.castCallback = castCallback
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet, castCallback: () -> Unit) : super(context, attrs) {
this.castCallback = castCallback
}
override fun performClick(): Boolean {
return if (PrefManager.getVal(PrefName.UseInternalCast)) {
super.performClick()
} else {
castCallback?.let { it() }
true
}
}
}

View file

@ -1,6 +1,7 @@
package ani.dantotsu.media.anime package ani.dantotsu.media.anime
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
@ -316,6 +317,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
val subtitles = extractor.subtitles val subtitles = extractor.subtitles
val subtitleNames = subtitles.map { it.language } val subtitleNames = subtitles.map { it.language }
var subtitleToDownload: Subtitle? = null var subtitleToDownload: Subtitle? = null
val activity = currActivity()?:requireActivity()
if (subtitles.isNotEmpty()) { if (subtitles.isNotEmpty()) {
val alertDialog = AlertDialog.Builder(context, R.style.MyPopup) val alertDialog = AlertDialog.Builder(context, R.style.MyPopup)
.setTitle("Download Subtitle") .setTitle("Download Subtitle")
@ -329,7 +331,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
dialog?.dismiss() dialog?.dismiss()
if (selectedVideo != null) { if (selectedVideo != null) {
Helper.startAnimeDownloadService( Helper.startAnimeDownloadService(
currActivity()!!, activity,
media!!.mainName(), media!!.mainName(),
episode.number, episode.number,
selectedVideo, selectedVideo,
@ -337,7 +339,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
media, media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover episode.thumb?.url ?: media!!.banner ?: media!!.cover
) )
broadcastDownloadStarted(episode.number) broadcastDownloadStarted(episode.number, activity)
} else { } else {
snackString("No Video Selected") snackString("No Video Selected")
} }
@ -354,7 +356,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
media, media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover episode.thumb?.url ?: media!!.banner ?: media!!.cover
) )
broadcastDownloadStarted(episode.number) broadcastDownloadStarted(episode.number, activity)
} else { } else {
snackString("No Video Selected") snackString("No Video Selected")
} }
@ -378,7 +380,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
media, media,
episode.thumb?.url ?: media!!.banner ?: media!!.cover episode.thumb?.url ?: media!!.banner ?: media!!.cover
) )
broadcastDownloadStarted(episode.number) broadcastDownloadStarted(episode.number, activity)
} else { } else {
snackString("No Video Selected") snackString("No Video Selected")
} }
@ -399,11 +401,11 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
binding.urlQuality.text = extractor.server.name binding.urlQuality.text = extractor.server.name
} }
private fun broadcastDownloadStarted(episodeNumber: String) { private fun broadcastDownloadStarted(episodeNumber: String, activity: Activity) {
val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_STARTED).apply { val intent = Intent(AnimeWatchFragment.ACTION_DOWNLOAD_STARTED).apply {
putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber) putExtra(AnimeWatchFragment.EXTRA_EPISODE_NUMBER, episodeNumber)
} }
requireActivity().sendBroadcast(intent) activity.sendBroadcast(intent)
} }
override fun getItemCount(): Int = extractor.videos.size override fun getItemCount(): Int = extractor.videos.size

View file

@ -154,15 +154,15 @@ class MangaChapterAdapter(
if (activeDownloads.contains(chapterNumber)) { if (activeDownloads.contains(chapterNumber)) {
// Show spinner // Show spinner
binding.itemDownload.setImageResource(R.drawable.ic_sync) binding.itemDownload.setImageResource(R.drawable.ic_sync)
startOrContinueRotation(chapterNumber) startOrContinueRotation(chapterNumber) {
binding.itemDownload.rotation = 0f
}
} else if (downloadedChapters.contains(chapterNumber)) { } else if (downloadedChapters.contains(chapterNumber)) {
// Show checkmark // Show checkmark
binding.itemDownload.setImageResource(R.drawable.ic_circle_check) binding.itemDownload.setImageResource(R.drawable.ic_circle_check)
//binding.itemDownload.setColorFilter(typedValue2.data) //TODO: colors go to wrong places
binding.itemDownload.postDelayed({ binding.itemDownload.postDelayed({
binding.itemDownload.setImageResource(R.drawable.ic_round_delete_24) binding.itemDownload.setImageResource(R.drawable.ic_round_delete_24)
binding.itemDownload.rotation = 0f binding.itemDownload.rotation = 0f
//binding.itemDownload.setColorFilter(typedValue2.data)
}, 1000) }, 1000)
} else { } else {
// Show download icon // Show download icon
@ -172,7 +172,7 @@ class MangaChapterAdapter(
} }
private fun startOrContinueRotation(chapterNumber: String) { private fun startOrContinueRotation(chapterNumber: String, resetRotation: () -> Unit) {
if (!isRotationCoroutineRunningFor(chapterNumber)) { if (!isRotationCoroutineRunningFor(chapterNumber)) {
val scope = fragment.lifecycle.coroutineScope val scope = fragment.lifecycle.coroutineScope
scope.launch { scope.launch {
@ -187,6 +187,7 @@ class MangaChapterAdapter(
} }
// Remove chapter number from active coroutines set // Remove chapter number from active coroutines set
activeCoroutines.remove(chapterNumber) activeCoroutines.remove(chapterNumber)
resetRotation()
} }
} }
} }

View file

@ -122,7 +122,7 @@ class MangaReaderActivity : AppCompatActivity() {
} }
private fun hideBars() { private fun hideBars() {
if (PrefManager.getVal(PrefName.ShowSystemBars)) hideSystemBars() if (!PrefManager.getVal<Boolean>(PrefName.ShowSystemBars)) hideSystemBars()
} }
override fun onDestroy() { override fun onDestroy() {

View file

@ -17,12 +17,14 @@ import uy.kohesive.injekt.api.get
class CookieCatcher : AppCompatActivity() { class CookieCatcher : AppCompatActivity() {
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
@Suppress("UNCHECKED_CAST")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
//get url from intent //get url from intent
val url = intent.getStringExtra("url") ?: "https://www.youtube.com/watch?v=dQw4w9WgXcQ" val url = intent.getStringExtra("url") ?: "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
val headers: Map<String, String> = intent.getSerializableExtra("headers") as? Map<String, String> ?: emptyMap()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val process = Application.getProcessName() val process = Application.getProcessName()
@ -54,7 +56,7 @@ class CookieCatcher : AppCompatActivity() {
} }
} }
webView.loadUrl(url) webView.loadUrl(url, headers)
} }
} }

View file

@ -234,6 +234,11 @@ class PlayerSettingsActivity : AppCompatActivity() {
PrefManager.setVal(PrefName.Cast, isChecked) PrefManager.setVal(PrefName.Cast, isChecked)
} }
binding.playerSettingsInternalCast.isChecked = PrefManager.getVal(PrefName.UseInternalCast)
binding.playerSettingsInternalCast.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.UseInternalCast, isChecked)
}
binding.playerSettingsRotate.isChecked = PrefManager.getVal(PrefName.RotationPlayer) binding.playerSettingsRotate.isChecked = PrefManager.getVal(PrefName.RotationPlayer)
binding.playerSettingsRotate.setOnCheckedChangeListener { _, isChecked -> binding.playerSettingsRotate.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.RotationPlayer, isChecked) PrefManager.setVal(PrefName.RotationPlayer, isChecked)

View file

@ -24,7 +24,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
Pref( Pref(
Location.General, Location.General,
String::class, String::class,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0" "Mozilla/5.0 (Linux; Android 13; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36"
) )
), ),
AnimeSourcesOrder(Pref(Location.General, List::class, listOf<String>())), AnimeSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
@ -94,6 +94,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
SeekTime(Pref(Location.Player, Int::class, 10)), SeekTime(Pref(Location.Player, Int::class, 10)),
SkipTime(Pref(Location.Player, Int::class, 85)), SkipTime(Pref(Location.Player, Int::class, 85)),
Cast(Pref(Location.Player, Boolean::class, true)), Cast(Pref(Location.Player, Boolean::class, true)),
UseInternalCast(Pref(Location.Player, Boolean::class, false)),
Pip(Pref(Location.Player, Boolean::class, true)), Pip(Pref(Location.Player, Boolean::class, true)),
RotationPlayer(Pref(Location.Player, Boolean::class, true)), RotationPlayer(Pref(Location.Player, Boolean::class, true)),
ContinuedAnime(Pref(Location.Player, List::class, listOf<String>())), ContinuedAnime(Pref(Location.Player, List::class, listOf<String>())),

View file

@ -131,7 +131,7 @@ internal class AnimeExtensionGithubApi {
hasChangelog = it.hasChangelog == 1, hasChangelog = it.hasChangelog == 1,
sources = it.sources?.toAnimeExtensionSources().orEmpty(), sources = it.sources?.toAnimeExtensionSources().orEmpty(),
apkName = it.apk, apkName = it.apk,
iconUrl = "${getUrlPrefix()}icon/${it.apk.replace(".apk", ".png")}", iconUrl = "${getUrlPrefix()}icon/${it.pkg}.png",
) )
} }
} }

View file

@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
import okhttp3.Cache import okhttp3.Cache
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.brotli.BrotliInterceptor
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -19,26 +20,23 @@ class NetworkHelper(
context: Context context: Context
) { ) {
private val cacheDir = File(context.cacheDir, "network_cache")
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
val cookieJar = AndroidCookieJar() val cookieJar = AndroidCookieJar()
private val userAgentInterceptor by lazy { val client: OkHttpClient = run {
UserAgentInterceptor(::defaultUserAgentProvider)
}
private val cloudflareInterceptor by lazy {
CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider)
}
private fun baseClientBuilder(callTimeout: Int = 2): OkHttpClient.Builder {
val builder = OkHttpClient.Builder() val builder = OkHttpClient.Builder()
.cookieJar(cookieJar) .cookieJar(cookieJar)
.connectTimeout(30, TimeUnit.SECONDS) .connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.callTimeout(callTimeout.toLong(), TimeUnit.MINUTES) .callTimeout(2, TimeUnit.MINUTES)
.cache(
Cache(
directory = File(context.cacheDir, "network_cache"),
maxSize = 5L * 1024 * 1024, // 5 MiB
),
)
.addInterceptor(BrotliInterceptor)
.addInterceptor(UncaughtExceptionInterceptor()) .addInterceptor(UncaughtExceptionInterceptor())
.addInterceptor(userAgentInterceptor) .addInterceptor(UserAgentInterceptor(::defaultUserAgentProvider))
if (PrefManager.getVal(PrefName.VerboseLogging)) { if (PrefManager.getVal(PrefName.VerboseLogging)) {
val httpLoggingInterceptor = HttpLoggingInterceptor().apply { val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
@ -47,6 +45,10 @@ class NetworkHelper(
builder.addNetworkInterceptor(httpLoggingInterceptor) builder.addNetworkInterceptor(httpLoggingInterceptor)
} }
builder.addInterceptor(
CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider),
)
when (PrefManager.getVal<Int>(PrefName.DohProvider)) { when (PrefManager.getVal<Int>(PrefName.DohProvider)) {
PREF_DOH_CLOUDFLARE -> builder.dohCloudflare() PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
PREF_DOH_GOOGLE -> builder.dohGoogle() PREF_DOH_GOOGLE -> builder.dohGoogle()
@ -63,19 +65,17 @@ class NetworkHelper(
PREF_DOH_LIBREDNS -> builder.dohLibreDNS() PREF_DOH_LIBREDNS -> builder.dohLibreDNS()
} }
return builder builder.build()
} }
val downloadClient = client.newBuilder().callTimeout(20, TimeUnit.MINUTES).build()
val client by lazy { baseClientBuilder().cache(Cache(cacheDir, cacheSize)).build() } /**
val downloadClient by lazy { baseClientBuilder(20).build() } * @deprecated Since extension-lib 1.5
*/
@Deprecated("The regular client handles Cloudflare by default")
@Suppress("UNUSED") @Suppress("UNUSED")
val cloudflareClient by lazy { val cloudflareClient: OkHttpClient = client
client.newBuilder()
.addInterceptor(cloudflareInterceptor)
.build()
}
val requestClient = Requests( val requestClient = Requests(
client, client,

View file

@ -1072,6 +1072,28 @@
</com.google.android.material.materialswitch.MaterialSwitch> </com.google.android.material.materialswitch.MaterialSwitch>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/playerSettingsInternalCast"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:drawableStart="@drawable/ic_round_cast_24"
android:drawablePadding="16dp"
android:elegantTextHeight="true"
android:fontFamily="@font/poppins_bold"
android:minHeight="64dp"
android:paddingStart="32dp"
android:paddingEnd="32dp"
android:text="@string/try_internal_cast_experimental"
android:textAlignment="viewStart"
android:textColor="@color/bg_opp"
app:cornerRadius="0dp"
app:drawableTint="?attr/colorPrimary"
app:showText="false"
app:thumbTint="@color/button_switch_track">
</com.google.android.material.materialswitch.MaterialSwitch>
<com.google.android.material.materialswitch.MaterialSwitch <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/playerSettingsRotate" android:id="@+id/playerSettingsRotate"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -151,7 +151,7 @@
app:srcCompat="@drawable/ic_round_screen_rotation_alt_24" app:srcCompat="@drawable/ic_round_screen_rotation_alt_24"
tools:ignore="ContentDescription,SpeakableTextPresentCheck" /> tools:ignore="ContentDescription,SpeakableTextPresentCheck" />
<androidx.mediarouter.app.MediaRouteButton <ani.dantotsu.media.anime.CustomCastButton
android:id="@+id/exo_cast" android:id="@+id/exo_cast"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="48dp" android:layout_height="48dp"

View file

@ -48,7 +48,7 @@
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"
android:background="?android:attr/selectableItemBackground" android:background="@android:color/transparent"
app:srcCompat="@drawable/ic_download_24" app:srcCompat="@drawable/ic_download_24"
app:tint="?attr/colorOnBackground" /> app:tint="?attr/colorOnBackground" />

View file

@ -150,7 +150,7 @@
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"
android:background="?android:attr/selectableItemBackground" android:background="@android:color/transparent"
app:srcCompat="@drawable/ic_download_24" app:srcCompat="@drawable/ic_download_24"
app:tint="?attr/colorOnBackground" app:tint="?attr/colorOnBackground"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />

View file

@ -16,7 +16,7 @@
android:layout_marginTop="-16dp" android:layout_marginTop="-16dp"
android:layout_marginBottom="-16dp" android:layout_marginBottom="-16dp"
android:clipToPadding="false" android:clipToPadding="false"
android:padding="24dp"> android:padding="22dp">
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/itemCompactCard" android:id="@+id/itemCompactCard"
@ -28,8 +28,8 @@
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/itemCompactImage" android:id="@+id/itemCompactImage"
android:layout_width="108dp" android:layout_width="102dp"
android:layout_height="160dp" android:layout_height="154dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:transitionName="mediaCover" android:transitionName="mediaCover"
app:shapeAppearanceOverlay="@style/roundedImageView" app:shapeAppearanceOverlay="@style/roundedImageView"

View file

@ -655,5 +655,6 @@
<string name="pinned_sources">Pinned Sources</string> <string name="pinned_sources">Pinned Sources</string>
<string name="import_export_settings">Import/Export Settings</string> <string name="import_export_settings">Import/Export Settings</string>
<string name="import_settings">Import Settings</string> <string name="import_settings">Import Settings</string>
<string name="try_internal_cast_experimental">Try Internal Cast (Experimental)</string>
</resources> </resources>

View file

@ -1,29 +1,29 @@
# 2.1.0 # 2.2.0
## Update????? - **Important:**
- All settings will be reset due to the new settings system. Sorry for the inconvenience!
- **IMPORTANT PLEASE READ**
- Manga extensions installed will need to be uninstalled before they can be updated. You can do this in your phone settings, or long press the extension in Dantotsu settings.
- If you used a pretest from discord, PLEASE DELETE ALL ANIME DOWNLOADS. you can do this in settings.
- This also could break previous manga downloads.
- **Bugfixes:**
- Source fixes
- Manga extension fix
- General theme tweaks
- Various bugs/crashes
- Many others
- **New Features:** - **New Features:**
- Anime Downloads - Import/Export settings
- Tap to scroll in manga (paged mode) - New source organization system in extension settings
- Webview to set cookies - Filter sources by language
- New theme - Defaulting to the external casting system (internal cast can be enabled in settings)
- Internal casting (long press cast to use old method) - sub/dub toggle for some sources (requires source settings page to be opened at least once)
- Manga page ui rework - SoftSub downloads (when available)
- Video source ui cleanup - Various UI uplifts
- Offline mode declutter - Many small features (see beta changelogs)
- Better incognito awareness - New easter egg :3
- **Bugfixes:**
- Many source fixes
- Better information on Anilist rate limiting
- User will get a notification when rate limited
- Rate limiting less likely to occur (especially on app startup)
- Various bug/crash fixes
- General theme tweaks
- Popups will now follow OLED mode
- Fix for file permissions on older Android versions
- Many small bug fixes (see beta changelogs)
- **Like what you see?** - **Like what you see?**
- Consider supporting me on [Github](https://github.com/sponsors/rebelonion) or [Buy Me a Coffee](https://www.buymeacoffee.com/rebelonion)! - Consider supporting me on [Github](https://github.com/sponsors/rebelonion) or [Buy Me a Coffee](https://www.buymeacoffee.com/rebelonion)!