commit
ab199a3502
28 changed files with 438 additions and 112 deletions
2
.github/workflows/beta.yml
vendored
2
.github/workflows/beta.yml
vendored
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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>())),
|
||||||
|
|
|
@ -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",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
46
stable.md
46
stable.md
|
@ -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)!
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue