fix(it/animeunity): Update playlist name, remove no longer existent URL

argument, fix file name without proper extension (#2694)
This commit is contained in:
giorgionegro 2024-01-02 22:50:53 +01:00 committed by GitHub
parent 328b0daff1
commit d6d20c08ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 278 additions and 189 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'AnimeUnity' extName = 'AnimeUnity'
pkgNameSuffix = 'it.animeunity' pkgNameSuffix = 'it.animeunity'
extClass = '.AnimeUnity' extClass = '.AnimeUnity'
extVersionCode = 6 extVersionCode = 7
libVersion = '13' libVersion = '13'
} }

View File

@ -36,8 +36,9 @@ import uy.kohesive.injekt.injectLazy
import java.lang.Exception import java.lang.Exception
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() { class AnimeUnity :
AnimeHttpSource(),
ConfigurableAnimeSource {
override val name = "AnimeUnity" override val name = "AnimeUnity"
override val baseUrl by lazy { preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! } override val baseUrl by lazy { preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! }
@ -56,23 +57,25 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request = override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/top-anime?popular=true&page=$page", headers = headers)
GET("$baseUrl/top-anime?popular=true&page=$page", headers = headers)
override fun popularAnimeParse(response: Response): AnimesPage { override fun popularAnimeParse(response: Response): AnimesPage {
val parsed = response.parseAs<AnimeResponse> { val parsed =
it.substringAfter("top-anime animes=\"") response.parseAs<AnimeResponse> {
.substringBefore("\"></top-anime>") it
.replace("&quot;", "\"") .substringAfter("top-anime animes=\"")
} .substringBefore("\"></top-anime>")
.replace("&quot;", "\"")
val animeList = parsed.data.map { ani -> }
SAnime.create().apply {
title = ani.title_eng val animeList =
url = "${ani.id}-${ani.slug}" parsed.data.map { ani ->
thumbnail_url = ani.imageurl ?: "" SAnime.create().apply {
title = ani.title_eng
url = "${ani.id}-${ani.slug}"
thumbnail_url = ani.imageurl ?: ""
}
} }
}
return AnimesPage(animeList, parsed.current_page < parsed.last_page) return AnimesPage(animeList, parsed.current_page < parsed.last_page)
} }
@ -84,13 +87,14 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
override fun latestUpdatesParse(response: Response): AnimesPage { override fun latestUpdatesParse(response: Response): AnimesPage {
val document = response.asJsoup() val document = response.asJsoup()
val animeList = document.select("div.home-wrapper-body > div.row > div.latest-anime-container").map { val animeList =
SAnime.create().apply { document.select("div.home-wrapper-body > div.row > div.latest-anime-container").map {
title = it.select("a > strong").text() SAnime.create().apply {
url = it.selectFirst("a")!!.attr("href").substringAfter("/anime/") title = it.select("a > strong").text()
thumbnail_url = it.select("img").attr("src") url = it.selectFirst("a")!!.attr("href").substringAfter("/anime/")
thumbnail_url = it.select("img").attr("src")
}
} }
}
val hasNextPage = document.select("ul.pagination > li.active ~ li").first() != null val hasNextPage = document.select("ul.pagination > li.active ~ li").first() != null
@ -99,21 +103,36 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
// =============================== Search =============================== // =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw Exception("Not used") override fun searchAnimeRequest(
page: Int,
query: String,
filters: AnimeFilterList,
): Request = throw Exception("Not used")
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> { override fun fetchSearchAnime(
page: Int,
query: String,
filters: AnimeFilterList,
): Observable<AnimesPage> {
val params = AnimeUnityFilters.getSearchParameters(filters) val params = AnimeUnityFilters.getSearchParameters(filters)
return client.newCall(searchAnimeRequest(page, query, params)) return client
.newCall(searchAnimeRequest(page, query, params))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
searchAnimeParse(response, page) searchAnimeParse(response, page)
} }
} }
private fun searchAnimeRequest(page: Int, query: String, filters: AnimeUnityFilters.FilterSearchParams): Request { private fun searchAnimeRequest(
val archivioResponse = client.newCall( page: Int,
GET("$baseUrl/archivio", headers = headers), query: String,
).execute() filters: AnimeUnityFilters.FilterSearchParams,
): Request {
val archivioResponse =
client
.newCall(
GET("$baseUrl/archivio", headers = headers),
).execute()
val document = archivioResponse.asJsoup() val document = archivioResponse.asJsoup()
@ -121,68 +140,83 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
var newHeadersBuilder = headers.newBuilder() var newHeadersBuilder = headers.newBuilder()
for (cookie in archivioResponse.headers) { for (cookie in archivioResponse.headers) {
if (cookie.first == "set-cookie" && cookie.second.startsWith("XSRF-TOKEN")) { if (cookie.first == "set-cookie" && cookie.second.startsWith("XSRF-TOKEN")) {
newHeadersBuilder.add("X-XSRF-TOKEN", cookie.second.substringAfter("=").substringBefore(";").replace("%3D", "=")) newHeadersBuilder.add(
"X-XSRF-TOKEN",
cookie
.second
.substringAfter("=")
.substringBefore(";")
.replace("%3D", "="),
)
} }
if (cookie.first == "set-cookie" && cookie.second.startsWith("animeunity_session")) { if (cookie.first == "set-cookie" && cookie.second.startsWith("animeunity_session")) {
newHeadersBuilder.add("Cookie", cookie.second.substringBefore(";").replace("%3D", "=")) newHeadersBuilder.add("Cookie", cookie.second.substringBefore(";").replace("%3D", "="))
} }
} }
newHeadersBuilder.add("X-CSRF-TOKEN", crsfToken) newHeadersBuilder
.add("X-CSRF-TOKEN", crsfToken)
.add("Accept-Language", "en-US,en;q=0.5") .add("Accept-Language", "en-US,en;q=0.5")
.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0") .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
if (filters.top.isNotEmpty()) { if (filters.top.isNotEmpty()) {
val topHeaders = newHeadersBuilder.add("X-CSRF-TOKEN", crsfToken) val topHeaders =
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8") newHeadersBuilder
.add("Referer", "$baseUrl/${filters.top}") .add("X-CSRF-TOKEN", crsfToken)
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Referer", "$baseUrl/${filters.top}")
return GET("$baseUrl/${filters.top}", headers = topHeaders.build()) return GET("$baseUrl/${filters.top}", headers = topHeaders.build())
} }
val searchHeaders = newHeadersBuilder val searchHeaders =
.add("Accept", "application/json, text/plain, */*") newHeadersBuilder
.add("Content-Type", "application/json;charset=utf-8") .add("Accept", "application/json, text/plain, */*")
.add("Origin", baseUrl) .add("Content-Type", "application/json;charset=utf-8")
.add("Referer", archivioResponse.request.url.toString()) .add("Origin", baseUrl)
.add("X-Requested-With", "XMLHttpRequest") .add("Referer", archivioResponse.request.url.toString())
.build() .add("X-Requested-With", "XMLHttpRequest")
.build()
val body = """ val body =
{ """
"title": ${query.falseIfEmpty()}, {
"type": ${filters.type.falseIfEmpty()}, "title": ${query.falseIfEmpty()},
"year": ${filters.year.falseIfEmpty()}, "type": ${filters.type.falseIfEmpty()},
"order": ${filters.order.falseIfEmpty()}, "year": ${filters.year.falseIfEmpty()},
"status": ${filters.state.falseIfEmpty()}, "order": ${filters.order.falseIfEmpty()},
"genres": ${filters.genre.ifEmpty { "false" }}, "status": ${filters.state.falseIfEmpty()},
"offset": ${(page - 1) * 30}, "genres": ${filters.genre.ifEmpty { "false" }},
"dubbed": ${if (filters.dub.isEmpty()) "false" else "true"}, "offset": ${(page - 1) * 30},
"season": ${filters.season.falseIfEmpty()} "dubbed": ${if (filters.dub.isEmpty()) "false" else "true"},
} "season": ${filters.season.falseIfEmpty()}
""".trimIndent().toRequestBody("application/json".toMediaType()) }
""".trimIndent().toRequestBody("application/json".toMediaType())
return POST("$baseUrl/archivio/get-animes", body = body, headers = searchHeaders) return POST("$baseUrl/archivio/get-animes", body = body, headers = searchHeaders)
} }
override fun searchAnimeParse(response: Response): AnimesPage = throw Exception("Not used") override fun searchAnimeParse(response: Response): AnimesPage = throw Exception("Not used")
private fun searchAnimeParse(response: Response, page: Int): AnimesPage { private fun searchAnimeParse(
return if (response.request.method == "POST") { response: Response,
page: Int,
): AnimesPage =
if (response.request.method == "POST") {
val data = response.parseAs<SearchResponse>() val data = response.parseAs<SearchResponse>()
val animeList = data.records.map { val animeList =
SAnime.create().apply { data.records.map {
title = it.title_eng SAnime.create().apply {
thumbnail_url = it.imageurl title = it.title_eng
url = "${it.id}-${it.slug}" thumbnail_url = it.imageurl
url = "${it.id}-${it.slug}"
}
} }
}
AnimesPage(animeList, data.tot - page * 30 >= 30 && data.tot > 30) AnimesPage(animeList, data.tot - page * 30 >= 30 && data.tot > 30)
} else { } else {
popularAnimeParse(response) popularAnimeParse(response)
} }
}
override fun getFilterList(): AnimeFilterList = AnimeUnityFilters.FILTER_LIST override fun getFilterList(): AnimeFilterList = AnimeUnityFilters.FILTER_LIST
@ -195,21 +229,23 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
val videoPlayer = document.selectFirst("video-player[episodes_count]")!! val videoPlayer = document.selectFirst("video-player[episodes_count]")!!
val animeDetails = json.decodeFromString<AnimeInfo>( val animeDetails =
videoPlayer.attr("anime").replace("&quot;", "\""), json.decodeFromString<AnimeInfo>(
) videoPlayer.attr("anime").replace("&quot;", "\""),
)
return SAnime.create().apply { return SAnime.create().apply {
title = animeDetails.title_eng title = animeDetails.title_eng
status = parseStatus(animeDetails.status) status = parseStatus(animeDetails.status)
artist = animeDetails.studio ?: "" artist = animeDetails.studio ?: ""
genre = animeDetails.genres.joinToString(", ") { it.name } genre = animeDetails.genres.joinToString(", ") { it.name }
description = buildString { description =
append(animeDetails.plot) buildString {
append("\n\nTipo: ${animeDetails.type}") append(animeDetails.plot)
append("\nStagione: ${animeDetails.season} ${animeDetails.date}") append("\n\nTipo: ${animeDetails.type}")
append("\nValutazione: ★${animeDetails.score ?: "-"}") append("\nStagione: ${animeDetails.season} ${animeDetails.date}")
} append("\nValutazione: ★${animeDetails.score ?: "-"}")
}
} }
} }
@ -225,14 +261,22 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
var newHeadersBuilder = headers.newBuilder() var newHeadersBuilder = headers.newBuilder()
for (cookie in response.headers) { for (cookie in response.headers) {
if (cookie.first == "set-cookie" && cookie.second.startsWith("XSRF-TOKEN")) { if (cookie.first == "set-cookie" && cookie.second.startsWith("XSRF-TOKEN")) {
newHeadersBuilder.add("X-XSRF-TOKEN", cookie.second.substringAfter("=").substringBefore(";").replace("%3D", "=")) newHeadersBuilder.add(
"X-XSRF-TOKEN",
cookie
.second
.substringAfter("=")
.substringBefore(";")
.replace("%3D", "="),
)
} }
if (cookie.first == "set-cookie" && cookie.second.startsWith("animeunity_session")) { if (cookie.first == "set-cookie" && cookie.second.startsWith("animeunity_session")) {
newHeadersBuilder.add("Cookie", cookie.second.substringBefore(";").replace("%3D", "=")) newHeadersBuilder.add("Cookie", cookie.second.substringBefore(";").replace("%3D", "="))
} }
} }
newHeadersBuilder.add("X-CSRF-TOKEN", crsfToken) newHeadersBuilder
.add("X-CSRF-TOKEN", crsfToken)
.add("Content-Type", "application/json") .add("Content-Type", "application/json")
.add("Referer", response.request.url.toString()) .add("Referer", response.request.url.toString())
.add("Accept", "application/json, text/plain, */*") .add("Accept", "application/json, text/plain, */*")
@ -242,27 +286,38 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
val videoPlayer = document.selectFirst("video-player[episodes_count]")!! val videoPlayer = document.selectFirst("video-player[episodes_count]")!!
val episodeCount = videoPlayer.attr("episodes_count").toInt() val episodeCount = videoPlayer.attr("episodes_count").toInt()
val animeId = response.request.url.toString().substringAfter("/anime/").substringBefore("-") val animeId =
response
.request
.url
.toString()
.substringAfter("/anime/")
.substringBefore("-")
val episodes = json.decodeFromString<List<Episode>>( val episodes =
videoPlayer.attr("episodes").replace("&quot;", "\""), json.decodeFromString<List<Episode>>(
) videoPlayer.attr("episodes").replace("&quot;", "\""),
)
episodeList.addAll( episodeList.addAll(
episodes.filter { episodes
it.id != null .filter {
}.map { it.id != null
SEpisode.create().apply { }.map {
name = "Episode ${it.number}" SEpisode.create().apply {
date_upload = parseDate(it.created_at) name = "Episode ${it.number}"
episode_number = it.number.split("-")[0].toFloatOrNull() ?: 0F date_upload = parseDate(it.created_at)
setUrlWithoutDomain( episode_number = it.number.split("-")[0].toFloatOrNull() ?: 0F
response.request.url.newBuilder() setUrlWithoutDomain(
.addPathSegment(it.id.toString()) response
.toString(), .request
) .url
} .newBuilder()
}, .addPathSegment(it.id.toString())
.toString(),
)
}
},
) )
if (episodeCount > 120) { if (episodeCount > 120) {
@ -291,48 +346,66 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> { override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val doc =
client
.newCall(
GET(baseUrl + episode.url, headers),
).execute()
.asJsoup()
val iframeUrl =
doc.selectFirst("video-player[embed_url]")?.attr("abs:embed_url")
?: error("Failed to extract iframe")
val iframeHeaders =
headers
.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Host", iframeUrl.toHttpUrl().host)
.add("Referer", "$baseUrl/")
.build()
val doc = client.newCall( val iframe =
GET(baseUrl + episode.url, headers), client
).execute().asJsoup() .newCall(
val iframeUrl = doc.selectFirst("video-player[embed_url]")?.attr("abs:embed_url") ?: error("Failed to extract iframe") GET(iframeUrl, headers = iframeHeaders),
).execute()
.asJsoup()
val scripts = iframe.select("script")
val script = scripts.find { it.data().contains("masterPlaylist") }!!.data().replace("\n", "\t")
var playlistUrl = Regex("""url: ?'(.*?)'""").find(script)!!.groupValues[1]
val filename = playlistUrl.slice(playlistUrl.lastIndexOf("/") + 1 until playlistUrl.length)
if (!filename.endsWith(".m3u8")) {
playlistUrl = playlistUrl.replace(filename, filename + ".m3u8")
}
val iframeHeaders = headers.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Host", iframeUrl.toHttpUrl().host)
.add("Referer", "$baseUrl/")
.build()
val iframe = client.newCall(
GET(iframeUrl, headers = iframeHeaders),
).execute().asJsoup()
val script = iframe.selectFirst("script:containsData(masterPlaylistParams)")!!.data()
val playlistUrl = Regex("""masterPlaylistUrl.*?'(.*?)'""").find(script)!!.groupValues[1]
val expires = Regex("""'expires': ?'(\d+)'""").find(script)!!.groupValues[1] val expires = Regex("""'expires': ?'(\d+)'""").find(script)!!.groupValues[1]
val canCast = Regex("""'canCast': ?'(\d*)'""").find(script)!!.groupValues[1]
val token = Regex("""'token': ?'([\w-]+)'""").find(script)!!.groupValues[1] val token = Regex("""'token': ?'([\w-]+)'""").find(script)!!.groupValues[1]
// Get subtitles // Get subtitles
val masterPlUrl = "$playlistUrl?token=$token&expires=$expires&canCast=$canCast&n=1" val masterPlUrl = "$playlistUrl?token=$token&expires=$expires&n=1"
val masterPl = client.newCall(GET(masterPlUrl)).execute().body.string() val masterPl =
val subList = Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""").findAll(masterPl).map { client
Track(it.groupValues[2], it.groupValues[1]) .newCall(GET(masterPlUrl))
}.toList() .execute()
.body
.string()
val subList =
Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""")
.findAll(masterPl)
.map {
Track(it.groupValues[2], it.groupValues[1])
}.toList()
Regex("""'token(\d+p?)': ?'([\w-]+)'""").findAll(script).forEach { match -> Regex("""'token(\d+p?)': ?'([\w-]+)'""").findAll(script).forEach { match ->
val quality = match.groupValues[1] val quality = match.groupValues[1]
val videoUrl = buildString { val videoUrl =
append(playlistUrl) buildString {
append("?type=video&rendition=") append(playlistUrl)
append(quality) append("?type=video&rendition=")
append("&token=") append(quality)
append(match.groupValues[2]) append("&token=")
append("&expires=$expires") append(match.groupValues[2])
append("&canCast=$canCast") append("&expires=$expires")
append("&n=1") append("&n=1")
} }
videoList.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subList)) videoList.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subList))
} }
@ -352,38 +425,51 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
return json.decodeFromString(responseBody) return json.decodeFromString(responseBody)
} }
private fun parseStatus(statusString: String): Int = when (statusString) { private fun parseStatus(statusString: String): Int =
"In Corso" -> SAnime.ONGOING when (statusString) {
"Terminato" -> SAnime.COMPLETED "In Corso" -> SAnime.ONGOING
else -> SAnime.UNKNOWN "Terminato" -> SAnime.COMPLETED
} else -> SAnime.UNKNOWN
private fun addFromApi(start: Int, end: Int, animeId: String, headers: Headers, url: HttpUrl): List<SEpisode> {
val response = client.newCall(
GET("$baseUrl/info_api/$animeId/1?start_range=$start&end_range=$end", headers = headers),
).execute()
val json = response.parseAs<ApiResponse>()
return json.episodes.filter {
it.id != null
}.map {
SEpisode.create().apply {
name = "Episode ${it.number}"
date_upload = parseDate(it.created_at)
episode_number = it.number.split("-")[0].toFloatOrNull() ?: 0F
setUrlWithoutDomain(
url.newBuilder()
.addPathSegment(it.id.toString())
.toString(),
)
}
} }
private fun addFromApi(
start: Int,
end: Int,
animeId: String,
headers: Headers,
url: HttpUrl,
): List<SEpisode> {
val response =
client
.newCall(
GET("$baseUrl/info_api/$animeId/1?start_range=$start&end_range=$end", headers = headers),
).execute()
val json = response.parseAs<ApiResponse>()
return json
.episodes
.filter {
it.id != null
}.map {
SEpisode.create().apply {
name = "Episode ${it.number}"
date_upload = parseDate(it.created_at)
episode_number = it.number.split("-")[0].toFloatOrNull() ?: 0F
setUrlWithoutDomain(
url
.newBuilder()
.addPathSegment(it.id.toString())
.toString(),
)
}
}
} }
private fun String.falseIfEmpty(): String = if (this.isEmpty()) { private fun String.falseIfEmpty(): String =
"false" if (this.isEmpty()) {
} else { "false"
"\"${this}\"" } else {
} "\"${this}\""
}
@SuppressLint("SimpleDateFormat") @SuppressLint("SimpleDateFormat")
private fun parseDate(date: String): Long { private fun parseDate(date: String): Long {
@ -404,12 +490,13 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!! val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
return this.sortedWith( return this
compareBy( .sortedWith(
{ it.quality.contains(quality) }, compareBy(
{ it.quality.substringBefore("p").toIntOrNull() ?: 0 }, { it.quality.contains(quality) },
), { it.quality.substringBefore("p").toIntOrNull() ?: 0 },
).reversed() ),
).reversed()
} }
companion object { companion object {
@ -425,35 +512,37 @@ class AnimeUnity : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================== Settings ============================== // ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
EditTextPreference(screen.context).apply { EditTextPreference(screen.context)
key = PREF_DOMAIN_KEY .apply {
title = PREF_DOMAIN_TITLE key = PREF_DOMAIN_KEY
summary = PREF_DOMAIN_SUMMARY title = PREF_DOMAIN_TITLE
dialogTitle = PREF_DOMAIN_TITLE summary = PREF_DOMAIN_SUMMARY
dialogMessage = "Default: $PREF_DOMAIN_DEFAULT" dialogTitle = PREF_DOMAIN_TITLE
setDefaultValue(PREF_DOMAIN_DEFAULT) dialogMessage = "Default: $PREF_DOMAIN_DEFAULT"
setDefaultValue(PREF_DOMAIN_DEFAULT)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val newValueString = newValue as String val newValueString = newValue as String
Toast.makeText(screen.context, "Restart Aniyomi to apply new setting.", Toast.LENGTH_LONG).show() Toast.makeText(screen.context, "Restart Aniyomi to apply new setting.", Toast.LENGTH_LONG).show()
preferences.edit().putString(key, newValueString.trim()).commit() preferences.edit().putString(key, newValueString.trim()).commit()
} }
}.also(screen::addPreference) }.also(screen::addPreference)
ListPreference(screen.context).apply { ListPreference(screen.context)
key = PREF_QUALITY_KEY .apply {
title = "Preferred quality" key = PREF_QUALITY_KEY
entries = arrayOf("1080p", "720p", "480p", "360p", "240p", "80p") title = "Preferred quality"
entryValues = arrayOf("1080", "720", "480", "360", "240", "80") entries = arrayOf("1080p", "720p", "480p", "360p", "240p", "80p")
setDefaultValue(PREF_QUALITY_DEFAULT) entryValues = arrayOf("1080", "720", "480", "360", "240", "80")
summary = "%s" setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val selected = newValue as String
val index = findIndexOfValue(selected) val index = findIndexOfValue(selected)
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference) }.also(screen::addPreference)
} }
} }