[new extension] Shabakaty Cinemana extension (#685)

* add shabakaty cinemana extension

* fix kinds default value retrieving from preferences

* fix browse langauge without category
This commit is contained in:
Hasan Pasha 2025-02-17 01:31:03 +03:00 committed by GitHub
parent 65411a254d
commit 80ec94f022
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 639 additions and 0 deletions

View file

@ -0,0 +1,11 @@
ext {
extName = 'ShabakatyCinemana'
extClass = '.ShabakatyCinemana'
extVersionCode = 1
isNsfw = false
}
apply from: "$rootDir/common.gradle"
dependencies {
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -0,0 +1,628 @@
package eu.kanade.tachiyomi.animeextension.all.shabakatycinemana
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Track
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
inline fun <reified T> Response.asModel(deserializer: DeserializationStrategy<T>): T {
return Json.decodeFromString(deserializer, this.body.string())
}
inline fun <reified T> Response.asModelList(deserializer: DeserializationStrategy<T>): List<T> {
return Json.parseToJsonElement(this.body.string()).jsonArray.map {
Json.decodeFromJsonElement(deserializer, it)
}
}
object SEpisodeDeserializer : DeserializationStrategy<SEpisode> {
override val descriptor: SerialDescriptor
get() = buildClassSerialDescriptor("SEpisode")
override fun deserialize(decoder: Decoder): SEpisode {
val jsonDecoder = decoder as JsonDecoder
val jsonObject = jsonDecoder.decodeJsonElement() as JsonObject
val nb = jsonObject["nb"]?.jsonPrimitive?.content!!
val episodeNumber = jsonObject["episodeNummer"]?.jsonPrimitive?.content
val seasonNumber = jsonObject["season"]?.jsonPrimitive?.content
val seasonEpisode = arrayOf(seasonNumber, episodeNumber).joinToString(ShabakatyCinemana.SEASON_EPISODE_DELIMITER)
val uploadDate = jsonObject["videoUploadDate"]?.jsonPrimitive?.content.runCatching {
this?.let { ShabakatyCinemana.DATE_FORMATTER.parse(it)?.time }
}.getOrNull() ?: 0L
return SEpisode.create().apply {
url = nb
episode_number = "$seasonNumber.$episodeNumber".parseAs()
name = seasonEpisode
date_upload = uploadDate
}
}
}
object VideoDeserializer : DeserializationStrategy<Video> {
override val descriptor: SerialDescriptor
get() = buildClassSerialDescriptor("Video")
override fun deserialize(decoder: Decoder): Video {
val jsonDecoder = decoder as JsonDecoder
val jsonObject = jsonDecoder.decodeJsonElement() as JsonObject
val videoUrl = jsonObject["videoUrl"]?.jsonPrimitive?.content!!
val quality = jsonObject["resolution"]?.jsonPrimitive?.content.orEmpty()
return Video(url = videoUrl, videoUrl = videoUrl, quality = quality)
}
}
object SubtitleDeserialize : DeserializationStrategy<List<Track>> {
override val descriptor: SerialDescriptor
get() = buildClassSerialDescriptor("Track")
override fun deserialize(decoder: Decoder): List<Track> {
val jsonDecoder = decoder as JsonDecoder
val jsonObject = jsonDecoder.decodeJsonElement() as JsonObject
return jsonObject["translations"]?.jsonArray?.map {
val url = it.jsonObject["file"]?.jsonPrimitive?.content!!
val name = it.jsonObject["name"]?.jsonPrimitive?.content
val extension = it.jsonObject["extention"]?.jsonPrimitive?.content
val lang = arrayOf(name, extension).joinToString(ShabakatyCinemana.SUBTITLE_DELIMITER)
Track(url, lang)
}.orEmpty()
}
}
data class SAnimeListWithInfo(val animes: List<SAnime>, val offset: Int)
object SAnimeWithInfoDeserializer : DeserializationStrategy<SAnimeListWithInfo> {
override val descriptor: SerialDescriptor
get() = buildClassSerialDescriptor("SAnimeListWithInfo")
override fun deserialize(decoder: Decoder): SAnimeListWithInfo {
val jsonDecoder = decoder as JsonDecoder
val jsonObject = jsonDecoder.decodeJsonElement() as JsonObject
val animeList = jsonObject["info"]?.jsonArray?.map {
Json.decodeFromJsonElement(SAnimeDeserializer, it)
}.orEmpty()
val offset = jsonObject["offset"]?.jsonPrimitive?.int ?: 0
return SAnimeListWithInfo(animeList, offset)
}
}
object SAnimeDeserializer : DeserializationStrategy<SAnime> {
override val descriptor: SerialDescriptor
get() = buildClassSerialDescriptor("SAnime")
override fun deserialize(decoder: Decoder): SAnime {
val jsonDecoder = decoder as JsonDecoder
val jsonObject = jsonDecoder.decodeJsonElement() as JsonObject
val nb = jsonObject["nb"]?.jsonPrimitive?.content!!
val enTitle = jsonObject["en_title"]?.jsonPrimitive?.content ?: "no title"
val imgObjUrl = jsonObject["imgObjUrl"]?.jsonPrimitive?.content
val categories = jsonObject["categories"]?.jsonArray?.map {
it.jsonObject["en_title"]?.jsonPrimitive?.content
}?.joinToString(", ")
val enContent = jsonObject["en_content"]?.jsonPrimitive?.content
val year = jsonObject["year"]?.jsonPrimitive?.content ?: "N/A"
val stars = jsonObject["stars"]?.jsonPrimitive?.content?.parseAs<Float>()?.toInt() ?: 0
val starsText = "${"★".repeat(stars / 2)}${"☆".repeat(5 - (stars / 2))}"
val likes = jsonObject["Likes"]?.jsonPrimitive?.content?.parseAs<Int>() ?: 0
val dislikes = jsonObject["DisLikes"]?.jsonPrimitive?.content?.parseAs<Int>() ?: 0
// val ref = jsonObject["imdbUrlRef"]?.jsonPrimitive?.content ?: ""
return SAnime.create().apply {
url = nb
title = enTitle
thumbnail_url = imgObjUrl
genre = categories
description = "$year | $starsText | $likes\uD83D\uDC4D $dislikes\uD83D\uDC4E\n\n$enContent"
}
}
}
class ShabakatyCinemana : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "Shabakaty Cinemana"
override val baseUrl = "https://cinemana.shabakaty.com"
private val apiBaseUrl = "$baseUrl/api/android"
override val lang = "all"
override val supportsLatest = true
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object {
private const val IS_BROWSING_FILTER_NAME = "Browse"
private const val KIND_FILTER_NAME = "Kind"
private const val MAIN_CATEGORY_FILTER_NAME = "Main Category"
private const val SUB_CATEGORY_FILTER_NAME = "Sub Category"
private const val LANGUAGE_FILTER_NAME = "Language"
private const val YEAR_FILTER_NAME = "Year"
private const val BROWSE_RESULT_SORT_FILTER = "Browse Sort"
private const val POPULAR_ITEMS_PER_PAGE = 30
private const val SEARCH_ITEMS_PER_PAGE = 12
private const val LATEST_ITEMS_PER_PAGE = 24
private const val PREF_LATEST_KIND_KEY = "preferred_latest_kind"
private const val PREF_LATEST_KIND_DEFAULT = "Movies"
private val KINDS_LIST = arrayOf(
Pair("Movies", 1),
Pair("Series", 2),
)
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("2160", "1080", "720", "480", "360", "240")
private const val PREF_SUBTITLE_LANG_KEY = "preferred_subtitle_language"
private const val PREF_SUBTITLE_LANG_DEFAULT = "arabic"
private val LANG_LIST = arrayOf("arabic", "english")
private const val PREF_SUBTITLE_EXT_KEY = "preferred_subtitle_extension"
private const val PREF_SUBTITLE_EXT_DEFAULT = "ass"
private val EXT_LIST = arrayOf("srt", "vtt", "ass")
const val SUBTITLE_DELIMITER = " - "
const val SEASON_EPISODE_DELIMITER = " - "
val DATE_FORMATTER by lazy {
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH)
}
}
override fun getAnimeUrl(anime: SAnime) = "$baseUrl/video/en/${anime.url}"
override fun animeDetailsRequest(anime: SAnime) = GET("$apiBaseUrl/allVideoInfo/id/${anime.url}")
override fun animeDetailsParse(response: Response) = response.asModel(SAnimeDeserializer)
override fun latestUpdatesRequest(page: Int): Request {
val kind = preferences.getString(PREF_LATEST_KIND_KEY, PREF_LATEST_KIND_DEFAULT)!!
return GET("$apiBaseUrl/latest$kind/level/0/itemsPerPage/$LATEST_ITEMS_PER_PAGE/page/${page - 1}/", headers)
}
override fun latestUpdatesParse(response: Response): AnimesPage {
val animeList = response.asModelList(SAnimeDeserializer)
return AnimesPage(animeList, animeList.size < LATEST_ITEMS_PER_PAGE)
}
override fun popularAnimeRequest(page: Int): Request {
val kindPref = preferences.getString(PREF_LATEST_KIND_KEY, PREF_LATEST_KIND_DEFAULT)!!
val kind = KINDS_LIST.first { it.first == kindPref }.second
val url = "$apiBaseUrl/video/V/2/itemsPerPage/$POPULAR_ITEMS_PER_PAGE/level/0/videoKind/$kind/sortParam/desc/pageNumber/${page - 1}"
println(url)
return GET(url, headers)
}
override fun popularAnimeParse(response: Response): AnimesPage {
val animeList = response.asModelList(SAnimeDeserializer)
return AnimesPage(animeList, animeList.size < POPULAR_ITEMS_PER_PAGE)
}
override suspend fun getSearchAnime(
page: Int,
query: String,
filters: AnimeFilterList,
): AnimesPage {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val isBrowsingFilter = filterList.find { it.name == IS_BROWSING_FILTER_NAME } as CheckBoxFilter
val kindFilter = filterList.find { it.name == KIND_FILTER_NAME } as SingleSelectFilter
val mainCategoryFilter = filterList.find { it.name == MAIN_CATEGORY_FILTER_NAME } as MultipleSelectFilter
val subCategoryFilter = filterList.find { it.name == SUB_CATEGORY_FILTER_NAME } as MultipleSelectFilter
val languageFilter = filterList.find { it.name == LANGUAGE_FILTER_NAME } as SingleSelectFilter
val yearFilter = filterList.find { it.name == YEAR_FILTER_NAME } as YearFilter
val browseResultSortFilter = filterList.find { it.name == BROWSE_RESULT_SORT_FILTER } as BrowseResultSort
val isBrowsing = isBrowsingFilter.state
val kindName = kindFilter.getNameValue()
val kindNumber = kindFilter.getNumberValue().toString()
val selectedMainCategories = mainCategoryFilter.getSelectedIds()
val mainCategory = selectedMainCategories.joinToString(",")
val selectedSubCategories = subCategoryFilter.getSelectedIds()
val bothCategory = (selectedMainCategories + selectedSubCategories).joinToString(",")
val language = languageFilter.getNumberValue().toString()
val year = yearFilter.getFormatted()
val browseResultSort = browseResultSortFilter.getValue()
var url = apiBaseUrl.toHttpUrl()
if (isBrowsing) {
if (languageFilter.state != 0 && mainCategory.isNotBlank()) {
url = url.newBuilder()
.addPathSegment("videosByCategoryAndLanguage")
.addQueryParameter("language_id", language)
.addQueryParameter("category_id", mainCategory)
.build()
} else {
url = url.newBuilder()
.addPathSegment("videosByCategory")
.build()
if (mainCategoryFilter.hasSelected()) {
url = url.newBuilder().addQueryParameter("categoryID", mainCategory).build()
}
}
url = url.newBuilder()
.addQueryParameter("level", "0")
.addQueryParameter("offset", "${(page - 1) * POPULAR_ITEMS_PER_PAGE}")
.addQueryParameter("videoKind", kindNumber)
.addQueryParameter("orderby", browseResultSort)
.build()
println(url)
val resp = client.newCall(GET(url, headers)).execute()
// Todo: remove SAnimeWithInfo data class if no longer needed
val animeListWithInfo = resp.asModel(SAnimeWithInfoDeserializer)
return AnimesPage(animeListWithInfo.animes, animeListWithInfo.animes.isNotEmpty())
} else {
// star=8&year=1900,2025
url = url.newBuilder()
.addPathSegment("AdvancedSearch")
.addQueryParameter("type", kindName)
.addQueryParameter("page", "${page - 1}")
.addQueryParameter("year", year)
.build()
if (bothCategory.isNotBlank()) {
url = url.newBuilder().addQueryParameter("category_id", bothCategory).build()
}
if (query.isNotBlank()) {
url = url.newBuilder().addQueryParameter("videoTitle", query).build()
}
println(url)
val resp = client.newCall(GET(url, headers)).execute()
val animeList = resp.asModelList(SAnimeDeserializer)
return AnimesPage(animeList, animeList.size < SEARCH_ITEMS_PER_PAGE)
}
}
override fun searchAnimeParse(response: Response) =
throw UnsupportedOperationException("Not used.")
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList) =
throw UnsupportedOperationException("Not used.")
override suspend fun getEpisodeList(anime: SAnime): List<SEpisode> {
val episodeList = super.getEpisodeList(anime)
if (episodeList.isNotEmpty()) {
return episodeList.sortedWith(
compareBy(
{ it.name.split(SEASON_EPISODE_DELIMITER).first().parseAs<Int>() },
{ it.name.split(SEASON_EPISODE_DELIMITER).last().parseAs<Int>() },
),
)
} else {
return listOf(
SEpisode.create().apply {
url = anime.url
episode_number = 1.0F
name = "movie"
},
)
}
}
override fun episodeListRequest(anime: SAnime): Request = GET("$apiBaseUrl/videoSeason/id/${anime.url}")
override fun episodeListParse(response: Response) = response.asModelList(SEpisodeDeserializer)
override suspend fun getVideoList(episode: SEpisode): List<Video> {
val extension = preferences.getString(PREF_SUBTITLE_EXT_KEY, PREF_SUBTITLE_EXT_DEFAULT)!!
val language = preferences.getString(PREF_SUBTITLE_LANG_KEY, PREF_SUBTITLE_LANG_DEFAULT)!!
val subs = this.client.newCall(GET("$apiBaseUrl/translationFiles/id/${episode.url}")).execute()
.asModel(SubtitleDeserialize)
.sortedWith(
compareBy(
{ it.lang.split(SUBTITLE_DELIMITER).contains(extension) },
{ it.lang.split(SUBTITLE_DELIMITER).contains(language) },
),
).reversed()
return super.getVideoList(episode).map {
Video(url = it.url, quality = it.quality, videoUrl = it.videoUrl, subtitleTracks = subs)
}
}
override fun videoListRequest(episode: SEpisode) = GET("$apiBaseUrl/transcoddedFiles/id/${episode.url}")
override fun videoListParse(response: Response) = response.asModelList(VideoDeserializer)
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
private open class SingleSelectFilter(displayName: String, val vals: Array<Pair<String, Int>>, default: Int = 0) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), default) {
fun getNameValue() = vals[state].first.lowercase()
fun getNumberValue() = vals[state].second
}
private open class MultipleSelectFilter(displayName: String, vals: Array<Pair<String, Int>>) :
AnimeFilter.Group<CheckBoxFilter>(displayName, vals.map { CheckBoxFilter(it.first, false, it.second) }) {
fun getSelectedIds(): List<Int> =
this.state.filter { it.state }.map { it.value }
fun hasSelected(): Boolean = this.state.any { it.state }
}
private open class CheckBoxFilter(displayName: String, default: Boolean, val value: Int = 0) : AnimeFilter.CheckBox(displayName, default)
private open class YearFilter(displayName: String, years: Pair<YearTextFilter, YearTextFilter>) : AnimeFilter.Group<YearTextFilter>(
displayName,
years.toList(),
) {
fun getFormatted(): String = this.state.map {
it.state.ifBlank { it.default }
}.joinToString(",")
}
private open class YearTextFilter(displayName: String, val default: String) : AnimeFilter.Text(displayName, default)
private open class BrowseResultSort(
displayName: String,
val vals: Array<Pair<String, Pair<String, String>>>,
val default: Selection = Selection(0, false),
) : AnimeFilter.Sort(displayName, vals.map { it.first }.toTypedArray(), default) {
fun getValue(): String {
val currentState = state ?: default
val sortKind = vals[currentState.index].second
return if (currentState.ascending) {
sortKind.first
} else {
sortKind.second
}
}
}
override fun getFilterList() = AnimeFilterList(
AnimeFilter.Header("Filter Search Result"),
CheckBoxFilter(IS_BROWSING_FILTER_NAME, false),
SingleSelectFilter(
KIND_FILTER_NAME,
KINDS_LIST,
),
MultipleSelectFilter(
MAIN_CATEGORY_FILTER_NAME,
arrayOf(
Pair("Action", 84),
Pair("Adventure", 56),
Pair("Animation", 57),
Pair("Comedy", 59),
Pair("Crime", 60),
Pair("Documentary", 61),
Pair("Drama", 62),
Pair("Fantasy", 67),
Pair("Horror", 70),
Pair("Mystery", 76),
Pair("Romance", 77),
Pair("Sci-Fi", 78),
Pair("Sport", 79),
Pair("Thriller", 80),
Pair("Western", 89),
),
),
MultipleSelectFilter(
SUB_CATEGORY_FILTER_NAME,
arrayOf(
Pair("Biography", 58),
Pair("Family", 65),
Pair("History", 68),
Pair("Musical", 75),
Pair("War", 81),
Pair("Supernatural", 87),
Pair("Music", 88),
Pair("Talk-Show", 90),
Pair("Short", 97),
Pair("Reality-TV", 101),
Pair("Arabic dubbed", 102),
Pair("News", 104),
Pair("Ecchi", 105),
Pair("Film-Noir", 106),
Pair("Game", 111),
Pair("Psychological", 112),
Pair("Slice of Life", 113),
Pair("Game-Show", 118),
Pair("Magic", 123),
Pair("Super Power", 124),
Pair("Seinen", 125),
Pair("Shounen", 126),
Pair("School", 127),
Pair("Sports", 128),
Pair("Iraqi", 130),
),
),
SingleSelectFilter(
LANGUAGE_FILTER_NAME,
arrayOf(
Pair("", 0),
Pair("English", 7),
Pair("Arabic", 9),
Pair("Hindi", 10),
Pair("French", 11),
Pair("German", SEARCH_ITEMS_PER_PAGE),
Pair("Italian", 13),
Pair("Spanish", 14),
Pair("Chinese", 21),
Pair("Japanese", 22),
Pair("Korean", 23),
Pair("Russian", LATEST_ITEMS_PER_PAGE),
Pair("Turkish", 25),
Pair("Norwegian", 26),
Pair("Persian", 27),
Pair("Swedish", 35),
Pair("Hungary", 36),
Pair("Polish", 38),
Pair("Dutch", 39),
Pair("Portuguese", 40),
Pair("Indonesian", 41),
Pair("Danish", 43),
Pair("Romania", 44),
Pair("Ukrainian", 48),
Pair("Mandarin", 52),
Pair("Catalan", 65),
Pair("Filipino", 68),
Pair("Hungarian", 76),
Pair("Thai", 80),
Pair("Croatian", 84),
Pair(" Malay", 85),
Pair("Finnish", 86),
Pair("Vietnamese", 88),
Pair("Zulu", 89),
Pair("Taiwan", 47),
Pair("Bulgarian", 95),
Pair("Serbian", 97),
Pair("Greek", 28),
Pair("Finland", 37),
Pair("Iran", 42),
Pair("Hebrew", 46),
Pair("Icelandic", 56),
Pair("Georgian", 58),
Pair("Pakistani", 61),
Pair("Czeck", 72),
Pair("Latvian", 87),
Pair("Kazakh", 90),
Pair("Estonian", 91),
Pair("Quechua", 92),
Pair("Multi Language", 93),
Pair("Papiamento", 94),
Pair("Albanian", 100),
Pair("Slovenian", 103),
Pair("Macedonian", 109),
Pair("Kurdish", 112),
Pair("Irish", 115),
Pair("Afghani", 106),
),
),
YearFilter(
YEAR_FILTER_NAME,
YearTextFilter("start", "1900") to
YearTextFilter("end", Calendar.getInstance().get(Calendar.YEAR).toString()),
),
BrowseResultSort(
BROWSE_RESULT_SORT_FILTER,
arrayOf(
Pair("Upload", Pair("asc", "desc")),
Pair("Release", Pair("r_asc", "r_desc")),
Pair("Name", Pair("title_asc", "title_desc")),
Pair("View", Pair("views_asc", "views_desc")),
Pair("Age Rating", Pair("rating_asc", "rating_desc")),
),
),
)
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_LATEST_KIND_KEY
title = "Preferred Latest kind"
entries = KINDS_LIST.map { it.first }.toTypedArray()
entryValues = KINDS_LIST.map { it.first }.toTypedArray()
setDefaultValue(PREF_LATEST_KIND_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SUBTITLE_LANG_KEY
title = "Preferred Subtitle Language"
entries = LANG_LIST
entryValues = LANG_LIST
setDefaultValue(PREF_SUBTITLE_LANG_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SUBTITLE_EXT_KEY
title = "Preferred Subtitle Extension"
entries = EXT_LIST
entryValues = EXT_LIST
setDefaultValue(PREF_SUBTITLE_EXT_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
}
}