commit
9dd59bb592
70 changed files with 2750 additions and 1408 deletions
11
.github/workflows/beta.yml
vendored
11
.github/workflows/beta.yml
vendored
|
@ -141,11 +141,12 @@ jobs:
|
||||||
|
|
||||||
# Decimal color codes for contributors
|
# Decimal color codes for contributors
|
||||||
declare -A contributor_colors
|
declare -A contributor_colors
|
||||||
default_color="16721401"
|
default_color="#bf2cc8"
|
||||||
contributor_colors["grayankit"]="#350297"
|
|
||||||
contributor_colors["ibo"]="#ff9b46"
|
contributor_colors["ibo"]="#ff9b46"
|
||||||
contributor_colors["aayush262"]="#5d689d"
|
contributor_colors["aayush262"]="#5d689d"
|
||||||
contributor_colors["Sadwhy"]="#ff7e95"
|
contributor_colors["Sadwhy"]="#ff7e95"
|
||||||
|
contributor_colors["grayankit"]="#c51aa1"
|
||||||
|
contributor_colors["rebelonion"]="#d4e5ed"
|
||||||
|
|
||||||
hex_to_decimal() { printf '%d' "0x${1#"#"}"; }
|
hex_to_decimal() { printf '%d' "0x${1#"#"}"; }
|
||||||
|
|
||||||
|
@ -179,7 +180,7 @@ jobs:
|
||||||
top_contributor=""
|
top_contributor=""
|
||||||
top_contributor_count=0
|
top_contributor_count=0
|
||||||
top_contributor_avatar=""
|
top_contributor_avatar=""
|
||||||
embed_color=$default_color
|
embed_color=$(hex_to_decimal "$default_color")
|
||||||
|
|
||||||
# Process contributors in the new order
|
# Process contributors in the new order
|
||||||
while read -r login; do
|
while read -r login; do
|
||||||
|
@ -201,7 +202,7 @@ jobs:
|
||||||
elif [ $commit_count -eq $max_commits ]; then
|
elif [ $commit_count -eq $max_commits ]; then
|
||||||
top_contributors+=("$login")
|
top_contributors+=("$login")
|
||||||
top_contributor_count=$((top_contributor_count + 1))
|
top_contributor_count=$((top_contributor_count + 1))
|
||||||
embed_color=$default_color
|
embed_color=$(hex_to_decimal "$default_color")
|
||||||
fi
|
fi
|
||||||
echo "Debug top contributors:"
|
echo "Debug top contributors:"
|
||||||
echo "$top_contributors"
|
echo "$top_contributors"
|
||||||
|
@ -241,7 +242,7 @@ jobs:
|
||||||
thumbnail_url="$top_contributor_avatar"
|
thumbnail_url="$top_contributor_avatar"
|
||||||
else
|
else
|
||||||
thumbnail_url="https://i.imgur.com/5o3Y9Jb.gif"
|
thumbnail_url="https://i.imgur.com/5o3Y9Jb.gif"
|
||||||
embed_color=$default_color
|
embed_color=$(hex_to_decimal "$default_color")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Truncate field values
|
# Truncate field values
|
||||||
|
|
|
@ -18,8 +18,8 @@ android {
|
||||||
minSdk 21
|
minSdk 21
|
||||||
targetSdk 35
|
targetSdk 35
|
||||||
versionCode((System.currentTimeMillis() / 60000).toInteger())
|
versionCode((System.currentTimeMillis() / 60000).toInteger())
|
||||||
versionName "3.1.0"
|
versionName "3.2.0"
|
||||||
versionCode 300100000
|
versionCode 300200000
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ import ani.dantotsu.util.Logger
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.time.delay
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
|
|
@ -394,11 +394,10 @@
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<!-- Support both schemes -->
|
<data android:host="add-repo"/>
|
||||||
<data android:scheme="tachiyomi"/>
|
<data android:scheme="tachiyomi"/>
|
||||||
<data android:host="add-repo"/>
|
|
||||||
<data android:scheme="aniyomi"/>
|
<data android:scheme="aniyomi"/>
|
||||||
<data android:host="add-repo"/>
|
<data android:scheme="novelyomi"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
|
|
|
@ -449,19 +449,20 @@ class MainActivity : AppCompatActivity() {
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
throw Exception("Uri is null")
|
throw Exception("Uri is null")
|
||||||
}
|
}
|
||||||
if ((uri.scheme == "tachiyomi" || uri.scheme == "aniyomi") && uri.host == "add-repo") {
|
if ((uri.scheme == "tachiyomi" || uri.scheme == "aniyomi" || uri.scheme == "novelyomi") && uri.host == "add-repo") {
|
||||||
val url = uri.getQueryParameter("url") ?: throw Exception("No url for repo import")
|
val url = uri.getQueryParameter("url") ?: throw Exception("No url for repo import")
|
||||||
val prefName = if (uri.scheme == "tachiyomi") {
|
val (prefName, name) = when (uri.scheme) {
|
||||||
PrefName.MangaExtensionRepos
|
"tachiyomi" -> PrefName.MangaExtensionRepos to "Manga"
|
||||||
} else {
|
"aniyomi" -> PrefName.AnimeExtensionRepos to "Anime"
|
||||||
PrefName.AnimeExtensionRepos
|
"novelyomi" -> PrefName.NovelExtensionRepos to "Novel"
|
||||||
|
else -> throw Exception("Invalid scheme")
|
||||||
}
|
}
|
||||||
val savedRepos: Set<String> = PrefManager.getVal(prefName)
|
val savedRepos: Set<String> = PrefManager.getVal(prefName)
|
||||||
val newRepos = savedRepos.toMutableSet()
|
val newRepos = savedRepos.toMutableSet()
|
||||||
AddRepositoryBottomSheet.addRepoWarning(this) {
|
AddRepositoryBottomSheet.addRepoWarning(this) {
|
||||||
newRepos.add(url)
|
newRepos.add(url)
|
||||||
PrefManager.setVal(prefName, newRepos)
|
PrefManager.setVal(prefName, newRepos)
|
||||||
toast("${if (uri.scheme == "tachiyomi") "Manga" else "Anime"} Extension Repo added")
|
toast("$name Extension Repo added")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -488,9 +489,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
return@passwordAlertDialog
|
return@passwordAlertDialog
|
||||||
}
|
}
|
||||||
if (PreferencePackager.unpack(decryptedJson)) {
|
if (PreferencePackager.unpack(decryptedJson)) {
|
||||||
val intent = Intent(this, this.javaClass)
|
val newIntent = Intent(this, this.javaClass)
|
||||||
this.finish()
|
this.finish()
|
||||||
startActivity(intent)
|
startActivity(newIntent)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast("Password cannot be empty")
|
toast("Password cannot be empty")
|
||||||
|
@ -499,9 +500,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
} else if (name.endsWith(".ani")) {
|
} else if (name.endsWith(".ani")) {
|
||||||
val decryptedJson = jsonString.toString(Charsets.UTF_8)
|
val decryptedJson = jsonString.toString(Charsets.UTF_8)
|
||||||
if (PreferencePackager.unpack(decryptedJson)) {
|
if (PreferencePackager.unpack(decryptedJson)) {
|
||||||
val intent = Intent(this, this.javaClass)
|
val newIntent = Intent(this, this.javaClass)
|
||||||
this.finish()
|
this.finish()
|
||||||
startActivity(intent)
|
startActivity(newIntent)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast("Invalid file type")
|
toast("Invalid file type")
|
||||||
|
|
|
@ -2,15 +2,25 @@ package ani.dantotsu.connections.anilist
|
||||||
|
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
|
import ani.dantotsu.media.Author
|
||||||
|
import ani.dantotsu.media.Character
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
|
import ani.dantotsu.media.Studio
|
||||||
|
import ani.dantotsu.profile.User
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
data class SearchResults(
|
interface SearchResults<T> {
|
||||||
|
var search: String?
|
||||||
|
var page: Int
|
||||||
|
var results: MutableList<T>
|
||||||
|
var hasNextPage: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AniMangaSearchResults(
|
||||||
val type: String,
|
val type: String,
|
||||||
var isAdult: Boolean,
|
var isAdult: Boolean,
|
||||||
var onList: Boolean? = null,
|
var onList: Boolean? = null,
|
||||||
var perPage: Int? = null,
|
var perPage: Int? = null,
|
||||||
var search: String? = null,
|
|
||||||
var countryOfOrigin: String? = null,
|
var countryOfOrigin: String? = null,
|
||||||
var sort: String? = null,
|
var sort: String? = null,
|
||||||
var genres: MutableList<String>? = null,
|
var genres: MutableList<String>? = null,
|
||||||
|
@ -23,10 +33,11 @@ data class SearchResults(
|
||||||
var seasonYear: Int? = null,
|
var seasonYear: Int? = null,
|
||||||
var startYear: Int? = null,
|
var startYear: Int? = null,
|
||||||
var season: String? = null,
|
var season: String? = null,
|
||||||
var page: Int = 1,
|
override var search: String? = null,
|
||||||
var results: MutableList<Media>,
|
override var page: Int = 1,
|
||||||
var hasNextPage: Boolean,
|
override var results: MutableList<Media>,
|
||||||
) : Serializable {
|
override var hasNextPage: Boolean,
|
||||||
|
) : SearchResults<Media>, Serializable {
|
||||||
fun toChipList(): List<SearchChip> {
|
fun toChipList(): List<SearchChip> {
|
||||||
val list = mutableListOf<SearchChip>()
|
val list = mutableListOf<SearchChip>()
|
||||||
sort?.let {
|
sort?.let {
|
||||||
|
@ -109,3 +120,32 @@ data class SearchResults(
|
||||||
val text: String
|
val text: String
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class CharacterSearchResults(
|
||||||
|
override var search: String?,
|
||||||
|
override var page: Int = 1,
|
||||||
|
override var results: MutableList<Character>,
|
||||||
|
override var hasNextPage: Boolean,
|
||||||
|
) : SearchResults<Character>, Serializable
|
||||||
|
|
||||||
|
data class StudioSearchResults(
|
||||||
|
override var search: String?,
|
||||||
|
override var page: Int = 1,
|
||||||
|
override var results: MutableList<Studio>,
|
||||||
|
override var hasNextPage: Boolean,
|
||||||
|
) : SearchResults<Studio>, Serializable
|
||||||
|
|
||||||
|
|
||||||
|
data class StaffSearchResults(
|
||||||
|
override var search: String?,
|
||||||
|
override var page: Int = 1,
|
||||||
|
override var results: MutableList<Author>,
|
||||||
|
override var hasNextPage: Boolean,
|
||||||
|
) : SearchResults<Author>, Serializable
|
||||||
|
|
||||||
|
data class UserSearchResults(
|
||||||
|
override var search: String?,
|
||||||
|
override var page: Int = 1,
|
||||||
|
override var results: MutableList<User>,
|
||||||
|
override var hasNextPage: Boolean,
|
||||||
|
) : SearchResults<User>, Serializable
|
|
@ -311,7 +311,6 @@ object Anilist {
|
||||||
)
|
)
|
||||||
val remaining = json.headers["X-RateLimit-Remaining"]?.toIntOrNull() ?: -1
|
val remaining = json.headers["X-RateLimit-Remaining"]?.toIntOrNull() ?: -1
|
||||||
Logger.log("Remaining requests: $remaining")
|
Logger.log("Remaining requests: $remaining")
|
||||||
println("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
|
||||||
|
|
|
@ -44,7 +44,8 @@ class AnilistQueries {
|
||||||
val response: Query.Viewer?
|
val response: Query.Viewer?
|
||||||
measureTimeMillis {
|
measureTimeMillis {
|
||||||
response = executeQuery(
|
response = executeQuery(
|
||||||
"""{Viewer{name options{timezone titleLanguage staffNameLanguage activityMergeTime airingNotifications displayAdultContent restrictMessagesToFollowing} avatar{medium} bannerImage id mediaListOptions{scoreFormat rowOrder animeList{customLists} mangaList{customLists}} statistics{anime{episodesWatched} manga{chaptersRead}} unreadNotificationCount}}""")
|
"""{Viewer{name options{timezone titleLanguage staffNameLanguage activityMergeTime airingNotifications displayAdultContent restrictMessagesToFollowing} avatar{medium} bannerImage id mediaListOptions{scoreFormat rowOrder animeList{customLists} mangaList{customLists}} statistics{anime{episodesWatched} manga{chaptersRead}} unreadNotificationCount}}"""
|
||||||
|
)
|
||||||
}.also { println("time : $it") }
|
}.also { println("time : $it") }
|
||||||
val user = response?.data?.user ?: return false
|
val user = response?.data?.user ?: return false
|
||||||
|
|
||||||
|
@ -96,12 +97,10 @@ class AnilistQueries {
|
||||||
|
|
||||||
fun mediaDetails(media: Media): Media {
|
fun mediaDetails(media: Media): Media {
|
||||||
media.cameFromContinue = false
|
media.cameFromContinue = false
|
||||||
|
|
||||||
val query =
|
|
||||||
"""{Media(id:${media.id}){id favourites popularity episodes chapters streamingEpisodes {title thumbnail url site} mediaListEntry{id status score(format:POINT_100)progress private notes repeat customLists updatedAt startedAt{year month day}completedAt{year month day}}reviews(perPage:3, sort:SCORE_DESC){nodes{id mediaId mediaType summary body(asHtml:true) rating ratingAmount userRating score private siteUrl createdAt updatedAt user{id name bannerImage avatar{medium large}}}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer{site id}synonyms tags{name rank isMediaSpoiler}characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role voiceActors { id name { first middle last full native userPreferred } image { large medium } languageV2 } node{id image{medium}name{userPreferred}isFavourite}}}relations{edges{relationType(version:2)node{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{role node{id image{large medium}name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}Page(page:1){pageInfo{total perPage currentPage lastPage hasNextPage}mediaList(isFollowing:true,sort:[STATUS],mediaId:${media.id}){id status score(format: POINT_100) progress progressVolumes user{id name avatar{large medium}}}}}"""
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val anilist = async {
|
val anilist = async {
|
||||||
var response = executeQuery<Query.Media>(query, force = true)
|
var response =
|
||||||
|
executeQuery<Query.Media>(fullMediaInformation(media.id), force = true)
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
fun parse() {
|
fun parse() {
|
||||||
val fetchedMedia = response?.data?.media ?: return
|
val fetchedMedia = response?.data?.media ?: return
|
||||||
|
@ -291,7 +290,10 @@ class AnilistQueries {
|
||||||
val firstStudio = get(0)
|
val firstStudio = get(0)
|
||||||
media.anime.mainStudio = Studio(
|
media.anime.mainStudio = Studio(
|
||||||
firstStudio.id.toString(),
|
firstStudio.id.toString(),
|
||||||
firstStudio.name ?: "N/A"
|
firstStudio.name ?: "N/A",
|
||||||
|
firstStudio.isFavourite ?: false,
|
||||||
|
firstStudio.favourites ?: 0,
|
||||||
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,7 +335,11 @@ class AnilistQueries {
|
||||||
if (response.data?.media != null) parse()
|
if (response.data?.media != null) parse()
|
||||||
else {
|
else {
|
||||||
snackString(currContext()?.getString(R.string.adult_stuff))
|
snackString(currContext()?.getString(R.string.adult_stuff))
|
||||||
response = executeQuery(query, force = true, useToken = false)
|
response = executeQuery(
|
||||||
|
fullMediaInformation(media.id),
|
||||||
|
force = true,
|
||||||
|
useToken = false
|
||||||
|
)
|
||||||
if (response?.data?.media != null) parse()
|
if (response?.data?.media != null) parse()
|
||||||
else snackString(currContext()?.getString(R.string.what_did_you_open))
|
else snackString(currContext()?.getString(R.string.what_did_you_open))
|
||||||
}
|
}
|
||||||
|
@ -400,8 +406,6 @@ class AnilistQueries {
|
||||||
return media
|
return media
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private suspend fun favMedia(anime: Boolean, id: Int? = Anilist.userid): ArrayList<Media> {
|
private suspend fun favMedia(anime: Boolean, id: Int? = Anilist.userid): ArrayList<Media> {
|
||||||
var hasNextPage = true
|
var hasNextPage = true
|
||||||
var page = 0
|
var page = 0
|
||||||
|
@ -426,8 +430,6 @@ class AnilistQueries {
|
||||||
return responseArray
|
return responseArray
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
suspend fun getUserStatus(): ArrayList<User>? {
|
suspend fun getUserStatus(): ArrayList<User>? {
|
||||||
val toShow: List<Boolean> =
|
val toShow: List<Boolean> =
|
||||||
PrefManager.getVal(PrefName.HomeLayout)
|
PrefManager.getVal(PrefName.HomeLayout)
|
||||||
|
@ -485,19 +487,23 @@ class AnilistQueries {
|
||||||
return list.toCollection(ArrayList())
|
return list.toCollection(ArrayList())
|
||||||
} else return null
|
} else return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun favMediaQuery(anime: Boolean, page: Int, id: Int? = Anilist.userid): String {
|
private fun favMediaQuery(anime: Boolean, page: Int, id: Int? = Anilist.userid): String {
|
||||||
return """User(id:${id}){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}}}}}}"""
|
return """User(id:${id}){id favourites{${if (anime) "anime" else "manga"}(page:$page){$standardPageInformation 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}}}}}}"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recommendationQuery(): String {
|
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 } } } } """
|
return """ Page(page: 1, perPage:30) { $standardPageInformation 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 {
|
private fun recommendationPlannedQuery(type: String): String {
|
||||||
return """ MediaListCollection(userId: ${Anilist.userid}, type: $type, status: PLANNING${if (type == "ANIME") ", sort: MEDIA_POPULARITY_DESC" else ""} ) { 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 } } } } }"""
|
return """ MediaListCollection(userId: ${Anilist.userid}, type: $type, status: PLANNING${if (type == "ANIME") ", sort: MEDIA_POPULARITY_DESC" else ""} ) { 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 } } } } }"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun continueMediaQuery(type: String, status: String): String {
|
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 } } } } } """
|
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 initHomePage(): Map<String, ArrayList<Media>> {
|
suspend fun initHomePage(): Map<String, ArrayList<Media>> {
|
||||||
val removeList = PrefManager.getCustomVal("removeList", setOf<Int>())
|
val removeList = PrefManager.getCustomVal("removeList", setOf<Int>())
|
||||||
val hidePrivate = PrefManager.getVal<Boolean>(PrefName.HidePrivate)
|
val hidePrivate = PrefManager.getVal<Boolean>(PrefName.HidePrivate)
|
||||||
|
@ -888,7 +894,7 @@ class AnilistQueries {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun search(
|
suspend fun searchAniManga(
|
||||||
type: String,
|
type: String,
|
||||||
page: Int? = null,
|
page: Int? = null,
|
||||||
perPage: Int? = null,
|
perPage: Int? = null,
|
||||||
|
@ -910,52 +916,7 @@ class AnilistQueries {
|
||||||
id: Int? = null,
|
id: Int? = null,
|
||||||
hd: Boolean = false,
|
hd: Boolean = false,
|
||||||
adultOnly: Boolean = false
|
adultOnly: Boolean = false
|
||||||
): SearchResults? {
|
): AniMangaSearchResults? {
|
||||||
val query = """
|
|
||||||
query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult: Boolean = false, ${"$"}search: String, ${"$"}format: [MediaFormat], ${"$"}status: MediaStatus, ${"$"}countryOfOrigin: CountryCode, ${"$"}source: MediaSource, ${"$"}season: MediaSeason, ${"$"}seasonYear: Int, ${"$"}year: String, ${"$"}onList: Boolean, ${"$"}yearLesser: FuzzyDateInt, ${"$"}yearGreater: FuzzyDateInt, ${"$"}episodeLesser: Int, ${"$"}episodeGreater: Int, ${"$"}durationLesser: Int, ${"$"}durationGreater: Int, ${"$"}chapterLesser: Int, ${"$"}chapterGreater: Int, ${"$"}volumeLesser: Int, ${"$"}volumeGreater: Int, ${"$"}licensedBy: [String], ${"$"}isLicensed: Boolean, ${"$"}genres: [String], ${"$"}excludedGenres: [String], ${"$"}tags: [String], ${"$"}excludedTags: [String], ${"$"}minimumTagRank: Int, ${"$"}sort: [MediaSort] = [POPULARITY_DESC, SCORE_DESC, START_DATE_DESC]) {
|
|
||||||
Page(page: ${"$"}page, perPage: ${perPage ?: 50}) {
|
|
||||||
pageInfo {
|
|
||||||
total
|
|
||||||
perPage
|
|
||||||
currentPage
|
|
||||||
lastPage
|
|
||||||
hasNextPage
|
|
||||||
}
|
|
||||||
media(id: ${"$"}id, type: ${"$"}type, season: ${"$"}season, format_in: ${"$"}format, status: ${"$"}status, countryOfOrigin: ${"$"}countryOfOrigin, source: ${"$"}source, search: ${"$"}search, onList: ${"$"}onList, seasonYear: ${"$"}seasonYear, startDate_like: ${"$"}year, startDate_lesser: ${"$"}yearLesser, startDate_greater: ${"$"}yearGreater, episodes_lesser: ${"$"}episodeLesser, episodes_greater: ${"$"}episodeGreater, duration_lesser: ${"$"}durationLesser, duration_greater: ${"$"}durationGreater, chapters_lesser: ${"$"}chapterLesser, chapters_greater: ${"$"}chapterGreater, volumes_lesser: ${"$"}volumeLesser, volumes_greater: ${"$"}volumeGreater, licensedBy_in: ${"$"}licensedBy, isLicensed: ${"$"}isLicensed, genre_in: ${"$"}genres, genre_not_in: ${"$"}excludedGenres, tag_in: ${"$"}tags, tag_not_in: ${"$"}excludedTags, minimumTagRank: ${"$"}minimumTagRank, sort: ${"$"}sort, isAdult: ${"$"}isAdult) {
|
|
||||||
id
|
|
||||||
idMal
|
|
||||||
isAdult
|
|
||||||
status
|
|
||||||
chapters
|
|
||||||
episodes
|
|
||||||
nextAiringEpisode {
|
|
||||||
episode
|
|
||||||
}
|
|
||||||
type
|
|
||||||
genres
|
|
||||||
meanScore
|
|
||||||
isFavourite
|
|
||||||
format
|
|
||||||
bannerImage
|
|
||||||
coverImage {
|
|
||||||
large
|
|
||||||
extraLarge
|
|
||||||
}
|
|
||||||
title {
|
|
||||||
english
|
|
||||||
romaji
|
|
||||||
userPreferred
|
|
||||||
}
|
|
||||||
mediaListEntry {
|
|
||||||
progress
|
|
||||||
private
|
|
||||||
score(format: POINT_100)
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""".replace("\n", " ").replace(""" """, "")
|
|
||||||
val variables = """{"type":"$type","isAdult":$isAdult
|
val variables = """{"type":"$type","isAdult":$isAdult
|
||||||
${if (adultOnly) ""","isAdult":true""" else ""}
|
${if (adultOnly) ""","isAdult":true""" else ""}
|
||||||
${if (onList != null) ""","onList":$onList""" else ""}
|
${if (onList != null) ""","onList":$onList""" else ""}
|
||||||
|
@ -1000,8 +961,9 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult:
|
||||||
}]"""
|
}]"""
|
||||||
else ""
|
else ""
|
||||||
}
|
}
|
||||||
}""".replace("\n", " ").replace(""" """, "")
|
}""".prepare()
|
||||||
val response = executeQuery<Query.Page>(query, variables, true)?.data?.page
|
val response =
|
||||||
|
executeQuery<Query.Page>(aniMangaSearch(perPage), variables, true)?.data?.page
|
||||||
if (response?.media != null) {
|
if (response?.media != null) {
|
||||||
val responseArray = arrayListOf<Media>()
|
val responseArray = arrayListOf<Media>()
|
||||||
response.media?.forEach { i ->
|
response.media?.forEach { i ->
|
||||||
|
@ -1021,7 +983,7 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult:
|
||||||
|
|
||||||
val pageInfo = response.pageInfo ?: return null
|
val pageInfo = response.pageInfo ?: return null
|
||||||
|
|
||||||
return SearchResults(
|
return AniMangaSearchResults(
|
||||||
type = type,
|
type = type,
|
||||||
perPage = perPage,
|
perPage = perPage,
|
||||||
search = search,
|
search = search,
|
||||||
|
@ -1047,6 +1009,169 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun searchCharacters(page: Int, search: String?): CharacterSearchResults? {
|
||||||
|
if (search.isNullOrBlank()) return null
|
||||||
|
val query = """
|
||||||
|
{
|
||||||
|
Page(page: $page, perPage: $ITEMS_PER_PAGE) {
|
||||||
|
$standardPageInformation
|
||||||
|
characters(search: "$search") {
|
||||||
|
${characterInformation(false)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
val response = executeQuery<Query.Page>(query, force = true)?.data?.page
|
||||||
|
|
||||||
|
if (response?.characters != null) {
|
||||||
|
val responseArray = arrayListOf<Character>()
|
||||||
|
response.characters?.forEach { i ->
|
||||||
|
responseArray.add(
|
||||||
|
Character(
|
||||||
|
i.id,
|
||||||
|
i.name?.full,
|
||||||
|
i.image?.medium ?: i.image?.large,
|
||||||
|
null,
|
||||||
|
null.toString(),
|
||||||
|
i.isFavourite ?: false,
|
||||||
|
i.description,
|
||||||
|
i.age,
|
||||||
|
i.gender,
|
||||||
|
i.dateOfBirth,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val pageInfo = response.pageInfo ?: return null
|
||||||
|
|
||||||
|
return CharacterSearchResults(
|
||||||
|
search = search,
|
||||||
|
results = responseArray,
|
||||||
|
page = pageInfo.currentPage ?: 0,
|
||||||
|
hasNextPage = pageInfo.hasNextPage == true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun searchStudios(page: Int, search: String?): StudioSearchResults? {
|
||||||
|
if (search.isNullOrBlank()) return null
|
||||||
|
val query = """
|
||||||
|
{
|
||||||
|
Page(page: $page, perPage: $ITEMS_PER_PAGE) {
|
||||||
|
$standardPageInformation
|
||||||
|
studios(search: "$search") {
|
||||||
|
${studioInformation(1, 1)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
val response = executeQuery<Query.Page>(query, force = true)?.data?.page
|
||||||
|
|
||||||
|
if (response?.studios != null) {
|
||||||
|
val responseArray = arrayListOf<Studio>()
|
||||||
|
response.studios?.forEach { i ->
|
||||||
|
responseArray.add(
|
||||||
|
Studio(
|
||||||
|
i.id.toString(),
|
||||||
|
i.name ?: return null,
|
||||||
|
i.isFavourite ?: false,
|
||||||
|
i.favourites,
|
||||||
|
i.media?.edges?.firstOrNull()?.node?.let { it.coverImage?.large }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val pageInfo = response.pageInfo ?: return null
|
||||||
|
|
||||||
|
return StudioSearchResults(
|
||||||
|
search = search,
|
||||||
|
results = responseArray,
|
||||||
|
page = pageInfo.currentPage ?: 0,
|
||||||
|
hasNextPage = pageInfo.hasNextPage == true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun searchStaff(page: Int, search: String?): StaffSearchResults? {
|
||||||
|
if (search.isNullOrBlank()) return null
|
||||||
|
val query = """
|
||||||
|
{
|
||||||
|
Page(page: $page, perPage: $ITEMS_PER_PAGE) {
|
||||||
|
$standardPageInformation
|
||||||
|
staff(search: "$search") {
|
||||||
|
${staffInformation(1, 1)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
val response = executeQuery<Query.Page>(query, force = true)?.data?.page
|
||||||
|
|
||||||
|
if (response?.staff != null) {
|
||||||
|
val responseArray = arrayListOf<Author>()
|
||||||
|
response.staff?.forEach { i ->
|
||||||
|
responseArray.add(
|
||||||
|
Author(
|
||||||
|
i.id,
|
||||||
|
i.name?.userPreferred ?: return null,
|
||||||
|
i.image?.large,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val pageInfo = response.pageInfo ?: return null
|
||||||
|
|
||||||
|
return StaffSearchResults(
|
||||||
|
search = search,
|
||||||
|
results = responseArray,
|
||||||
|
page = pageInfo.currentPage ?: 0,
|
||||||
|
hasNextPage = pageInfo.hasNextPage == true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun searchUsers(page: Int, search: String?): UserSearchResults? {
|
||||||
|
val query = """
|
||||||
|
{
|
||||||
|
Page(page: $page, perPage: $ITEMS_PER_PAGE) {
|
||||||
|
$standardPageInformation
|
||||||
|
users(search: "$search") {
|
||||||
|
${userInformation()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
val response = executeQuery<Query.Page>(query, force = true)?.data?.page
|
||||||
|
|
||||||
|
if (response?.users != null) {
|
||||||
|
val users = response.users?.map { user ->
|
||||||
|
User(
|
||||||
|
user.id,
|
||||||
|
user.name ?: return null,
|
||||||
|
user.avatar?.medium,
|
||||||
|
user.bannerImage
|
||||||
|
)
|
||||||
|
} ?: return null
|
||||||
|
|
||||||
|
return UserSearchResults(
|
||||||
|
search = search,
|
||||||
|
results = users.toMutableList(),
|
||||||
|
page = response.pageInfo?.currentPage ?: 0,
|
||||||
|
hasNextPage = response.pageInfo?.hasNextPage == true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private fun mediaList(media1: Page?): ArrayList<Media> {
|
private fun mediaList(media1: Page?): ArrayList<Media> {
|
||||||
val combinedList = arrayListOf<Media>()
|
val combinedList = arrayListOf<Media>()
|
||||||
media1?.media?.mapTo(combinedList) { Media(it) }
|
media1?.media?.mapTo(combinedList) { Media(it) }
|
||||||
|
@ -1071,26 +1196,65 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult:
|
||||||
val countryFilter = country?.let { "countryOfOrigin:$it, " } ?: ""
|
val countryFilter = country?.let { "countryOfOrigin:$it, " } ?: ""
|
||||||
|
|
||||||
return buildString {
|
return buildString {
|
||||||
append("""Page(page:1,perPage:50){pageInfo{hasNextPage total}media(sort:$sort, type:$type, $formatFilter $countryFilter $includeList $isAdult){id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}""")
|
append("""Page(page:1,perPage:50){$standardPageInformation media(sort:$sort, type:$type, $formatFilter $countryFilter $includeList $isAdult){id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}""")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recentAnimeUpdates(page: Int): String {
|
private fun recentAnimeUpdates(page: Int): String {
|
||||||
val currentTime = System.currentTimeMillis() / 1000
|
val currentTime = System.currentTimeMillis() / 1000
|
||||||
return buildString {
|
return buildString {
|
||||||
append("""Page(page:$page,perPage:50){pageInfo{hasNextPage total}airingSchedules(airingAt_greater:0 airingAt_lesser:${currentTime - 10000} sort:TIME_DESC){episode airingAt media{id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}}""")
|
append("""Page(page:$page,perPage:50){$standardPageInformation airingSchedules(airingAt_greater:0 airingAt_lesser:${currentTime - 10000} sort:TIME_DESC){episode airingAt media{id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}}""")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queryAnimeList(): String {
|
private fun queryAnimeList(): String {
|
||||||
return buildString {
|
return buildString {
|
||||||
append("""{recentUpdates:${recentAnimeUpdates(1)} recentUpdates2:${recentAnimeUpdates(2)} trendingMovies:${buildQueryString("POPULARITY_DESC", "ANIME", "MOVIE")} topRated:${buildQueryString("SCORE_DESC", "ANIME")} mostFav:${buildQueryString("FAVOURITES_DESC", "ANIME")}}""")
|
append(
|
||||||
|
"""{recentUpdates:${recentAnimeUpdates(1)} recentUpdates2:${recentAnimeUpdates(2)} trendingMovies:${
|
||||||
|
buildQueryString(
|
||||||
|
"POPULARITY_DESC",
|
||||||
|
"ANIME",
|
||||||
|
"MOVIE"
|
||||||
|
)
|
||||||
|
} topRated:${
|
||||||
|
buildQueryString(
|
||||||
|
"SCORE_DESC",
|
||||||
|
"ANIME"
|
||||||
|
)
|
||||||
|
} mostFav:${buildQueryString("FAVOURITES_DESC", "ANIME")}}"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queryMangaList(): String {
|
private fun queryMangaList(): String {
|
||||||
return buildString {
|
return buildString {
|
||||||
append("""{trendingManga:${buildQueryString("POPULARITY_DESC", "MANGA", country = "JP")} trendingManhwa:${buildQueryString("POPULARITY_DESC", "MANGA", country = "KR")} trendingNovel:${buildQueryString("POPULARITY_DESC", "MANGA", format = "NOVEL", country = "JP")} topRated:${buildQueryString("SCORE_DESC", "MANGA")} mostFav:${buildQueryString("FAVOURITES_DESC", "MANGA")}}""")
|
append(
|
||||||
|
"""{trendingManga:${
|
||||||
|
buildQueryString(
|
||||||
|
"POPULARITY_DESC",
|
||||||
|
"MANGA",
|
||||||
|
country = "JP"
|
||||||
|
)
|
||||||
|
} trendingManhwa:${
|
||||||
|
buildQueryString(
|
||||||
|
"POPULARITY_DESC",
|
||||||
|
"MANGA",
|
||||||
|
country = "KR"
|
||||||
|
)
|
||||||
|
} trendingNovel:${
|
||||||
|
buildQueryString(
|
||||||
|
"POPULARITY_DESC",
|
||||||
|
"MANGA",
|
||||||
|
format = "NOVEL",
|
||||||
|
country = "JP"
|
||||||
|
)
|
||||||
|
} topRated:${
|
||||||
|
buildQueryString(
|
||||||
|
"SCORE_DESC",
|
||||||
|
"MANGA"
|
||||||
|
)
|
||||||
|
} mostFav:${buildQueryString("FAVOURITES_DESC", "MANGA")}}"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1145,7 +1309,6 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult:
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun recentlyUpdated(
|
suspend fun recentlyUpdated(
|
||||||
greater: Long = 0,
|
greater: Long = 0,
|
||||||
lesser: Long = System.currentTimeMillis() / 1000 - 10000
|
lesser: Long = System.currentTimeMillis() / 1000 - 10000
|
||||||
|
@ -1153,10 +1316,7 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult:
|
||||||
suspend fun execute(page: Int = 1): Page? {
|
suspend fun execute(page: Int = 1): Page? {
|
||||||
val query = """{
|
val query = """{
|
||||||
Page(page:$page,perPage:50) {
|
Page(page:$page,perPage:50) {
|
||||||
pageInfo {
|
$standardPageInformation
|
||||||
hasNextPage
|
|
||||||
total
|
|
||||||
}
|
|
||||||
airingSchedules(
|
airingSchedules(
|
||||||
airingAt_greater: $greater
|
airingAt_greater: $greater
|
||||||
airingAt_lesser: $lesser
|
airingAt_lesser: $lesser
|
||||||
|
@ -1165,35 +1325,11 @@ Page(page:$page,perPage:50) {
|
||||||
episode
|
episode
|
||||||
airingAt
|
airingAt
|
||||||
media {
|
media {
|
||||||
id
|
${standardMediaInformation()}
|
||||||
idMal
|
|
||||||
status
|
|
||||||
chapters
|
|
||||||
episodes
|
|
||||||
nextAiringEpisode { episode }
|
|
||||||
isAdult
|
|
||||||
type
|
|
||||||
meanScore
|
|
||||||
isFavourite
|
|
||||||
format
|
|
||||||
bannerImage
|
|
||||||
countryOfOrigin
|
|
||||||
coverImage { large }
|
|
||||||
title {
|
|
||||||
english
|
|
||||||
romaji
|
|
||||||
userPreferred
|
|
||||||
}
|
|
||||||
mediaListEntry {
|
|
||||||
progress
|
|
||||||
private
|
|
||||||
score(format: POINT_100)
|
|
||||||
status
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}""".prepare()
|
||||||
}""".replace("\n", " ").replace(""" """, "")
|
|
||||||
return executeQuery<Query.Page>(query, force = true)?.data?.page
|
return executeQuery<Query.Page>(query, force = true)?.data?.page
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1221,68 +1357,37 @@ Page(page:$page,perPage:50) {
|
||||||
suspend fun getCharacterDetails(character: Character): Character {
|
suspend fun getCharacterDetails(character: Character): Character {
|
||||||
val query = """ {
|
val query = """ {
|
||||||
Character(id: ${character.id}) {
|
Character(id: ${character.id}) {
|
||||||
id
|
${characterInformation(true)}
|
||||||
age
|
|
||||||
gender
|
|
||||||
description
|
|
||||||
dateOfBirth {
|
|
||||||
year
|
|
||||||
month
|
|
||||||
day
|
|
||||||
}
|
|
||||||
media(page: 0,sort:[POPULARITY_DESC,SCORE_DESC]) {
|
|
||||||
pageInfo {
|
|
||||||
total
|
|
||||||
perPage
|
|
||||||
currentPage
|
|
||||||
lastPage
|
|
||||||
hasNextPage
|
|
||||||
}
|
|
||||||
edges {
|
|
||||||
id
|
|
||||||
characterRole
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
idMal
|
|
||||||
isAdult
|
|
||||||
status
|
|
||||||
chapters
|
|
||||||
episodes
|
|
||||||
nextAiringEpisode { episode }
|
|
||||||
type
|
|
||||||
meanScore
|
|
||||||
isFavourite
|
|
||||||
format
|
|
||||||
bannerImage
|
|
||||||
countryOfOrigin
|
|
||||||
coverImage { large }
|
|
||||||
title {
|
|
||||||
english
|
|
||||||
romaji
|
|
||||||
userPreferred
|
|
||||||
}
|
|
||||||
mediaListEntry {
|
|
||||||
progress
|
|
||||||
private
|
|
||||||
score(format: POINT_100)
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}""".replace("\n", " ").replace(""" """, "")
|
|
||||||
executeQuery<Query.Character>(query, force = true)?.data?.character?.apply {
|
|
||||||
character.age = age
|
|
||||||
character.gender = gender
|
|
||||||
character.description = description
|
|
||||||
character.dateOfBirth = dateOfBirth
|
|
||||||
character.roles = arrayListOf()
|
|
||||||
media?.edges?.forEach { i ->
|
|
||||||
val m = Media(i)
|
|
||||||
m.relation = i.characterRole.toString()
|
|
||||||
character.roles?.add(m)
|
|
||||||
}
|
}
|
||||||
|
}""".prepare()
|
||||||
|
executeQuery<Query.Character>(query, force = true)?.data?.character?.let { i ->
|
||||||
|
return Character(
|
||||||
|
i.id,
|
||||||
|
i.name?.full,
|
||||||
|
i.image?.large ?: i.image?.medium,
|
||||||
|
null,
|
||||||
|
null.toString(),
|
||||||
|
i.isFavourite ?: false,
|
||||||
|
i.description,
|
||||||
|
i.age,
|
||||||
|
i.gender,
|
||||||
|
i.dateOfBirth,
|
||||||
|
i.media?.edges?.map {
|
||||||
|
val m = Media(it)
|
||||||
|
m.relation = it.characterRole.toString()
|
||||||
|
m
|
||||||
|
}?.let { ArrayList(it) },
|
||||||
|
i.media?.edges?.flatMap { edge ->
|
||||||
|
edge.voiceActors?.map { va ->
|
||||||
|
Author(
|
||||||
|
va.id,
|
||||||
|
va.name?.userPreferred,
|
||||||
|
va.image?.large ?: va.image?.medium,
|
||||||
|
va.languageV2
|
||||||
|
)
|
||||||
|
} ?: emptyList()
|
||||||
|
} as ArrayList<Author>?
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return character
|
return character
|
||||||
}
|
}
|
||||||
|
@ -1290,45 +1395,9 @@ Page(page:$page,perPage:50) {
|
||||||
suspend fun getStudioDetails(studio: Studio): Studio {
|
suspend fun getStudioDetails(studio: Studio): Studio {
|
||||||
fun query(page: Int = 0) = """ {
|
fun query(page: Int = 0) = """ {
|
||||||
Studio(id: ${studio.id}) {
|
Studio(id: ${studio.id}) {
|
||||||
id
|
${studioInformation(page, ITEMS_PER_PAGE)}
|
||||||
media(page: $page,sort:START_DATE_DESC) {
|
|
||||||
pageInfo{
|
|
||||||
hasNextPage
|
|
||||||
}
|
}
|
||||||
edges {
|
}""".prepare()
|
||||||
id
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
idMal
|
|
||||||
isAdult
|
|
||||||
status
|
|
||||||
chapters
|
|
||||||
episodes
|
|
||||||
nextAiringEpisode { episode }
|
|
||||||
type
|
|
||||||
meanScore
|
|
||||||
startDate{ year }
|
|
||||||
isFavourite
|
|
||||||
format
|
|
||||||
bannerImage
|
|
||||||
countryOfOrigin
|
|
||||||
coverImage { large }
|
|
||||||
title {
|
|
||||||
english
|
|
||||||
romaji
|
|
||||||
userPreferred
|
|
||||||
}
|
|
||||||
mediaListEntry {
|
|
||||||
progress
|
|
||||||
private
|
|
||||||
score(format: POINT_100)
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}""".replace("\n", " ").replace(""" """, "")
|
|
||||||
|
|
||||||
var hasNextPage = true
|
var hasNextPage = true
|
||||||
val yearMedia = mutableMapOf<String, ArrayList<Media>>()
|
val yearMedia = mutableMapOf<String, ArrayList<Media>>()
|
||||||
|
@ -1363,66 +1432,15 @@ Page(page:$page,perPage:50) {
|
||||||
suspend fun getAuthorDetails(author: Author): Author {
|
suspend fun getAuthorDetails(author: Author): Author {
|
||||||
fun query(page: Int = 0) = """ {
|
fun query(page: Int = 0) = """ {
|
||||||
Staff(id: ${author.id}) {
|
Staff(id: ${author.id}) {
|
||||||
id
|
${staffInformation(page, ITEMS_PER_PAGE)}
|
||||||
staffMedia(page: $page,sort:START_DATE_DESC) {
|
|
||||||
pageInfo{
|
|
||||||
hasNextPage
|
|
||||||
}
|
|
||||||
edges {
|
|
||||||
staffRole
|
|
||||||
id
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
idMal
|
|
||||||
isAdult
|
|
||||||
status
|
|
||||||
chapters
|
|
||||||
episodes
|
|
||||||
nextAiringEpisode { episode }
|
|
||||||
type
|
|
||||||
meanScore
|
|
||||||
startDate{ year }
|
|
||||||
isFavourite
|
|
||||||
format
|
|
||||||
bannerImage
|
|
||||||
countryOfOrigin
|
|
||||||
coverImage { large }
|
|
||||||
title {
|
|
||||||
english
|
|
||||||
romaji
|
|
||||||
userPreferred
|
|
||||||
}
|
|
||||||
mediaListEntry {
|
|
||||||
progress
|
|
||||||
private
|
|
||||||
score(format: POINT_100)
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
characters(page: $page,sort:FAVOURITES_DESC) {
|
characters(page: $page,sort:FAVOURITES_DESC) {
|
||||||
pageInfo{
|
$standardPageInformation
|
||||||
hasNextPage
|
|
||||||
}
|
|
||||||
nodes{
|
nodes{
|
||||||
id
|
${characterInformation(false)}
|
||||||
name {
|
|
||||||
first
|
|
||||||
middle
|
|
||||||
last
|
|
||||||
full
|
|
||||||
native
|
|
||||||
userPreferred
|
|
||||||
}
|
|
||||||
image {
|
|
||||||
large
|
|
||||||
medium
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}""".prepare()
|
||||||
}""".replace("\n", " ").replace(""" """, "")
|
|
||||||
|
|
||||||
var hasNextPage = true
|
var hasNextPage = true
|
||||||
val yearMedia = mutableMapOf<String, ArrayList<Media>>()
|
val yearMedia = mutableMapOf<String, ArrayList<Media>>()
|
||||||
|
@ -1433,6 +1451,16 @@ Page(page:$page,perPage:50) {
|
||||||
val query = executeQuery<Query.Author>(
|
val query = executeQuery<Query.Author>(
|
||||||
query(page), force = true
|
query(page), force = true
|
||||||
)?.data?.author
|
)?.data?.author
|
||||||
|
author.age = query?.age
|
||||||
|
author.yearsActive =
|
||||||
|
if (query?.yearsActive?.isEmpty() == true) null else query?.yearsActive
|
||||||
|
author.homeTown = if (query?.homeTown?.isBlank() == true) null else query?.homeTown
|
||||||
|
author.dateOfDeath = if (query?.dateOfDeath?.toStringOrEmpty()
|
||||||
|
?.isBlank() == true
|
||||||
|
) null else query?.dateOfDeath?.toStringOrEmpty()
|
||||||
|
author.dateOfBirth = if (query?.dateOfBirth?.toStringOrEmpty()
|
||||||
|
?.isBlank() == true
|
||||||
|
) null else query?.dateOfBirth?.toStringOrEmpty()
|
||||||
hasNextPage = query?.staffMedia?.let {
|
hasNextPage = query?.staffMedia?.let {
|
||||||
it.edges?.forEach { i ->
|
it.edges?.forEach { i ->
|
||||||
i.node?.apply {
|
i.node?.apply {
|
||||||
|
@ -1480,14 +1508,14 @@ Page(page:$page,perPage:50) {
|
||||||
sort: String = "SCORE_DESC"
|
sort: String = "SCORE_DESC"
|
||||||
): Query.ReviewsResponse? {
|
): Query.ReviewsResponse? {
|
||||||
return executeQuery<Query.ReviewsResponse>(
|
return executeQuery<Query.ReviewsResponse>(
|
||||||
"""{Page(page:$page,perPage:10){pageInfo{currentPage,hasNextPage,total}reviews(mediaId:$mediaId,sort:$sort){id,mediaId,mediaType,summary,body(asHtml:true)rating,ratingAmount,userRating,score,private,siteUrl,createdAt,updatedAt,user{id,name,bannerImage avatar{medium,large}}}}}""",
|
"""{Page(page:$page,perPage:10){$standardPageInformation reviews(mediaId:$mediaId,sort:$sort){id,mediaId,mediaType,summary,body(asHtml:true)rating,ratingAmount,userRating,score,private,siteUrl,createdAt,updatedAt,user{id,name,bannerImage avatar{medium,large}}}}}""",
|
||||||
force = true
|
force = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUserProfile(id: Int): Query.UserProfileResponse? {
|
suspend fun getUserProfile(id: Int): Query.UserProfileResponse? {
|
||||||
return executeQuery<Query.UserProfileResponse>(
|
return executeQuery<Query.UserProfileResponse>(
|
||||||
"""{followerPage:Page{followers(userId:$id){id}pageInfo{total}}followingPage:Page{following(userId:$id){id}pageInfo{total}}user:User(id:$id){id name about(asHtml:true)avatar{medium large}bannerImage isFollowing isFollower isBlocked favourites{anime{nodes{id coverImage{extraLarge large medium color}}}manga{nodes{id coverImage{extraLarge large medium color}}}characters{nodes{id name{first middle last full native alternative userPreferred}image{large medium}isFavourite}}staff{nodes{id name{first middle last full native alternative userPreferred}image{large medium}isFavourite}}studios{nodes{id name isFavourite}}}statistics{anime{count meanScore standardDeviation minutesWatched episodesWatched chaptersRead volumesRead}manga{count meanScore standardDeviation minutesWatched episodesWatched chaptersRead volumesRead}}siteUrl}}""",
|
"""{followerPage:Page{followers(userId:$id){id}$standardPageInformation}followingPage:Page{following(userId:$id){id}$standardPageInformation}user:User(id:$id){id name about(asHtml:true)avatar{medium large}bannerImage isFollowing isFollower isBlocked favourites{anime{nodes{id coverImage{extraLarge large medium color}}}manga{nodes{id coverImage{extraLarge large medium color}}}characters{nodes{id name{first middle last full native alternative userPreferred}image{large medium}isFavourite}}staff{nodes{id name{first middle last full native alternative userPreferred}image{large medium}isFavourite}}studios{nodes{id name isFavourite}}}statistics{anime{count meanScore standardDeviation minutesWatched episodesWatched chaptersRead volumesRead}manga{count meanScore standardDeviation minutesWatched episodesWatched chaptersRead volumesRead}}siteUrl}}""",
|
||||||
force = true
|
force = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1513,7 +1541,7 @@ Page(page:$page,perPage:50) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun userFavMediaQuery(anime: Boolean, id: Int): String {
|
private fun userFavMediaQuery(anime: Boolean, id: Int): String {
|
||||||
return """User(id:${id}){id favourites{${if (anime) "anime" else "manga"}(page:1){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}}}}}}"""
|
return """User(id:${id}){id favourites{${if (anime) "anime" else "manga"}(page:1){$standardPageInformation 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 userFollowing(id: Int): Query.Following? {
|
suspend fun userFollowing(id: Int): Query.Following? {
|
||||||
|
@ -1535,7 +1563,7 @@ Page(page:$page,perPage:50) {
|
||||||
"""{
|
"""{
|
||||||
favoriteAnime:${userFavMediaQuery(true, id)}
|
favoriteAnime:${userFavMediaQuery(true, id)}
|
||||||
favoriteManga:${userFavMediaQuery(false, id)}
|
favoriteManga:${userFavMediaQuery(false, id)}
|
||||||
}""".trimIndent(), force = true
|
}""".prepare(), force = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1549,7 +1577,7 @@ Page(page:$page,perPage:50) {
|
||||||
val typeIn = "type_in:[AIRING,MEDIA_MERGE,MEDIA_DELETION,MEDIA_DATA_CHANGE]"
|
val typeIn = "type_in:[AIRING,MEDIA_MERGE,MEDIA_DELETION,MEDIA_DATA_CHANGE]"
|
||||||
val reset = if (resetNotification) "true" else "false"
|
val reset = if (resetNotification) "true" else "false"
|
||||||
val res = executeQuery<NotificationResponse>(
|
val res = executeQuery<NotificationResponse>(
|
||||||
"""{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){pageInfo{currentPage,hasNextPage}notifications(resetNotificationCount:$reset , ${if (type == true) typeIn else ""}){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""",
|
"""{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){$standardPageInformation notifications(resetNotificationCount:$reset , ${if (type == true) typeIn else ""}){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""",
|
||||||
force = true
|
force = true
|
||||||
)
|
)
|
||||||
if (res != null && resetNotification) {
|
if (res != null && resetNotification) {
|
||||||
|
@ -1571,7 +1599,8 @@ Page(page:$page,perPage:50) {
|
||||||
else if (userId != null) "userId:$userId,"
|
else if (userId != null) "userId:$userId,"
|
||||||
else if (global) "isFollowing:false,hasRepliesOrTypeText:true,"
|
else if (global) "isFollowing:false,hasRepliesOrTypeText:true,"
|
||||||
else "isFollowing:true,"
|
else "isFollowing:true,"
|
||||||
val typeIn = if (filter == "isFollowing:true,") "type_in:[TEXT,ANIME_LIST,MANGA_LIST,MEDIA_LIST]," else ""
|
val typeIn =
|
||||||
|
if (filter == "isFollowing:true,") "type_in:[TEXT,ANIME_LIST,MANGA_LIST,MEDIA_LIST]," else ""
|
||||||
return executeQuery<FeedResponse>(
|
return executeQuery<FeedResponse>(
|
||||||
"""{Page(page:$page,perPage:$ITEMS_PER_PAGE){activities(${filter}${typeIn}sort:ID_DESC){__typename ... on TextActivity{id userId type replyCount text(asHtml:true)siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on ListActivity{id userId type replyCount status progress siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}media{id title{english romaji native userPreferred}bannerImage coverImage{medium large}isAdult}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on MessageActivity{id recipientId messengerId type replyCount likeCount message(asHtml:true)isLocked isSubscribed isLiked isPrivate siteUrl createdAt recipient{id name bannerImage avatar{medium large}}messenger{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}}}}""",
|
"""{Page(page:$page,perPage:$ITEMS_PER_PAGE){activities(${filter}${typeIn}sort:ID_DESC){__typename ... on TextActivity{id userId type replyCount text(asHtml:true)siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on ListActivity{id userId type replyCount status progress siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}media{id title{english romaji native userPreferred}bannerImage coverImage{medium large}isAdult}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on MessageActivity{id recipientId messengerId type replyCount likeCount message(asHtml:true)isLocked isSubscribed isLiked isPrivate siteUrl createdAt recipient{id name bannerImage avatar{medium large}}messenger{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}}}}""",
|
||||||
force = true
|
force = true
|
||||||
|
|
|
@ -128,7 +128,7 @@ class AnilistHomeViewModel : ViewModel() {
|
||||||
class AnilistAnimeViewModel : ViewModel() {
|
class AnilistAnimeViewModel : ViewModel() {
|
||||||
var searched = false
|
var searched = false
|
||||||
var notSet = true
|
var notSet = true
|
||||||
lateinit var searchResults: SearchResults
|
lateinit var aniMangaSearchResults: AniMangaSearchResults
|
||||||
private val type = "ANIME"
|
private val type = "ANIME"
|
||||||
private val trending: MutableLiveData<MutableList<Media>> =
|
private val trending: MutableLiveData<MutableList<Media>> =
|
||||||
MutableLiveData<MutableList<Media>>(null)
|
MutableLiveData<MutableList<Media>>(null)
|
||||||
|
@ -137,7 +137,7 @@ class AnilistAnimeViewModel : ViewModel() {
|
||||||
suspend fun loadTrending(i: Int) {
|
suspend fun loadTrending(i: Int) {
|
||||||
val (season, year) = Anilist.currentSeasons[i]
|
val (season, year) = Anilist.currentSeasons[i]
|
||||||
trending.postValue(
|
trending.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchAniManga(
|
||||||
type,
|
type,
|
||||||
perPage = 12,
|
perPage = 12,
|
||||||
sort = Anilist.sortBy[2],
|
sort = Anilist.sortBy[2],
|
||||||
|
@ -150,9 +150,9 @@ class AnilistAnimeViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val animePopular = MutableLiveData<SearchResults?>(null)
|
private val animePopular = MutableLiveData<AniMangaSearchResults?>(null)
|
||||||
|
|
||||||
fun getPopular(): LiveData<SearchResults?> = animePopular
|
fun getPopular(): LiveData<AniMangaSearchResults?> = animePopular
|
||||||
suspend fun loadPopular(
|
suspend fun loadPopular(
|
||||||
type: String,
|
type: String,
|
||||||
searchVal: String? = null,
|
searchVal: String? = null,
|
||||||
|
@ -161,7 +161,7 @@ class AnilistAnimeViewModel : ViewModel() {
|
||||||
onList: Boolean = true,
|
onList: Boolean = true,
|
||||||
) {
|
) {
|
||||||
animePopular.postValue(
|
animePopular.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchAniManga(
|
||||||
type,
|
type,
|
||||||
search = searchVal,
|
search = searchVal,
|
||||||
onList = if (onList) null else false,
|
onList = if (onList) null else false,
|
||||||
|
@ -173,8 +173,8 @@ class AnilistAnimeViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun loadNextPage(r: SearchResults) = animePopular.postValue(
|
suspend fun loadNextPage(r: AniMangaSearchResults) = animePopular.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchAniManga(
|
||||||
r.type,
|
r.type,
|
||||||
r.page + 1,
|
r.page + 1,
|
||||||
r.perPage,
|
r.perPage,
|
||||||
|
@ -224,7 +224,7 @@ class AnilistAnimeViewModel : ViewModel() {
|
||||||
class AnilistMangaViewModel : ViewModel() {
|
class AnilistMangaViewModel : ViewModel() {
|
||||||
var searched = false
|
var searched = false
|
||||||
var notSet = true
|
var notSet = true
|
||||||
lateinit var searchResults: SearchResults
|
lateinit var aniMangaSearchResults: AniMangaSearchResults
|
||||||
private val type = "MANGA"
|
private val type = "MANGA"
|
||||||
private val trending: MutableLiveData<MutableList<Media>> =
|
private val trending: MutableLiveData<MutableList<Media>> =
|
||||||
MutableLiveData<MutableList<Media>>(null)
|
MutableLiveData<MutableList<Media>>(null)
|
||||||
|
@ -232,7 +232,7 @@ class AnilistMangaViewModel : ViewModel() {
|
||||||
fun getTrending(): LiveData<MutableList<Media>> = trending
|
fun getTrending(): LiveData<MutableList<Media>> = trending
|
||||||
suspend fun loadTrending() =
|
suspend fun loadTrending() =
|
||||||
trending.postValue(
|
trending.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchAniManga(
|
||||||
type,
|
type,
|
||||||
perPage = 10,
|
perPage = 10,
|
||||||
sort = Anilist.sortBy[2],
|
sort = Anilist.sortBy[2],
|
||||||
|
@ -242,8 +242,8 @@ class AnilistMangaViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
private val mangaPopular = MutableLiveData<SearchResults?>(null)
|
private val mangaPopular = MutableLiveData<AniMangaSearchResults?>(null)
|
||||||
fun getPopular(): LiveData<SearchResults?> = mangaPopular
|
fun getPopular(): LiveData<AniMangaSearchResults?> = mangaPopular
|
||||||
suspend fun loadPopular(
|
suspend fun loadPopular(
|
||||||
type: String,
|
type: String,
|
||||||
searchVal: String? = null,
|
searchVal: String? = null,
|
||||||
|
@ -252,7 +252,7 @@ class AnilistMangaViewModel : ViewModel() {
|
||||||
onList: Boolean = true,
|
onList: Boolean = true,
|
||||||
) {
|
) {
|
||||||
mangaPopular.postValue(
|
mangaPopular.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchAniManga(
|
||||||
type,
|
type,
|
||||||
search = searchVal,
|
search = searchVal,
|
||||||
onList = if (onList) null else false,
|
onList = if (onList) null else false,
|
||||||
|
@ -264,8 +264,8 @@ class AnilistMangaViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun loadNextPage(r: SearchResults) = mangaPopular.postValue(
|
suspend fun loadNextPage(r: AniMangaSearchResults) = mangaPopular.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchAniManga(
|
||||||
r.type,
|
r.type,
|
||||||
r.page + 1,
|
r.page + 1,
|
||||||
r.perPage,
|
r.perPage,
|
||||||
|
@ -325,14 +325,126 @@ class AnilistMangaViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnilistSearch : ViewModel() {
|
class AnilistSearch : ViewModel() {
|
||||||
|
|
||||||
|
enum class SearchType {
|
||||||
|
ANIME, MANGA, CHARACTER, STAFF, STUDIO, USER;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun SearchType.toAnilistString(): String {
|
||||||
|
return when (this) {
|
||||||
|
ANIME -> "ANIME"
|
||||||
|
MANGA -> "MANGA"
|
||||||
|
CHARACTER -> "CHARACTER"
|
||||||
|
STAFF -> "STAFF"
|
||||||
|
STUDIO -> "STUDIO"
|
||||||
|
USER -> "USER"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromString(string: String): SearchType {
|
||||||
|
return when (string.uppercase()) {
|
||||||
|
"ANIME" -> ANIME
|
||||||
|
"MANGA" -> MANGA
|
||||||
|
"CHARACTER" -> CHARACTER
|
||||||
|
"STAFF" -> STAFF
|
||||||
|
"STUDIO" -> STUDIO
|
||||||
|
"USER" -> USER
|
||||||
|
else -> throw IllegalArgumentException("Invalid search type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var searched = false
|
var searched = false
|
||||||
var notSet = true
|
var notSet = true
|
||||||
lateinit var searchResults: SearchResults
|
lateinit var aniMangaSearchResults: AniMangaSearchResults
|
||||||
private val result: MutableLiveData<SearchResults?> = MutableLiveData<SearchResults?>(null)
|
private val aniMangaResult: MutableLiveData<AniMangaSearchResults?> = MutableLiveData<AniMangaSearchResults?>(null)
|
||||||
|
|
||||||
fun getSearch(): LiveData<SearchResults?> = result
|
lateinit var characterSearchResults: CharacterSearchResults
|
||||||
suspend fun loadSearch(r: SearchResults) = result.postValue(
|
private val characterResult: MutableLiveData<CharacterSearchResults?> = MutableLiveData<CharacterSearchResults?>(null)
|
||||||
Anilist.query.search(
|
|
||||||
|
lateinit var studioSearchResults: StudioSearchResults
|
||||||
|
private val studioResult: MutableLiveData<StudioSearchResults?> = MutableLiveData<StudioSearchResults?>(null)
|
||||||
|
|
||||||
|
lateinit var staffSearchResults: StaffSearchResults
|
||||||
|
private val staffResult: MutableLiveData<StaffSearchResults?> = MutableLiveData<StaffSearchResults?>(null)
|
||||||
|
|
||||||
|
lateinit var userSearchResults: UserSearchResults
|
||||||
|
private val userResult: MutableLiveData<UserSearchResults?> = MutableLiveData<UserSearchResults?>(null)
|
||||||
|
|
||||||
|
fun <T> getSearch(type: SearchType): MutableLiveData<T?> {
|
||||||
|
return when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> aniMangaResult as MutableLiveData<T?>
|
||||||
|
SearchType.CHARACTER -> characterResult as MutableLiveData<T?>
|
||||||
|
SearchType.STUDIO -> studioResult as MutableLiveData<T?>
|
||||||
|
SearchType.STAFF -> staffResult as MutableLiveData<T?>
|
||||||
|
SearchType.USER -> userResult as MutableLiveData<T?>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadSearch(type: SearchType) {
|
||||||
|
when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> loadAniMangaSearch(aniMangaSearchResults)
|
||||||
|
SearchType.CHARACTER -> loadCharacterSearch(characterSearchResults)
|
||||||
|
SearchType.STUDIO -> loadStudiosSearch(studioSearchResults)
|
||||||
|
SearchType.STAFF -> loadStaffSearch(staffSearchResults)
|
||||||
|
SearchType.USER -> loadUserSearch(userSearchResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadNextPage(type: SearchType) {
|
||||||
|
when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> loadNextAniMangaPage(aniMangaSearchResults)
|
||||||
|
SearchType.CHARACTER -> loadNextCharacterPage(characterSearchResults)
|
||||||
|
SearchType.STUDIO -> loadNextStudiosPage(studioSearchResults)
|
||||||
|
SearchType.STAFF -> loadNextStaffPage(staffSearchResults)
|
||||||
|
SearchType.USER -> loadNextUserPage(userSearchResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasNextPage(type: SearchType): Boolean {
|
||||||
|
return when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> aniMangaSearchResults.hasNextPage
|
||||||
|
SearchType.CHARACTER -> characterSearchResults.hasNextPage
|
||||||
|
SearchType.STUDIO -> studioSearchResults.hasNextPage
|
||||||
|
SearchType.STAFF -> staffSearchResults.hasNextPage
|
||||||
|
SearchType.USER -> userSearchResults.hasNextPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resultsIsNotEmpty(type: SearchType): Boolean {
|
||||||
|
return when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> aniMangaSearchResults.results.isNotEmpty()
|
||||||
|
SearchType.CHARACTER -> characterSearchResults.results.isNotEmpty()
|
||||||
|
SearchType.STUDIO -> studioSearchResults.results.isNotEmpty()
|
||||||
|
SearchType.STAFF -> staffSearchResults.results.isNotEmpty()
|
||||||
|
SearchType.USER -> userSearchResults.results.isNotEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun size(type: SearchType): Int {
|
||||||
|
return when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> aniMangaSearchResults.results.size
|
||||||
|
SearchType.CHARACTER -> characterSearchResults.results.size
|
||||||
|
SearchType.STUDIO -> studioSearchResults.results.size
|
||||||
|
SearchType.STAFF -> staffSearchResults.results.size
|
||||||
|
SearchType.USER -> userSearchResults.results.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearResults(type: SearchType) {
|
||||||
|
when (type) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> aniMangaSearchResults.results.clear()
|
||||||
|
SearchType.CHARACTER -> characterSearchResults.results.clear()
|
||||||
|
SearchType.STUDIO -> studioSearchResults.results.clear()
|
||||||
|
SearchType.STAFF -> staffSearchResults.results.clear()
|
||||||
|
SearchType.USER -> userSearchResults.results.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loadAniMangaSearch(r: AniMangaSearchResults) = aniMangaResult.postValue(
|
||||||
|
Anilist.query.searchAniManga(
|
||||||
r.type,
|
r.type,
|
||||||
r.page,
|
r.page,
|
||||||
r.perPage,
|
r.perPage,
|
||||||
|
@ -354,8 +466,36 @@ class AnilistSearch : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun loadNextPage(r: SearchResults) = result.postValue(
|
private suspend fun loadCharacterSearch(r: CharacterSearchResults) = characterResult.postValue(
|
||||||
Anilist.query.search(
|
Anilist.query.searchCharacters(
|
||||||
|
r.page,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadStudiosSearch(r: StudioSearchResults) = studioResult.postValue(
|
||||||
|
Anilist.query.searchStudios(
|
||||||
|
r.page,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadStaffSearch(r: StaffSearchResults) = staffResult.postValue(
|
||||||
|
Anilist.query.searchStaff(
|
||||||
|
r.page,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadUserSearch(r: UserSearchResults) = userResult.postValue(
|
||||||
|
Anilist.query.searchUsers(
|
||||||
|
r.page,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadNextAniMangaPage(r: AniMangaSearchResults) = aniMangaResult.postValue(
|
||||||
|
Anilist.query.searchAniManga(
|
||||||
r.type,
|
r.type,
|
||||||
r.page + 1,
|
r.page + 1,
|
||||||
r.perPage,
|
r.perPage,
|
||||||
|
@ -376,6 +516,34 @@ class AnilistSearch : ViewModel() {
|
||||||
r.season
|
r.season
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private suspend fun loadNextCharacterPage(r: CharacterSearchResults) = characterResult.postValue(
|
||||||
|
Anilist.query.searchCharacters(
|
||||||
|
r.page + 1,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadNextStudiosPage(r: StudioSearchResults) = studioResult.postValue(
|
||||||
|
Anilist.query.searchStudios(
|
||||||
|
r.page + 1,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadNextStaffPage(r: StaffSearchResults) = staffResult.postValue(
|
||||||
|
Anilist.query.searchStaff(
|
||||||
|
r.page + 1,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadNextUserPage(r: UserSearchResults) = userResult.postValue(
|
||||||
|
Anilist.query.searchUsers(
|
||||||
|
r.page + 1,
|
||||||
|
r.search,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenresViewModel : ViewModel() {
|
class GenresViewModel : ViewModel() {
|
||||||
|
|
|
@ -0,0 +1,429 @@
|
||||||
|
package ani.dantotsu.connections.anilist
|
||||||
|
|
||||||
|
val standardPageInformation = """
|
||||||
|
pageInfo {
|
||||||
|
total
|
||||||
|
perPage
|
||||||
|
currentPage
|
||||||
|
lastPage
|
||||||
|
hasNextPage
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun String.prepare() = this.trimIndent().replace("\n", " ").replace(""" """, "")
|
||||||
|
|
||||||
|
fun characterInformation(includeMediaInfo: Boolean) = """
|
||||||
|
id
|
||||||
|
name {
|
||||||
|
first
|
||||||
|
middle
|
||||||
|
last
|
||||||
|
full
|
||||||
|
native
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
image {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
age
|
||||||
|
gender
|
||||||
|
description
|
||||||
|
dateOfBirth {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
${if (includeMediaInfo) """
|
||||||
|
media(page: 0,sort:[POPULARITY_DESC,SCORE_DESC]) {
|
||||||
|
$standardPageInformation
|
||||||
|
edges {
|
||||||
|
id
|
||||||
|
voiceActors {
|
||||||
|
id,
|
||||||
|
name {
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
languageV2,
|
||||||
|
image {
|
||||||
|
medium,
|
||||||
|
large
|
||||||
|
}
|
||||||
|
}
|
||||||
|
characterRole
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
idMal
|
||||||
|
isAdult
|
||||||
|
status
|
||||||
|
chapters
|
||||||
|
episodes
|
||||||
|
nextAiringEpisode { episode }
|
||||||
|
type
|
||||||
|
meanScore
|
||||||
|
isFavourite
|
||||||
|
format
|
||||||
|
bannerImage
|
||||||
|
countryOfOrigin
|
||||||
|
coverImage { large }
|
||||||
|
title {
|
||||||
|
english
|
||||||
|
romaji
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
mediaListEntry {
|
||||||
|
progress
|
||||||
|
private
|
||||||
|
score(format: POINT_100)
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}""".prepare() else ""}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun studioInformation(page: Int, perPage: Int) = """
|
||||||
|
id
|
||||||
|
name
|
||||||
|
isFavourite
|
||||||
|
favourites
|
||||||
|
media(page: $page, sort:START_DATE_DESC, perPage: $perPage) {
|
||||||
|
$standardPageInformation
|
||||||
|
edges {
|
||||||
|
id
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
idMal
|
||||||
|
isAdult
|
||||||
|
status
|
||||||
|
chapters
|
||||||
|
episodes
|
||||||
|
nextAiringEpisode { episode }
|
||||||
|
type
|
||||||
|
meanScore
|
||||||
|
startDate{ year }
|
||||||
|
isFavourite
|
||||||
|
format
|
||||||
|
bannerImage
|
||||||
|
countryOfOrigin
|
||||||
|
coverImage { large }
|
||||||
|
title {
|
||||||
|
english
|
||||||
|
romaji
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
mediaListEntry {
|
||||||
|
progress
|
||||||
|
private
|
||||||
|
score(format: POINT_100)
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun staffInformation(page: Int, perPage: Int) = """
|
||||||
|
id
|
||||||
|
name {
|
||||||
|
first
|
||||||
|
middle
|
||||||
|
last
|
||||||
|
full
|
||||||
|
native
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
image {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
dateOfBirth {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
dateOfDeath {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
age
|
||||||
|
yearsActive
|
||||||
|
homeTown
|
||||||
|
staffMedia(page: $page,sort:START_DATE_DESC, perPage: $perPage) {
|
||||||
|
$standardPageInformation
|
||||||
|
edges {
|
||||||
|
staffRole
|
||||||
|
id
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
idMal
|
||||||
|
isAdult
|
||||||
|
status
|
||||||
|
chapters
|
||||||
|
episodes
|
||||||
|
nextAiringEpisode { episode }
|
||||||
|
type
|
||||||
|
meanScore
|
||||||
|
startDate{ year }
|
||||||
|
isFavourite
|
||||||
|
format
|
||||||
|
bannerImage
|
||||||
|
countryOfOrigin
|
||||||
|
coverImage { large }
|
||||||
|
title {
|
||||||
|
english
|
||||||
|
romaji
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
mediaListEntry {
|
||||||
|
progress
|
||||||
|
private
|
||||||
|
score(format: POINT_100)
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun userInformation() = """
|
||||||
|
id
|
||||||
|
name
|
||||||
|
about(asHtml: true)
|
||||||
|
avatar {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
bannerImage
|
||||||
|
isFollowing
|
||||||
|
isFollower
|
||||||
|
isBlocked
|
||||||
|
siteUrl
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun aniMangaSearch(perPage: Int?) = """
|
||||||
|
query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult: Boolean = false, ${"$"}search: String, ${"$"}format: [MediaFormat], ${"$"}status: MediaStatus, ${"$"}countryOfOrigin: CountryCode, ${"$"}source: MediaSource, ${"$"}season: MediaSeason, ${"$"}seasonYear: Int, ${"$"}year: String, ${"$"}onList: Boolean, ${"$"}yearLesser: FuzzyDateInt, ${"$"}yearGreater: FuzzyDateInt, ${"$"}episodeLesser: Int, ${"$"}episodeGreater: Int, ${"$"}durationLesser: Int, ${"$"}durationGreater: Int, ${"$"}chapterLesser: Int, ${"$"}chapterGreater: Int, ${"$"}volumeLesser: Int, ${"$"}volumeGreater: Int, ${"$"}licensedBy: [String], ${"$"}isLicensed: Boolean, ${"$"}genres: [String], ${"$"}excludedGenres: [String], ${"$"}tags: [String], ${"$"}excludedTags: [String], ${"$"}minimumTagRank: Int, ${"$"}sort: [MediaSort] = [POPULARITY_DESC, SCORE_DESC, START_DATE_DESC]) {
|
||||||
|
Page(page: ${"$"}page, perPage: ${perPage ?: 50}) {
|
||||||
|
$standardPageInformation
|
||||||
|
media(id: ${"$"}id, type: ${"$"}type, season: ${"$"}season, format_in: ${"$"}format, status: ${"$"}status, countryOfOrigin: ${"$"}countryOfOrigin, source: ${"$"}source, search: ${"$"}search, onList: ${"$"}onList, seasonYear: ${"$"}seasonYear, startDate_like: ${"$"}year, startDate_lesser: ${"$"}yearLesser, startDate_greater: ${"$"}yearGreater, episodes_lesser: ${"$"}episodeLesser, episodes_greater: ${"$"}episodeGreater, duration_lesser: ${"$"}durationLesser, duration_greater: ${"$"}durationGreater, chapters_lesser: ${"$"}chapterLesser, chapters_greater: ${"$"}chapterGreater, volumes_lesser: ${"$"}volumeLesser, volumes_greater: ${"$"}volumeGreater, licensedBy_in: ${"$"}licensedBy, isLicensed: ${"$"}isLicensed, genre_in: ${"$"}genres, genre_not_in: ${"$"}excludedGenres, tag_in: ${"$"}tags, tag_not_in: ${"$"}excludedTags, minimumTagRank: ${"$"}minimumTagRank, sort: ${"$"}sort, isAdult: ${"$"}isAdult) {
|
||||||
|
${standardMediaInformation()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun standardMediaInformation() = """
|
||||||
|
id
|
||||||
|
idMal
|
||||||
|
siteUrl
|
||||||
|
isAdult
|
||||||
|
status(version: 2)
|
||||||
|
chapters
|
||||||
|
episodes
|
||||||
|
nextAiringEpisode {
|
||||||
|
episode
|
||||||
|
airingAt
|
||||||
|
}
|
||||||
|
type
|
||||||
|
genres
|
||||||
|
meanScore
|
||||||
|
popularity
|
||||||
|
favourites
|
||||||
|
isFavourite
|
||||||
|
format
|
||||||
|
bannerImage
|
||||||
|
countryOfOrigin
|
||||||
|
coverImage {
|
||||||
|
large
|
||||||
|
extraLarge
|
||||||
|
}
|
||||||
|
title {
|
||||||
|
english
|
||||||
|
romaji
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
mediaListEntry {
|
||||||
|
progress
|
||||||
|
private
|
||||||
|
score(format: POINT_100)
|
||||||
|
status
|
||||||
|
}
|
||||||
|
""".prepare()
|
||||||
|
|
||||||
|
fun fullMediaInformation(id: Int) = """
|
||||||
|
{
|
||||||
|
Media(id: $id) {
|
||||||
|
streamingEpisodes {
|
||||||
|
title
|
||||||
|
thumbnail
|
||||||
|
url
|
||||||
|
site
|
||||||
|
}
|
||||||
|
mediaListEntry {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
score(format: POINT_100)
|
||||||
|
progress
|
||||||
|
private
|
||||||
|
notes
|
||||||
|
repeat
|
||||||
|
customLists
|
||||||
|
updatedAt
|
||||||
|
startedAt {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
completedAt {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reviews(perPage: 3, sort: SCORE_DESC) {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
mediaId
|
||||||
|
mediaType
|
||||||
|
summary
|
||||||
|
body(asHtml: true)
|
||||||
|
rating
|
||||||
|
ratingAmount
|
||||||
|
userRating
|
||||||
|
score
|
||||||
|
private
|
||||||
|
siteUrl
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
bannerImage
|
||||||
|
avatar {
|
||||||
|
medium
|
||||||
|
large
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${standardMediaInformation()}
|
||||||
|
source
|
||||||
|
duration
|
||||||
|
season
|
||||||
|
seasonYear
|
||||||
|
startDate {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
endDate {
|
||||||
|
year
|
||||||
|
month
|
||||||
|
day
|
||||||
|
}
|
||||||
|
studios(isMain: true) {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
siteUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
description
|
||||||
|
trailer {
|
||||||
|
site
|
||||||
|
id
|
||||||
|
}
|
||||||
|
synonyms
|
||||||
|
tags {
|
||||||
|
name
|
||||||
|
rank
|
||||||
|
isMediaSpoiler
|
||||||
|
}
|
||||||
|
characters(sort: [ROLE, FAVOURITES_DESC], perPage: 25, page: 1) {
|
||||||
|
edges {
|
||||||
|
role
|
||||||
|
voiceActors {
|
||||||
|
id
|
||||||
|
name {
|
||||||
|
first
|
||||||
|
middle
|
||||||
|
last
|
||||||
|
full
|
||||||
|
native
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
image {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
languageV2
|
||||||
|
}
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
image {
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
name {
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
isFavourite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relations {
|
||||||
|
edges {
|
||||||
|
relationType(version: 2)
|
||||||
|
node {
|
||||||
|
${standardMediaInformation()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
staffPreview: staff(perPage: 8, sort: [RELEVANCE, ID]) {
|
||||||
|
edges {
|
||||||
|
role
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
image {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
name {
|
||||||
|
userPreferred
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recommendations(sort: RATING_DESC) {
|
||||||
|
nodes {
|
||||||
|
mediaRecommendation {
|
||||||
|
${standardMediaInformation()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
externalLinks {
|
||||||
|
url
|
||||||
|
site
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Page(page: 1) {
|
||||||
|
$standardPageInformation
|
||||||
|
mediaList(isFollowing: true, sort: [STATUS], mediaId: $id) {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
score(format: POINT_100)
|
||||||
|
progress
|
||||||
|
progressVolumes
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
avatar {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
""".prepare()
|
|
@ -446,7 +446,7 @@ data class MediaEdge(
|
||||||
@SerialName("staffRole") var staffRole: String?,
|
@SerialName("staffRole") var staffRole: String?,
|
||||||
|
|
||||||
// The voice actors of the character
|
// The voice actors of the character
|
||||||
// @SerialName("voiceActors") var voiceActors: List<Staff>?,
|
@SerialName("voiceActors") var voiceActors: List<Staff>?,
|
||||||
|
|
||||||
// The voice actors of the character with role date
|
// The voice actors of the character with role date
|
||||||
// @SerialName("voiceActorRoles") var voiceActorRoles: List<StaffRoleType>?,
|
// @SerialName("voiceActorRoles") var voiceActorRoles: List<StaffRoleType>?,
|
||||||
|
|
|
@ -260,7 +260,7 @@ class DownloadCompat {
|
||||||
"$mangaLink/${it.name}",
|
"$mangaLink/${it.name}",
|
||||||
it.name,
|
it.name,
|
||||||
null,
|
null,
|
||||||
null,
|
"Unknown",
|
||||||
SChapter.create()
|
SChapter.create()
|
||||||
)
|
)
|
||||||
chapters.add(chapter)
|
chapters.add(chapter)
|
||||||
|
|
|
@ -60,7 +60,7 @@ class DownloadsManager(private val context: Context) {
|
||||||
onFinished: () -> Unit
|
onFinished: () -> Unit
|
||||||
) {
|
) {
|
||||||
removeDownloadCompat(context, downloadedType, toast)
|
removeDownloadCompat(context, downloadedType, toast)
|
||||||
downloadsList.remove(downloadedType)
|
downloadsList.removeAll { it.titleName == downloadedType.titleName && it.chapterName == downloadedType.chapterName }
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
removeDirectory(downloadedType, toast)
|
removeDirectory(downloadedType, toast)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
@ -234,7 +234,7 @@ class DownloadsManager(private val context: Context) {
|
||||||
val directory =
|
val directory =
|
||||||
baseDirectory?.findFolder(downloadedType.titleName)
|
baseDirectory?.findFolder(downloadedType.titleName)
|
||||||
?.findFolder(downloadedType.chapterName)
|
?.findFolder(downloadedType.chapterName)
|
||||||
downloadsList.remove(downloadedType)
|
downloadsList.removeAll { it.titleName == downloadedType.titleName && it.chapterName == downloadedType.chapterName }
|
||||||
// Check if the directory exists and delete it recursively
|
// Check if the directory exists and delete it recursively
|
||||||
if (directory?.exists() == true) {
|
if (directory?.exists() == true) {
|
||||||
val deleted = directory.deleteRecursively(context, false)
|
val deleted = directory.deleteRecursively(context, false)
|
||||||
|
@ -401,10 +401,13 @@ data class DownloadedType(
|
||||||
@Deprecated("use pTitle instead")
|
@Deprecated("use pTitle instead")
|
||||||
private val title: String? = null,
|
private val title: String? = null,
|
||||||
@Deprecated("use pChapter instead")
|
@Deprecated("use pChapter instead")
|
||||||
private val chapter: String? = null
|
private val chapter: String? = null,
|
||||||
|
val scanlator: String = "Unknown"
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
val titleName: String
|
val titleName: String
|
||||||
get() = title ?: pTitle.findValidName()
|
get() = title ?: pTitle.findValidName()
|
||||||
val chapterName: String
|
val chapterName: String
|
||||||
get() = chapter ?: pChapter.findValidName()
|
get() = chapter ?: pChapter.findValidName()
|
||||||
|
val uniqueName: String
|
||||||
|
get() = "$chapterName-${scanlator}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -243,7 +243,7 @@ class MangaDownloaderService : Service() {
|
||||||
builder.setProgress(task.imageData.size, farthest, false)
|
builder.setProgress(task.imageData.size, farthest, false)
|
||||||
|
|
||||||
broadcastDownloadProgress(
|
broadcastDownloadProgress(
|
||||||
task.chapter,
|
task.uniqueName,
|
||||||
farthest * 100 / task.imageData.size
|
farthest * 100 / task.imageData.size
|
||||||
)
|
)
|
||||||
if (notifi) {
|
if (notifi) {
|
||||||
|
@ -270,17 +270,18 @@ class MangaDownloaderService : Service() {
|
||||||
DownloadedType(
|
DownloadedType(
|
||||||
task.title,
|
task.title,
|
||||||
task.chapter,
|
task.chapter,
|
||||||
MediaType.MANGA
|
MediaType.MANGA,
|
||||||
|
scanlator = task.scanlator,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
broadcastDownloadFinished(task.chapter)
|
broadcastDownloadFinished(task.uniqueName)
|
||||||
snackString("${task.title} - ${task.chapter} Download finished")
|
snackString("${task.title} - ${task.chapter} Download finished")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.log("Exception while downloading file: ${e.message}")
|
Logger.log("Exception while downloading file: ${e.message}")
|
||||||
snackString("Exception while downloading file: ${e.message}")
|
snackString("Exception while downloading file: ${e.message}")
|
||||||
Injekt.get<CrashlyticsInterface>().logException(e)
|
Injekt.get<CrashlyticsInterface>().logException(e)
|
||||||
broadcastDownloadFailed(task.chapter)
|
broadcastDownloadFailed(task.uniqueName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,11 +424,15 @@ class MangaDownloaderService : Service() {
|
||||||
data class DownloadTask(
|
data class DownloadTask(
|
||||||
val title: String,
|
val title: String,
|
||||||
val chapter: String,
|
val chapter: String,
|
||||||
|
val scanlator: String,
|
||||||
val imageData: List<ImageData>,
|
val imageData: List<ImageData>,
|
||||||
val sourceMedia: Media? = null,
|
val sourceMedia: Media? = null,
|
||||||
val retries: Int = 2,
|
val retries: Int = 2,
|
||||||
val simultaneousDownloads: Int = 2,
|
val simultaneousDownloads: Int = 2,
|
||||||
)
|
) {
|
||||||
|
val uniqueName: String
|
||||||
|
get() = "$chapter-$scanlator"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val NOTIFICATION_ID = 1103
|
private const val NOTIFICATION_ID = 1103
|
||||||
|
|
|
@ -24,7 +24,7 @@ import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.bottomBar
|
import ani.dantotsu.bottomBar
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.AnilistAnimeViewModel
|
import ani.dantotsu.connections.anilist.AnilistAnimeViewModel
|
||||||
import ani.dantotsu.connections.anilist.SearchResults
|
import ani.dantotsu.connections.anilist.AniMangaSearchResults
|
||||||
import ani.dantotsu.connections.anilist.getUserId
|
import ani.dantotsu.connections.anilist.getUserId
|
||||||
import ani.dantotsu.databinding.FragmentAnimeBinding
|
import ani.dantotsu.databinding.FragmentAnimeBinding
|
||||||
import ani.dantotsu.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
|
@ -100,7 +100,7 @@ class AnimeFragment : Fragment() {
|
||||||
var loading = true
|
var loading = true
|
||||||
if (model.notSet) {
|
if (model.notSet) {
|
||||||
model.notSet = false
|
model.notSet = false
|
||||||
model.searchResults = SearchResults(
|
model.aniMangaSearchResults = AniMangaSearchResults(
|
||||||
"ANIME",
|
"ANIME",
|
||||||
isAdult = false,
|
isAdult = false,
|
||||||
onList = false,
|
onList = false,
|
||||||
|
@ -109,7 +109,7 @@ class AnimeFragment : Fragment() {
|
||||||
sort = Anilist.sortBy[1]
|
sort = Anilist.sortBy[1]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val popularAdaptor = MediaAdaptor(1, model.searchResults.results, requireActivity())
|
val popularAdaptor = MediaAdaptor(1, model.aniMangaSearchResults.results, requireActivity())
|
||||||
val progressAdaptor = ProgressAdapter(searched = model.searched)
|
val progressAdaptor = ProgressAdapter(searched = model.searched)
|
||||||
val adapter = ConcatAdapter(animePageAdapter, popularAdaptor, progressAdaptor)
|
val adapter = ConcatAdapter(animePageAdapter, popularAdaptor, progressAdaptor)
|
||||||
binding.animePageRecyclerView.adapter = adapter
|
binding.animePageRecyclerView.adapter = adapter
|
||||||
|
@ -142,7 +142,7 @@ class AnimeFragment : Fragment() {
|
||||||
animePageAdapter.onIncludeListClick = { checked ->
|
animePageAdapter.onIncludeListClick = { checked ->
|
||||||
oldIncludeList = !checked
|
oldIncludeList = !checked
|
||||||
loading = true
|
loading = true
|
||||||
model.searchResults.results.clear()
|
model.aniMangaSearchResults.results.clear()
|
||||||
popularAdaptor.notifyDataSetChanged()
|
popularAdaptor.notifyDataSetChanged()
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
model.loadPopular("ANIME", sort = Anilist.sortBy[1], onList = checked)
|
model.loadPopular("ANIME", sort = Anilist.sortBy[1], onList = checked)
|
||||||
|
@ -152,17 +152,17 @@ class AnimeFragment : Fragment() {
|
||||||
model.getPopular().observe(viewLifecycleOwner) {
|
model.getPopular().observe(viewLifecycleOwner) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
if (oldIncludeList == (it.onList != false)) {
|
if (oldIncludeList == (it.onList != false)) {
|
||||||
val prev = model.searchResults.results.size
|
val prev = model.aniMangaSearchResults.results.size
|
||||||
model.searchResults.results.addAll(it.results)
|
model.aniMangaSearchResults.results.addAll(it.results)
|
||||||
popularAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
popularAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||||
} else {
|
} else {
|
||||||
model.searchResults.results.addAll(it.results)
|
model.aniMangaSearchResults.results.addAll(it.results)
|
||||||
popularAdaptor.notifyDataSetChanged()
|
popularAdaptor.notifyDataSetChanged()
|
||||||
oldIncludeList = it.onList ?: true
|
oldIncludeList = it.onList ?: true
|
||||||
}
|
}
|
||||||
model.searchResults.onList = it.onList
|
model.aniMangaSearchResults.onList = it.onList
|
||||||
model.searchResults.hasNextPage = it.hasNextPage
|
model.aniMangaSearchResults.hasNextPage = it.hasNextPage
|
||||||
model.searchResults.page = it.page
|
model.aniMangaSearchResults.page = it.page
|
||||||
if (it.hasNextPage)
|
if (it.hasNextPage)
|
||||||
progressAdaptor.bar?.visibility = View.VISIBLE
|
progressAdaptor.bar?.visibility = View.VISIBLE
|
||||||
else {
|
else {
|
||||||
|
@ -177,10 +177,10 @@ class AnimeFragment : Fragment() {
|
||||||
RecyclerView.OnScrollListener() {
|
RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
||||||
if (!v.canScrollVertically(1)) {
|
if (!v.canScrollVertically(1)) {
|
||||||
if (model.searchResults.hasNextPage && model.searchResults.results.isNotEmpty() && !loading) {
|
if (model.aniMangaSearchResults.hasNextPage && model.aniMangaSearchResults.results.isNotEmpty() && !loading) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
loading = true
|
loading = true
|
||||||
model.loadNextPage(model.searchResults)
|
model.loadNextPage(model.aniMangaSearchResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -21,7 +20,7 @@ import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.MediaPageTransformer
|
import ani.dantotsu.MediaPageTransformer
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType.Companion.toAnilistString
|
||||||
import ani.dantotsu.databinding.ItemAnimePageBinding
|
import ani.dantotsu.databinding.ItemAnimePageBinding
|
||||||
import ani.dantotsu.databinding.LayoutTrendingBinding
|
import ani.dantotsu.databinding.LayoutTrendingBinding
|
||||||
import ani.dantotsu.getAppString
|
import ani.dantotsu.getAppString
|
||||||
|
@ -83,13 +82,21 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
||||||
|
|
||||||
updateAvatar()
|
updateAvatar()
|
||||||
|
|
||||||
trendingBinding.searchBar.hint = "ANIME"
|
trendingBinding.searchBar.hint = binding.root.context.getString(R.string.search)
|
||||||
trendingBinding.searchBarText.setOnClickListener {
|
trendingBinding.searchBarText.setOnClickListener {
|
||||||
|
val context = binding.root.context
|
||||||
|
if (PrefManager.getVal(PrefName.AniMangaSearchDirect) && Anilist.token != null) {
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
it.context,
|
context,
|
||||||
Intent(it.context, SearchActivity::class.java).putExtra("type", "ANIME"),
|
Intent(context, SearchActivity::class.java).putExtra("type", "ANIME"),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
SearchBottomSheet.newInstance().show(
|
||||||
|
(context as AppCompatActivity).supportFragmentManager,
|
||||||
|
"search"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trendingBinding.userAvatar.setSafeOnClickListener {
|
trendingBinding.userAvatar.setSafeOnClickListener {
|
||||||
|
|
|
@ -48,7 +48,6 @@ import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -134,6 +133,12 @@ class HomeFragment : Fragment() {
|
||||||
"dialog"
|
"dialog"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
binding.searchImageContainer.setSafeOnClickListener {
|
||||||
|
SearchBottomSheet.newInstance().show(
|
||||||
|
(it.context as androidx.appcompat.app.AppCompatActivity).supportFragmentManager,
|
||||||
|
"search"
|
||||||
|
)
|
||||||
|
}
|
||||||
binding.homeUserAvatarContainer.setOnLongClickListener {
|
binding.homeUserAvatarContainer.setOnLongClickListener {
|
||||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
package ani.dantotsu.home
|
package ani.dantotsu.home
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
@ -20,7 +18,6 @@ import ani.dantotsu.settings.saving.internal.PreferencePackager
|
||||||
import ani.dantotsu.toast
|
import ani.dantotsu.toast
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
import ani.dantotsu.util.customAlertDialog
|
import ani.dantotsu.util.customAlertDialog
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
|
||||||
|
|
||||||
class LoginFragment : Fragment() {
|
class LoginFragment : Fragment() {
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.bottomBar
|
import ani.dantotsu.bottomBar
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.AnilistMangaViewModel
|
import ani.dantotsu.connections.anilist.AnilistMangaViewModel
|
||||||
import ani.dantotsu.connections.anilist.SearchResults
|
import ani.dantotsu.connections.anilist.AniMangaSearchResults
|
||||||
import ani.dantotsu.connections.anilist.getUserId
|
import ani.dantotsu.connections.anilist.getUserId
|
||||||
import ani.dantotsu.databinding.FragmentMangaBinding
|
import ani.dantotsu.databinding.FragmentMangaBinding
|
||||||
import ani.dantotsu.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
|
@ -94,7 +94,7 @@ class MangaFragment : Fragment() {
|
||||||
var loading = true
|
var loading = true
|
||||||
if (model.notSet) {
|
if (model.notSet) {
|
||||||
model.notSet = false
|
model.notSet = false
|
||||||
model.searchResults = SearchResults(
|
model.aniMangaSearchResults = AniMangaSearchResults(
|
||||||
"MANGA",
|
"MANGA",
|
||||||
isAdult = false,
|
isAdult = false,
|
||||||
onList = false,
|
onList = false,
|
||||||
|
@ -103,7 +103,7 @@ class MangaFragment : Fragment() {
|
||||||
sort = Anilist.sortBy[1]
|
sort = Anilist.sortBy[1]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val popularAdaptor = MediaAdaptor(1, model.searchResults.results, requireActivity())
|
val popularAdaptor = MediaAdaptor(1, model.aniMangaSearchResults.results, requireActivity())
|
||||||
val progressAdaptor = ProgressAdapter(searched = model.searched)
|
val progressAdaptor = ProgressAdapter(searched = model.searched)
|
||||||
binding.mangaPageRecyclerView.adapter =
|
binding.mangaPageRecyclerView.adapter =
|
||||||
ConcatAdapter(mangaPageAdapter, popularAdaptor, progressAdaptor)
|
ConcatAdapter(mangaPageAdapter, popularAdaptor, progressAdaptor)
|
||||||
|
@ -135,10 +135,10 @@ class MangaFragment : Fragment() {
|
||||||
RecyclerView.OnScrollListener() {
|
RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
||||||
if (!v.canScrollVertically(1)) {
|
if (!v.canScrollVertically(1)) {
|
||||||
if (model.searchResults.hasNextPage && model.searchResults.results.isNotEmpty() && !loading) {
|
if (model.aniMangaSearchResults.hasNextPage && model.aniMangaSearchResults.results.isNotEmpty() && !loading) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
loading = true
|
loading = true
|
||||||
model.loadNextPage(model.searchResults)
|
model.loadNextPage(model.aniMangaSearchResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,7 @@ class MangaFragment : Fragment() {
|
||||||
mangaPageAdapter.onIncludeListClick = { checked ->
|
mangaPageAdapter.onIncludeListClick = { checked ->
|
||||||
oldIncludeList = !checked
|
oldIncludeList = !checked
|
||||||
loading = true
|
loading = true
|
||||||
model.searchResults.results.clear()
|
model.aniMangaSearchResults.results.clear()
|
||||||
popularAdaptor.notifyDataSetChanged()
|
popularAdaptor.notifyDataSetChanged()
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
model.loadPopular("MANGA", sort = Anilist.sortBy[1], onList = checked)
|
model.loadPopular("MANGA", sort = Anilist.sortBy[1], onList = checked)
|
||||||
|
@ -230,17 +230,17 @@ class MangaFragment : Fragment() {
|
||||||
model.getPopular().observe(viewLifecycleOwner) {
|
model.getPopular().observe(viewLifecycleOwner) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
if (oldIncludeList == (it.onList != false)) {
|
if (oldIncludeList == (it.onList != false)) {
|
||||||
val prev = model.searchResults.results.size
|
val prev = model.aniMangaSearchResults.results.size
|
||||||
model.searchResults.results.addAll(it.results)
|
model.aniMangaSearchResults.results.addAll(it.results)
|
||||||
popularAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
popularAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||||
} else {
|
} else {
|
||||||
model.searchResults.results.addAll(it.results)
|
model.aniMangaSearchResults.results.addAll(it.results)
|
||||||
popularAdaptor.notifyDataSetChanged()
|
popularAdaptor.notifyDataSetChanged()
|
||||||
oldIncludeList = it.onList ?: true
|
oldIncludeList = it.onList ?: true
|
||||||
}
|
}
|
||||||
model.searchResults.onList = it.onList
|
model.aniMangaSearchResults.onList = it.onList
|
||||||
model.searchResults.hasNextPage = it.hasNextPage
|
model.aniMangaSearchResults.hasNextPage = it.hasNextPage
|
||||||
model.searchResults.page = it.page
|
model.aniMangaSearchResults.page = it.page
|
||||||
if (it.hasNextPage)
|
if (it.hasNextPage)
|
||||||
progressAdaptor.bar?.visibility = View.VISIBLE
|
progressAdaptor.bar?.visibility = View.VISIBLE
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -82,13 +82,21 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
||||||
trendingBinding.notificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
trendingBinding.notificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
||||||
&& PrefManager.getVal<Boolean>(PrefName.ShowNotificationRedDot) == true
|
&& PrefManager.getVal<Boolean>(PrefName.ShowNotificationRedDot) == true
|
||||||
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||||
trendingBinding.searchBar.hint = "MANGA"
|
trendingBinding.searchBar.hint = binding.root.context.getString(R.string.search)
|
||||||
trendingBinding.searchBarText.setOnClickListener {
|
trendingBinding.searchBarText.setOnClickListener {
|
||||||
|
val context = binding.root.context
|
||||||
|
if (PrefManager.getVal(PrefName.AniMangaSearchDirect) && Anilist.token != null) {
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
it.context,
|
context,
|
||||||
Intent(it.context, SearchActivity::class.java).putExtra("type", "MANGA"),
|
Intent(context, SearchActivity::class.java).putExtra("type", "MANGA"),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
SearchBottomSheet.newInstance().show(
|
||||||
|
(context as AppCompatActivity).supportFragmentManager,
|
||||||
|
"search"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trendingBinding.userAvatar.setSafeOnClickListener {
|
trendingBinding.userAvatar.setSafeOnClickListener {
|
||||||
|
|
74
app/src/main/java/ani/dantotsu/home/SearchBottomSheet.kt
Normal file
74
app/src/main/java/ani/dantotsu/home/SearchBottomSheet.kt
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package ani.dantotsu.home
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import ani.dantotsu.BottomSheetDialogFragment
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType.Companion.toAnilistString
|
||||||
|
import ani.dantotsu.databinding.BottomSheetSearchBinding
|
||||||
|
import ani.dantotsu.media.SearchActivity
|
||||||
|
|
||||||
|
class SearchBottomSheet : BottomSheetDialogFragment() {
|
||||||
|
private var _binding: BottomSheetSearchBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = BottomSheetSearchBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
binding.animeSearch.setOnClickListener {
|
||||||
|
startActivity(requireContext(), SearchType.ANIME)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
binding.mangaSearch.setOnClickListener {
|
||||||
|
startActivity(requireContext(), SearchType.MANGA)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
binding.characterSearch.setOnClickListener {
|
||||||
|
startActivity(requireContext(), SearchType.CHARACTER)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
binding.staffSearch.setOnClickListener {
|
||||||
|
startActivity(requireContext(), SearchType.STAFF)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
binding.studioSearch.setOnClickListener {
|
||||||
|
startActivity(requireContext(), SearchType.STUDIO)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
binding.userSearch.setOnClickListener {
|
||||||
|
startActivity(requireContext(), SearchType.USER)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startActivity(context: Context, type: SearchType) {
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
context,
|
||||||
|
Intent(context, SearchActivity::class.java).putExtra("type", type.toAnilistString()),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance() = SearchBottomSheet()
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,14 +9,13 @@ import androidx.core.view.updateLayoutParams
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.api.Activity
|
import ani.dantotsu.connections.anilist.api.Activity
|
||||||
import ani.dantotsu.databinding.ActivityStatusBinding
|
import ani.dantotsu.databinding.ActivityStatusBinding
|
||||||
import ani.dantotsu.initActivity
|
|
||||||
import ani.dantotsu.themes.ThemeManager
|
|
||||||
import ani.dantotsu.home.status.listener.StoriesCallback
|
import ani.dantotsu.home.status.listener.StoriesCallback
|
||||||
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.profile.User
|
import ani.dantotsu.profile.User
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.toast
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
|
|
||||||
class StatusActivity : AppCompatActivity(), StoriesCallback {
|
class StatusActivity : AppCompatActivity(), StoriesCallback {
|
||||||
|
|
|
@ -7,6 +7,12 @@ data class Author(
|
||||||
var name: String?,
|
var name: String?,
|
||||||
var image: String?,
|
var image: String?,
|
||||||
var role: String?,
|
var role: String?,
|
||||||
|
var age: Int? = null,
|
||||||
|
var yearsActive: List<Int>? = null,
|
||||||
|
var dateOfBirth: String? = null,
|
||||||
|
var dateOfDeath: String? = null,
|
||||||
|
var homeTown: String? = null,
|
||||||
var yearMedia: MutableMap<String, ArrayList<Media>>? = null,
|
var yearMedia: MutableMap<String, ArrayList<Media>>? = null,
|
||||||
var character: ArrayList<Character>? = null
|
var character: ArrayList<Character>? = null,
|
||||||
|
var isFav: Boolean = false
|
||||||
) : Serializable
|
) : Serializable
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package ani.dantotsu.media
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.math.MathUtils.clamp
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
@ -16,57 +18,127 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import ani.dantotsu.EmptyAdapter
|
import ani.dantotsu.EmptyAdapter
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.Refresh
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.databinding.ActivityAuthorBinding
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistMutations
|
||||||
|
import ani.dantotsu.databinding.ActivityCharacterBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.openLinkInBrowser
|
||||||
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
|
import ani.dantotsu.others.SpoilerPlugin
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import io.noties.markwon.Markwon
|
||||||
|
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
class AuthorActivity : AppCompatActivity() {
|
class AuthorActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListener {
|
||||||
private lateinit var binding: ActivityAuthorBinding
|
private lateinit var binding: ActivityCharacterBinding
|
||||||
private val scope = lifecycleScope
|
private val scope = lifecycleScope
|
||||||
private val model: OtherDetailsViewModel by viewModels()
|
private val model: OtherDetailsViewModel by viewModels()
|
||||||
private var author: Author? = null
|
private lateinit var author: Author
|
||||||
private var loaded = false
|
private var loaded = false
|
||||||
|
|
||||||
|
private var screenWidth: Float = 0f
|
||||||
|
private val percent = 30
|
||||||
|
private var mMaxScrollSize = 0
|
||||||
|
private var isCollapsed = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityAuthorBinding.inflate(layoutInflater)
|
binding = ActivityCharacterBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
this.window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg)
|
screenWidth = resources.displayMetrics.run { widthPixels / density }
|
||||||
|
if (PrefManager.getVal(PrefName.ImmersiveMode)) this.window.statusBarColor =
|
||||||
|
ContextCompat.getColor(this, R.color.transparent)
|
||||||
|
|
||||||
val screenWidth = resources.displayMetrics.run { widthPixels / density }
|
val banner =
|
||||||
|
if (PrefManager.getVal(PrefName.BannerAnimations)) binding.characterBanner else binding.characterBannerNoKen
|
||||||
|
|
||||||
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
banner.updateLayoutParams { height += statusBarHeight }
|
||||||
binding.studioRecycler.updatePadding(bottom = 64f.px + navBarHeight)
|
binding.characterClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
||||||
binding.studioTitle.isSelected = true
|
binding.characterCollapsing.minimumHeight = statusBarHeight
|
||||||
|
binding.characterCover.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
||||||
|
binding.characterRecyclerView.updatePadding(bottom = 64f.px + navBarHeight)
|
||||||
|
binding.characterTitle.isSelected = true
|
||||||
|
binding.characterAppBar.addOnOffsetChangedListener(this)
|
||||||
|
|
||||||
author = intent.getSerialized("author")
|
binding.characterClose.setOnClickListener {
|
||||||
binding.studioTitle.text = author?.name
|
|
||||||
|
|
||||||
binding.studioClose.setOnClickListener {
|
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
author = intent.getSerialized("author") ?: return
|
||||||
|
binding.characterTitle.text = author.name
|
||||||
|
binding.characterCoverImage.loadImage(author.image)
|
||||||
|
binding.characterCoverImage.setOnLongClickListener {
|
||||||
|
ImageViewDialog.newInstance(
|
||||||
|
this,
|
||||||
|
author.name,
|
||||||
|
author.image
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val link = "https://anilist.co/staff/${author.id}"
|
||||||
|
binding.characterShare.setOnClickListener {
|
||||||
|
val i = Intent(Intent.ACTION_SEND)
|
||||||
|
i.type = "text/plain"
|
||||||
|
i.putExtra(Intent.EXTRA_TEXT, link)
|
||||||
|
startActivity(Intent.createChooser(i, author.name))
|
||||||
|
}
|
||||||
|
binding.characterShare.setOnLongClickListener {
|
||||||
|
openLinkInBrowser(link)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
author.isFav =
|
||||||
|
Anilist.query.isUserFav(AnilistMutations.FavType.STAFF, author.id)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
binding.characterFav.setImageResource(
|
||||||
|
if (author.isFav) R.drawable.ic_round_favorite_24 else R.drawable.ic_round_favorite_border_24
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.characterFav.setOnClickListener {
|
||||||
|
scope.launch {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
if (Anilist.mutation.toggleFav(AnilistMutations.FavType.CHARACTER, author.id)) {
|
||||||
|
author.isFav = !author.isFav
|
||||||
|
binding.characterFav.setImageResource(
|
||||||
|
if (author.isFav) R.drawable.ic_round_favorite_24 else R.drawable.ic_round_favorite_border_24
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
snackString("Failed to toggle favorite")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
model.getAuthor().observe(this) {
|
model.getAuthor().observe(this) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
author = it
|
author = it
|
||||||
loaded = true
|
loaded = true
|
||||||
binding.studioProgressBar.visibility = View.GONE
|
binding.characterProgress.visibility = View.GONE
|
||||||
binding.studioRecycler.visibility = View.VISIBLE
|
binding.characterRecyclerView.visibility = View.VISIBLE
|
||||||
if (author!!.yearMedia.isNullOrEmpty()) {
|
if (author.yearMedia.isNullOrEmpty()) {
|
||||||
binding.studioRecycler.visibility = View.GONE
|
binding.characterRecyclerView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
val titlePosition = arrayListOf<Int>()
|
val titlePosition = arrayListOf<Int>()
|
||||||
val concatAdapter = ConcatAdapter()
|
val concatAdapter = ConcatAdapter()
|
||||||
val map = author!!.yearMedia ?: return@observe
|
val map = author.yearMedia ?: return@observe
|
||||||
val keys = map.keys.toTypedArray()
|
val keys = map.keys.toTypedArray()
|
||||||
var pos = 0
|
var pos = 0
|
||||||
|
|
||||||
|
@ -80,6 +152,10 @@ class AuthorActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val desc = createDesc(author)
|
||||||
|
val markWon = Markwon.builder(this).usePlugin(SoftBreakAddsNewLinePlugin.create())
|
||||||
|
.usePlugin(SpoilerPlugin()).build()
|
||||||
|
markWon.setMarkdown(binding.authorCharacterDesc, desc)
|
||||||
for (i in keys.indices) {
|
for (i in keys.indices) {
|
||||||
val medias = map[keys[i]]!!
|
val medias = map[keys[i]]!!
|
||||||
val empty = if (medias.size >= 4) medias.size % 4 else 4 - medias.size
|
val empty = if (medias.size >= 4) medias.size % 4 else 4 - medias.size
|
||||||
|
@ -90,18 +166,18 @@ class AuthorActivity : AppCompatActivity() {
|
||||||
concatAdapter.addAdapter(MediaAdaptor(0, medias, this, true))
|
concatAdapter.addAdapter(MediaAdaptor(0, medias, this, true))
|
||||||
concatAdapter.addAdapter(EmptyAdapter(empty))
|
concatAdapter.addAdapter(EmptyAdapter(empty))
|
||||||
}
|
}
|
||||||
binding.studioRecycler.adapter = concatAdapter
|
binding.characterRecyclerView.adapter = concatAdapter
|
||||||
binding.studioRecycler.layoutManager = gridLayoutManager
|
binding.characterRecyclerView.layoutManager = gridLayoutManager
|
||||||
|
|
||||||
binding.charactersRecycler.visibility = View.VISIBLE
|
binding.authorCharactersRecycler.visibility = View.VISIBLE
|
||||||
binding.charactersText.visibility = View.VISIBLE
|
binding.AuthorCharactersText.visibility = View.VISIBLE
|
||||||
binding.charactersRecycler.adapter =
|
binding.authorCharactersRecycler.adapter =
|
||||||
CharacterAdapter(author!!.character ?: arrayListOf())
|
CharacterAdapter(author.character ?: arrayListOf())
|
||||||
binding.charactersRecycler.layoutManager =
|
binding.authorCharactersRecycler.layoutManager =
|
||||||
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
|
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
|
||||||
if (author!!.character.isNullOrEmpty()) {
|
if (author.character.isNullOrEmpty()) {
|
||||||
binding.charactersRecycler.visibility = View.GONE
|
binding.authorCharactersRecycler.visibility = View.GONE
|
||||||
binding.charactersText.visibility = View.GONE
|
binding.AuthorCharactersText.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,14 +185,28 @@ class AuthorActivity : AppCompatActivity() {
|
||||||
live.observe(this) {
|
live.observe(this) {
|
||||||
if (it) {
|
if (it) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (author != null)
|
withContext(Dispatchers.IO) { model.loadAuthor(author) }
|
||||||
withContext(Dispatchers.IO) { model.loadAuthor(author!!) }
|
|
||||||
live.postValue(false)
|
live.postValue(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createDesc(author: Author): String {
|
||||||
|
val age = if (author.age != null) "${getString(R.string.age)} ${author.age}" else ""
|
||||||
|
val yearsActive =
|
||||||
|
if (author.yearsActive != null) "${getString(R.string.years_active)} ${author.yearsActive}" else ""
|
||||||
|
val dob =
|
||||||
|
if (author.dateOfBirth != null) "${getString(R.string.birthday)} ${author.dateOfBirth}" else ""
|
||||||
|
val homeTown =
|
||||||
|
if (author.homeTown != null) "${getString(R.string.hometown)} ${author.homeTown}" else ""
|
||||||
|
val dod =
|
||||||
|
if (author.dateOfDeath != null) "${getString(R.string.date_of_death)} ${author.dateOfDeath}" else ""
|
||||||
|
|
||||||
|
return "$age $yearsActive $dob $homeTown $dod"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
if (Refresh.activity.containsKey(this.hashCode())) {
|
if (Refresh.activity.containsKey(this.hashCode())) {
|
||||||
Refresh.activity.remove(this.hashCode())
|
Refresh.activity.remove(this.hashCode())
|
||||||
|
@ -125,7 +215,31 @@ class AuthorActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
binding.studioProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE
|
binding.characterProgress.visibility = if (!loaded) View.VISIBLE else View.GONE
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onOffsetChanged(appBar: AppBarLayout, i: Int) {
|
||||||
|
if (mMaxScrollSize == 0) mMaxScrollSize = appBar.totalScrollRange
|
||||||
|
val percentage = abs(i) * 100 / mMaxScrollSize
|
||||||
|
val cap = clamp((percent - percentage) / percent.toFloat(), 0f, 1f)
|
||||||
|
|
||||||
|
binding.characterCover.scaleX = 1f * cap
|
||||||
|
binding.characterCover.scaleY = 1f * cap
|
||||||
|
binding.characterCover.cardElevation = 32f * cap
|
||||||
|
|
||||||
|
binding.characterCover.visibility =
|
||||||
|
if (binding.characterCover.scaleX == 0f) View.GONE else View.VISIBLE
|
||||||
|
val immersiveMode: Boolean = PrefManager.getVal(PrefName.ImmersiveMode)
|
||||||
|
if (percentage >= percent && !isCollapsed) {
|
||||||
|
isCollapsed = true
|
||||||
|
if (immersiveMode) this.window.statusBarColor =
|
||||||
|
ContextCompat.getColor(this, R.color.nav_bg)
|
||||||
|
}
|
||||||
|
if (percentage <= percent && isCollapsed) {
|
||||||
|
isCollapsed = false
|
||||||
|
if (immersiveMode) this.window.statusBarColor =
|
||||||
|
ContextCompat.getColor(this, R.color.transparent)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -15,7 +15,7 @@ import ani.dantotsu.setAnimation
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
class AuthorAdapter(
|
class AuthorAdapter(
|
||||||
private val authorList: ArrayList<Author>,
|
private val authorList: MutableList<Author>,
|
||||||
) : RecyclerView.Adapter<AuthorAdapter.AuthorViewHolder>() {
|
) : RecyclerView.Adapter<AuthorAdapter.AuthorViewHolder>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AuthorViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AuthorViewHolder {
|
||||||
val binding =
|
val binding =
|
||||||
|
@ -26,7 +26,7 @@ class AuthorAdapter(
|
||||||
override fun onBindViewHolder(holder: AuthorViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: AuthorViewHolder, position: Int) {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
setAnimation(binding.root.context, holder.binding.root)
|
setAnimation(binding.root.context, holder.binding.root)
|
||||||
val author = authorList[position]
|
val author = authorList.getOrNull(position) ?: return
|
||||||
binding.itemCompactRelation.text = author.role
|
binding.itemCompactRelation.text = author.role
|
||||||
binding.itemCompactImage.loadImage(author.image)
|
binding.itemCompactImage.loadImage(author.image)
|
||||||
binding.itemCompactTitle.text = author.name
|
binding.itemCompactTitle.text = author.name
|
||||||
|
|
|
@ -16,7 +16,7 @@ import ani.dantotsu.setAnimation
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
class CharacterAdapter(
|
class CharacterAdapter(
|
||||||
private val characterList: ArrayList<Character>
|
private val characterList: MutableList<Character>
|
||||||
) : RecyclerView.Adapter<CharacterAdapter.CharacterViewHolder>() {
|
) : RecyclerView.Adapter<CharacterAdapter.CharacterViewHolder>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharacterViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharacterViewHolder {
|
||||||
val binding =
|
val binding =
|
||||||
|
@ -27,9 +27,8 @@ class CharacterAdapter(
|
||||||
override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
setAnimation(binding.root.context, holder.binding.root)
|
setAnimation(binding.root.context, holder.binding.root)
|
||||||
val character = characterList[position]
|
val character = characterList.getOrNull(position) ?: return
|
||||||
val whitespace = "${character.role} "
|
val whitespace = "${if (character.role.lowercase() == "null") "" else character.role} "
|
||||||
character.voiceActor
|
|
||||||
binding.itemCompactRelation.text = whitespace
|
binding.itemCompactRelation.text = whitespace
|
||||||
binding.itemCompactImage.loadImage(character.image)
|
binding.itemCompactImage.loadImage(character.image)
|
||||||
binding.itemCompactTitle.text = character.name
|
binding.itemCompactTitle.text = character.name
|
||||||
|
|
|
@ -9,6 +9,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.math.MathUtils.clamp
|
import androidx.core.math.MathUtils.clamp
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
@ -45,6 +46,11 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
|
||||||
private lateinit var character: Character
|
private lateinit var character: Character
|
||||||
private var loaded = false
|
private var loaded = false
|
||||||
|
|
||||||
|
private var isCollapsed = false
|
||||||
|
private val percent = 30
|
||||||
|
private var mMaxScrollSize = 0
|
||||||
|
private var screenWidth: Float = 0f
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
@ -71,6 +77,11 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
|
||||||
binding.characterClose.setOnClickListener {
|
binding.characterClose.setOnClickListener {
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.authorCharactersRecycler.isVisible = false
|
||||||
|
binding.AuthorCharactersText.isVisible = false
|
||||||
|
binding.authorCharacterDesc.isVisible = false
|
||||||
|
|
||||||
character = intent.getSerialized("character") ?: return
|
character = intent.getSerialized("character") ?: return
|
||||||
binding.characterTitle.text = character.name
|
binding.characterTitle.text = character.name
|
||||||
banner.loadImage(character.banner)
|
banner.loadImage(character.banner)
|
||||||
|
@ -158,11 +169,6 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isCollapsed = false
|
|
||||||
private val percent = 30
|
|
||||||
private var mMaxScrollSize = 0
|
|
||||||
private var screenWidth: Float = 0f
|
|
||||||
|
|
||||||
override fun onOffsetChanged(appBar: AppBarLayout, i: Int) {
|
override fun onOffsetChanged(appBar: AppBarLayout, i: Int) {
|
||||||
if (mMaxScrollSize == 0) mMaxScrollSize = appBar.totalScrollRange
|
if (mMaxScrollSize == 0) mMaxScrollSize = appBar.totalScrollRange
|
||||||
val percentage = abs(i) * 100 / mMaxScrollSize
|
val percentage = abs(i) * 100 / mMaxScrollSize
|
||||||
|
|
77
app/src/main/java/ani/dantotsu/media/HeaderInterface.kt
Normal file
77
app/src/main/java/ani/dantotsu/media/HeaderInterface.kt
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.animation.AlphaAnimation
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.databinding.ItemSearchHeaderBinding
|
||||||
|
|
||||||
|
abstract class HeaderInterface: RecyclerView.Adapter<HeaderInterface.SearchHeaderViewHolder>() {
|
||||||
|
private val itemViewType = 6969
|
||||||
|
var search: Runnable? = null
|
||||||
|
var requestFocus: Runnable? = null
|
||||||
|
protected var textWatcher: TextWatcher? = null
|
||||||
|
protected lateinit var searchHistoryAdapter: SearchHistoryAdapter
|
||||||
|
protected lateinit var binding: ItemSearchHeaderBinding
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHeaderViewHolder {
|
||||||
|
val binding =
|
||||||
|
ItemSearchHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return SearchHeaderViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setHistoryVisibility(visible: Boolean) {
|
||||||
|
if (visible) {
|
||||||
|
binding.searchResultLayout.startAnimation(fadeOutAnimation())
|
||||||
|
binding.searchHistoryList.startAnimation(fadeInAnimation())
|
||||||
|
binding.searchResultLayout.visibility = View.GONE
|
||||||
|
binding.searchHistoryList.visibility = View.VISIBLE
|
||||||
|
binding.searchByImage.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
if (binding.searchResultLayout.visibility != View.VISIBLE) {
|
||||||
|
binding.searchResultLayout.startAnimation(fadeInAnimation())
|
||||||
|
binding.searchHistoryList.startAnimation(fadeOutAnimation())
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.searchResultLayout.visibility = View.VISIBLE
|
||||||
|
binding.clearHistory.visibility = View.GONE
|
||||||
|
binding.searchHistoryList.visibility = View.GONE
|
||||||
|
binding.searchByImage.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fadeInAnimation(): Animation {
|
||||||
|
return AlphaAnimation(0f, 1f).apply {
|
||||||
|
duration = 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun fadeOutAnimation(): Animation {
|
||||||
|
return AlphaAnimation(1f, 0f).apply {
|
||||||
|
duration = 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun updateClearHistoryVisibility() {
|
||||||
|
binding.clearHistory.visibility =
|
||||||
|
if (searchHistoryAdapter.itemCount > 0) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addHistory() {
|
||||||
|
if (::searchHistoryAdapter.isInitialized && binding.searchBarText.text.toString()
|
||||||
|
.isNotBlank()
|
||||||
|
) searchHistoryAdapter.add(binding.searchBarText.text.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class SearchHeaderViewHolder(val binding: ItemSearchHeaderBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = 1
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
return itemViewType
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,10 +15,16 @@ import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.AnilistSearch
|
import ani.dantotsu.connections.anilist.AnilistSearch
|
||||||
import ani.dantotsu.connections.anilist.SearchResults
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType
|
||||||
|
import ani.dantotsu.connections.anilist.AniMangaSearchResults
|
||||||
|
import ani.dantotsu.connections.anilist.CharacterSearchResults
|
||||||
|
import ani.dantotsu.connections.anilist.StaffSearchResults
|
||||||
|
import ani.dantotsu.connections.anilist.StudioSearchResults
|
||||||
|
import ani.dantotsu.connections.anilist.UserSearchResults
|
||||||
import ani.dantotsu.databinding.ActivitySearchBinding
|
import ani.dantotsu.databinding.ActivitySearchBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.profile.UsersAdapter
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
@ -35,14 +41,25 @@ class SearchActivity : AppCompatActivity() {
|
||||||
val model: AnilistSearch by viewModels()
|
val model: AnilistSearch by viewModels()
|
||||||
|
|
||||||
var style: Int = 0
|
var style: Int = 0
|
||||||
|
lateinit var searchType: SearchType
|
||||||
private var screenWidth: Float = 0f
|
private var screenWidth: Float = 0f
|
||||||
|
|
||||||
private lateinit var mediaAdaptor: MediaAdaptor
|
private lateinit var mediaAdaptor: MediaAdaptor
|
||||||
|
private lateinit var characterAdaptor: CharacterAdapter
|
||||||
|
private lateinit var studioAdaptor: StudioAdapter
|
||||||
|
private lateinit var staffAdaptor: AuthorAdapter
|
||||||
|
private lateinit var usersAdapter: UsersAdapter
|
||||||
|
|
||||||
private lateinit var progressAdapter: ProgressAdapter
|
private lateinit var progressAdapter: ProgressAdapter
|
||||||
private lateinit var concatAdapter: ConcatAdapter
|
private lateinit var concatAdapter: ConcatAdapter
|
||||||
private lateinit var headerAdaptor: SearchAdapter
|
private lateinit var headerAdaptor: HeaderInterface
|
||||||
|
|
||||||
|
lateinit var aniMangaResult: AniMangaSearchResults
|
||||||
|
lateinit var characterResult: CharacterSearchResults
|
||||||
|
lateinit var studioResult: StudioSearchResults
|
||||||
|
lateinit var staffResult: StaffSearchResults
|
||||||
|
lateinit var userResult: UserSearchResults
|
||||||
|
|
||||||
lateinit var result: SearchResults
|
|
||||||
lateinit var updateChips: (() -> Unit)
|
lateinit var updateChips: (() -> Unit)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -59,16 +76,22 @@ class SearchActivity : AppCompatActivity() {
|
||||||
bottom = navBarHeight + 80f.px
|
bottom = navBarHeight + 80f.px
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val notSet = model.notSet
|
||||||
|
searchType = SearchType.fromString(intent.getStringExtra("type") ?: "ANIME")
|
||||||
|
when (searchType) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> {
|
||||||
style = PrefManager.getVal(PrefName.SearchStyle)
|
style = PrefManager.getVal(PrefName.SearchStyle)
|
||||||
var listOnly: Boolean? = intent.getBooleanExtra("listOnly", false)
|
var listOnly: Boolean? = intent.getBooleanExtra("listOnly", false)
|
||||||
if (!listOnly!!) listOnly = null
|
if (!listOnly!!) listOnly = null
|
||||||
|
|
||||||
val notSet = model.notSet
|
|
||||||
if (model.notSet) {
|
if (model.notSet) {
|
||||||
model.notSet = false
|
model.notSet = false
|
||||||
model.searchResults = SearchResults(
|
model.aniMangaSearchResults = AniMangaSearchResults(
|
||||||
intent.getStringExtra("type") ?: "ANIME",
|
intent.getStringExtra("type") ?: "ANIME",
|
||||||
isAdult = if (Anilist.adult) intent.getBooleanExtra("hentai", false) else false,
|
isAdult = if (Anilist.adult) intent.getBooleanExtra(
|
||||||
|
"hentai",
|
||||||
|
false
|
||||||
|
) else false,
|
||||||
onList = listOnly,
|
onList = listOnly,
|
||||||
search = intent.getStringExtra("query"),
|
search = intent.getStringExtra("query"),
|
||||||
genres = intent.getStringExtra("genre")?.let { mutableListOf(it) },
|
genres = intent.getStringExtra("genre")?.let { mutableListOf(it) },
|
||||||
|
@ -78,20 +101,92 @@ class SearchActivity : AppCompatActivity() {
|
||||||
source = intent.getStringExtra("source"),
|
source = intent.getStringExtra("source"),
|
||||||
countryOfOrigin = intent.getStringExtra("country"),
|
countryOfOrigin = intent.getStringExtra("country"),
|
||||||
season = intent.getStringExtra("season"),
|
season = intent.getStringExtra("season"),
|
||||||
seasonYear = if (intent.getStringExtra("type") == "ANIME") intent.getStringExtra("seasonYear")
|
seasonYear = if (intent.getStringExtra("type") == "ANIME") intent.getStringExtra(
|
||||||
|
"seasonYear"
|
||||||
|
)
|
||||||
?.toIntOrNull() else null,
|
?.toIntOrNull() else null,
|
||||||
startYear = if (intent.getStringExtra("type") == "MANGA") intent.getStringExtra("seasonYear")
|
startYear = if (intent.getStringExtra("type") == "MANGA") intent.getStringExtra(
|
||||||
|
"seasonYear"
|
||||||
|
)
|
||||||
?.toIntOrNull() else null,
|
?.toIntOrNull() else null,
|
||||||
results = mutableListOf(),
|
results = mutableListOf(),
|
||||||
hasNextPage = false
|
hasNextPage = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
result = model.searchResults
|
aniMangaResult = model.aniMangaSearchResults
|
||||||
|
mediaAdaptor =
|
||||||
|
MediaAdaptor(
|
||||||
|
style,
|
||||||
|
model.aniMangaSearchResults.results,
|
||||||
|
this,
|
||||||
|
matchParent = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.CHARACTER -> {
|
||||||
|
if (model.notSet) {
|
||||||
|
model.notSet = false
|
||||||
|
model.characterSearchResults = CharacterSearchResults(
|
||||||
|
search = intent.getStringExtra("query"),
|
||||||
|
results = mutableListOf(),
|
||||||
|
hasNextPage = false
|
||||||
|
)
|
||||||
|
|
||||||
|
characterResult = model.characterSearchResults
|
||||||
|
characterAdaptor = CharacterAdapter(model.characterSearchResults.results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STUDIO -> {
|
||||||
|
if (model.notSet) {
|
||||||
|
model.notSet = false
|
||||||
|
model.studioSearchResults = StudioSearchResults(
|
||||||
|
search = intent.getStringExtra("query"),
|
||||||
|
results = mutableListOf(),
|
||||||
|
hasNextPage = false
|
||||||
|
)
|
||||||
|
|
||||||
|
studioResult = model.studioSearchResults
|
||||||
|
studioAdaptor = StudioAdapter(model.studioSearchResults.results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STAFF -> {
|
||||||
|
if (model.notSet) {
|
||||||
|
model.notSet = false
|
||||||
|
model.staffSearchResults = StaffSearchResults(
|
||||||
|
search = intent.getStringExtra("query"),
|
||||||
|
results = mutableListOf(),
|
||||||
|
hasNextPage = false
|
||||||
|
)
|
||||||
|
|
||||||
|
staffResult = model.staffSearchResults
|
||||||
|
staffAdaptor = AuthorAdapter(model.staffSearchResults.results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.USER -> {
|
||||||
|
if (model.notSet) {
|
||||||
|
model.notSet = false
|
||||||
|
model.userSearchResults = UserSearchResults(
|
||||||
|
search = intent.getStringExtra("query"),
|
||||||
|
results = mutableListOf(),
|
||||||
|
hasNextPage = false
|
||||||
|
)
|
||||||
|
|
||||||
|
userResult = model.userSearchResults
|
||||||
|
usersAdapter = UsersAdapter(model.userSearchResults.results, grid = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
progressAdapter = ProgressAdapter(searched = model.searched)
|
progressAdapter = ProgressAdapter(searched = model.searched)
|
||||||
mediaAdaptor = MediaAdaptor(style, model.searchResults.results, this, matchParent = true)
|
headerAdaptor = if (searchType == SearchType.ANIME || searchType == SearchType.MANGA) {
|
||||||
headerAdaptor = SearchAdapter(this, model.searchResults.type)
|
SearchAdapter(this, searchType)
|
||||||
|
} else {
|
||||||
|
SupportingSearchAdapter(this, searchType)
|
||||||
|
}
|
||||||
|
|
||||||
val gridSize = (screenWidth / 120f).toInt()
|
val gridSize = (screenWidth / 120f).toInt()
|
||||||
val gridLayoutManager = GridLayoutManager(this, gridSize)
|
val gridLayoutManager = GridLayoutManager(this, gridSize)
|
||||||
|
@ -108,7 +203,27 @@ class SearchActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
concatAdapter = ConcatAdapter(headerAdaptor, mediaAdaptor, progressAdapter)
|
concatAdapter = when (searchType) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> {
|
||||||
|
ConcatAdapter(headerAdaptor, mediaAdaptor, progressAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.CHARACTER -> {
|
||||||
|
ConcatAdapter(headerAdaptor, characterAdaptor, progressAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STUDIO -> {
|
||||||
|
ConcatAdapter(headerAdaptor, studioAdaptor, progressAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STAFF -> {
|
||||||
|
ConcatAdapter(headerAdaptor, staffAdaptor, progressAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.USER -> {
|
||||||
|
ConcatAdapter(headerAdaptor, usersAdapter, progressAdapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.searchRecyclerView.layoutManager = gridLayoutManager
|
binding.searchRecyclerView.layoutManager = gridLayoutManager
|
||||||
binding.searchRecyclerView.adapter = concatAdapter
|
binding.searchRecyclerView.adapter = concatAdapter
|
||||||
|
@ -117,9 +232,9 @@ class SearchActivity : AppCompatActivity() {
|
||||||
RecyclerView.OnScrollListener() {
|
RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(v: RecyclerView, dx: Int, dy: Int) {
|
||||||
if (!v.canScrollVertically(1)) {
|
if (!v.canScrollVertically(1)) {
|
||||||
if (model.searchResults.hasNextPage && model.searchResults.results.isNotEmpty() && !loading) {
|
if (model.hasNextPage(searchType) && model.resultsIsNotEmpty(searchType) && !loading) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
model.loadNextPage(model.searchResults)
|
model.loadNextPage(searchType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,9 +242,11 @@ class SearchActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
model.getSearch().observe(this) {
|
when (searchType) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> {
|
||||||
|
model.getSearch<AniMangaSearchResults>(searchType).observe(this) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
model.searchResults.apply {
|
model.aniMangaSearchResults.apply {
|
||||||
onList = it.onList
|
onList = it.onList
|
||||||
isAdult = it.isAdult
|
isAdult = it.isAdult
|
||||||
perPage = it.perPage
|
perPage = it.perPage
|
||||||
|
@ -150,13 +267,87 @@ class SearchActivity : AppCompatActivity() {
|
||||||
hasNextPage = it.hasNextPage
|
hasNextPage = it.hasNextPage
|
||||||
}
|
}
|
||||||
|
|
||||||
val prev = model.searchResults.results.size
|
val prev = model.aniMangaSearchResults.results.size
|
||||||
model.searchResults.results.addAll(it.results)
|
model.aniMangaSearchResults.results.addAll(it.results)
|
||||||
mediaAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
mediaAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||||
|
|
||||||
progressAdapter.bar?.isVisible = it.hasNextPage
|
progressAdapter.bar?.isVisible = it.hasNextPage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.CHARACTER -> {
|
||||||
|
model.getSearch<CharacterSearchResults>(searchType).observe(this) {
|
||||||
|
if (it != null) {
|
||||||
|
model.characterSearchResults.apply {
|
||||||
|
search = it.search
|
||||||
|
page = it.page
|
||||||
|
hasNextPage = it.hasNextPage
|
||||||
|
}
|
||||||
|
|
||||||
|
val prev = model.characterSearchResults.results.size
|
||||||
|
model.characterSearchResults.results.addAll(it.results)
|
||||||
|
characterAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||||
|
|
||||||
|
progressAdapter.bar?.isVisible = it.hasNextPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STUDIO -> {
|
||||||
|
model.getSearch<StudioSearchResults>(searchType).observe(this) {
|
||||||
|
if (it != null) {
|
||||||
|
model.studioSearchResults.apply {
|
||||||
|
search = it.search
|
||||||
|
page = it.page
|
||||||
|
hasNextPage = it.hasNextPage
|
||||||
|
}
|
||||||
|
|
||||||
|
val prev = model.studioSearchResults.results.size
|
||||||
|
model.studioSearchResults.results.addAll(it.results)
|
||||||
|
studioAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||||
|
|
||||||
|
progressAdapter.bar?.isVisible = it.hasNextPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STAFF -> {
|
||||||
|
model.getSearch<StaffSearchResults>(searchType).observe(this) {
|
||||||
|
if (it != null) {
|
||||||
|
model.staffSearchResults.apply {
|
||||||
|
search = it.search
|
||||||
|
page = it.page
|
||||||
|
hasNextPage = it.hasNextPage
|
||||||
|
}
|
||||||
|
|
||||||
|
val prev = model.staffSearchResults.results.size
|
||||||
|
model.staffSearchResults.results.addAll(it.results)
|
||||||
|
staffAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||||
|
|
||||||
|
progressAdapter.bar?.isVisible = it.hasNextPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.USER -> {
|
||||||
|
model.getSearch<UserSearchResults>(searchType).observe(this) {
|
||||||
|
if (it != null) {
|
||||||
|
model.userSearchResults.apply {
|
||||||
|
search = it.search
|
||||||
|
page = it.page
|
||||||
|
hasNextPage = it.hasNextPage
|
||||||
|
}
|
||||||
|
|
||||||
|
val prev = model.userSearchResults.results.size
|
||||||
|
model.userSearchResults.results.addAll(it.results)
|
||||||
|
usersAdapter.notifyItemRangeInserted(prev, it.results.size)
|
||||||
|
|
||||||
|
progressAdapter.bar?.isVisible = it.hasNextPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
progressAdapter.ready.observe(this) {
|
progressAdapter.ready.observe(this) {
|
||||||
if (it == true) {
|
if (it == true) {
|
||||||
|
@ -179,8 +370,32 @@ class SearchActivity : AppCompatActivity() {
|
||||||
fun emptyMediaAdapter() {
|
fun emptyMediaAdapter() {
|
||||||
searchTimer.cancel()
|
searchTimer.cancel()
|
||||||
searchTimer.purge()
|
searchTimer.purge()
|
||||||
mediaAdaptor.notifyItemRangeRemoved(0, model.searchResults.results.size)
|
when (searchType) {
|
||||||
model.searchResults.results.clear()
|
SearchType.ANIME, SearchType.MANGA -> {
|
||||||
|
mediaAdaptor.notifyItemRangeRemoved(0, model.aniMangaSearchResults.results.size)
|
||||||
|
model.aniMangaSearchResults.results.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.CHARACTER -> {
|
||||||
|
characterAdaptor.notifyItemRangeRemoved(0, model.characterSearchResults.results.size)
|
||||||
|
model.characterSearchResults.results.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STUDIO -> {
|
||||||
|
studioAdaptor.notifyItemRangeRemoved(0, model.studioSearchResults.results.size)
|
||||||
|
model.studioSearchResults.results.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STAFF -> {
|
||||||
|
staffAdaptor.notifyItemRangeRemoved(0, model.staffSearchResults.results.size)
|
||||||
|
model.staffSearchResults.results.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.USER -> {
|
||||||
|
usersAdapter.notifyItemRangeRemoved(0, model.userSearchResults.results.size)
|
||||||
|
model.userSearchResults.results.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
progressAdapter.bar?.visibility = View.GONE
|
progressAdapter.bar?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,12 +403,32 @@ class SearchActivity : AppCompatActivity() {
|
||||||
private var loading = false
|
private var loading = false
|
||||||
fun search() {
|
fun search() {
|
||||||
headerAdaptor.setHistoryVisibility(false)
|
headerAdaptor.setHistoryVisibility(false)
|
||||||
val size = model.searchResults.results.size
|
val size = model.size(searchType)
|
||||||
model.searchResults.results.clear()
|
model.clearResults(searchType)
|
||||||
binding.searchRecyclerView.post {
|
binding.searchRecyclerView.post {
|
||||||
|
when (searchType) {
|
||||||
|
SearchType.ANIME, SearchType.MANGA -> {
|
||||||
mediaAdaptor.notifyItemRangeRemoved(0, size)
|
mediaAdaptor.notifyItemRangeRemoved(0, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SearchType.CHARACTER -> {
|
||||||
|
characterAdaptor.notifyItemRangeRemoved(0, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STUDIO -> {
|
||||||
|
studioAdaptor.notifyItemRangeRemoved(0, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STAFF -> {
|
||||||
|
staffAdaptor.notifyItemRangeRemoved(0, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.USER -> {
|
||||||
|
usersAdapter.notifyItemRangeRemoved(0, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
progressAdapter.bar?.visibility = View.VISIBLE
|
progressAdapter.bar?.visibility = View.VISIBLE
|
||||||
|
|
||||||
searchTimer.cancel()
|
searchTimer.cancel()
|
||||||
|
@ -202,7 +437,7 @@ class SearchActivity : AppCompatActivity() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
loading = true
|
loading = true
|
||||||
model.loadSearch(result)
|
model.loadSearch(searchType)
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,9 +448,11 @@ class SearchActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun recycler() {
|
fun recycler() {
|
||||||
|
if (searchType == SearchType.ANIME || searchType == SearchType.MANGA) {
|
||||||
mediaAdaptor.type = style
|
mediaAdaptor.type = style
|
||||||
mediaAdaptor.notifyDataSetChanged()
|
mediaAdaptor.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var state: Parcelable? = null
|
var state: Parcelable? = null
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
|
|
@ -9,8 +9,6 @@ import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AlphaAnimation
|
|
||||||
import android.view.animation.Animation
|
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
|
@ -22,8 +20,8 @@ import androidx.recyclerview.widget.RecyclerView.HORIZONTAL
|
||||||
import ani.dantotsu.App.Companion.context
|
import ani.dantotsu.App.Companion.context
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
import ani.dantotsu.databinding.ItemSearchHeaderBinding
|
|
||||||
import ani.dantotsu.openLinkInBrowser
|
import ani.dantotsu.openLinkInBrowser
|
||||||
import ani.dantotsu.others.imagesearch.ImageSearchActivity
|
import ani.dantotsu.others.imagesearch.ImageSearchActivity
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
@ -36,18 +34,11 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class SearchAdapter(private val activity: SearchActivity, private val type: SearchType) :
|
||||||
class SearchAdapter(private val activity: SearchActivity, private val type: String) :
|
HeaderInterface() {
|
||||||
RecyclerView.Adapter<SearchAdapter.SearchHeaderViewHolder>() {
|
|
||||||
private val itemViewType = 6969
|
|
||||||
var search: Runnable? = null
|
|
||||||
var requestFocus: Runnable? = null
|
|
||||||
private var textWatcher: TextWatcher? = null
|
|
||||||
private lateinit var searchHistoryAdapter: SearchHistoryAdapter
|
|
||||||
private lateinit var binding: ItemSearchHeaderBinding
|
|
||||||
|
|
||||||
private fun updateFilterTextViewDrawable() {
|
private fun updateFilterTextViewDrawable() {
|
||||||
val filterDrawable = when (activity.result.sort) {
|
val filterDrawable = when (activity.aniMangaResult.sort) {
|
||||||
Anilist.sortBy[0] -> R.drawable.ic_round_area_chart_24
|
Anilist.sortBy[0] -> R.drawable.ic_round_area_chart_24
|
||||||
Anilist.sortBy[1] -> R.drawable.ic_round_filter_peak_24
|
Anilist.sortBy[1] -> R.drawable.ic_round_filter_peak_24
|
||||||
Anilist.sortBy[2] -> R.drawable.ic_round_star_graph_24
|
Anilist.sortBy[2] -> R.drawable.ic_round_star_graph_24
|
||||||
|
@ -60,12 +51,6 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
||||||
binding.filterTextView.setCompoundDrawablesWithIntrinsicBounds(filterDrawable, 0, 0, 0)
|
binding.filterTextView.setCompoundDrawablesWithIntrinsicBounds(filterDrawable, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHeaderViewHolder {
|
|
||||||
val binding =
|
|
||||||
ItemSearchHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
|
||||||
return SearchHeaderViewHolder(binding)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onBindViewHolder(holder: SearchHeaderViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: SearchHeaderViewHolder, position: Int) {
|
||||||
binding = holder.binding
|
binding = holder.binding
|
||||||
|
@ -79,6 +64,10 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
||||||
val imm: InputMethodManager =
|
val imm: InputMethodManager =
|
||||||
activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
|
||||||
|
if (activity.searchType != SearchType.MANGA && activity.searchType != SearchType.ANIME) {
|
||||||
|
throw IllegalArgumentException("Invalid search type (wrong adapter)")
|
||||||
|
}
|
||||||
|
|
||||||
when (activity.style) {
|
when (activity.style) {
|
||||||
0 -> {
|
0 -> {
|
||||||
binding.searchResultGrid.alpha = 1f
|
binding.searchResultGrid.alpha = 1f
|
||||||
|
@ -91,7 +80,7 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.searchBar.hint = activity.result.type
|
binding.searchBar.hint = activity.aniMangaResult.type
|
||||||
if (PrefManager.getVal(PrefName.Incognito)) {
|
if (PrefManager.getVal(PrefName.Incognito)) {
|
||||||
val startIconDrawableRes = R.drawable.ic_incognito_24
|
val startIconDrawableRes = R.drawable.ic_incognito_24
|
||||||
val startIconDrawable: Drawable? =
|
val startIconDrawable: Drawable? =
|
||||||
|
@ -99,11 +88,11 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
||||||
binding.searchBar.startIconDrawable = startIconDrawable
|
binding.searchBar.startIconDrawable = startIconDrawable
|
||||||
}
|
}
|
||||||
|
|
||||||
var adult = activity.result.isAdult
|
var adult = activity.aniMangaResult.isAdult
|
||||||
var listOnly = activity.result.onList
|
var listOnly = activity.aniMangaResult.onList
|
||||||
|
|
||||||
binding.searchBarText.removeTextChangedListener(textWatcher)
|
binding.searchBarText.removeTextChangedListener(textWatcher)
|
||||||
binding.searchBarText.setText(activity.result.search)
|
binding.searchBarText.setText(activity.aniMangaResult.search)
|
||||||
|
|
||||||
binding.searchAdultCheck.isChecked = adult
|
binding.searchAdultCheck.isChecked = adult
|
||||||
binding.searchList.isChecked = listOnly == true
|
binding.searchList.isChecked = listOnly == true
|
||||||
|
@ -124,49 +113,49 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
||||||
popupMenu.setOnMenuItemClickListener { item ->
|
popupMenu.setOnMenuItemClickListener { item ->
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.sort_by_score -> {
|
R.id.sort_by_score -> {
|
||||||
activity.result.sort = Anilist.sortBy[0]
|
activity.aniMangaResult.sort = Anilist.sortBy[0]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_popular -> {
|
R.id.sort_by_popular -> {
|
||||||
activity.result.sort = Anilist.sortBy[1]
|
activity.aniMangaResult.sort = Anilist.sortBy[1]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_trending -> {
|
R.id.sort_by_trending -> {
|
||||||
activity.result.sort = Anilist.sortBy[2]
|
activity.aniMangaResult.sort = Anilist.sortBy[2]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_recent -> {
|
R.id.sort_by_recent -> {
|
||||||
activity.result.sort = Anilist.sortBy[3]
|
activity.aniMangaResult.sort = Anilist.sortBy[3]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_a_z -> {
|
R.id.sort_by_a_z -> {
|
||||||
activity.result.sort = Anilist.sortBy[4]
|
activity.aniMangaResult.sort = Anilist.sortBy[4]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_z_a -> {
|
R.id.sort_by_z_a -> {
|
||||||
activity.result.sort = Anilist.sortBy[5]
|
activity.aniMangaResult.sort = Anilist.sortBy[5]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_pure_pain -> {
|
R.id.sort_by_pure_pain -> {
|
||||||
activity.result.sort = Anilist.sortBy[6]
|
activity.aniMangaResult.sort = Anilist.sortBy[6]
|
||||||
activity.updateChips.invoke()
|
activity.updateChips.invoke()
|
||||||
activity.search()
|
activity.search()
|
||||||
updateFilterTextViewDrawable()
|
updateFilterTextViewDrawable()
|
||||||
|
@ -177,7 +166,7 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
||||||
popupMenu.show()
|
popupMenu.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
if (activity.result.type != "ANIME") {
|
if (activity.aniMangaResult.type != "ANIME") {
|
||||||
binding.searchByImage.visibility = View.GONE
|
binding.searchByImage.visibility = View.GONE
|
||||||
}
|
}
|
||||||
binding.searchByImage.setOnClickListener {
|
binding.searchByImage.setOnClickListener {
|
||||||
|
@ -190,7 +179,7 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
||||||
}
|
}
|
||||||
updateClearHistoryVisibility()
|
updateClearHistoryVisibility()
|
||||||
fun searchTitle() {
|
fun searchTitle() {
|
||||||
activity.result.apply {
|
activity.aniMangaResult.apply {
|
||||||
search =
|
search =
|
||||||
if (binding.searchBarText.text.toString() != "") binding.searchBarText.text.toString() else null
|
if (binding.searchBarText.text.toString() != "") binding.searchBarText.text.toString() else null
|
||||||
onList = listOnly
|
onList = listOnly
|
||||||
|
@ -292,67 +281,12 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
||||||
requestFocus = Runnable { binding.searchBarText.requestFocus() }
|
requestFocus = Runnable { binding.searchBarText.requestFocus() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setHistoryVisibility(visible: Boolean) {
|
|
||||||
if (visible) {
|
|
||||||
binding.searchResultLayout.startAnimation(fadeOutAnimation())
|
|
||||||
binding.searchHistoryList.startAnimation(fadeInAnimation())
|
|
||||||
binding.searchResultLayout.visibility = View.GONE
|
|
||||||
binding.searchHistoryList.visibility = View.VISIBLE
|
|
||||||
binding.searchByImage.visibility = View.VISIBLE
|
|
||||||
} else {
|
|
||||||
if (binding.searchResultLayout.visibility != View.VISIBLE) {
|
|
||||||
binding.searchResultLayout.startAnimation(fadeInAnimation())
|
|
||||||
binding.searchHistoryList.startAnimation(fadeOutAnimation())
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.searchResultLayout.visibility = View.VISIBLE
|
|
||||||
binding.clearHistory.visibility = View.GONE
|
|
||||||
binding.searchHistoryList.visibility = View.GONE
|
|
||||||
binding.searchByImage.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateClearHistoryVisibility() {
|
|
||||||
binding.clearHistory.visibility =
|
|
||||||
if (searchHistoryAdapter.itemCount > 0) View.VISIBLE else View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fadeInAnimation(): Animation {
|
|
||||||
return AlphaAnimation(0f, 1f).apply {
|
|
||||||
duration = 150
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fadeOutAnimation(): Animation {
|
|
||||||
return AlphaAnimation(1f, 0f).apply {
|
|
||||||
duration = 150
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun addHistory() {
|
|
||||||
if (::searchHistoryAdapter.isInitialized &&
|
|
||||||
binding.searchBarText.text.toString().isNotBlank()
|
|
||||||
)
|
|
||||||
searchHistoryAdapter.add(binding.searchBarText.text.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int = 1
|
|
||||||
|
|
||||||
inner class SearchHeaderViewHolder(val binding: ItemSearchHeaderBinding) :
|
|
||||||
RecyclerView.ViewHolder(binding.root)
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
|
||||||
return itemViewType
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SearchChipAdapter(
|
class SearchChipAdapter(
|
||||||
val activity: SearchActivity,
|
val activity: SearchActivity,
|
||||||
private val searchAdapter: SearchAdapter
|
private val searchAdapter: SearchAdapter
|
||||||
) :
|
) :
|
||||||
RecyclerView.Adapter<SearchChipAdapter.SearchChipViewHolder>() {
|
RecyclerView.Adapter<SearchChipAdapter.SearchChipViewHolder>() {
|
||||||
private var chips = activity.result.toChipList()
|
private var chips = activity.aniMangaResult.toChipList()
|
||||||
|
|
||||||
inner class SearchChipViewHolder(val binding: ItemChipBinding) :
|
inner class SearchChipViewHolder(val binding: ItemChipBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
@ -369,7 +303,7 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
||||||
holder.binding.root.apply {
|
holder.binding.root.apply {
|
||||||
text = chip.text.replace("_", " ")
|
text = chip.text.replace("_", " ")
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
activity.result.removeChip(chip)
|
activity.aniMangaResult.removeChip(chip)
|
||||||
update()
|
update()
|
||||||
activity.search()
|
activity.search()
|
||||||
searchAdapter.updateFilterTextViewDrawable()
|
searchAdapter.updateFilterTextViewDrawable()
|
||||||
|
@ -379,7 +313,7 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun update() {
|
fun update() {
|
||||||
chips = activity.result.toChipList()
|
chips = activity.aniMangaResult.toChipList()
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
searchAdapter.updateFilterTextViewDrawable()
|
searchAdapter.updateFilterTextViewDrawable()
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setSortByFilterImage() {
|
private fun setSortByFilterImage() {
|
||||||
val filterDrawable = when (activity.result.sort) {
|
val filterDrawable = when (activity.aniMangaResult.sort) {
|
||||||
Anilist.sortBy[0] -> R.drawable.ic_round_area_chart_24
|
Anilist.sortBy[0] -> R.drawable.ic_round_area_chart_24
|
||||||
Anilist.sortBy[1] -> R.drawable.ic_round_filter_peak_24
|
Anilist.sortBy[1] -> R.drawable.ic_round_filter_peak_24
|
||||||
Anilist.sortBy[2] -> R.drawable.ic_round_star_graph_24
|
Anilist.sortBy[2] -> R.drawable.ic_round_star_graph_24
|
||||||
|
@ -71,10 +71,10 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetSearchFilter() {
|
private fun resetSearchFilter() {
|
||||||
activity.result.sort = null
|
activity.aniMangaResult.sort = null
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_alt_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_alt_24)
|
||||||
startBounceZoomAnimation(binding.sortByFilter)
|
startBounceZoomAnimation(binding.sortByFilter)
|
||||||
activity.result.countryOfOrigin = null
|
activity.aniMangaResult.countryOfOrigin = null
|
||||||
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_search_googlefonts)
|
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_search_googlefonts)
|
||||||
startBounceZoomAnimation(binding.countryFilter)
|
startBounceZoomAnimation(binding.countryFilter)
|
||||||
|
|
||||||
|
@ -98,10 +98,10 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
|
|
||||||
activity = requireActivity() as SearchActivity
|
activity = requireActivity() as SearchActivity
|
||||||
|
|
||||||
selectedGenres = activity.result.genres ?: mutableListOf()
|
selectedGenres = activity.aniMangaResult.genres ?: mutableListOf()
|
||||||
exGenres = activity.result.excludedGenres ?: mutableListOf()
|
exGenres = activity.aniMangaResult.excludedGenres ?: mutableListOf()
|
||||||
selectedTags = activity.result.tags ?: mutableListOf()
|
selectedTags = activity.aniMangaResult.tags ?: mutableListOf()
|
||||||
exTags = activity.result.excludedTags ?: mutableListOf()
|
exTags = activity.aniMangaResult.excludedTags ?: mutableListOf()
|
||||||
setSortByFilterImage()
|
setSortByFilterImage()
|
||||||
|
|
||||||
binding.resetSearchFilter.setOnClickListener {
|
binding.resetSearchFilter.setOnClickListener {
|
||||||
|
@ -126,7 +126,7 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
resetSearchFilter()
|
resetSearchFilter()
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
activity.result.apply {
|
activity.aniMangaResult.apply {
|
||||||
status =
|
status =
|
||||||
binding.searchStatus.text.toString().replace(" ", "_").ifBlank { null }
|
binding.searchStatus.text.toString().replace(" ", "_").ifBlank { null }
|
||||||
source =
|
source =
|
||||||
|
@ -135,7 +135,7 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
season = binding.searchSeason.text.toString().ifBlank { null }
|
season = binding.searchSeason.text.toString().ifBlank { null }
|
||||||
startYear = binding.searchYear.text.toString().toIntOrNull()
|
startYear = binding.searchYear.text.toString().toIntOrNull()
|
||||||
seasonYear = binding.searchYear.text.toString().toIntOrNull()
|
seasonYear = binding.searchYear.text.toString().toIntOrNull()
|
||||||
sort = activity.result.sort
|
sort = activity.aniMangaResult.sort
|
||||||
genres = selectedGenres
|
genres = selectedGenres
|
||||||
tags = selectedTags
|
tags = selectedTags
|
||||||
excludedGenres = exGenres
|
excludedGenres = exGenres
|
||||||
|
@ -155,43 +155,43 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
popupMenu.setOnMenuItemClickListener { menuItem ->
|
popupMenu.setOnMenuItemClickListener { menuItem ->
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.sort_by_score -> {
|
R.id.sort_by_score -> {
|
||||||
activity.result.sort = Anilist.sortBy[0]
|
activity.aniMangaResult.sort = Anilist.sortBy[0]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_area_chart_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_area_chart_24)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_popular -> {
|
R.id.sort_by_popular -> {
|
||||||
activity.result.sort = Anilist.sortBy[1]
|
activity.aniMangaResult.sort = Anilist.sortBy[1]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_peak_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_peak_24)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_trending -> {
|
R.id.sort_by_trending -> {
|
||||||
activity.result.sort = Anilist.sortBy[2]
|
activity.aniMangaResult.sort = Anilist.sortBy[2]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_star_graph_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_star_graph_24)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_recent -> {
|
R.id.sort_by_recent -> {
|
||||||
activity.result.sort = Anilist.sortBy[3]
|
activity.aniMangaResult.sort = Anilist.sortBy[3]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_new_releases_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_new_releases_24)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_a_z -> {
|
R.id.sort_by_a_z -> {
|
||||||
activity.result.sort = Anilist.sortBy[4]
|
activity.aniMangaResult.sort = Anilist.sortBy[4]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_list_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_list_24)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_z_a -> {
|
R.id.sort_by_z_a -> {
|
||||||
activity.result.sort = Anilist.sortBy[5]
|
activity.aniMangaResult.sort = Anilist.sortBy[5]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_list_24_reverse)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_filter_list_24_reverse)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.sort_by_pure_pain -> {
|
R.id.sort_by_pure_pain -> {
|
||||||
activity.result.sort = Anilist.sortBy[6]
|
activity.aniMangaResult.sort = Anilist.sortBy[6]
|
||||||
binding.sortByFilter.setImageResource(R.drawable.ic_round_assist_walker_24)
|
binding.sortByFilter.setImageResource(R.drawable.ic_round_assist_walker_24)
|
||||||
startBounceZoomAnimation()
|
startBounceZoomAnimation()
|
||||||
}
|
}
|
||||||
|
@ -212,25 +212,25 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.country_china -> {
|
R.id.country_china -> {
|
||||||
activity.result.countryOfOrigin = "CN"
|
activity.aniMangaResult.countryOfOrigin = "CN"
|
||||||
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_china_googlefonts)
|
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_china_googlefonts)
|
||||||
startBounceZoomAnimation(binding.countryFilter)
|
startBounceZoomAnimation(binding.countryFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.country_south_korea -> {
|
R.id.country_south_korea -> {
|
||||||
activity.result.countryOfOrigin = "KR"
|
activity.aniMangaResult.countryOfOrigin = "KR"
|
||||||
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_south_korea_googlefonts)
|
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_south_korea_googlefonts)
|
||||||
startBounceZoomAnimation(binding.countryFilter)
|
startBounceZoomAnimation(binding.countryFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.country_japan -> {
|
R.id.country_japan -> {
|
||||||
activity.result.countryOfOrigin = "JP"
|
activity.aniMangaResult.countryOfOrigin = "JP"
|
||||||
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_japan_googlefonts)
|
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_japan_googlefonts)
|
||||||
startBounceZoomAnimation(binding.countryFilter)
|
startBounceZoomAnimation(binding.countryFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.country_taiwan -> {
|
R.id.country_taiwan -> {
|
||||||
activity.result.countryOfOrigin = "TW"
|
activity.aniMangaResult.countryOfOrigin = "TW"
|
||||||
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_taiwan_googlefonts)
|
binding.countryFilter.setImageResource(R.drawable.ic_round_globe_taiwan_googlefonts)
|
||||||
startBounceZoomAnimation(binding.countryFilter)
|
startBounceZoomAnimation(binding.countryFilter)
|
||||||
}
|
}
|
||||||
|
@ -241,18 +241,18 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.searchFilterApply.setOnClickListener {
|
binding.searchFilterApply.setOnClickListener {
|
||||||
activity.result.apply {
|
activity.aniMangaResult.apply {
|
||||||
status = binding.searchStatus.text.toString().replace(" ", "_").ifBlank { null }
|
status = binding.searchStatus.text.toString().replace(" ", "_").ifBlank { null }
|
||||||
source = binding.searchSource.text.toString().replace(" ", "_").ifBlank { null }
|
source = binding.searchSource.text.toString().replace(" ", "_").ifBlank { null }
|
||||||
format = binding.searchFormat.text.toString().ifBlank { null }
|
format = binding.searchFormat.text.toString().ifBlank { null }
|
||||||
season = binding.searchSeason.text.toString().ifBlank { null }
|
season = binding.searchSeason.text.toString().ifBlank { null }
|
||||||
if (activity.result.type == "ANIME") {
|
if (activity.aniMangaResult.type == "ANIME") {
|
||||||
seasonYear = binding.searchYear.text.toString().toIntOrNull()
|
seasonYear = binding.searchYear.text.toString().toIntOrNull()
|
||||||
} else {
|
} else {
|
||||||
startYear = binding.searchYear.text.toString().toIntOrNull()
|
startYear = binding.searchYear.text.toString().toIntOrNull()
|
||||||
}
|
}
|
||||||
sort = activity.result.sort
|
sort = activity.aniMangaResult.sort
|
||||||
countryOfOrigin = activity.result.countryOfOrigin
|
countryOfOrigin = activity.aniMangaResult.countryOfOrigin
|
||||||
genres = selectedGenres
|
genres = selectedGenres
|
||||||
tags = selectedTags
|
tags = selectedTags
|
||||||
excludedGenres = exGenres
|
excludedGenres = exGenres
|
||||||
|
@ -266,8 +266,8 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
val format =
|
val format =
|
||||||
if (activity.result.type == "ANIME") Anilist.animeStatus else Anilist.mangaStatus
|
if (activity.aniMangaResult.type == "ANIME") Anilist.animeStatus else Anilist.mangaStatus
|
||||||
binding.searchStatus.setText(activity.result.status?.replace("_", " "))
|
binding.searchStatus.setText(activity.aniMangaResult.status?.replace("_", " "))
|
||||||
binding.searchStatus.setAdapter(
|
binding.searchStatus.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
binding.root.context,
|
binding.root.context,
|
||||||
|
@ -276,7 +276,7 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.searchSource.setText(activity.result.source?.replace("_", " "))
|
binding.searchSource.setText(activity.aniMangaResult.source?.replace("_", " "))
|
||||||
binding.searchSource.setAdapter(
|
binding.searchSource.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
binding.root.context,
|
binding.root.context,
|
||||||
|
@ -285,19 +285,19 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.searchFormat.setText(activity.result.format)
|
binding.searchFormat.setText(activity.aniMangaResult.format)
|
||||||
binding.searchFormat.setAdapter(
|
binding.searchFormat.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
binding.root.context,
|
binding.root.context,
|
||||||
R.layout.item_dropdown,
|
R.layout.item_dropdown,
|
||||||
(if (activity.result.type == "ANIME") Anilist.animeFormats else Anilist.mangaFormats).toTypedArray()
|
(if (activity.aniMangaResult.type == "ANIME") Anilist.animeFormats else Anilist.mangaFormats).toTypedArray()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (activity.result.type == "ANIME") {
|
if (activity.aniMangaResult.type == "ANIME") {
|
||||||
binding.searchYear.setText(activity.result.seasonYear?.toString())
|
binding.searchYear.setText(activity.aniMangaResult.seasonYear?.toString())
|
||||||
} else {
|
} else {
|
||||||
binding.searchYear.setText(activity.result.startYear?.toString())
|
binding.searchYear.setText(activity.aniMangaResult.startYear?.toString())
|
||||||
}
|
}
|
||||||
binding.searchYear.setAdapter(
|
binding.searchYear.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
|
@ -308,9 +308,9 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (activity.result.type == "MANGA") binding.searchSeasonCont.visibility = GONE
|
if (activity.aniMangaResult.type == "MANGA") binding.searchSeasonCont.visibility = GONE
|
||||||
else {
|
else {
|
||||||
binding.searchSeason.setText(activity.result.season)
|
binding.searchSeason.setText(activity.aniMangaResult.season)
|
||||||
binding.searchSeason.setAdapter(
|
binding.searchSeason.setAdapter(
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
binding.root.context,
|
binding.root.context,
|
||||||
|
@ -346,7 +346,7 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
binding.searchGenresGrid.isChecked = false
|
binding.searchGenresGrid.isChecked = false
|
||||||
|
|
||||||
binding.searchFilterTags.adapter =
|
binding.searchFilterTags.adapter =
|
||||||
FilterChipAdapter(Anilist.tags?.get(activity.result.isAdult) ?: listOf()) { chip ->
|
FilterChipAdapter(Anilist.tags?.get(activity.aniMangaResult.isAdult) ?: listOf()) { chip ->
|
||||||
val tag = chip.text.toString()
|
val tag = chip.text.toString()
|
||||||
chip.isChecked = selectedTags.contains(tag)
|
chip.isChecked = selectedTags.contains(tag)
|
||||||
chip.isCloseIconVisible = exTags.contains(tag)
|
chip.isCloseIconVisible = exTags.contains(tag)
|
||||||
|
|
|
@ -7,52 +7,73 @@ import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType
|
||||||
import ani.dantotsu.databinding.ItemSearchHistoryBinding
|
import ani.dantotsu.databinding.ItemSearchHistoryBinding
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefManager.asLiveStringSet
|
import ani.dantotsu.settings.saving.PrefManager.asLiveClass
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.settings.saving.SharedPreferenceStringSetLiveData
|
import ani.dantotsu.settings.saving.SharedPreferenceClassLiveData
|
||||||
import java.util.Locale
|
import java.io.Serializable
|
||||||
|
|
||||||
class SearchHistoryAdapter(private val type: String, private val searchClicked: (String) -> Unit) :
|
data class SearchHistory(val search: String, val time: Long) : Serializable {
|
||||||
|
companion object {
|
||||||
|
private const val serialVersionUID = 1L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchHistoryAdapter(type: SearchType, private val searchClicked: (String) -> Unit) :
|
||||||
ListAdapter<String, SearchHistoryAdapter.SearchHistoryViewHolder>(
|
ListAdapter<String, SearchHistoryAdapter.SearchHistoryViewHolder>(
|
||||||
DIFF_CALLBACK_INSTALLED
|
DIFF_CALLBACK_INSTALLED
|
||||||
) {
|
) {
|
||||||
private var searchHistoryLiveData: SharedPreferenceStringSetLiveData? = null
|
private var searchHistoryLiveData: SharedPreferenceClassLiveData<List<SearchHistory>>? = null
|
||||||
private var searchHistory: MutableSet<String>? = null
|
private var searchHistory: MutableList<SearchHistory>? = null
|
||||||
private var historyType: PrefName = when (type.lowercase(Locale.ROOT)) {
|
private var historyType: PrefName = when (type) {
|
||||||
"anime" -> PrefName.AnimeSearchHistory
|
SearchType.ANIME -> PrefName.SortedAnimeSH
|
||||||
"manga" -> PrefName.MangaSearchHistory
|
SearchType.MANGA -> PrefName.SortedMangaSH
|
||||||
else -> throw IllegalArgumentException("Invalid type")
|
SearchType.CHARACTER -> PrefName.SortedCharacterSH
|
||||||
|
SearchType.STAFF -> PrefName.SortedStaffSH
|
||||||
|
SearchType.STUDIO -> PrefName.SortedStudioSH
|
||||||
|
SearchType.USER -> PrefName.SortedUserSH
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun MutableList<SearchHistory>?.sorted(): List<String>? =
|
||||||
|
this?.sortedByDescending { it.time }?.map { it.search }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
searchHistoryLiveData =
|
searchHistoryLiveData =
|
||||||
PrefManager.getLiveVal(historyType, mutableSetOf<String>()).asLiveStringSet()
|
PrefManager.getLiveVal(historyType, mutableListOf<SearchHistory>()).asLiveClass()
|
||||||
searchHistoryLiveData?.observeForever {
|
searchHistoryLiveData?.observeForever { data ->
|
||||||
searchHistory = it.toMutableSet()
|
searchHistory = data.toMutableList()
|
||||||
submitList(searchHistory?.toList())
|
submitList(searchHistory?.sorted())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(item: String) {
|
fun remove(item: String) {
|
||||||
searchHistory?.remove(item)
|
searchHistory?.let { list ->
|
||||||
|
list.removeAll { it.search == item }
|
||||||
|
}
|
||||||
PrefManager.setVal(historyType, searchHistory)
|
PrefManager.setVal(historyType, searchHistory)
|
||||||
submitList(searchHistory?.toList())
|
submitList(searchHistory?.sorted())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(item: String) {
|
fun add(item: String) {
|
||||||
if (searchHistory?.contains(item) == true || item.isBlank()) return
|
val maxSize = 25
|
||||||
|
if (searchHistory?.any { it.search == item } == true || item.isBlank()) return
|
||||||
if (PrefManager.getVal(PrefName.Incognito)) return
|
if (PrefManager.getVal(PrefName.Incognito)) return
|
||||||
searchHistory?.add(item)
|
searchHistory?.add(SearchHistory(item, System.currentTimeMillis()))
|
||||||
submitList(searchHistory?.toList())
|
if ((searchHistory?.size ?: 0) > maxSize) {
|
||||||
|
searchHistory?.removeAt(
|
||||||
|
searchHistory?.sorted()?.lastIndex ?: 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
submitList(searchHistory?.sorted())
|
||||||
PrefManager.setVal(historyType, searchHistory)
|
PrefManager.setVal(historyType, searchHistory)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearHistory() {
|
fun clearHistory() {
|
||||||
searchHistory?.clear()
|
searchHistory?.clear()
|
||||||
PrefManager.setVal(historyType, searchHistory)
|
PrefManager.setVal(historyType, searchHistory)
|
||||||
submitList(searchHistory?.toList())
|
submitList(searchHistory?.sorted())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
|
|
|
@ -5,5 +5,8 @@ import java.io.Serializable
|
||||||
data class Studio(
|
data class Studio(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val isFavourite: Boolean?,
|
||||||
|
val favourites: Int?,
|
||||||
|
val imageUrl: String?,
|
||||||
var yearMedia: MutableMap<String, ArrayList<Media>>? = null
|
var yearMedia: MutableMap<String, ArrayList<Media>>? = null
|
||||||
) : Serializable
|
) : Serializable
|
||||||
|
|
61
app/src/main/java/ani/dantotsu/media/StudioAdapter.kt
Normal file
61
app/src/main/java/ani/dantotsu/media/StudioAdapter.kt
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.util.Pair
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.copyToClipboard
|
||||||
|
import ani.dantotsu.databinding.ItemCharacterBinding
|
||||||
|
import ani.dantotsu.loadImage
|
||||||
|
import ani.dantotsu.setAnimation
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
class StudioAdapter(
|
||||||
|
private val studioList: MutableList<Studio>
|
||||||
|
) : RecyclerView.Adapter<StudioAdapter.StudioViewHolder>() {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StudioViewHolder {
|
||||||
|
val binding =
|
||||||
|
ItemCharacterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return StudioViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: StudioViewHolder, position: Int) {
|
||||||
|
val binding = holder.binding
|
||||||
|
setAnimation(binding.root.context, holder.binding.root)
|
||||||
|
val studio = studioList.getOrNull(position) ?: return
|
||||||
|
binding.itemCompactRelation.isVisible = false
|
||||||
|
binding.itemCompactImage.loadImage(studio.imageUrl)
|
||||||
|
binding.itemCompactTitle.text = studio.name
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = studioList.size
|
||||||
|
inner class StudioViewHolder(val binding: ItemCharacterBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
val studio = studioList[bindingAdapterPosition]
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
itemView.context,
|
||||||
|
Intent(
|
||||||
|
itemView.context,
|
||||||
|
StudioActivity::class.java
|
||||||
|
).putExtra("studio", studio as Serializable),
|
||||||
|
ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||||
|
itemView.context as Activity,
|
||||||
|
Pair.create(
|
||||||
|
binding.itemCompactImage,
|
||||||
|
ViewCompat.getTransitionName(binding.itemCompactImage)!!
|
||||||
|
),
|
||||||
|
).toBundle()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
itemView.setOnLongClickListener { copyToClipboard(studioList[bindingAdapterPosition].name ?: ""); true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
142
app/src/main/java/ani/dantotsu/media/SupportingSearchAdapter.kt
Normal file
142
app/src/main/java/ani/dantotsu/media/SupportingSearchAdapter.kt
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import ani.dantotsu.App.Companion.context
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType
|
||||||
|
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType.Companion.toAnilistString
|
||||||
|
import ani.dantotsu.connections.anilist.SearchResults
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class SupportingSearchAdapter(private val activity: SearchActivity, private val type: SearchType) :
|
||||||
|
HeaderInterface() {
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
override fun onBindViewHolder(holder: SearchHeaderViewHolder, position: Int) {
|
||||||
|
binding = holder.binding
|
||||||
|
|
||||||
|
searchHistoryAdapter = SearchHistoryAdapter(type) {
|
||||||
|
binding.searchBarText.setText(it)
|
||||||
|
}
|
||||||
|
binding.searchHistoryList.layoutManager = LinearLayoutManager(binding.root.context)
|
||||||
|
binding.searchHistoryList.adapter = searchHistoryAdapter
|
||||||
|
|
||||||
|
val imm: InputMethodManager =
|
||||||
|
activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
|
||||||
|
if (activity.searchType == SearchType.MANGA || activity.searchType == SearchType.ANIME) {
|
||||||
|
throw IllegalArgumentException("Invalid search type (wrong adapter)")
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.searchByImage.visibility = View.GONE
|
||||||
|
binding.searchResultGrid.visibility = View.GONE
|
||||||
|
binding.searchResultList.visibility = View.GONE
|
||||||
|
binding.searchFilter.visibility = View.GONE
|
||||||
|
binding.searchAdultCheck.visibility = View.GONE
|
||||||
|
binding.searchList.visibility = View.GONE
|
||||||
|
binding.searchChipRecycler.visibility = View.GONE
|
||||||
|
|
||||||
|
binding.searchBar.hint = activity.searchType.toAnilistString()
|
||||||
|
if (PrefManager.getVal(PrefName.Incognito)) {
|
||||||
|
val startIconDrawableRes = R.drawable.ic_incognito_24
|
||||||
|
val startIconDrawable: Drawable? =
|
||||||
|
context?.let { AppCompatResources.getDrawable(it, startIconDrawableRes) }
|
||||||
|
binding.searchBar.startIconDrawable = startIconDrawable
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.searchBarText.removeTextChangedListener(textWatcher)
|
||||||
|
when (type) {
|
||||||
|
SearchType.CHARACTER -> {
|
||||||
|
binding.searchBarText.setText(activity.characterResult.search)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STUDIO -> {
|
||||||
|
binding.searchBarText.setText(activity.studioResult.search)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.STAFF -> {
|
||||||
|
binding.searchBarText.setText(activity.staffResult.search)
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.USER -> {
|
||||||
|
binding.searchBarText.setText(activity.userResult.search)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw IllegalArgumentException("Invalid search type")
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.clearHistory.setOnClickListener {
|
||||||
|
it.startAnimation(fadeOutAnimation())
|
||||||
|
it.visibility = View.GONE
|
||||||
|
searchHistoryAdapter.clearHistory()
|
||||||
|
}
|
||||||
|
updateClearHistoryVisibility()
|
||||||
|
fun searchTitle() {
|
||||||
|
val searchText = binding.searchBarText.text.toString().takeIf { it.isNotEmpty() }
|
||||||
|
|
||||||
|
val result: SearchResults<*> = when (type) {
|
||||||
|
SearchType.CHARACTER -> activity.characterResult
|
||||||
|
SearchType.STUDIO -> activity.studioResult
|
||||||
|
SearchType.STAFF -> activity.staffResult
|
||||||
|
SearchType.USER -> activity.userResult
|
||||||
|
else -> throw IllegalArgumentException("Invalid search type")
|
||||||
|
}
|
||||||
|
|
||||||
|
result.search = searchText
|
||||||
|
activity.search()
|
||||||
|
}
|
||||||
|
|
||||||
|
textWatcher = object : TextWatcher {
|
||||||
|
override fun afterTextChanged(s: Editable) {}
|
||||||
|
|
||||||
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
|
if (s.toString().isBlank()) {
|
||||||
|
activity.emptyMediaAdapter()
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
delay(200)
|
||||||
|
activity.runOnUiThread {
|
||||||
|
setHistoryVisibility(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setHistoryVisibility(false)
|
||||||
|
searchTitle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.searchBarText.addTextChangedListener(textWatcher)
|
||||||
|
|
||||||
|
binding.searchBarText.setOnEditorActionListener { _, actionId, _ ->
|
||||||
|
return@setOnEditorActionListener when (actionId) {
|
||||||
|
EditorInfo.IME_ACTION_SEARCH -> {
|
||||||
|
searchTitle()
|
||||||
|
binding.searchBarText.clearFocus()
|
||||||
|
imm.hideSoftInputFromWindow(binding.searchBarText.windowToken, 0)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.searchBar.setEndIconOnClickListener { searchTitle() }
|
||||||
|
|
||||||
|
search = Runnable { searchTitle() }
|
||||||
|
requestFocus = Runnable { binding.searchBarText.requestFocus() }
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import java.io.Serializable
|
||||||
|
|
||||||
data class Manga(
|
data class Manga(
|
||||||
var totalChapters: Int? = null,
|
var totalChapters: Int? = null,
|
||||||
var selectedChapter: String? = null,
|
var selectedChapter: MangaChapter? = null,
|
||||||
var chapters: MutableMap<String, MangaChapter>? = null,
|
var chapters: MutableMap<String, MangaChapter>? = null,
|
||||||
var slug: String? = null,
|
var slug: String? = null,
|
||||||
var author: Author? = null,
|
var author: Author? = null,
|
||||||
|
|
|
@ -40,4 +40,6 @@ data class MangaChapter(
|
||||||
private val dualPages = mutableListOf<Pair<MangaImage, MangaImage?>>()
|
private val dualPages = mutableListOf<Pair<MangaImage, MangaImage?>>()
|
||||||
fun dualPages(): List<Pair<MangaImage, MangaImage?>> = dualPages
|
fun dualPages(): List<Pair<MangaImage, MangaImage?>> = dualPages
|
||||||
|
|
||||||
|
fun uniqueNumber(): String = "${number}-${scanlator ?: "Unknown"}"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ class MangaChapterAdapter(
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
||||||
fragment.onMangaChapterClick(arr[bindingAdapterPosition].number)
|
fragment.onMangaChapterClick(arr[bindingAdapterPosition])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ class MangaChapterAdapter(
|
||||||
fun startDownload(chapterNumber: String) {
|
fun startDownload(chapterNumber: String) {
|
||||||
activeDownloads.add(chapterNumber)
|
activeDownloads.add(chapterNumber)
|
||||||
// Find the position of the chapter and notify only that item
|
// Find the position of the chapter and notify only that item
|
||||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
val position = arr.indexOfFirst { it.uniqueNumber() == chapterNumber }
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
|
@ -84,17 +84,17 @@ class MangaChapterAdapter(
|
||||||
activeDownloads.remove(chapterNumber)
|
activeDownloads.remove(chapterNumber)
|
||||||
downloadedChapters.add(chapterNumber)
|
downloadedChapters.add(chapterNumber)
|
||||||
// Find the position of the chapter and notify only that item
|
// Find the position of the chapter and notify only that item
|
||||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
val position = arr.indexOfFirst { it.uniqueNumber() == chapterNumber }
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
arr[position].progress = "Downloaded"
|
arr[position].progress = "Downloaded"
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteDownload(chapterNumber: String) {
|
fun deleteDownload(chapterNumber: MangaChapter) {
|
||||||
downloadedChapters.remove(chapterNumber)
|
downloadedChapters.remove(chapterNumber.uniqueNumber())
|
||||||
// Find the position of the chapter and notify only that item
|
// Find the position of the chapter and notify only that item
|
||||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
val position = arr.indexOfFirst { it.uniqueNumber() == chapterNumber.uniqueNumber() }
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
arr[position].progress = ""
|
arr[position].progress = ""
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
|
@ -105,7 +105,7 @@ class MangaChapterAdapter(
|
||||||
activeDownloads.remove(chapterNumber)
|
activeDownloads.remove(chapterNumber)
|
||||||
downloadedChapters.remove(chapterNumber)
|
downloadedChapters.remove(chapterNumber)
|
||||||
// Find the position of the chapter and notify only that item
|
// Find the position of the chapter and notify only that item
|
||||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
val position = arr.indexOfFirst { it.uniqueNumber() == chapterNumber }
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
arr[position].progress = ""
|
arr[position].progress = ""
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
|
@ -114,7 +114,7 @@ class MangaChapterAdapter(
|
||||||
|
|
||||||
fun updateDownloadProgress(chapterNumber: String, progress: Int) {
|
fun updateDownloadProgress(chapterNumber: String, progress: Int) {
|
||||||
// Find the position of the chapter and notify only that item
|
// Find the position of the chapter and notify only that item
|
||||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
val position = arr.indexOfFirst { it.uniqueNumber() == chapterNumber }
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
arr[position].progress = "Downloading: ${progress}%"
|
arr[position].progress = "Downloading: ${progress}%"
|
||||||
|
|
||||||
|
@ -127,7 +127,8 @@ class MangaChapterAdapter(
|
||||||
if (position < 0 || position >= arr.size) return
|
if (position < 0 || position >= arr.size) return
|
||||||
for (i in 0..<n) {
|
for (i in 0..<n) {
|
||||||
if (position + i < arr.size) {
|
if (position + i < arr.size) {
|
||||||
val chapterNumber = arr[position + i].number
|
val chapter = arr[position + i]
|
||||||
|
val chapterNumber = chapter.uniqueNumber()
|
||||||
if (activeDownloads.contains(chapterNumber)) {
|
if (activeDownloads.contains(chapterNumber)) {
|
||||||
//do nothing
|
//do nothing
|
||||||
continue
|
continue
|
||||||
|
@ -135,8 +136,8 @@ class MangaChapterAdapter(
|
||||||
//do nothing
|
//do nothing
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
fragment.onMangaChapterDownloadClick(chapterNumber)
|
fragment.onMangaChapterDownloadClick(chapter)
|
||||||
startDownload(chapterNumber)
|
startDownload(chapter.uniqueNumber())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,28 +202,29 @@ class MangaChapterAdapter(
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
||||||
fragment.onMangaChapterClick(arr[bindingAdapterPosition].number)
|
fragment.onMangaChapterClick(arr[bindingAdapterPosition])
|
||||||
}
|
}
|
||||||
binding.itemDownload.setOnClickListener {
|
binding.itemDownload.setOnClickListener {
|
||||||
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size) {
|
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size) {
|
||||||
val chapterNumber = arr[bindingAdapterPosition].number
|
val chapter = arr[bindingAdapterPosition]
|
||||||
|
val chapterNumber = chapter.uniqueNumber()
|
||||||
if (activeDownloads.contains(chapterNumber)) {
|
if (activeDownloads.contains(chapterNumber)) {
|
||||||
fragment.onMangaChapterStopDownloadClick(chapterNumber)
|
fragment.onMangaChapterStopDownloadClick(chapter)
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
} else if (downloadedChapters.contains(chapterNumber)) {
|
} else if (downloadedChapters.contains(chapterNumber)) {
|
||||||
it.context.customAlertDialog().apply {
|
it.context.customAlertDialog().apply {
|
||||||
setTitle("Delete Chapter")
|
setTitle("Delete Chapter")
|
||||||
setMessage("Are you sure you want to delete ${chapterNumber}?")
|
setMessage("Are you sure you want to delete ${chapterNumber}?")
|
||||||
setPosButton(R.string.delete) {
|
setPosButton(R.string.delete) {
|
||||||
fragment.onMangaChapterRemoveDownloadClick(chapterNumber)
|
fragment.onMangaChapterRemoveDownloadClick(chapter)
|
||||||
}
|
}
|
||||||
setNegButton(R.string.cancel)
|
setNegButton(R.string.cancel)
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
} else {
|
} else {
|
||||||
fragment.onMangaChapterDownloadClick(chapterNumber)
|
fragment.onMangaChapterDownloadClick(chapter)
|
||||||
startDownload(chapterNumber)
|
startDownload(chapter.uniqueNumber())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,7 +279,7 @@ class MangaChapterAdapter(
|
||||||
is ChapterListViewHolder -> {
|
is ChapterListViewHolder -> {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
val ep = arr[position]
|
val ep = arr[position]
|
||||||
holder.bind(ep.number, ep.progress)
|
holder.bind(ep.uniqueNumber(), ep.progress)
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root)
|
setAnimation(fragment.requireContext(), holder.binding.root)
|
||||||
binding.itemChapterNumber.text = ep.number
|
binding.itemChapterNumber.text = ep.number
|
||||||
|
|
||||||
|
|
|
@ -422,12 +422,12 @@ class MangaReadAdapter(
|
||||||
val startChapter = MediaNameAdapter.findChapterNumber(names[limit * (position)])
|
val startChapter = MediaNameAdapter.findChapterNumber(names[limit * (position)])
|
||||||
val endChapter = MediaNameAdapter.findChapterNumber(names[last - 1])
|
val endChapter = MediaNameAdapter.findChapterNumber(names[last - 1])
|
||||||
val startChapterString = if (startChapter != null) {
|
val startChapterString = if (startChapter != null) {
|
||||||
"Ch.$startChapter"
|
"Ch.%.1f".format(startChapter)
|
||||||
} else {
|
} else {
|
||||||
names[limit * (position)]
|
names[limit * (position)]
|
||||||
}
|
}
|
||||||
val endChapterString = if (endChapter != null) {
|
val endChapterString = if (endChapter != null) {
|
||||||
"Ch.$endChapter"
|
"Ch.%.1f".format(endChapter)
|
||||||
} else {
|
} else {
|
||||||
names[last - 1]
|
names[last - 1]
|
||||||
}
|
}
|
||||||
|
@ -472,7 +472,6 @@ class MangaReadAdapter(
|
||||||
val binding = _binding
|
val binding = _binding
|
||||||
if (binding != null) {
|
if (binding != null) {
|
||||||
if (media.manga?.chapters != null) {
|
if (media.manga?.chapters != null) {
|
||||||
val chapters = media.manga.chapters!!.keys.toTypedArray()
|
|
||||||
val anilistEp = (media.userProgress ?: 0).plus(1)
|
val anilistEp = (media.userProgress ?: 0).plus(1)
|
||||||
val appEp = PrefManager.getNullableCustomVal(
|
val appEp = PrefManager.getNullableCustomVal(
|
||||||
"${media.id}_current_chp",
|
"${media.id}_current_chp",
|
||||||
|
@ -480,37 +479,39 @@ class MangaReadAdapter(
|
||||||
String::class.java
|
String::class.java
|
||||||
)
|
)
|
||||||
?.toIntOrNull() ?: 1
|
?.toIntOrNull() ?: 1
|
||||||
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
|
val continueNumber = (if (anilistEp > appEp) anilistEp else appEp).toString()
|
||||||
val filteredChapters = chapters.filter { chapterKey ->
|
val filteredChapters = media.manga.chapters!!.filter { chapter ->
|
||||||
val chapter = media.manga.chapters!![chapterKey]!!
|
if (mangaReadSources[media.selected!!.sourceIndex] is OfflineMangaParser) {
|
||||||
chapter.scanlator !in hiddenScanlators
|
true
|
||||||
|
} else {
|
||||||
|
chapter.value.scanlator !in hiddenScanlators
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val formattedChapters = filteredChapters.map {
|
val formattedChapters = filteredChapters.map {
|
||||||
MediaNameAdapter.findChapterNumber(it)?.toInt()?.toString()
|
MediaNameAdapter.findChapterNumber(it.value.number)?.toInt()?.toString() to it.key
|
||||||
}
|
}
|
||||||
if (formattedChapters.contains(continueEp)) {
|
if (formattedChapters.any { it.first == continueNumber }) {
|
||||||
continueEp = chapters[formattedChapters.indexOf(continueEp)]
|
var continueEp = media.manga.chapters!![formattedChapters.first { it.first == continueNumber }.second]
|
||||||
binding.sourceContinue.visibility = View.VISIBLE
|
binding.sourceContinue.visibility = View.VISIBLE
|
||||||
handleProgress(
|
handleProgress(
|
||||||
binding.itemMediaProgressCont,
|
binding.itemMediaProgressCont,
|
||||||
binding.itemMediaProgress,
|
binding.itemMediaProgress,
|
||||||
binding.itemMediaProgressEmpty,
|
binding.itemMediaProgressEmpty,
|
||||||
media.id,
|
media.id,
|
||||||
continueEp
|
continueEp!!.number
|
||||||
)
|
)
|
||||||
if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight > 0.8f) {
|
if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight > 0.8f) {
|
||||||
val e = chapters.indexOf(continueEp)
|
val numberPlusOne = formattedChapters.indexOfFirst { it.first?.toIntOrNull() == continueNumber.toInt() + 1 }
|
||||||
if (e != -1 && e + 1 < chapters.size) {
|
if (numberPlusOne != -1) {
|
||||||
continueEp = chapters[e + 1]
|
continueEp = media.manga.chapters!![formattedChapters[numberPlusOne].second]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val ep = media.manga.chapters!![continueEp]!!
|
|
||||||
binding.itemMediaImage.loadImage(media.banner ?: media.cover)
|
binding.itemMediaImage.loadImage(media.banner ?: media.cover)
|
||||||
binding.mediaSourceContinueText.text =
|
binding.mediaSourceContinueText.text =
|
||||||
currActivity()!!.getString(
|
currActivity()!!.getString(
|
||||||
R.string.continue_chapter,
|
R.string.continue_chapter,
|
||||||
ep.number,
|
continueEp!!.number,
|
||||||
if (!ep.title.isNullOrEmpty()) ep.title else ""
|
if (!continueEp.title.isNullOrEmpty()) continueEp.title else ""
|
||||||
)
|
)
|
||||||
binding.sourceContinue.setOnClickListener {
|
binding.sourceContinue.setOnClickListener {
|
||||||
fragment.onMangaChapterClick(continueEp)
|
fragment.onMangaChapterClick(continueEp)
|
||||||
|
|
|
@ -52,6 +52,7 @@ import ani.dantotsu.parsers.DynamicMangaParser
|
||||||
import ani.dantotsu.parsers.HMangaSources
|
import ani.dantotsu.parsers.HMangaSources
|
||||||
import ani.dantotsu.parsers.MangaParser
|
import ani.dantotsu.parsers.MangaParser
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
|
import ani.dantotsu.parsers.OfflineMangaParser
|
||||||
import ani.dantotsu.setNavigationTheme
|
import ani.dantotsu.setNavigationTheme
|
||||||
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
|
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
@ -195,7 +196,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
|
|
||||||
for (download in downloadManager.mangaDownloadedTypes) {
|
for (download in downloadManager.mangaDownloadedTypes) {
|
||||||
if (media.compareName(download.titleName)) {
|
if (media.compareName(download.titleName)) {
|
||||||
chapterAdapter.stopDownload(download.chapterName)
|
chapterAdapter.stopDownload(download.uniqueName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +250,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
|
|
||||||
|
|
||||||
for (chapter in chaptersToDownload) {
|
for (chapter in chaptersToDownload) {
|
||||||
onMangaChapterDownloadClick(chapter.title!!)
|
onMangaChapterDownloadClick(chapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,9 +261,13 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
val chapters = loadedChapters[media.selected!!.sourceIndex]
|
val chapters = loadedChapters[media.selected!!.sourceIndex]
|
||||||
if (chapters != null) {
|
if (chapters != null) {
|
||||||
headerAdapter.options = getScanlators(chapters)
|
headerAdapter.options = getScanlators(chapters)
|
||||||
val filteredChapters = chapters.filterNot { (_, chapter) ->
|
val filteredChapters = if (model.mangaReadSources?.get(media.selected!!.sourceIndex) is OfflineMangaParser) {
|
||||||
|
chapters
|
||||||
|
} else {
|
||||||
|
chapters.filterNot { (_, chapter) ->
|
||||||
chapter.scanlator in headerAdapter.hiddenScanlators
|
chapter.scanlator in headerAdapter.hiddenScanlators
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
media.manga?.chapters = filteredChapters.toMutableMap()
|
media.manga?.chapters = filteredChapters.toMutableMap()
|
||||||
|
|
||||||
|
@ -430,9 +435,9 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onMangaChapterClick(i: String) {
|
fun onMangaChapterClick(i: MangaChapter) {
|
||||||
model.continueMedia = false
|
model.continueMedia = false
|
||||||
media.manga?.chapters?.get(i)?.let {
|
media.manga?.chapters?.get(i.uniqueNumber())?.let {
|
||||||
media.manga?.selectedChapter = i
|
media.manga?.selectedChapter = i
|
||||||
model.saveSelected(media.id, media.selected!!)
|
model.saveSelected(media.id, media.selected!!)
|
||||||
ChapterLoaderDialog.newInstance(it, true)
|
ChapterLoaderDialog.newInstance(it, true)
|
||||||
|
@ -440,7 +445,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onMangaChapterDownloadClick(i: String) {
|
fun onMangaChapterDownloadClick(i: MangaChapter) {
|
||||||
activity?.let {
|
activity?.let {
|
||||||
if (!isNotificationPermissionGranted()) {
|
if (!isNotificationPermissionGranted()) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
@ -453,7 +458,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
}
|
}
|
||||||
fun continueDownload() {
|
fun continueDownload() {
|
||||||
model.continueMedia = false
|
model.continueMedia = false
|
||||||
media.manga?.chapters?.get(i)?.let { chapter ->
|
media.manga?.chapters?.get(i.uniqueNumber())?.let { chapter ->
|
||||||
val parser =
|
val parser =
|
||||||
model.mangaReadSources?.get(media.selected!!.sourceIndex) as? DynamicMangaParser
|
model.mangaReadSources?.get(media.selected!!.sourceIndex) as? DynamicMangaParser
|
||||||
parser?.let {
|
parser?.let {
|
||||||
|
@ -464,6 +469,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
val downloadTask = MangaDownloaderService.DownloadTask(
|
val downloadTask = MangaDownloaderService.DownloadTask(
|
||||||
title = media.mainName(),
|
title = media.mainName(),
|
||||||
chapter = chapter.title!!,
|
chapter = chapter.title!!,
|
||||||
|
scanlator = chapter.scanlator ?: "Unknown",
|
||||||
imageData = images,
|
imageData = images,
|
||||||
sourceMedia = media,
|
sourceMedia = media,
|
||||||
retries = 2,
|
retries = 2,
|
||||||
|
@ -483,7 +489,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
|
|
||||||
// Inform the adapter that the download has started
|
// Inform the adapter that the download has started
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
chapterAdapter.startDownload(i)
|
chapterAdapter.startDownload(i.uniqueNumber())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -514,11 +520,11 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun onMangaChapterRemoveDownloadClick(i: String) {
|
fun onMangaChapterRemoveDownloadClick(i: MangaChapter) {
|
||||||
downloadManager.removeDownload(
|
downloadManager.removeDownload(
|
||||||
DownloadedType(
|
DownloadedType(
|
||||||
media.mainName(),
|
media.mainName(),
|
||||||
i,
|
i.number,
|
||||||
MediaType.MANGA
|
MediaType.MANGA
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
@ -526,7 +532,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onMangaChapterStopDownloadClick(i: String) {
|
fun onMangaChapterStopDownloadClick(i: MangaChapter) {
|
||||||
val cancelIntent = Intent().apply {
|
val cancelIntent = Intent().apply {
|
||||||
action = MangaDownloaderService.ACTION_CANCEL_DOWNLOAD
|
action = MangaDownloaderService.ACTION_CANCEL_DOWNLOAD
|
||||||
putExtra(MangaDownloaderService.EXTRA_CHAPTER, i)
|
putExtra(MangaDownloaderService.EXTRA_CHAPTER, i)
|
||||||
|
@ -537,11 +543,11 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
downloadManager.removeDownload(
|
downloadManager.removeDownload(
|
||||||
DownloadedType(
|
DownloadedType(
|
||||||
media.mainName(),
|
media.mainName(),
|
||||||
i,
|
i.number,
|
||||||
MediaType.MANGA
|
MediaType.MANGA
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
chapterAdapter.purgeDownload(i)
|
chapterAdapter.purgeDownload(i.uniqueNumber())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -584,7 +590,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
|
|
||||||
// Find latest chapter for subscription
|
// Find latest chapter for subscription
|
||||||
selected.latest =
|
selected.latest =
|
||||||
media.manga?.chapters?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f
|
media.manga?.chapters?.values?.maxOfOrNull { MediaNameAdapter.findChapterNumber(it.number) ?: 0f } ?: 0f
|
||||||
selected.latest =
|
selected.latest =
|
||||||
media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest
|
media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
||||||
finish()
|
finish()
|
||||||
return@addCallback
|
return@addCallback
|
||||||
}
|
}
|
||||||
val chapter = (MediaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
|
val chapter = (MediaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!.number)
|
||||||
?.minus(1L) ?: 0).toString()
|
?.minus(1L) ?: 0).toString()
|
||||||
if (chapter == "0.0" && PrefManager.getVal(PrefName.ChapterZeroReader)
|
if (chapter == "0.0" && PrefManager.getVal(PrefName.ChapterZeroReader)
|
||||||
// Not asking individually or incognito
|
// Not asking individually or incognito
|
||||||
|
@ -279,7 +279,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
||||||
defaultSettings = loadReaderSettings("${media.id}_current_settings") ?: defaultSettings
|
defaultSettings = loadReaderSettings("${media.id}_current_settings") ?: defaultSettings
|
||||||
|
|
||||||
chapters = media.manga?.chapters ?: return
|
chapters = media.manga?.chapters ?: return
|
||||||
chapter = chapters[media.manga!!.selectedChapter] ?: return
|
chapter = chapters[media.manga!!.selectedChapter!!.uniqueNumber()] ?: return
|
||||||
|
|
||||||
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
|
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
|
||||||
binding.mangaReaderSource.isVisible = PrefManager.getVal(PrefName.ShowSource)
|
binding.mangaReaderSource.isVisible = PrefManager.getVal(PrefName.ShowSource)
|
||||||
|
@ -309,7 +309,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
||||||
binding.mangaReaderTitle.text = media.userPreferredName
|
binding.mangaReaderTitle.text = media.userPreferredName
|
||||||
|
|
||||||
chaptersArr = chapters.keys.toList()
|
chaptersArr = chapters.keys.toList()
|
||||||
currentChapterIndex = chaptersArr.indexOf(media.manga!!.selectedChapter)
|
currentChapterIndex = chaptersArr.indexOf(media.manga!!.selectedChapter!!.uniqueNumber())
|
||||||
|
|
||||||
chaptersTitleArr = arrayListOf()
|
chaptersTitleArr = arrayListOf()
|
||||||
chapters.forEach {
|
chapters.forEach {
|
||||||
|
@ -394,10 +394,10 @@ class MangaReaderActivity : AppCompatActivity() {
|
||||||
model.getMangaChapter().observe(this) { chap ->
|
model.getMangaChapter().observe(this) { chap ->
|
||||||
if (chap != null) {
|
if (chap != null) {
|
||||||
chapter = chap
|
chapter = chap
|
||||||
media.manga!!.selectedChapter = chapter.number
|
media.manga!!.selectedChapter = chapter
|
||||||
media.selected = model.loadSelected(media)
|
media.selected = model.loadSelected(media)
|
||||||
PrefManager.setCustomVal("${media.id}_current_chp", chap.number)
|
PrefManager.setCustomVal("${media.id}_current_chp", chap.number)
|
||||||
currentChapterIndex = chaptersArr.indexOf(chap.number)
|
currentChapterIndex = chaptersArr.indexOf(chap.uniqueNumber())
|
||||||
binding.mangaReaderChapterSelect.setSelection(currentChapterIndex)
|
binding.mangaReaderChapterSelect.setSelection(currentChapterIndex)
|
||||||
if (directionRLBT) {
|
if (directionRLBT) {
|
||||||
binding.mangaReaderNextChap.text =
|
binding.mangaReaderNextChap.text =
|
||||||
|
@ -1036,7 +1036,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
||||||
PrefManager.setCustomVal("${media.id}_save_progress", true)
|
PrefManager.setCustomVal("${media.id}_save_progress", true)
|
||||||
updateProgress(
|
updateProgress(
|
||||||
media,
|
media,
|
||||||
MediaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
|
MediaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!.number)
|
||||||
.toString()
|
.toString()
|
||||||
)
|
)
|
||||||
runnable.run()
|
runnable.run()
|
||||||
|
@ -1057,7 +1057,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
updateProgress(
|
updateProgress(
|
||||||
media,
|
media,
|
||||||
MediaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
|
MediaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!.number)
|
||||||
.toString()
|
.toString()
|
||||||
)
|
)
|
||||||
runnable.run()
|
runnable.run()
|
||||||
|
|
|
@ -499,7 +499,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
||||||
sChapter.url,
|
sChapter.url,
|
||||||
sChapter.name,
|
sChapter.name,
|
||||||
null,
|
null,
|
||||||
sChapter.scanlator,
|
sChapter.scanlator ?: "Unknown",
|
||||||
sChapter,
|
sChapter,
|
||||||
sChapter.date_upload
|
sChapter.date_upload
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
package ani.dantotsu.parsers
|
package ani.dantotsu.parsers
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
|
|
@ -90,7 +90,7 @@ abstract class MangaReadSources : BaseSources() {
|
||||||
show.sManga?.let { sManga ->
|
show.sManga?.let { sManga ->
|
||||||
tryWithSuspend(true) {
|
tryWithSuspend(true) {
|
||||||
parser.loadChapters(show.link, show.extra, sManga).forEach {
|
parser.loadChapters(show.link, show.extra, sManga).forEach {
|
||||||
map[it.number] = MangaChapter(it)
|
map["${it.number}-${it.scanlator}"] = MangaChapter(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ abstract class MangaReadSources : BaseSources() {
|
||||||
tryWithSuspend(true) {
|
tryWithSuspend(true) {
|
||||||
// Since we've checked, we can safely cast parser to OfflineMangaParser and call its methods
|
// Since we've checked, we can safely cast parser to OfflineMangaParser and call its methods
|
||||||
parser.loadChapters(show.link, show.extra, SManga.create()).forEach {
|
parser.loadChapters(show.link, show.extra, SManga.create()).forEach {
|
||||||
map[it.number] = MangaChapter(it)
|
map["${it.number}-${it.scanlator}"] = MangaChapter(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -79,7 +79,7 @@ data class MangaChapter(
|
||||||
//Self-Descriptive
|
//Self-Descriptive
|
||||||
val title: String? = null,
|
val title: String? = null,
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
val scanlator: String? = null,
|
val scanlator: String,
|
||||||
val sChapter: SChapter,
|
val sChapter: SChapter,
|
||||||
val date: Long? = null,
|
val date: Long? = null,
|
||||||
)
|
)
|
||||||
|
|
|
@ -31,13 +31,17 @@ class OfflineMangaParser : MangaParser() {
|
||||||
val chapters = mutableListOf<MangaChapter>()
|
val chapters = mutableListOf<MangaChapter>()
|
||||||
if (directory?.exists() == true) {
|
if (directory?.exists() == true) {
|
||||||
directory.listFiles().forEach {
|
directory.listFiles().forEach {
|
||||||
|
val scanlator = downloadManager.mangaDownloadedTypes.find { items ->
|
||||||
|
items.titleName == mangaLink &&
|
||||||
|
items.chapterName == it.name
|
||||||
|
}?.scanlator ?: "Unknown"
|
||||||
if (it.isDirectory) {
|
if (it.isDirectory) {
|
||||||
val chapter = MangaChapter(
|
val chapter = MangaChapter(
|
||||||
it.name!!,
|
it.name!!,
|
||||||
"$mangaLink/${it.name}",
|
"$mangaLink/${it.name}",
|
||||||
it.name,
|
it.name,
|
||||||
null,
|
null,
|
||||||
null,
|
scanlator,
|
||||||
SChapter.create()
|
SChapter.create()
|
||||||
)
|
)
|
||||||
chapters.add(chapter)
|
chapters.add(chapter)
|
||||||
|
@ -45,8 +49,7 @@ class OfflineMangaParser : MangaParser() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chapters.addAll(loadChaptersCompat(mangaLink, extra, sManga))
|
chapters.addAll(loadChaptersCompat(mangaLink, extra, sManga))
|
||||||
return chapters.distinctBy { it.number }
|
return chapters.sortedBy { MediaNameAdapter.findChapterNumber(it.number) }
|
||||||
.sortedBy { MediaNameAdapter.findChapterNumber(it.number) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> {
|
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> {
|
||||||
|
|
|
@ -26,6 +26,7 @@ sealed class NovelExtension {
|
||||||
override val pkgName: String,
|
override val pkgName: String,
|
||||||
override val versionName: String,
|
override val versionName: String,
|
||||||
override val versionCode: Long,
|
override val versionCode: Long,
|
||||||
|
var repository: String,
|
||||||
val sources: List<AvailableNovelSources>,
|
val sources: List<AvailableNovelSources>,
|
||||||
val iconUrl: String,
|
val iconUrl: String,
|
||||||
) : NovelExtension()
|
) : NovelExtension()
|
||||||
|
|
|
@ -1,186 +0,0 @@
|
||||||
package ani.dantotsu.parsers.novel
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
|
||||||
import ani.dantotsu.util.Logger
|
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier
|
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
|
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.util.Date
|
|
||||||
import kotlin.time.Duration.Companion.days
|
|
||||||
|
|
||||||
class NovelExtensionGithubApi {
|
|
||||||
|
|
||||||
private val networkService: NetworkHelper by injectLazy()
|
|
||||||
private val novelExtensionManager: NovelExtensionManager by injectLazy()
|
|
||||||
private val json: Json by injectLazy()
|
|
||||||
|
|
||||||
private val lastExtCheck: Long = PrefManager.getVal(PrefName.NovelLastExtCheck)
|
|
||||||
|
|
||||||
private var requiresFallbackSource = false
|
|
||||||
|
|
||||||
suspend fun findExtensions(): List<NovelExtension.Available> {
|
|
||||||
return withIOContext {
|
|
||||||
val githubResponse = if (requiresFallbackSource) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
networkService.client
|
|
||||||
.newCall(GET("${REPO_URL_PREFIX}index.min.json"))
|
|
||||||
.awaitSuccess()
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Logger.log("Failed to get extensions from GitHub")
|
|
||||||
requiresFallbackSource = true
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val response = githubResponse ?: run {
|
|
||||||
Logger.log("using fallback source")
|
|
||||||
networkService.client
|
|
||||||
.newCall(GET("${FALLBACK_REPO_URL_PREFIX}index.min.json"))
|
|
||||||
.awaitSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.log("response: $response")
|
|
||||||
|
|
||||||
val extensions = with(json) {
|
|
||||||
response
|
|
||||||
.parseAs<List<NovelExtensionJsonObject>>()
|
|
||||||
.toExtensions()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanity check - a small number of extensions probably means something broke
|
|
||||||
// with the repo generator
|
|
||||||
/*if (extensions.size < 10) { //TODO: uncomment when more extensions are added
|
|
||||||
throw Exception()
|
|
||||||
}*/
|
|
||||||
Logger.log("extensions: $extensions")
|
|
||||||
extensions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun checkForUpdates(
|
|
||||||
context: Context,
|
|
||||||
fromAvailableExtensionList: Boolean = false
|
|
||||||
): List<AnimeExtension.Installed>? {
|
|
||||||
// Limit checks to once a day at most
|
|
||||||
if (fromAvailableExtensionList && Date().time < lastExtCheck + 1.days.inWholeMilliseconds) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val extensions = if (fromAvailableExtensionList) {
|
|
||||||
novelExtensionManager.availableExtensionsFlow.value
|
|
||||||
} else {
|
|
||||||
findExtensions().also {
|
|
||||||
PrefManager.setVal(PrefName.NovelLastExtCheck, Date().time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val installedExtensions = ExtensionLoader.loadNovelExtensions(context)
|
|
||||||
.filterIsInstance<AnimeLoadResult.Success>()
|
|
||||||
.map { it.extension }
|
|
||||||
|
|
||||||
val extensionsWithUpdate = mutableListOf<AnimeExtension.Installed>()
|
|
||||||
for (installedExt in installedExtensions) {
|
|
||||||
val pkgName = installedExt.pkgName
|
|
||||||
val availableExt = extensions.find { it.pkgName == pkgName } ?: continue
|
|
||||||
|
|
||||||
val hasUpdatedVer = availableExt.versionCode > installedExt.versionCode
|
|
||||||
val hasUpdate = installedExt.isUnofficial.not() && (hasUpdatedVer)
|
|
||||||
if (hasUpdate) {
|
|
||||||
extensionsWithUpdate.add(installedExt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extensionsWithUpdate.isNotEmpty()) {
|
|
||||||
ExtensionUpdateNotifier(context).promptUpdates(extensionsWithUpdate.map { it.name })
|
|
||||||
}
|
|
||||||
|
|
||||||
return extensionsWithUpdate
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun List<NovelExtensionJsonObject>.toExtensions(): List<NovelExtension.Available> {
|
|
||||||
return mapNotNull { extension ->
|
|
||||||
val sources = extension.sources?.map { source ->
|
|
||||||
NovelExtensionSourceJsonObject(
|
|
||||||
source.id,
|
|
||||||
source.lang,
|
|
||||||
source.name,
|
|
||||||
source.baseUrl,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val iconUrl = "${REPO_URL_PREFIX}icon/${extension.pkg}.png"
|
|
||||||
NovelExtension.Available(
|
|
||||||
extension.name,
|
|
||||||
extension.pkg,
|
|
||||||
extension.apk,
|
|
||||||
extension.code,
|
|
||||||
sources?.toSources() ?: emptyList(),
|
|
||||||
iconUrl,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun List<NovelExtensionSourceJsonObject>.toSources(): List<AvailableNovelSources> {
|
|
||||||
return map { source ->
|
|
||||||
AvailableNovelSources(
|
|
||||||
source.id,
|
|
||||||
source.lang,
|
|
||||||
source.name,
|
|
||||||
source.baseUrl,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getApkUrl(extension: NovelExtension.Available): String {
|
|
||||||
return "${getUrlPrefix()}apk/${extension.pkgName}.apk"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getUrlPrefix(): String {
|
|
||||||
return if (requiresFallbackSource) {
|
|
||||||
FALLBACK_REPO_URL_PREFIX
|
|
||||||
} else {
|
|
||||||
REPO_URL_PREFIX
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val REPO_URL_PREFIX =
|
|
||||||
"https://raw.githubusercontent.com/dannovels/novel-extensions/main/"
|
|
||||||
private const val FALLBACK_REPO_URL_PREFIX =
|
|
||||||
"https://gcore.jsdelivr.net/gh/dannovels/novel-extensions@latest/"
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
private data class NovelExtensionJsonObject(
|
|
||||||
val name: String,
|
|
||||||
val pkg: String,
|
|
||||||
val apk: String,
|
|
||||||
val lang: String,
|
|
||||||
val code: Long,
|
|
||||||
val version: String,
|
|
||||||
val nsfw: Int,
|
|
||||||
val hasReadme: Int = 0,
|
|
||||||
val hasChangelog: Int = 0,
|
|
||||||
val sources: List<NovelExtensionSourceJsonObject>?,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
private data class NovelExtensionSourceJsonObject(
|
|
||||||
val id: Long,
|
|
||||||
val lang: String,
|
|
||||||
val name: String,
|
|
||||||
val baseUrl: String,
|
|
||||||
)
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import ani.dantotsu.media.MediaType
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
import eu.kanade.tachiyomi.extension.InstallStep
|
import eu.kanade.tachiyomi.extension.InstallStep
|
||||||
|
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
|
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
|
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||||
|
@ -22,7 +23,7 @@ class NovelExtensionManager(private val context: Context) {
|
||||||
/**
|
/**
|
||||||
* API where all the available Novel extensions can be found.
|
* API where all the available Novel extensions can be found.
|
||||||
*/
|
*/
|
||||||
private val api = NovelExtensionGithubApi()
|
private val api = ExtensionGithubApi()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The installer which installs, updates and uninstalls the Novel extensions.
|
* The installer which installs, updates and uninstalls the Novel extensions.
|
||||||
|
@ -70,7 +71,7 @@ class NovelExtensionManager(private val context: Context) {
|
||||||
*/
|
*/
|
||||||
suspend fun findAvailableExtensions() {
|
suspend fun findAvailableExtensions() {
|
||||||
val extensions: List<NovelExtension.Available> = try {
|
val extensions: List<NovelExtension.Available> = try {
|
||||||
api.findExtensions()
|
api.findNovelExtensions()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.log("Error finding extensions: ${e.message}")
|
Logger.log("Error finding extensions: ${e.message}")
|
||||||
withUIContext { snackString("Failed to get Novel extensions list") }
|
withUIContext { snackString("Failed to get Novel extensions list") }
|
||||||
|
@ -119,7 +120,7 @@ class NovelExtensionManager(private val context: Context) {
|
||||||
* @param extension The anime extension to be installed.
|
* @param extension The anime extension to be installed.
|
||||||
*/
|
*/
|
||||||
fun installExtension(extension: NovelExtension.Available): Observable<InstallStep> {
|
fun installExtension(extension: NovelExtension.Available): Observable<InstallStep> {
|
||||||
return installer.downloadAndInstall(api.getApkUrl(extension), extension.pkgName,
|
return installer.downloadAndInstall(api.getNovelApkUrl(extension), extension.pkgName,
|
||||||
extension.name, MediaType.NOVEL)
|
extension.name, MediaType.NOVEL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +234,7 @@ class NovelExtensionManager(private val context: Context) {
|
||||||
private fun NovelExtension.Installed.updateExists(availableNovelExtension: NovelExtension.Available? = null): Boolean {
|
private fun NovelExtension.Installed.updateExists(availableNovelExtension: NovelExtension.Available? = null): Boolean {
|
||||||
val availableExt = availableNovelExtension
|
val availableExt = availableNovelExtension
|
||||||
?: _availableNovelExtensionsFlow.value.find { it.pkgName == pkgName }
|
?: _availableNovelExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||||
if (isUnofficial || availableExt == null) return false
|
if (availableExt == null) return false
|
||||||
|
|
||||||
return (availableExt.versionCode > versionCode)
|
return (availableExt.versionCode > versionCode)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,19 @@ import android.content.Intent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
import ani.dantotsu.databinding.ItemFollowerBinding
|
import ani.dantotsu.databinding.ItemFollowerBinding
|
||||||
|
import ani.dantotsu.databinding.ItemFollowerGridBinding
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
|
|
||||||
|
|
||||||
class UsersAdapter(private val user: ArrayList<User>) :
|
class UsersAdapter(private val user: MutableList<User>, private val grid: Boolean = false) :
|
||||||
RecyclerView.Adapter<UsersAdapter.UsersViewHolder>() {
|
RecyclerView.Adapter<UsersAdapter.UsersViewHolder>() {
|
||||||
|
|
||||||
inner class UsersViewHolder(val binding: ItemFollowerBinding) :
|
inner class UsersViewHolder(val binding: ViewBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
|
@ -27,6 +30,11 @@ class UsersAdapter(private val user: ArrayList<User>) :
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UsersViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UsersViewHolder {
|
||||||
return UsersViewHolder(
|
return UsersViewHolder(
|
||||||
|
if (grid) ItemFollowerGridBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
) else
|
||||||
ItemFollowerBinding.inflate(
|
ItemFollowerBinding.inflate(
|
||||||
LayoutInflater.from(parent.context),
|
LayoutInflater.from(parent.context),
|
||||||
parent,
|
parent,
|
||||||
|
@ -36,13 +44,22 @@ class UsersAdapter(private val user: ArrayList<User>) :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: UsersViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: UsersViewHolder, position: Int) {
|
||||||
val b = holder.binding
|
setAnimation(holder.binding.root.context, holder.binding.root)
|
||||||
setAnimation(b.root.context, b.root)
|
val user = user.getOrNull(position) ?: return
|
||||||
val user = user[position]
|
if (grid) {
|
||||||
|
val b = holder.binding as ItemFollowerGridBinding
|
||||||
|
b.profileUserAvatar.loadImage(user.pfp)
|
||||||
|
b.profileUserName.text = user.name
|
||||||
|
b.profileCompactScoreBG.isVisible = false
|
||||||
|
b.profileInfo.isVisible = false
|
||||||
|
b.profileCompactProgressContainer.isVisible = false
|
||||||
|
} else {
|
||||||
|
val b = holder.binding as ItemFollowerBinding
|
||||||
b.profileUserAvatar.loadImage(user.pfp)
|
b.profileUserAvatar.loadImage(user.pfp)
|
||||||
b.profileBannerImage.loadImage(user.banner ?: user.pfp)
|
b.profileBannerImage.loadImage(user.banner ?: user.pfp)
|
||||||
b.profileUserName.text = user.name
|
b.profileUserName.text = user.name
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = user.size
|
override fun getItemCount(): Int = user.size
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ani.dantotsu.settings
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -10,29 +11,52 @@ import android.view.inputmethod.EditorInfo
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import ani.dantotsu.BottomSheetDialogFragment
|
import ani.dantotsu.BottomSheetDialogFragment
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.copyToClipboard
|
||||||
import ani.dantotsu.databinding.BottomSheetAddRepositoryBinding
|
import ani.dantotsu.databinding.BottomSheetAddRepositoryBinding
|
||||||
import ani.dantotsu.databinding.ItemRepoBinding
|
import ani.dantotsu.databinding.ItemRepoBinding
|
||||||
import ani.dantotsu.media.MediaType
|
import ani.dantotsu.media.MediaType
|
||||||
|
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.util.customAlertDialog
|
import ani.dantotsu.util.customAlertDialog
|
||||||
import com.xwray.groupie.GroupieAdapter
|
import com.xwray.groupie.GroupieAdapter
|
||||||
import com.xwray.groupie.viewbinding.BindableItem
|
import com.xwray.groupie.viewbinding.BindableItem
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class RepoItem(
|
class RepoItem(
|
||||||
val url: String,
|
val url: String,
|
||||||
val onRemove: (String) -> Unit
|
private val mediaType: MediaType,
|
||||||
|
val onRemove: (String, MediaType) -> Unit
|
||||||
) :BindableItem<ItemRepoBinding>() {
|
) :BindableItem<ItemRepoBinding>() {
|
||||||
override fun getLayout() = R.layout.item_repo
|
override fun getLayout() = R.layout.item_repo
|
||||||
|
|
||||||
override fun bind(viewBinding: ItemRepoBinding, position: Int) {
|
override fun bind(viewBinding: ItemRepoBinding, position: Int) {
|
||||||
viewBinding.repoNameTextView.text = url
|
viewBinding.repoNameTextView.text = url.cleanShownUrl()
|
||||||
viewBinding.repoDeleteImageView.setOnClickListener {
|
viewBinding.repoDeleteImageView.setOnClickListener {
|
||||||
onRemove(url)
|
onRemove(url, mediaType)
|
||||||
|
}
|
||||||
|
viewBinding.repoCopyImageView.setOnClickListener {
|
||||||
|
viewBinding.repoCopyImageView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
|
copyToClipboard(url, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initializeViewBinding(view: View): ItemRepoBinding {
|
override fun initializeViewBinding(view: View): ItemRepoBinding {
|
||||||
return ItemRepoBinding.bind(view)
|
return ItemRepoBinding.bind(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.cleanShownUrl(): String {
|
||||||
|
return this
|
||||||
|
.removePrefix("https://raw.githubusercontent.com/")
|
||||||
|
.replace("index.min.json", "")
|
||||||
|
.removeSuffix("/")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
||||||
|
@ -41,7 +65,7 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
||||||
private var mediaType: MediaType = MediaType.ANIME
|
private var mediaType: MediaType = MediaType.ANIME
|
||||||
private var onRepositoryAdded: ((String, MediaType) -> Unit)? = null
|
private var onRepositoryAdded: ((String, MediaType) -> Unit)? = null
|
||||||
private var repositories: MutableList<String> = mutableListOf()
|
private var repositories: MutableList<String> = mutableListOf()
|
||||||
private var onRepositoryRemoved: ((String) -> Unit)? = null
|
private var onRepositoryRemoved: ((String, MediaType) -> Unit)? = null
|
||||||
private var adapter: GroupieAdapter = GroupieAdapter()
|
private var adapter: GroupieAdapter = GroupieAdapter()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
|
@ -62,24 +86,19 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
||||||
LinearLayoutManager.VERTICAL,
|
LinearLayoutManager.VERTICAL,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
adapter.addAll(repositories.map { RepoItem(it, ::onRepositoryRemoved) })
|
adapter.addAll(repositories.map { RepoItem(it, mediaType, ::onRepositoryRemoved) })
|
||||||
|
|
||||||
binding.repositoryInput.hint = when(mediaType) {
|
binding.repositoryInput.hint = when(mediaType) {
|
||||||
MediaType.ANIME -> getString(R.string.anime_add_repository)
|
MediaType.ANIME -> getString(R.string.anime_add_repository)
|
||||||
MediaType.MANGA -> getString(R.string.manga_add_repository)
|
MediaType.MANGA -> getString(R.string.manga_add_repository)
|
||||||
else -> ""
|
MediaType.NOVEL -> getString(R.string.novel_add_repository)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.addButton.setOnClickListener {
|
binding.addButton.setOnClickListener {
|
||||||
val input = binding.repositoryInput.text.toString()
|
val input = binding.repositoryInput.text.toString()
|
||||||
val error = isValidUrl(input)
|
val error = isValidUrl(input)
|
||||||
if (error == null) {
|
if (error == null) {
|
||||||
context?.let { context ->
|
acceptUrl(input)
|
||||||
addRepoWarning(context) {
|
|
||||||
onRepositoryAdded?.invoke(input, mediaType)
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
binding.repositoryInput.error = error
|
binding.repositoryInput.error = error
|
||||||
}
|
}
|
||||||
|
@ -96,12 +115,7 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
||||||
if (url.isNotBlank()) {
|
if (url.isNotBlank()) {
|
||||||
val error = isValidUrl(url)
|
val error = isValidUrl(url)
|
||||||
if (error == null) {
|
if (error == null) {
|
||||||
context?.let { context ->
|
acceptUrl(url)
|
||||||
addRepoWarning(context) {
|
|
||||||
onRepositoryAdded?.invoke(url, mediaType)
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return@setOnEditorActionListener true
|
return@setOnEditorActionListener true
|
||||||
} else {
|
} else {
|
||||||
binding.repositoryInput.error = error
|
binding.repositoryInput.error = error
|
||||||
|
@ -112,20 +126,62 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRepositoryRemoved(url: String) {
|
private fun acceptUrl(url: String) {
|
||||||
onRepositoryRemoved?.invoke(url)
|
val finalUrl = getRepoUrl(url)
|
||||||
repositories.remove(url)
|
context?.let { context ->
|
||||||
adapter.update(repositories.map { RepoItem(it, ::onRepositoryRemoved) })
|
addRepoWarning(context) {
|
||||||
|
onRepositoryAdded?.invoke(finalUrl, mediaType)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isValidUrl(url: String): String? {
|
private fun isValidUrl(input: String): String? {
|
||||||
if (!url.startsWith("https://") && !url.startsWith("http://"))
|
if (input.startsWith("http://") || input.startsWith("https://")) {
|
||||||
return "URL must start with http:// or https://"
|
if (!input.removeSuffix("/").endsWith("index.min.json")) {
|
||||||
if (!url.removeSuffix("/").endsWith("index.min.json"))
|
|
||||||
return "URL must end with index.min.json"
|
return "URL must end with index.min.json"
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val parts = input.split("/")
|
||||||
|
if (parts.size !in 2..3) {
|
||||||
|
return "Must be a full URL or in format: username/repo[/branch]"
|
||||||
|
}
|
||||||
|
|
||||||
|
val username = parts[0]
|
||||||
|
val repo = parts[1]
|
||||||
|
val branch = if (parts.size == 3) parts[2] else "repo"
|
||||||
|
|
||||||
|
if (username.isBlank() || repo.isBlank()) {
|
||||||
|
return "Username and repository name cannot be empty"
|
||||||
|
}
|
||||||
|
if (parts.size == 3 && branch.isBlank()) {
|
||||||
|
return "Branch name cannot be empty"
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRepoUrl(input: String): String {
|
||||||
|
if (input.startsWith("http://") || input.startsWith("https://")) {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
val parts = input.split("/")
|
||||||
|
val username = parts[0]
|
||||||
|
val repo = parts[1]
|
||||||
|
val branch = if (parts.size == 3) parts[2] else "repo"
|
||||||
|
|
||||||
|
return "https://raw.githubusercontent.com/$username/$repo/$branch/index.min.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onRepositoryRemoved(url: String, mediaType: MediaType) {
|
||||||
|
onRepositoryRemoved?.invoke(url, mediaType)
|
||||||
|
repositories.remove(url)
|
||||||
|
adapter.update(repositories.map { RepoItem(it, mediaType, ::onRepositoryRemoved) })
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
_binding = null
|
_binding = null
|
||||||
|
@ -142,11 +198,81 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
||||||
.setNegButton(R.string.cancel) { }
|
.setNegButton(R.string.cancel) { }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addRepo(input: String, mediaType: MediaType) {
|
||||||
|
val validLink = if (input.contains("github.com") && input.contains("blob")) {
|
||||||
|
input.replace("github.com", "raw.githubusercontent.com")
|
||||||
|
.replace("/blob/", "/")
|
||||||
|
} else input
|
||||||
|
|
||||||
|
when (mediaType) {
|
||||||
|
MediaType.ANIME -> {
|
||||||
|
val anime =
|
||||||
|
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos)
|
||||||
|
.plus(validLink)
|
||||||
|
PrefManager.setVal(PrefName.AnimeExtensionRepos, anime)
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
Injekt.get<AnimeExtensionManager>().findAvailableExtensions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MediaType.MANGA -> {
|
||||||
|
val manga =
|
||||||
|
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos)
|
||||||
|
.plus(validLink)
|
||||||
|
PrefManager.setVal(PrefName.MangaExtensionRepos, manga)
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
Injekt.get<MangaExtensionManager>().findAvailableExtensions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MediaType.NOVEL -> {
|
||||||
|
val novel =
|
||||||
|
PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos)
|
||||||
|
.plus(validLink)
|
||||||
|
PrefManager.setVal(PrefName.NovelExtensionRepos, novel)
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
Injekt.get<NovelExtensionManager>().findAvailableExtensions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeRepo(input: String, mediaType: MediaType) {
|
||||||
|
when (mediaType) {
|
||||||
|
MediaType.ANIME -> {
|
||||||
|
val anime =
|
||||||
|
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos)
|
||||||
|
.minus(input)
|
||||||
|
PrefManager.setVal(PrefName.AnimeExtensionRepos, anime)
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
Injekt.get<AnimeExtensionManager>().findAvailableExtensions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MediaType.MANGA -> {
|
||||||
|
val manga =
|
||||||
|
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos)
|
||||||
|
.minus(input)
|
||||||
|
PrefManager.setVal(PrefName.MangaExtensionRepos, manga)
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
Injekt.get<MangaExtensionManager>().findAvailableExtensions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MediaType.NOVEL -> {
|
||||||
|
val novel =
|
||||||
|
PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos)
|
||||||
|
.minus(input)
|
||||||
|
PrefManager.setVal(PrefName.NovelExtensionRepos, novel)
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
Injekt.get<NovelExtensionManager>().findAvailableExtensions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
mediaType: MediaType,
|
mediaType: MediaType,
|
||||||
repositories: List<String>,
|
repositories: List<String>,
|
||||||
onRepositoryAdded: (String, MediaType) -> Unit,
|
onRepositoryAdded: (String, MediaType) -> Unit,
|
||||||
onRepositoryRemoved: (String) -> Unit
|
onRepositoryRemoved: (String, MediaType) -> Unit
|
||||||
): AddRepositoryBottomSheet {
|
): AddRepositoryBottomSheet {
|
||||||
return AddRepositoryBottomSheet().apply {
|
return AddRepositoryBottomSheet().apply {
|
||||||
this.mediaType = mediaType
|
this.mediaType = mediaType
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
package ani.dantotsu.settings
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.HapticFeedbackConstants
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
import android.widget.EditText
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
|
@ -20,10 +14,7 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.copyToClipboard
|
|
||||||
import ani.dantotsu.databinding.ActivityExtensionsBinding
|
import ani.dantotsu.databinding.ActivityExtensionsBinding
|
||||||
import ani.dantotsu.databinding.DialogRepositoriesBinding
|
|
||||||
import ani.dantotsu.databinding.ItemRepositoryBinding
|
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.media.MediaType
|
import ani.dantotsu.media.MediaType
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
|
@ -37,20 +28,11 @@ import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.util.customAlertDialog
|
import ani.dantotsu.util.customAlertDialog
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class ExtensionsActivity : AppCompatActivity() {
|
class ExtensionsActivity : AppCompatActivity() {
|
||||||
lateinit var binding: ActivityExtensionsBinding
|
lateinit var binding: ActivityExtensionsBinding
|
||||||
|
|
||||||
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
|
|
||||||
private val mangaExtensionManager: MangaExtensionManager by injectLazy()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
@ -124,6 +106,9 @@ class ExtensionsActivity : AppCompatActivity() {
|
||||||
if (tab.text?.contains("Manga") == true) {
|
if (tab.text?.contains("Manga") == true) {
|
||||||
generateRepositoryButton(MediaType.MANGA)
|
generateRepositoryButton(MediaType.MANGA)
|
||||||
}
|
}
|
||||||
|
if (tab.text?.contains("Novels") == true) {
|
||||||
|
generateRepositoryButton(MediaType.NOVEL)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTabUnselected(tab: TabLayout.Tab) {
|
override fun onTabUnselected(tab: TabLayout.Tab) {
|
||||||
|
@ -199,136 +184,28 @@ class ExtensionsActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processUserInput(input: String, mediaType: MediaType) {
|
|
||||||
val entry = if (input.endsWith("/") || input.endsWith("index.min.json"))
|
|
||||||
input.substring(0, input.lastIndexOf("/")) else input
|
|
||||||
if (mediaType == MediaType.ANIME) {
|
|
||||||
val anime =
|
|
||||||
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos).plus(entry)
|
|
||||||
PrefManager.setVal(PrefName.AnimeExtensionRepos, anime)
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
animeExtensionManager.findAvailableExtensions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mediaType == MediaType.MANGA) {
|
|
||||||
val manga =
|
|
||||||
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos).plus(entry)
|
|
||||||
PrefManager.setVal(PrefName.MangaExtensionRepos, manga)
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
mangaExtensionManager.findAvailableExtensions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getSavedRepositories(repoInventory: ViewGroup, type: MediaType) {
|
|
||||||
repoInventory.removeAllViews()
|
|
||||||
val prefName: PrefName? = when (type) {
|
|
||||||
MediaType.ANIME -> {
|
|
||||||
PrefName.AnimeExtensionRepos
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaType.MANGA -> {
|
|
||||||
PrefName.MangaExtensionRepos
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prefName?.let { repoList ->
|
|
||||||
PrefManager.getVal<Set<String>>(repoList).forEach { item ->
|
|
||||||
val view = ItemRepositoryBinding.inflate(
|
|
||||||
LayoutInflater.from(repoInventory.context), repoInventory, true
|
|
||||||
)
|
|
||||||
view.repositoryItem.text = item.removePrefix("https://raw.githubusercontent.com")
|
|
||||||
view.repositoryItem.setOnClickListener {
|
|
||||||
customAlertDialog().apply {
|
|
||||||
setTitle(R.string.rem_repository)
|
|
||||||
setMessage(item)
|
|
||||||
setPosButton(R.string.ok) {
|
|
||||||
val repos = PrefManager.getVal<Set<String>>(prefName).minus(item)
|
|
||||||
PrefManager.setVal(prefName, repos)
|
|
||||||
repoInventory.removeView(view.root)
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
when (type) {
|
|
||||||
MediaType.ANIME -> {
|
|
||||||
animeExtensionManager.findAvailableExtensions()
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaType.MANGA -> {
|
|
||||||
mangaExtensionManager.findAvailableExtensions()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setNegButton(R.string.cancel)
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
view.repositoryItem.setOnLongClickListener {
|
|
||||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
|
||||||
copyToClipboard(item, true)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processEditorAction(editText: EditText, mediaType: MediaType) {
|
|
||||||
editText.setOnEditorActionListener { textView, action, keyEvent ->
|
|
||||||
if (action == EditorInfo.IME_ACTION_SEARCH || action == EditorInfo.IME_ACTION_DONE ||
|
|
||||||
(keyEvent?.action == KeyEvent.ACTION_UP
|
|
||||||
&& keyEvent.keyCode == KeyEvent.KEYCODE_ENTER)
|
|
||||||
) {
|
|
||||||
return@setOnEditorActionListener if (textView.text.isNullOrBlank()) {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
processUserInput(textView.text.toString(), mediaType)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateRepositoryButton(type: MediaType) {
|
private fun generateRepositoryButton(type: MediaType) {
|
||||||
val hintResource: Int? = when (type) {
|
binding.openSettingsButton.setOnClickListener {
|
||||||
|
val repos: Set<String> = when (type) {
|
||||||
MediaType.ANIME -> {
|
MediaType.ANIME -> {
|
||||||
R.string.anime_add_repository
|
PrefManager.getVal(PrefName.AnimeExtensionRepos)
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaType.MANGA -> {
|
MediaType.MANGA -> {
|
||||||
R.string.manga_add_repository
|
PrefManager.getVal(PrefName.MangaExtensionRepos)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
MediaType.NOVEL -> {
|
||||||
null
|
PrefManager.getVal(PrefName.NovelExtensionRepos)
|
||||||
}
|
|
||||||
}
|
|
||||||
hintResource?.let { res ->
|
|
||||||
binding.openSettingsButton.setOnClickListener {
|
|
||||||
val dialogView = DialogRepositoriesBinding.inflate(
|
|
||||||
LayoutInflater.from(binding.openSettingsButton.context), null, false
|
|
||||||
)
|
|
||||||
dialogView.repositoryTextBox.hint = getString(res)
|
|
||||||
dialogView.repoInventory.apply {
|
|
||||||
getSavedRepositories(this, type)
|
|
||||||
}
|
|
||||||
processEditorAction(dialogView.repositoryTextBox, type)
|
|
||||||
customAlertDialog().apply {
|
|
||||||
setTitle(R.string.edit_repositories)
|
|
||||||
setCustomView(dialogView.root)
|
|
||||||
setPosButton(R.string.add_list) {
|
|
||||||
if (!dialogView.repositoryTextBox.text.isNullOrBlank()) {
|
|
||||||
processUserInput(dialogView.repositoryTextBox.text.toString(), type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setNegButton(R.string.close)
|
|
||||||
show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AddRepositoryBottomSheet.newInstance(
|
||||||
|
type,
|
||||||
|
repos.toList(),
|
||||||
|
AddRepositoryBottomSheet::addRepo,
|
||||||
|
AddRepositoryBottomSheet::removeRepo
|
||||||
|
|
||||||
|
).show(supportFragmentManager, "add_repo")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,16 @@ class SettingsCommonActivity : AppCompatActivity() {
|
||||||
},
|
},
|
||||||
isActivity = true
|
isActivity = true
|
||||||
),
|
),
|
||||||
|
Settings(
|
||||||
|
type = 2,
|
||||||
|
name = getString(R.string.open_animanga_directly),
|
||||||
|
desc = getString(R.string.open_animanga_directly_info),
|
||||||
|
icon = R.drawable.ic_round_search_24,
|
||||||
|
isChecked = PrefManager.getVal(PrefName.AniMangaSearchDirect),
|
||||||
|
switch = { isChecked, _ ->
|
||||||
|
PrefManager.setVal(PrefName.AniMangaSearchDirect, isChecked)
|
||||||
|
}
|
||||||
|
),
|
||||||
Settings(
|
Settings(
|
||||||
type = 1,
|
type = 1,
|
||||||
name = getString(R.string.download_manager_select),
|
name = getString(R.string.download_manager_select),
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
package ani.dantotsu.settings
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import android.widget.EditText
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
@ -32,9 +28,6 @@ import ani.dantotsu.util.customAlertDialog
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
@ -42,8 +35,7 @@ import uy.kohesive.injekt.injectLazy
|
||||||
class SettingsExtensionsActivity : AppCompatActivity() {
|
class SettingsExtensionsActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivitySettingsExtensionsBinding
|
private lateinit var binding: ActivitySettingsExtensionsBinding
|
||||||
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller()
|
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller()
|
||||||
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
|
|
||||||
private val mangaExtensionManager: MangaExtensionManager by injectLazy()
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
|
@ -61,7 +53,7 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
fun setExtensionOutput(repoInventory: ViewGroup, type: MediaType) {
|
fun setExtensionOutput(repoInventory: ViewGroup, type: MediaType) {
|
||||||
repoInventory.removeAllViews()
|
repoInventory.removeAllViews()
|
||||||
val prefName: PrefName? = when (type) {
|
val prefName: PrefName = when (type) {
|
||||||
MediaType.ANIME -> {
|
MediaType.ANIME -> {
|
||||||
PrefName.AnimeExtensionRepos
|
PrefName.AnimeExtensionRepos
|
||||||
}
|
}
|
||||||
|
@ -70,41 +62,17 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
||||||
PrefName.MangaExtensionRepos
|
PrefName.MangaExtensionRepos
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
MediaType.NOVEL -> {
|
||||||
null
|
PrefName.NovelExtensionRepos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prefName?.let { repoList ->
|
PrefManager.getVal<Set<String>>(prefName).forEach { item ->
|
||||||
PrefManager.getVal<Set<String>>(repoList).forEach { item ->
|
|
||||||
val view = ItemRepositoryBinding.inflate(
|
val view = ItemRepositoryBinding.inflate(
|
||||||
LayoutInflater.from(repoInventory.context), repoInventory, true
|
LayoutInflater.from(repoInventory.context), repoInventory, true
|
||||||
)
|
)
|
||||||
view.repositoryItem.text =
|
view.repositoryItem.text =
|
||||||
item.removePrefix("https://raw.githubusercontent.com/")
|
item.removePrefix("https://raw.githubusercontent.com/")
|
||||||
view.repositoryItem.setOnClickListener {
|
|
||||||
context.customAlertDialog().apply {
|
|
||||||
setTitle(R.string.rem_repository)
|
|
||||||
setMessage(item)
|
|
||||||
setPosButton(R.string.ok) {
|
|
||||||
val repos = PrefManager.getVal<Set<String>>(repoList).minus(item)
|
|
||||||
PrefManager.setVal(repoList, repos)
|
|
||||||
setExtensionOutput(repoInventory, type)
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
when (type) {
|
|
||||||
MediaType.ANIME -> {
|
|
||||||
animeExtensionManager.findAvailableExtensions()
|
|
||||||
}
|
|
||||||
MediaType.MANGA -> {
|
|
||||||
mangaExtensionManager.findAvailableExtensions()
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setNegButton(R.string.cancel)
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
view.repositoryItem.setOnLongClickListener {
|
view.repositoryItem.setOnLongClickListener {
|
||||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
copyToClipboard(item, true)
|
copyToClipboard(item, true)
|
||||||
|
@ -113,32 +81,6 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
repoInventory.isVisible = repoInventory.childCount > 0
|
repoInventory.isVisible = repoInventory.childCount > 0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun processUserInput(input: String, mediaType: MediaType, view: ViewGroup) {
|
|
||||||
val validLink = if (input.contains("github.com") && input.contains("blob")) {
|
|
||||||
input.replace("github.com", "raw.githubusercontent.com")
|
|
||||||
.replace("/blob/", "/")
|
|
||||||
} else input
|
|
||||||
if (mediaType == MediaType.ANIME) {
|
|
||||||
val anime =
|
|
||||||
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos).plus(validLink)
|
|
||||||
PrefManager.setVal(PrefName.AnimeExtensionRepos, anime)
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
animeExtensionManager.findAvailableExtensions()
|
|
||||||
}
|
|
||||||
setExtensionOutput(view, MediaType.ANIME)
|
|
||||||
}
|
|
||||||
if (mediaType == MediaType.MANGA) {
|
|
||||||
val manga =
|
|
||||||
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos).plus(validLink)
|
|
||||||
PrefManager.setVal(PrefName.MangaExtensionRepos, manga)
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
mangaExtensionManager.findAvailableExtensions()
|
|
||||||
}
|
|
||||||
setExtensionOutput(view, MediaType.MANGA)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsRecyclerView.adapter = SettingsAdapter(
|
settingsRecyclerView.adapter = SettingsAdapter(
|
||||||
arrayListOf(
|
arrayListOf(
|
||||||
|
@ -148,17 +90,18 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
||||||
desc = getString(R.string.anime_add_repository_desc),
|
desc = getString(R.string.anime_add_repository_desc),
|
||||||
icon = R.drawable.ic_github,
|
icon = R.drawable.ic_github,
|
||||||
onClick = {
|
onClick = {
|
||||||
val animeRepos = PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos)
|
val animeRepos =
|
||||||
|
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos)
|
||||||
AddRepositoryBottomSheet.newInstance(
|
AddRepositoryBottomSheet.newInstance(
|
||||||
MediaType.ANIME,
|
MediaType.ANIME,
|
||||||
animeRepos.toList(),
|
animeRepos.toList(),
|
||||||
onRepositoryAdded = { input, mediaType ->
|
onRepositoryAdded = { input, mediaType ->
|
||||||
processUserInput(input, mediaType, it.attachView)
|
AddRepositoryBottomSheet.addRepo(input, mediaType)
|
||||||
|
setExtensionOutput(it.attachView, mediaType)
|
||||||
},
|
},
|
||||||
onRepositoryRemoved = { item ->
|
onRepositoryRemoved = { item, mediaType ->
|
||||||
val repos = PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos).minus(item)
|
AddRepositoryBottomSheet.removeRepo(item, mediaType)
|
||||||
PrefManager.setVal(PrefName.AnimeExtensionRepos, repos)
|
setExtensionOutput(it.attachView, mediaType)
|
||||||
setExtensionOutput(it.attachView, MediaType.ANIME)
|
|
||||||
}
|
}
|
||||||
).show(supportFragmentManager, "add_repo")
|
).show(supportFragmentManager, "add_repo")
|
||||||
},
|
},
|
||||||
|
@ -172,17 +115,18 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
||||||
desc = getString(R.string.manga_add_repository_desc),
|
desc = getString(R.string.manga_add_repository_desc),
|
||||||
icon = R.drawable.ic_github,
|
icon = R.drawable.ic_github,
|
||||||
onClick = {
|
onClick = {
|
||||||
val mangaRepos = PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos)
|
val mangaRepos =
|
||||||
|
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos)
|
||||||
AddRepositoryBottomSheet.newInstance(
|
AddRepositoryBottomSheet.newInstance(
|
||||||
MediaType.MANGA,
|
MediaType.MANGA,
|
||||||
mangaRepos.toList(),
|
mangaRepos.toList(),
|
||||||
onRepositoryAdded = { input, mediaType ->
|
onRepositoryAdded = { input, mediaType ->
|
||||||
processUserInput(input, mediaType, it.attachView)
|
AddRepositoryBottomSheet.addRepo(input, mediaType)
|
||||||
|
setExtensionOutput(it.attachView, mediaType)
|
||||||
},
|
},
|
||||||
onRepositoryRemoved = { item ->
|
onRepositoryRemoved = { item, mediaType ->
|
||||||
val repos = PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos).minus(item)
|
AddRepositoryBottomSheet.removeRepo(item, mediaType)
|
||||||
PrefManager.setVal(PrefName.MangaExtensionRepos, repos)
|
setExtensionOutput(it.attachView, mediaType)
|
||||||
setExtensionOutput(it.attachView, MediaType.MANGA)
|
|
||||||
}
|
}
|
||||||
).show(supportFragmentManager, "add_repo")
|
).show(supportFragmentManager, "add_repo")
|
||||||
},
|
},
|
||||||
|
@ -190,6 +134,31 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
||||||
setExtensionOutput(it.attachView, MediaType.MANGA)
|
setExtensionOutput(it.attachView, MediaType.MANGA)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
Settings(
|
||||||
|
type = 1,
|
||||||
|
name = getString(R.string.novel_add_repository),
|
||||||
|
desc = getString(R.string.novel_add_repository_desc),
|
||||||
|
icon = R.drawable.ic_github,
|
||||||
|
onClick = {
|
||||||
|
val novelRepos =
|
||||||
|
PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos)
|
||||||
|
AddRepositoryBottomSheet.newInstance(
|
||||||
|
MediaType.NOVEL,
|
||||||
|
novelRepos.toList(),
|
||||||
|
onRepositoryAdded = { input, mediaType ->
|
||||||
|
AddRepositoryBottomSheet.addRepo(input, mediaType)
|
||||||
|
setExtensionOutput(it.attachView, mediaType)
|
||||||
|
},
|
||||||
|
onRepositoryRemoved = { item, mediaType ->
|
||||||
|
AddRepositoryBottomSheet.removeRepo(item, mediaType)
|
||||||
|
setExtensionOutput(it.attachView, mediaType)
|
||||||
|
}
|
||||||
|
).show(supportFragmentManager, "add_repo")
|
||||||
|
},
|
||||||
|
attach = {
|
||||||
|
setExtensionOutput(it.attachView, MediaType.NOVEL)
|
||||||
|
}
|
||||||
|
),
|
||||||
Settings(
|
Settings(
|
||||||
type = 1,
|
type = 1,
|
||||||
name = getString(R.string.extension_test),
|
name = getString(R.string.extension_test),
|
||||||
|
@ -217,7 +186,10 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
||||||
setTitle(R.string.user_agent)
|
setTitle(R.string.user_agent)
|
||||||
setCustomView(dialogView.root)
|
setCustomView(dialogView.root)
|
||||||
setPosButton(R.string.ok) {
|
setPosButton(R.string.ok) {
|
||||||
PrefManager.setVal(PrefName.DefaultUserAgent, editText.text.toString())
|
PrefManager.setVal(
|
||||||
|
PrefName.DefaultUserAgent,
|
||||||
|
editText.text.toString()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
setNeutralButton(R.string.reset) {
|
setNeutralButton(R.string.reset) {
|
||||||
PrefManager.removeVal(PrefName.DefaultUserAgent)
|
PrefManager.removeVal(PrefName.DefaultUserAgent)
|
||||||
|
|
|
@ -255,9 +255,6 @@ object PrefManager {
|
||||||
return allEntries
|
return allEntries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T> getLiveVal(prefName: PrefName, default: T): SharedPreferenceLiveData<T> {
|
fun <T> getLiveVal(prefName: PrefName, default: T): SharedPreferenceLiveData<T> {
|
||||||
val pref = getPrefLocation(prefName.data.prefLocation)
|
val pref = getPrefLocation(prefName.data.prefLocation)
|
||||||
|
@ -298,7 +295,11 @@ object PrefManager {
|
||||||
default as Set<String>
|
default as Set<String>
|
||||||
) as SharedPreferenceLiveData<T>
|
) as SharedPreferenceLiveData<T>
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("Type not supported")
|
else -> SharedPreferenceClassLiveData(
|
||||||
|
pref,
|
||||||
|
prefName.name,
|
||||||
|
default
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +327,11 @@ object PrefManager {
|
||||||
this as? SharedPreferenceStringSetLiveData
|
this as? SharedPreferenceStringSetLiveData
|
||||||
?: throw ClassCastException("Cannot cast to SharedPreferenceLiveData<Set<String>>")
|
?: throw ClassCastException("Cannot cast to SharedPreferenceLiveData<Set<String>>")
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
inline fun <reified T> SharedPreferenceLiveData<*>.asLiveClass(): SharedPreferenceClassLiveData<T> =
|
||||||
|
this as? SharedPreferenceClassLiveData<T>
|
||||||
|
?: throw ClassCastException("Cannot cast to SharedPreferenceLiveData<T>")
|
||||||
|
|
||||||
fun getAnimeDownloadPreferences(): SharedPreferences =
|
fun getAnimeDownloadPreferences(): SharedPreferences =
|
||||||
animeDownloadsPreferences!! //needs to be used externally
|
animeDownloadsPreferences!! //needs to be used externally
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,13 @@ package ani.dantotsu.settings.saving
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import ani.dantotsu.connections.comments.AuthResponse
|
import ani.dantotsu.connections.comments.AuthResponse
|
||||||
import ani.dantotsu.connections.mal.MAL
|
import ani.dantotsu.connections.mal.MAL
|
||||||
|
import ani.dantotsu.media.SearchHistory
|
||||||
import ani.dantotsu.notifications.comment.CommentStore
|
import ani.dantotsu.notifications.comment.CommentStore
|
||||||
import ani.dantotsu.notifications.subscription.SubscriptionStore
|
import ani.dantotsu.notifications.subscription.SubscriptionStore
|
||||||
import ani.dantotsu.settings.saving.internal.Location
|
import ani.dantotsu.settings.saving.internal.Location
|
||||||
import ani.dantotsu.settings.saving.internal.Pref
|
import ani.dantotsu.settings.saving.internal.Pref
|
||||||
|
|
||||||
enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
|
enum class PrefName(val data: Pref) {
|
||||||
//General
|
//General
|
||||||
SharedUserID(Pref(Location.General, Boolean::class, true)),
|
SharedUserID(Pref(Location.General, Boolean::class, true)),
|
||||||
OfflineView(Pref(Location.General, Int::class, 0)),
|
OfflineView(Pref(Location.General, Int::class, 0)),
|
||||||
|
@ -32,10 +33,15 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
|
||||||
),
|
),
|
||||||
AnimeExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
|
AnimeExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
|
||||||
MangaExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
|
MangaExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
|
||||||
|
NovelExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
|
||||||
AnimeSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
|
AnimeSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
|
||||||
AnimeSearchHistory(Pref(Location.General, Set::class, setOf<String>())),
|
|
||||||
MangaSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
|
MangaSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
|
||||||
MangaSearchHistory(Pref(Location.General, Set::class, setOf<String>())),
|
SortedAnimeSH(Pref(Location.General, List::class, listOf<SearchHistory>())),
|
||||||
|
SortedMangaSH(Pref(Location.General, List::class, listOf<SearchHistory>())),
|
||||||
|
SortedCharacterSH(Pref(Location.General, List::class, listOf<SearchHistory>())),
|
||||||
|
SortedStaffSH(Pref(Location.General, List::class, listOf<SearchHistory>())),
|
||||||
|
SortedStudioSH(Pref(Location.General, List::class, listOf<SearchHistory>())),
|
||||||
|
SortedUserSH(Pref(Location.General, List::class, listOf<SearchHistory>())),
|
||||||
NovelSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
|
NovelSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
|
||||||
CommentNotificationInterval(Pref(Location.General, Int::class, 0)),
|
CommentNotificationInterval(Pref(Location.General, Int::class, 0)),
|
||||||
AnilistNotificationInterval(Pref(Location.General, Int::class, 3)),
|
AnilistNotificationInterval(Pref(Location.General, Int::class, 3)),
|
||||||
|
@ -49,6 +55,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
|
||||||
CommentsEnabled(Pref(Location.General, Int::class, 0)),
|
CommentsEnabled(Pref(Location.General, Int::class, 0)),
|
||||||
EnableSocks5Proxy(Pref(Location.General, Boolean::class, false)),
|
EnableSocks5Proxy(Pref(Location.General, Boolean::class, false)),
|
||||||
ProxyAuthEnabled(Pref(Location.General, Boolean::class, false)),
|
ProxyAuthEnabled(Pref(Location.General, Boolean::class, false)),
|
||||||
|
AniMangaSearchDirect(Pref(Location.General, Boolean::class, true)),
|
||||||
|
|
||||||
//User Interface
|
//User Interface
|
||||||
UseOLED(Pref(Location.UI, Boolean::class, false)),
|
UseOLED(Pref(Location.UI, Boolean::class, false)),
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
package ani.dantotsu.settings.saving
|
package ani.dantotsu.settings.saving
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.util.Base64
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ObjectInputStream
|
||||||
|
|
||||||
abstract class SharedPreferenceLiveData<T>(
|
abstract class SharedPreferenceLiveData<T>(
|
||||||
val sharedPrefs: SharedPreferences,
|
val sharedPrefs: SharedPreferences,
|
||||||
|
@ -78,6 +82,41 @@ class SharedPreferenceStringSetLiveData(
|
||||||
sharedPrefs.getStringSet(key, defValue)?.toSet() ?: defValue
|
sharedPrefs.getStringSet(key, defValue)?.toSet() ?: defValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class SharedPreferenceClassLiveData<T>(
|
||||||
|
sharedPrefs: SharedPreferences,
|
||||||
|
key: String,
|
||||||
|
defValue: T
|
||||||
|
) : SharedPreferenceLiveData<T>(sharedPrefs, key, defValue) {
|
||||||
|
override fun getValueFromPreferences(key: String, defValue: T): T {
|
||||||
|
return try {
|
||||||
|
val serialized = sharedPrefs.getString(key, null)
|
||||||
|
if (serialized != null) {
|
||||||
|
val data = Base64.decode(serialized, Base64.DEFAULT)
|
||||||
|
val bis = ByteArrayInputStream(data)
|
||||||
|
val ois = ObjectInputStream(bis)
|
||||||
|
val obj = ois.readObject() as T
|
||||||
|
obj
|
||||||
|
} else {
|
||||||
|
Logger.log("Serialized data is null (key: $key)")
|
||||||
|
defValue
|
||||||
|
}
|
||||||
|
} catch (e: java.io.InvalidClassException) {
|
||||||
|
Logger.log(e)
|
||||||
|
try {
|
||||||
|
sharedPrefs.edit().remove(key).apply()
|
||||||
|
defValue
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.log(e)
|
||||||
|
defValue
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.log(e)
|
||||||
|
defValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun SharedPreferences.intLiveData(key: String, defValue: Int): SharedPreferenceLiveData<Int> {
|
fun SharedPreferences.intLiveData(key: String, defValue: Int): SharedPreferenceLiveData<Int> {
|
||||||
return SharedPreferenceIntLiveData(this, key, defValue)
|
return SharedPreferenceIntLiveData(this, key, defValue)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
|
|
@ -29,8 +29,6 @@ class SourcePreferences(
|
||||||
fun migrationSortingDirection() =
|
fun migrationSortingDirection() =
|
||||||
preferenceStore.getEnum("pref_migration_direction", SetMigrateSorting.Direction.ASCENDING)
|
preferenceStore.getEnum("pref_migration_direction", SetMigrateSorting.Direction.ASCENDING)
|
||||||
|
|
||||||
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
|
|
||||||
|
|
||||||
// Mixture Sources
|
// Mixture Sources
|
||||||
|
|
||||||
fun disabledAnimeSources() = preferenceStore.getStringSet("hidden_anime_catalogues", emptySet())
|
fun disabledAnimeSources() = preferenceStore.getStringSet("hidden_anime_catalogues", emptySet())
|
||||||
|
|
|
@ -253,45 +253,6 @@ class AnimeExtensionManager(
|
||||||
installer.uninstallApk(pkgName)
|
installer.uninstallApk(pkgName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the given signature to the list of trusted signatures. It also loads in background the
|
|
||||||
* anime extensions that match this signature.
|
|
||||||
*
|
|
||||||
* @param signature The signature to whitelist.
|
|
||||||
*/
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun trustSignature(signature: String) {
|
|
||||||
val untrustedSignatures =
|
|
||||||
_untrustedAnimeExtensionsFlow.value.map { it.signatureHash }.toSet()
|
|
||||||
if (signature !in untrustedSignatures) return
|
|
||||||
|
|
||||||
ExtensionLoader.trustedSignaturesAnime += signature
|
|
||||||
preferences.trustedSignatures() += signature
|
|
||||||
|
|
||||||
val nowTrustedAnimeExtensions =
|
|
||||||
_untrustedAnimeExtensionsFlow.value.filter { it.signatureHash == signature }
|
|
||||||
_untrustedAnimeExtensionsFlow.value -= nowTrustedAnimeExtensions
|
|
||||||
|
|
||||||
val ctx = context
|
|
||||||
launchNow {
|
|
||||||
nowTrustedAnimeExtensions
|
|
||||||
.map { animeextension ->
|
|
||||||
async {
|
|
||||||
ExtensionLoader.loadAnimeExtensionFromPkgName(
|
|
||||||
ctx,
|
|
||||||
animeextension.pkgName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map { it.await() }
|
|
||||||
.forEach { result ->
|
|
||||||
if (result is AnimeLoadResult.Success) {
|
|
||||||
registerNewExtension(result.extension)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the given anime extension in this and the source managers.
|
* Registers the given anime extension in this and the source managers.
|
||||||
*
|
*
|
||||||
|
@ -375,7 +336,7 @@ class AnimeExtensionManager(
|
||||||
private fun AnimeExtension.Installed.updateExists(availableAnimeExtension: AnimeExtension.Available? = null): Boolean {
|
private fun AnimeExtension.Installed.updateExists(availableAnimeExtension: AnimeExtension.Available? = null): Boolean {
|
||||||
val availableExt = availableAnimeExtension
|
val availableExt = availableAnimeExtension
|
||||||
?: _availableAnimeExtensionsFlow.value.find { it.pkgName == pkgName }
|
?: _availableAnimeExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||||
if (isUnofficial || availableExt == null) return false
|
if (availableExt == null) return false
|
||||||
|
|
||||||
return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
|
return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.kanade.tachiyomi.extension.api
|
package eu.kanade.tachiyomi.extension.api
|
||||||
|
|
||||||
|
import ani.dantotsu.parsers.novel.AvailableNovelSources
|
||||||
|
import ani.dantotsu.parsers.novel.NovelExtension
|
||||||
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 ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
|
@ -66,13 +68,18 @@ internal class ExtensionGithubApi {
|
||||||
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos).toMutableList()
|
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos).toMutableList()
|
||||||
|
|
||||||
repos.forEach {
|
repos.forEach {
|
||||||
|
val repoUrl = if (it.contains("index.min.json")) {
|
||||||
|
it
|
||||||
|
} else {
|
||||||
|
"$it${if (it.endsWith('/')) "" else "/"}index.min.json"
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
val githubResponse = try {
|
val githubResponse = try {
|
||||||
networkService.client
|
networkService.client
|
||||||
.newCall(GET("${it}/index.min.json"))
|
.newCall(GET(repoUrl))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Logger.log("Failed to get repo: $it")
|
Logger.log("Failed to get repo: $repoUrl")
|
||||||
Logger.log(e)
|
Logger.log(e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
@ -101,7 +108,7 @@ internal class ExtensionGithubApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAnimeApkUrl(extension: AnimeExtension.Available): String {
|
fun getAnimeApkUrl(extension: AnimeExtension.Available): String {
|
||||||
return "${extension.repository}/apk/${extension.apkName}"
|
return "${extension.repository.removeSuffix("index.min.json")}/apk/${extension.apkName}"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<ExtensionSourceJsonObject>.toMangaExtensionSources(): List<AvailableMangaSources> {
|
private fun List<ExtensionSourceJsonObject>.toMangaExtensionSources(): List<AvailableMangaSources> {
|
||||||
|
@ -189,7 +196,93 @@ internal class ExtensionGithubApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMangaApkUrl(extension: MangaExtension.Available): String {
|
fun getMangaApkUrl(extension: MangaExtension.Available): String {
|
||||||
return "${extension.repository}/apk/${extension.apkName}"
|
return "${extension.repository.removeSuffix("index.min.json")}/apk/${extension.apkName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun findNovelExtensions(): List<NovelExtension.Available> {
|
||||||
|
return withIOContext {
|
||||||
|
|
||||||
|
val extensions: ArrayList<NovelExtension.Available> = arrayListOf()
|
||||||
|
|
||||||
|
val repos =
|
||||||
|
PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos).toMutableList()
|
||||||
|
|
||||||
|
repos.forEach {
|
||||||
|
val repoUrl = if (it.contains("index.min.json")) {
|
||||||
|
it
|
||||||
|
} else {
|
||||||
|
"$it${if (it.endsWith('/')) "" else "/"}index.min.json"
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val githubResponse = try {
|
||||||
|
networkService.client
|
||||||
|
.newCall(GET(repoUrl))
|
||||||
|
.awaitSuccess()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.log("Failed to get repo: $repoUrl")
|
||||||
|
Logger.log(e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = githubResponse ?: run {
|
||||||
|
networkService.client
|
||||||
|
.newCall(GET(fallbackRepoUrl(it) + "/index.min.json"))
|
||||||
|
.awaitSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
val repoExtensions = with(json) {
|
||||||
|
response
|
||||||
|
.parseAs<List<ExtensionJsonObject>>()
|
||||||
|
.toNovelExtensions(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.addAll(repoExtensions)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.log("Failed to get extensions from GitHub")
|
||||||
|
Logger.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<ExtensionJsonObject>.toNovelExtensions(repository: String): List<NovelExtension.Available> {
|
||||||
|
return mapNotNull { extension ->
|
||||||
|
val sources = extension.sources?.map { source ->
|
||||||
|
ExtensionSourceJsonObject(
|
||||||
|
source.id,
|
||||||
|
source.lang,
|
||||||
|
source.name,
|
||||||
|
source.baseUrl,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val iconUrl = "${repository.removeSuffix("/index.min.json")}/icon/${extension.pkg}.png"
|
||||||
|
NovelExtension.Available(
|
||||||
|
extension.name,
|
||||||
|
extension.pkg,
|
||||||
|
extension.apk,
|
||||||
|
extension.code,
|
||||||
|
repository,
|
||||||
|
sources?.toNovelSources() ?: emptyList(),
|
||||||
|
iconUrl,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<ExtensionSourceJsonObject>.toNovelSources(): List<AvailableNovelSources> {
|
||||||
|
return map { source ->
|
||||||
|
AvailableNovelSources(
|
||||||
|
source.id,
|
||||||
|
source.lang,
|
||||||
|
source.name,
|
||||||
|
source.baseUrl,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNovelApkUrl(extension: NovelExtension.Available): String {
|
||||||
|
return "${extension.repository.removeSuffix("index.min.json")}/apk/${extension.pkgName}.apk"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fallbackRepoUrl(repoUrl: String): String? {
|
private fun fallbackRepoUrl(repoUrl: String): String? {
|
||||||
|
|
|
@ -249,44 +249,6 @@ class MangaExtensionManager(
|
||||||
installer.uninstallApk(pkgName)
|
installer.uninstallApk(pkgName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the given signature to the list of trusted signatures. It also loads in background the
|
|
||||||
* extensions that match this signature.
|
|
||||||
*
|
|
||||||
* @param signature The signature to whitelist.
|
|
||||||
*/
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun trustSignature(signature: String) {
|
|
||||||
val untrustedSignatures = _untrustedExtensionsFlow.value.map { it.signatureHash }.toSet()
|
|
||||||
if (signature !in untrustedSignatures) return
|
|
||||||
|
|
||||||
ExtensionLoader.trustedSignaturesManga += signature
|
|
||||||
preferences.trustedSignatures() += signature
|
|
||||||
|
|
||||||
val nowTrustedExtensions =
|
|
||||||
_untrustedExtensionsFlow.value.filter { it.signatureHash == signature }
|
|
||||||
_untrustedExtensionsFlow.value -= nowTrustedExtensions
|
|
||||||
|
|
||||||
val ctx = context
|
|
||||||
launchNow {
|
|
||||||
nowTrustedExtensions
|
|
||||||
.map { extension ->
|
|
||||||
async {
|
|
||||||
ExtensionLoader.loadMangaExtensionFromPkgName(
|
|
||||||
ctx,
|
|
||||||
extension.pkgName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map { it.await() }
|
|
||||||
.forEach { result ->
|
|
||||||
if (result is MangaLoadResult.Success) {
|
|
||||||
registerNewExtension(result.extension)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the given extension in this and the source managers.
|
* Registers the given extension in this and the source managers.
|
||||||
*
|
*
|
||||||
|
@ -368,7 +330,7 @@ class MangaExtensionManager(
|
||||||
private fun MangaExtension.Installed.updateExists(availableExtension: MangaExtension.Available? = null): Boolean {
|
private fun MangaExtension.Installed.updateExists(availableExtension: MangaExtension.Available? = null): Boolean {
|
||||||
val availableExt =
|
val availableExt =
|
||||||
availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName }
|
availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||||
if (isUnofficial || availableExt == null) return false
|
if (availableExt == null) return false
|
||||||
|
|
||||||
return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
|
return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,27 +73,6 @@ internal object ExtensionLoader {
|
||||||
(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
||||||
PackageManager.GET_SIGNING_CERTIFICATES else 0)
|
PackageManager.GET_SIGNING_CERTIFICATES else 0)
|
||||||
|
|
||||||
// jmir1's key
|
|
||||||
private const val officialSignatureAnime =
|
|
||||||
"50ab1d1e3a20d204d0ad6d334c7691c632e41b98dfa132bf385695fdfa63839c"
|
|
||||||
|
|
||||||
var trustedSignaturesAnime =
|
|
||||||
mutableSetOf<String>() + preferences.trustedSignatures().get() + officialSignatureAnime
|
|
||||||
|
|
||||||
// inorichi's key
|
|
||||||
private const val officialSignatureManga =
|
|
||||||
"7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
|
|
||||||
|
|
||||||
//dan's key
|
|
||||||
private const val officialSignature =
|
|
||||||
"a3061edb369278749b8e8de810d440d38e96417bbd67bbdfc5d9d9ed475ce4a5"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of the trusted signatures.
|
|
||||||
*/
|
|
||||||
var trustedSignaturesManga =
|
|
||||||
mutableSetOf<String>() + preferences.trustedSignatures().get() + officialSignatureManga
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a list of all the installed extensions initialized concurrently.
|
* Return a list of all the installed extensions initialized concurrently.
|
||||||
*
|
*
|
||||||
|
@ -256,8 +235,6 @@ internal object ExtensionLoader {
|
||||||
return AnimeLoadResult.Error
|
return AnimeLoadResult.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
val signatureHash = getSignatureHash(pkgInfo)
|
|
||||||
|
|
||||||
val isNsfw = appInfo.metaData.getInt("$ANIME_PACKAGE$XX_METADATA_NSFW") == 1
|
val isNsfw = appInfo.metaData.getInt("$ANIME_PACKAGE$XX_METADATA_NSFW") == 1
|
||||||
if (!loadNsfwSource && isNsfw) {
|
if (!loadNsfwSource && isNsfw) {
|
||||||
Logger.log("NSFW extension $pkgName not allowed")
|
Logger.log("NSFW extension $pkgName not allowed")
|
||||||
|
@ -321,7 +298,7 @@ internal object ExtensionLoader {
|
||||||
hasChangelog = hasChangelog,
|
hasChangelog = hasChangelog,
|
||||||
sources = sources,
|
sources = sources,
|
||||||
pkgFactory = appInfo.metaData.getString("$ANIME_PACKAGE$XX_METADATA_SOURCE_FACTORY"),
|
pkgFactory = appInfo.metaData.getString("$ANIME_PACKAGE$XX_METADATA_SOURCE_FACTORY"),
|
||||||
isUnofficial = signatureHash != officialSignatureAnime,
|
isUnofficial = true,
|
||||||
icon = context.getApplicationIcon(pkgName),
|
icon = context.getApplicationIcon(pkgName),
|
||||||
)
|
)
|
||||||
return AnimeLoadResult.Success(extension)
|
return AnimeLoadResult.Success(extension)
|
||||||
|
@ -362,8 +339,6 @@ internal object ExtensionLoader {
|
||||||
return MangaLoadResult.Error
|
return MangaLoadResult.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
val signatureHash = getSignatureHash(pkgInfo)
|
|
||||||
|
|
||||||
val isNsfw = appInfo.metaData.getInt("$MANGA_PACKAGE$XX_METADATA_NSFW") == 1
|
val isNsfw = appInfo.metaData.getInt("$MANGA_PACKAGE$XX_METADATA_NSFW") == 1
|
||||||
if (!loadNsfwSource && isNsfw) {
|
if (!loadNsfwSource && isNsfw) {
|
||||||
Logger.log("NSFW extension $pkgName not allowed")
|
Logger.log("NSFW extension $pkgName not allowed")
|
||||||
|
@ -427,7 +402,7 @@ internal object ExtensionLoader {
|
||||||
hasChangelog = hasChangelog,
|
hasChangelog = hasChangelog,
|
||||||
sources = sources,
|
sources = sources,
|
||||||
pkgFactory = appInfo.metaData.getString("$MANGA_PACKAGE$XX_METADATA_SOURCE_FACTORY"),
|
pkgFactory = appInfo.metaData.getString("$MANGA_PACKAGE$XX_METADATA_SOURCE_FACTORY"),
|
||||||
isUnofficial = signatureHash != officialSignatureManga,
|
isUnofficial = true,
|
||||||
icon = context.getApplicationIcon(pkgName),
|
icon = context.getApplicationIcon(pkgName),
|
||||||
)
|
)
|
||||||
return MangaLoadResult.Success(extension)
|
return MangaLoadResult.Success(extension)
|
||||||
|
@ -458,8 +433,6 @@ internal object ExtensionLoader {
|
||||||
return NovelLoadResult.Error(Exception("Missing versionName for extension $extName"))
|
return NovelLoadResult.Error(Exception("Missing versionName for extension $extName"))
|
||||||
}
|
}
|
||||||
|
|
||||||
val signatureHash = getSignatureHash(pkgInfo)
|
|
||||||
|
|
||||||
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
|
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
|
||||||
val novelInterfaceInstance = try {
|
val novelInterfaceInstance = try {
|
||||||
val className = appInfo.loadLabel(context.packageManager).toString()
|
val className = appInfo.loadLabel(context.packageManager).toString()
|
||||||
|
@ -479,7 +452,7 @@ internal object ExtensionLoader {
|
||||||
versionName = versionName,
|
versionName = versionName,
|
||||||
versionCode = versionCode,
|
versionCode = versionCode,
|
||||||
sources = listOfNotNull(novelInterfaceInstance),
|
sources = listOfNotNull(novelInterfaceInstance),
|
||||||
isUnofficial = signatureHash != officialSignatureManga,
|
isUnofficial = true,
|
||||||
icon = context.getApplicationIcon(pkgName),
|
icon = context.getApplicationIcon(pkgName),
|
||||||
)
|
)
|
||||||
return NovelLoadResult.Success(extension)
|
return NovelLoadResult.Success(extension)
|
||||||
|
@ -505,21 +478,4 @@ internal object ExtensionLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the signature hash of the package or null if it's not signed.
|
|
||||||
*
|
|
||||||
* @param pkgInfo The package info of the application.
|
|
||||||
*/
|
|
||||||
private fun getSignatureHash(pkgInfo: PackageInfo): String? {
|
|
||||||
val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
|
||||||
pkgInfo.signingInfo?.signingCertificateHistory
|
|
||||||
else
|
|
||||||
@Suppress("DEPRECATION") pkgInfo.signatures
|
|
||||||
return if (signatures != null && signatures.isNotEmpty()) {
|
|
||||||
Hash.sha256(signatures.first().toByteArray())
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,111 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/colorSurface">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/studioTitle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:ellipsize="marquee"
|
|
||||||
android:fontFamily="@font/poppins_bold"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="@color/bg_opp"
|
|
||||||
android:textSize="20sp"
|
|
||||||
tools:text="@string/name" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="64dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/charactersText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginEnd="32dp"
|
|
||||||
android:fontFamily="@font/poppins_bold"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:text="@string/characters"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<ani.dantotsu.FadingEdgeRecyclerView
|
|
||||||
android:id="@+id/charactersRecycler"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:nestedScrollingEnabled="true"
|
|
||||||
android:paddingStart="20dp"
|
|
||||||
android:paddingEnd="20dp"
|
|
||||||
android:requiresFadingEdge="horizontal"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:itemCount="4"
|
|
||||||
tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
|
||||||
tools:listitem="@layout/item_media_compact"
|
|
||||||
tools:orientation="horizontal"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/studioRecycler"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:paddingTop="16dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
|
||||||
tools:itemCount="2"
|
|
||||||
tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
|
||||||
tools:listitem="@layout/item_media_compact"
|
|
||||||
tools:orientation="vertical"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/studioProgressBar"
|
|
||||||
style="?android:attr/progressBarStyle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="32dp"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
|
||||||
tools:visibility="gone" />
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:id="@+id/studioClose"
|
|
||||||
android:layout_width="32dp"
|
|
||||||
android:layout_height="32dp"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:translationZ="2dp"
|
|
||||||
app:cardBackgroundColor="@color/nav_bg"
|
|
||||||
app:cardCornerRadius="16dp">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@drawable/ic_round_close_24"
|
|
||||||
tools:ignore="ContentDescription" />
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
|
@ -104,6 +104,58 @@
|
||||||
android:indeterminate="true"
|
android:indeterminate="true"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/authorCharacterDesc"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:alpha="0.58"
|
||||||
|
tools:ignore="TextContrastCheck"
|
||||||
|
tools:maxLines="10"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/AuthorCharactersText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/characters"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
|
<ani.dantotsu.FadingEdgeRecyclerView
|
||||||
|
android:id="@+id/authorCharactersRecycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:nestedScrollingEnabled="true"
|
||||||
|
android:requiresFadingEdge="horizontal"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:itemCount="4"
|
||||||
|
tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
|
tools:listitem="@layout/item_media_compact"
|
||||||
|
tools:orientation="horizontal"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<ani.dantotsu.FadingEdgeRecyclerView
|
<ani.dantotsu.FadingEdgeRecyclerView
|
||||||
android:id="@+id/characterRecyclerView"
|
android:id="@+id/characterRecyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -118,6 +170,9 @@
|
||||||
tools:layoutManager="GridLayoutManager"
|
tools:layoutManager="GridLayoutManager"
|
||||||
tools:listitem="@layout/item_media_compact"
|
tools:listitem="@layout/item_media_compact"
|
||||||
tools:orientation="vertical" />
|
tools:orientation="vertical" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
android:id="@+id/characterClose"
|
android:id="@+id/characterClose"
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/bottom_sheet_background"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|
126
app/src/main/res/layout/bottom_sheet_search.xml
Normal file
126
app/src/main/res/layout/bottom_sheet_search.xml
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/bottom_sheet_background"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/search"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:textSize="17sp"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/animeSearch"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:paddingStart="32dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
|
android:text="@string/anime"
|
||||||
|
android:textSize="17sp"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="?attr/colorOnBackground"
|
||||||
|
app:cornerRadius="0dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/mangaSearch"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:paddingStart="32dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
|
android:text="@string/manga"
|
||||||
|
android:textSize="17sp"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="?attr/colorOnBackground"
|
||||||
|
app:cornerRadius="0dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/userSearch"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:paddingStart="32dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
|
android:text="@string/users"
|
||||||
|
android:textSize="17sp"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="?attr/colorOnBackground"
|
||||||
|
app:cornerRadius="0dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/characterSearch"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:paddingStart="32dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
|
android:text="@string/characters"
|
||||||
|
android:textSize="17sp"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="?attr/colorOnBackground"
|
||||||
|
app:cornerRadius="0dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/staffSearch"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:paddingStart="32dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
|
android:text="@string/staff"
|
||||||
|
android:textSize="17sp"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="?attr/colorOnBackground"
|
||||||
|
app:cornerRadius="0dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/studioSearch"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:paddingStart="32dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
|
android:text="@string/studios"
|
||||||
|
android:textSize="17sp"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="?attr/colorOnBackground"
|
||||||
|
app:cornerRadius="0dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -146,6 +146,36 @@
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="10dp">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/searchImageContainer"
|
||||||
|
android:layout_width="52dp"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:backgroundTint="@color/nav_bg_inv"
|
||||||
|
android:scaleX="0.96"
|
||||||
|
android:scaleY="0.96"
|
||||||
|
app:cardCornerRadius="26dp">
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/searchImage"
|
||||||
|
android:layout_width="52dp"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:scaleX="1.2"
|
||||||
|
android:scaleY="1.2"
|
||||||
|
android:tint="@color/bg_white"
|
||||||
|
app:srcCompat="@drawable/ic_round_search_24"
|
||||||
|
tools:ignore="ContentDescription,ImageContrastCheck" />
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
|
android:scaleX="0.8"
|
||||||
|
android:scaleY="0.8"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_marginStart="3dp"
|
android:layout_marginStart="3dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
@ -32,4 +34,18 @@
|
||||||
app:tint="?attr/colorOnBackground"
|
app:tint="?attr/colorOnBackground"
|
||||||
tools:ignore="ContentDescription" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/repoCopyImageView"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:scaleX="0.8"
|
||||||
|
android:scaleY="0.8"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="3dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
app:srcCompat="@drawable/format_link_24"
|
||||||
|
app:tint="?attr/colorOnBackground"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
|
@ -395,6 +395,8 @@
|
||||||
<string name="planned_manga">Planned Manga</string>
|
<string name="planned_manga">Planned Manga</string>
|
||||||
<string name="image_long_clicking">Open image by Long Clicking</string>
|
<string name="image_long_clicking">Open image by Long Clicking</string>
|
||||||
<string name="always_continue_content">Always continue previous items</string>
|
<string name="always_continue_content">Always continue previous items</string>
|
||||||
|
<string name="open_animanga_directly">Open Anime/Manga search directly</string>
|
||||||
|
<string name="open_animanga_directly_info">Open Anime/Manga search on their respective pages directly</string>
|
||||||
<string name="search_source_list">Search next available source</string>
|
<string name="search_source_list">Search next available source</string>
|
||||||
<string name="timestamp_proxy_desc">Useful if you are getting Handshake Fails</string>
|
<string name="timestamp_proxy_desc">Useful if you are getting Handshake Fails</string>
|
||||||
<string name="timestamp_proxy">Use Proxy for Timestamps</string>
|
<string name="timestamp_proxy">Use Proxy for Timestamps</string>
|
||||||
|
@ -624,6 +626,9 @@
|
||||||
<string name="age">"__Age:__ "</string>
|
<string name="age">"__Age:__ "</string>
|
||||||
<string name="birthday">\n"__Birthday:__ "</string>
|
<string name="birthday">\n"__Birthday:__ "</string>
|
||||||
<string name="gender">\n"__Gender:__ "</string>"
|
<string name="gender">\n"__Gender:__ "</string>"
|
||||||
|
<string name="hometown">\n"__Home Town:__ "</string>
|
||||||
|
<string name="years_active">\n"__Years Active:__ "</string>
|
||||||
|
<string name="date_of_death">\n"__Date of Death:__ "</string>
|
||||||
|
|
||||||
<string name="male">Male</string>
|
<string name="male">Male</string>
|
||||||
<string name="female">Female</string>
|
<string name="female">Female</string>
|
||||||
|
@ -896,6 +901,7 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
|
||||||
|
|
||||||
<string name="anime_add_repository">Add Anime Repo</string>
|
<string name="anime_add_repository">Add Anime Repo</string>
|
||||||
<string name="manga_add_repository">Add Manga Repo</string>
|
<string name="manga_add_repository">Add Manga Repo</string>
|
||||||
|
<string name="novel_add_repository">Add Novel Repo</string>
|
||||||
<string name="edit_repositories">Edit repositories</string>
|
<string name="edit_repositories">Edit repositories</string>
|
||||||
<string name="rem_repository">Remove repository?</string>
|
<string name="rem_repository">Remove repository?</string>
|
||||||
|
|
||||||
|
@ -963,6 +969,7 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
|
||||||
<string name="adult_only_content_desc">Show only adult content in the explore page</string>
|
<string name="adult_only_content_desc">Show only adult content in the explore page</string>
|
||||||
<string name="anime_add_repository_desc">Add Anime Extensions from various sources</string>
|
<string name="anime_add_repository_desc">Add Anime Extensions from various sources</string>
|
||||||
<string name="manga_add_repository_desc">Add Manga Extensions from various sources</string>
|
<string name="manga_add_repository_desc">Add Manga Extensions from various sources</string>
|
||||||
|
<string name="novel_add_repository_desc">Add Novel Extensions from various sources</string>
|
||||||
<string name="user_agent_desc">Change your default user agent</string>
|
<string name="user_agent_desc">Change your default user agent</string>
|
||||||
<string name="force_legacy_installer_desc">Use the legacy installer to install extensions (For older android phones)</string>
|
<string name="force_legacy_installer_desc">Use the legacy installer to install extensions (For older android phones)</string>
|
||||||
<string name="skip_loading_extension_icons_desc">Don\'t load icons of extensions on the extension page</string>
|
<string name="skip_loading_extension_icons_desc">Don\'t load icons of extensions on the extension page</string>
|
||||||
|
@ -1089,11 +1096,12 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
|
||||||
<string name="textview_sub_stroke">Subtitle Stroke</string>
|
<string name="textview_sub_stroke">Subtitle Stroke</string>
|
||||||
<string name="textview_sub_bottom_margin">Bottom Margin</string>
|
<string name="textview_sub_bottom_margin">Bottom Margin</string>
|
||||||
<string name="add_repository">Add Repository</string>
|
<string name="add_repository">Add Repository</string>
|
||||||
<string name="add_repository_desc">A repository link should look like this: https://raw.githubusercontent.com/username/repo/branch/index.min.json</string>
|
<string name="add_repository_desc">A repository link should look like this: https://raw.githubusercontent.com/username/repo/branch/index.min.json\nOr: username/repo/branch</string>
|
||||||
<string name="current_repositories">Current Repositories</string>
|
<string name="current_repositories">Current Repositories</string>
|
||||||
<string name="add_repository_warning">Warning: Extensions from the repository can run arbitrary code on your device. Only use repositories you trust. \n\nBy adding a repository, you agree to: \n\n1. Not use the app for viewing or distributing copyrighted content. \n2. Not use the app for any illegal activities. \n3. Not use the app for any activities that violate the terms of service of the content providers. \n\nThe app or it\'s maintainer are not affiliated in any way with extension providers. The developers are not responsible for any damages caused by the app. \n\nBy adding a repository, you agree to these terms.</string>
|
<string name="add_repository_warning">Warning: Extensions from the repository can run arbitrary code on your device. Only use repositories you trust. \n\nBy adding a repository, you agree to: \n\n1. Not use the app for viewing or distributing copyrighted content. \n2. Not use the app for any illegal activities. \n3. Not use the app for any activities that violate the terms of service of the content providers. \n\nThe app or it\'s maintainer are not affiliated in any way with extension providers. The developers are not responsible for any damages caused by the app. \n\nBy adding a repository, you agree to these terms.</string>
|
||||||
<string name="privacy_policy">Privacy Policy</string>
|
<string name="privacy_policy">Privacy Policy</string>
|
||||||
<string name="privacy_policy_desc">Read our privacy policy</string>
|
<string name="privacy_policy_desc">Read our privacy policy</string>
|
||||||
<string name="failed_to_load">Failed to load</string>
|
<string name="failed_to_load">Failed to load</string>
|
||||||
|
<string name="studios">Studios</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue