mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
not done
This commit is contained in:
parent
c5406acc1e
commit
3f19429805
8 changed files with 875 additions and 152 deletions
|
@ -635,6 +635,7 @@ enum class ShowStatus {
|
|||
}
|
||||
|
||||
enum class DubStatus(val id: Int) {
|
||||
None(-1),
|
||||
Dubbed(1),
|
||||
Subbed(0),
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ class APIRepository(val api: MainAPI) {
|
|||
val mainUrl = api.mainUrl
|
||||
val mainPage = api.mainPage
|
||||
val hasQuickSearch = api.hasQuickSearch
|
||||
val vpnStatus = api.vpnStatus
|
||||
val providerType = api.providerType
|
||||
|
||||
suspend fun load(url: String): Resource<LoadResponse> {
|
||||
return safeApiCall {
|
||||
|
|
|
@ -111,7 +111,8 @@ data class ResultEpisode(
|
|||
val name: String?,
|
||||
val poster: String?,
|
||||
val episode: Int,
|
||||
val season: Int?,
|
||||
val seasonIndex: Int?, // this is the "season" index used season names
|
||||
val season: Int?, // this is the display
|
||||
val data: String,
|
||||
val apiName: String,
|
||||
val id: Int,
|
||||
|
@ -146,6 +147,7 @@ fun buildResultEpisode(
|
|||
name: String? = null,
|
||||
poster: String? = null,
|
||||
episode: Int,
|
||||
seasonIndex: Int? = null,
|
||||
season: Int? = null,
|
||||
data: String,
|
||||
apiName: String,
|
||||
|
@ -163,6 +165,7 @@ fun buildResultEpisode(
|
|||
name,
|
||||
poster,
|
||||
episode,
|
||||
seasonIndex,
|
||||
season,
|
||||
data,
|
||||
apiName,
|
||||
|
@ -453,7 +456,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
|
||||
private var currentLoadingCount =
|
||||
0 // THIS IS USED TO PREVENT LATE EVENTS, AFTER DISMISS WAS CLICKED
|
||||
private lateinit var viewModel: ResultViewModel //by activityViewModels()
|
||||
private lateinit var viewModel: ResultViewModel2 //by activityViewModels()
|
||||
private lateinit var syncModel: SyncViewModel
|
||||
private var currentHeaderName: String? = null
|
||||
private var currentType: TvType? = null
|
||||
|
@ -467,7 +470,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
savedInstanceState: Bundle?,
|
||||
): View? {
|
||||
viewModel =
|
||||
ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
ViewModelProvider(this)[ResultViewModel2::class.java]
|
||||
syncModel =
|
||||
ViewModelProvider(this)[SyncViewModel::class.java]
|
||||
|
||||
|
@ -703,77 +706,6 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
loadTrailer()
|
||||
}
|
||||
|
||||
private fun setNextEpisode(nextAiring: NextAiring?) {
|
||||
result_next_airing_holder?.isVisible =
|
||||
if (nextAiring == null || nextAiring.episode <= 0 || nextAiring.unixTime <= unixTime) {
|
||||
false
|
||||
} else {
|
||||
val seconds = nextAiring.unixTime - unixTime
|
||||
val days = TimeUnit.SECONDS.toDays(seconds)
|
||||
val hours: Long = TimeUnit.SECONDS.toHours(seconds) - days * 24
|
||||
val minute =
|
||||
TimeUnit.SECONDS.toMinutes(seconds) - TimeUnit.SECONDS.toHours(seconds) * 60
|
||||
// val second =
|
||||
// TimeUnit.SECONDS.toSeconds(seconds) - TimeUnit.SECONDS.toMinutes(seconds) * 60
|
||||
try {
|
||||
val ctx = context
|
||||
if (ctx == null) {
|
||||
false
|
||||
} else {
|
||||
when {
|
||||
days > 0 -> {
|
||||
ctx.getString(R.string.next_episode_time_day_format).format(
|
||||
days,
|
||||
hours,
|
||||
minute
|
||||
)
|
||||
}
|
||||
hours > 0 -> ctx.getString(R.string.next_episode_time_hour_format)
|
||||
.format(
|
||||
hours,
|
||||
minute
|
||||
)
|
||||
minute > 0 -> ctx.getString(R.string.next_episode_time_min_format)
|
||||
.format(
|
||||
minute
|
||||
)
|
||||
else -> null
|
||||
}?.also { text ->
|
||||
result_next_airing_time?.text = text
|
||||
result_next_airing?.text =
|
||||
ctx.getString(R.string.next_episode_format)
|
||||
.format(nextAiring.episode)
|
||||
} != null
|
||||
}
|
||||
} catch (e: Exception) { // mistranslation
|
||||
result_next_airing_holder?.isVisible = false
|
||||
logError(e)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setActors(actors: List<ActorData>?) {
|
||||
if (actors.isNullOrEmpty()) {
|
||||
result_cast_text?.isVisible = false
|
||||
result_cast_items?.isVisible = false
|
||||
} else {
|
||||
val isImage = actors.first().actor.image != null
|
||||
if (isImage) {
|
||||
(result_cast_items?.adapter as ActorAdaptor?)?.apply {
|
||||
updateList(actors)
|
||||
}
|
||||
result_cast_text?.isVisible = false
|
||||
result_cast_items?.isVisible = true
|
||||
} else {
|
||||
result_cast_text?.isVisible = true
|
||||
result_cast_items?.isVisible = false
|
||||
setFormatText(result_cast_text, R.string.cast_format,
|
||||
actors.joinToString { it.actor.name })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
|
||||
val isInvalid = rec.isNullOrEmpty()
|
||||
result_recommendations?.isGone = isInvalid
|
||||
|
@ -1424,7 +1356,12 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
result_season_button?.setOnClickListener {
|
||||
result_season_button?.popupMenuNoIconsAndNoStringRes(
|
||||
items = seasonList
|
||||
.map { Pair(it ?: -2, fromIndexToSeasonText(it)) },
|
||||
.map { (name, season) ->
|
||||
Pair(
|
||||
season ?: -2,
|
||||
name ?: fromIndexToSeasonText(season)
|
||||
)
|
||||
},
|
||||
) {
|
||||
val id = this.itemId
|
||||
|
||||
|
@ -1730,7 +1667,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
startValue = null
|
||||
}
|
||||
|
||||
observe(viewModel.publicEpisodes) { episodes ->
|
||||
observe(viewModel.episodes) { episodes ->
|
||||
when (episodes) {
|
||||
is Resource.Failure -> {
|
||||
result_episode_loading?.isVisible = false
|
||||
|
@ -1753,18 +1690,17 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
}
|
||||
|
||||
observe(viewModel.dubStatus) { status ->
|
||||
result_dub_select?.text = status.toString()
|
||||
result_dub_select?.apply {
|
||||
isVisible = status != null
|
||||
status?.toString()?.let {
|
||||
text = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// val preferDub = context?.getApiDubstatusSettings()?.all { it == DubStatus.Dubbed } == true
|
||||
|
||||
observe(viewModel.dubSubSelections) { range ->
|
||||
dubRange = range
|
||||
|
||||
// if (preferDub && dubRange?.contains(DubStatus.Dubbed) == true) {
|
||||
// viewModel.changeDubStatus(DubStatus.Dubbed)
|
||||
// }
|
||||
|
||||
result_dub_select?.visibility = if (range.size <= 1) GONE else VISIBLE
|
||||
|
||||
if (result_season_button?.isVisible != true && result_episode_select?.isVisible != true) {
|
||||
|
@ -1810,7 +1746,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
syncModel.publishUserData()
|
||||
}
|
||||
|
||||
observe(viewModel.publicEpisodesCount) { count ->
|
||||
observe(viewModel.episodesCount) { count ->
|
||||
if (count < 0) {
|
||||
result_episodes_text?.isVisible = false
|
||||
} else {
|
||||
|
@ -1824,43 +1760,40 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
currentId = it
|
||||
}
|
||||
|
||||
observe(viewModel.result) { data ->
|
||||
observe(viewModel.page) { data ->
|
||||
when (data) {
|
||||
is Resource.Success -> {
|
||||
val d = data.value
|
||||
if (d !is AnimeLoadResponse && result_episode_loading.isVisible) { // no episode loading when not anime
|
||||
result_episode_loading.isVisible = false
|
||||
}
|
||||
|
||||
updateVisStatus(2)
|
||||
|
||||
result_vpn?.text = when (api.vpnStatus) {
|
||||
VPNStatus.MightBeNeeded -> getString(R.string.vpn_might_be_needed)
|
||||
VPNStatus.Torrent -> getString(R.string.vpn_torrent)
|
||||
else -> ""
|
||||
}
|
||||
result_vpn?.isGone = api.vpnStatus == VPNStatus.None
|
||||
result_vpn.setText(d.vpnText)
|
||||
result_info.setText(d.metaText)
|
||||
result_no_episodes.setText(d.noEpisodesFoundText)
|
||||
result_title.setText(d.titleText)
|
||||
result_meta_site.setText(d.apiName)
|
||||
result_meta_type.setText(d.typeText)
|
||||
result_meta_year.setText(d.yearText)
|
||||
result_meta_duration.setText(d.durationText)
|
||||
result_meta_rating.setText(d.ratingText)
|
||||
result_description.setTextHtml(d.plotText)
|
||||
result_cast_text.setText(d.actorsText)
|
||||
setRecommendations.setText(d.nextAiringEpisode)
|
||||
result_next_airing_time.setText(d.nextAiringDate)
|
||||
|
||||
result_info?.text = when (api.providerType) {
|
||||
ProviderType.MetaProvider -> getString(R.string.provider_info_meta)
|
||||
else -> ""
|
||||
}
|
||||
result_info?.isVisible = api.providerType == ProviderType.MetaProvider
|
||||
result_poster.setImage(d.posterImage)
|
||||
|
||||
if (d.type.isEpisodeBased()) {
|
||||
val ep = d as? TvSeriesLoadResponse
|
||||
val epCount = ep?.episodes?.size ?: 1
|
||||
if (epCount < 1) {
|
||||
result_info?.text = getString(R.string.no_episodes_found)
|
||||
result_info?.isVisible = true
|
||||
}
|
||||
if(!d.posterUrl.isNullOrBlank()) {
|
||||
result_poster?.setImage(d.posterUrl, d.posterHeaders)
|
||||
} else {
|
||||
result_poster?.setImageResource(R.drawable.default_cover)
|
||||
}
|
||||
|
||||
currentHeaderName = d.name
|
||||
currentType = d.type
|
||||
|
||||
currentPoster = d.posterUrl
|
||||
currentIsMovie = !d.isEpisodeBased()
|
||||
result_cast_items?.isVisible = d.actors != null
|
||||
(result_cast_items?.adapter as ActorAdaptor?)?.apply {
|
||||
updateList(d.actors ?: emptyList())
|
||||
}
|
||||
|
||||
result_open_in_browser?.setOnClickListener {
|
||||
val i = Intent(ACTION_VIEW)
|
||||
|
@ -1873,31 +1806,21 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
}
|
||||
|
||||
result_search?.setOnClickListener {
|
||||
QuickSearchFragment.pushSearch(activity, d.name)
|
||||
QuickSearchFragment.pushSearch(activity, d.title)
|
||||
}
|
||||
|
||||
result_share?.setOnClickListener {
|
||||
try {
|
||||
val i = Intent(ACTION_SEND)
|
||||
i.type = "text/plain"
|
||||
i.putExtra(EXTRA_SUBJECT, d.name)
|
||||
i.putExtra(EXTRA_SUBJECT, d.title)
|
||||
i.putExtra(EXTRA_TEXT, d.url)
|
||||
startActivity(createChooser(i, d.name))
|
||||
startActivity(createChooser(i, d.title))
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
|
||||
val showStatus = when (d) {
|
||||
is TvSeriesLoadResponse -> d.showStatus
|
||||
is AnimeLoadResponse -> d.showStatus
|
||||
else -> null
|
||||
}
|
||||
|
||||
setShow(showStatus)
|
||||
setDuration(d.duration)
|
||||
setYear(d.year)
|
||||
setRating(d.rating)
|
||||
setRecommendations(d.recommendations, null)
|
||||
setActors(d.actors)
|
||||
setNextEpisode(if (d is EpisodeResponse) d.nextAiring else null)
|
||||
|
@ -1911,10 +1834,9 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
}
|
||||
|
||||
result_meta_site?.text = d.apiName
|
||||
|
||||
val posterImageLink = d.posterUrl
|
||||
if (!posterImageLink.isNullOrEmpty()) {
|
||||
result_poster?.setImage(posterImageLink, d.posterHeaders)
|
||||
|
||||
//result_poster_blur?.setImageBlur(posterImageLink, 10, 3, d.posterHeaders)
|
||||
//Full screen view of Poster image
|
||||
if (context?.isTrueTvSettings() == false) // Poster not clickable on tv
|
||||
|
@ -1950,7 +1872,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
result_poster_holder?.visibility = VISIBLE
|
||||
|
||||
result_play_movie?.text =
|
||||
if (d.type == TvType.Live) getString(R.string.play_livestream_button) else getString(
|
||||
if (d.typeText == TvType.Live) getString(R.string.play_livestream_button) else getString(
|
||||
R.string.play_movie_button
|
||||
)
|
||||
//result_plot_header?.text =
|
||||
|
@ -1961,13 +1883,13 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
val builder: AlertDialog.Builder =
|
||||
AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
|
||||
builder.setMessage(syno.html())
|
||||
.setTitle(if (d.type == TvType.Torrent) R.string.torrent_plot else R.string.result_plot)
|
||||
.setTitle(if (d.typeText == TvType.Torrent) R.string.torrent_plot else R.string.result_plot)
|
||||
.show()
|
||||
}
|
||||
result_description?.text = syno.html()
|
||||
} else {
|
||||
result_description?.text =
|
||||
if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(
|
||||
if (d.typeText == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(
|
||||
R.string.normal_no_plot
|
||||
)
|
||||
}
|
||||
|
@ -1982,9 +1904,8 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
}
|
||||
|
||||
val tags = d.tags
|
||||
if (tags.isNullOrEmpty()) {
|
||||
//result_tag_holder?.visibility = GONE
|
||||
} else {
|
||||
result_tag_holder?.isVisible = tags.isNotEmpty()
|
||||
if (tags.isNotEmpty()) {
|
||||
//result_tag_holder?.visibility = VISIBLE
|
||||
val isOnTv = context?.isTrueTvSettings() == true
|
||||
for ((index, tag) in tags.withIndex()) {
|
||||
|
@ -1997,7 +1918,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
}
|
||||
}
|
||||
|
||||
if (d.type.isMovieType()) {
|
||||
if (d.typeText.isMovieType()) {
|
||||
val hasDownloadSupport = api.hasDownloadSupport
|
||||
lateFixDownloadButton(true)
|
||||
|
||||
|
@ -2125,22 +2046,6 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
lateFixDownloadButton(false)
|
||||
}
|
||||
|
||||
context?.getString(
|
||||
when (d.type) {
|
||||
TvType.TvSeries -> R.string.tv_series_singular
|
||||
TvType.Anime -> R.string.anime_singular
|
||||
TvType.OVA -> R.string.ova_singular
|
||||
TvType.AnimeMovie -> R.string.movies_singular
|
||||
TvType.Cartoon -> R.string.cartoons_singular
|
||||
TvType.Documentary -> R.string.documentaries_singular
|
||||
TvType.Movie -> R.string.movies_singular
|
||||
TvType.Torrent -> R.string.torrent_singular
|
||||
TvType.AsianDrama -> R.string.asian_drama_singular
|
||||
TvType.Live -> R.string.live_singular
|
||||
}
|
||||
)?.let {
|
||||
result_meta_type?.text = it
|
||||
}
|
||||
|
||||
when (d) {
|
||||
is AnimeLoadResponse -> {
|
||||
|
|
|
@ -77,7 +77,7 @@ class ResultViewModel : ViewModel() {
|
|||
|
||||
val id: MutableLiveData<Int> = MutableLiveData()
|
||||
val selectedSeason: MutableLiveData<Int> = MutableLiveData(-2)
|
||||
val seasonSelections: MutableLiveData<List<Int?>> = MutableLiveData()
|
||||
val seasonSelections: MutableLiveData<List<Pair<String?, Int?>>> = MutableLiveData()
|
||||
|
||||
val dubSubSelections: LiveData<Set<DubStatus>> get() = _dubSubSelections
|
||||
private val _dubSubSelections: MutableLiveData<Set<DubStatus>> = MutableLiveData()
|
||||
|
@ -228,14 +228,17 @@ class ResultViewModel : ViewModel() {
|
|||
seasonTypes[i.season] = true
|
||||
}
|
||||
}
|
||||
val seasons = seasonTypes.toList().map { it.first }.sortedBy { it }
|
||||
val seasons = seasonTypes.toList().map { null to it.first }.sortedBy { it.second }
|
||||
|
||||
|
||||
seasonSelections.postValue(seasons)
|
||||
if (seasons.isEmpty()) { // WHAT THE FUCK DID YOU DO????? HOW DID YOU DO THIS
|
||||
_publicEpisodes.postValue(Resource.Success(emptyList()))
|
||||
return
|
||||
}
|
||||
|
||||
val realSelection = if (!seasonTypes.containsKey(selection)) seasons.first() else selection
|
||||
val realSelection =
|
||||
if (!seasonTypes.containsKey(selection)) seasons.first().second else selection
|
||||
val internalId = id.value
|
||||
|
||||
if (internalId != null) setResultSeason(internalId, realSelection)
|
||||
|
@ -386,7 +389,6 @@ class ResultViewModel : ViewModel() {
|
|||
return
|
||||
}
|
||||
|
||||
// val status = getDub(mainId)
|
||||
val statuses = loadResponse.episodes.map { it.key }
|
||||
|
||||
// Extremely bruh to have to take in context here, but I'm not sure how to do this in a better way :(
|
||||
|
|
|
@ -0,0 +1,679 @@
|
|||
package com.lagradost.cloudstream3.ui.result
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getId
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||
import com.lagradost.cloudstream3.animeproviders.GogoanimeProvider
|
||||
import com.lagradost.cloudstream3.animeproviders.NineAnimeProvider
|
||||
import com.lagradost.cloudstream3.metaproviders.SyncRedirector
|
||||
import com.lagradost.cloudstream3.mvvm.*
|
||||
import com.lagradost.cloudstream3.ui.APIRepository
|
||||
import com.lagradost.cloudstream3.ui.player.IGenerator
|
||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.FillerEpisodeCheck
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
/** This starts at 1 */
|
||||
data class EpisodeRange(
|
||||
// used to index data
|
||||
val startIndex: Int,
|
||||
val length: Int,
|
||||
// used to display data
|
||||
val startEpisode: Int,
|
||||
val endEpisode: Int,
|
||||
)
|
||||
|
||||
data class ResultData(
|
||||
val url: String,
|
||||
val tags: List<String>,
|
||||
val actors: List<ActorData>?,
|
||||
val actorsText: UiText?,
|
||||
|
||||
val comingSoon: Boolean,
|
||||
val backgroundPosterUrl: String?,
|
||||
val title: String,
|
||||
|
||||
val posterImage: UiImage?,
|
||||
val plotText: UiText,
|
||||
val apiName: UiText,
|
||||
val ratingText: UiText?,
|
||||
val vpnText: UiText?,
|
||||
val metaText: UiText?,
|
||||
val durationText: UiText?,
|
||||
val onGoingText: UiText?,
|
||||
val noEpisodesFoundText: UiText?,
|
||||
val titleText: UiText,
|
||||
val typeText: UiText,
|
||||
val yearText: UiText?,
|
||||
val nextAiringDate: UiText?,
|
||||
val nextAiringEpisode: UiText?,
|
||||
)
|
||||
|
||||
fun LoadResponse.toResultData(repo: APIRepository): ResultData {
|
||||
debugAssert({ repo.name == apiName }) {
|
||||
"Api returned wrong apiName"
|
||||
}
|
||||
|
||||
val hasActorImages = actors?.firstOrNull()?.actor?.image?.isNotBlank() == true
|
||||
|
||||
var nextAiringEpisode: UiText? = null
|
||||
var nextAiringDate: UiText? = null
|
||||
|
||||
if (this is EpisodeResponse) {
|
||||
val airing = this.nextAiring
|
||||
if (airing != null && airing.unixTime > unixTime) {
|
||||
val seconds = airing.unixTime - unixTime
|
||||
val days = TimeUnit.SECONDS.toDays(seconds)
|
||||
val hours: Long = TimeUnit.SECONDS.toHours(seconds) - days * 24
|
||||
val minute =
|
||||
TimeUnit.SECONDS.toMinutes(seconds) - TimeUnit.SECONDS.toHours(seconds) * 60
|
||||
nextAiringEpisode = when {
|
||||
days > 0 -> {
|
||||
txt(
|
||||
R.string.next_episode_time_day_format,
|
||||
days,
|
||||
hours,
|
||||
minute
|
||||
)
|
||||
}
|
||||
hours > 0 -> txt(
|
||||
R.string.next_episode_time_hour_format,
|
||||
hours,
|
||||
minute
|
||||
)
|
||||
minute > 0 -> txt(
|
||||
R.string.next_episode_time_min_format,
|
||||
minute
|
||||
)
|
||||
else -> null
|
||||
}?.also {
|
||||
nextAiringDate = txt(R.string.next_episode_format, airing.episode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultData(
|
||||
nextAiringDate = nextAiringDate,
|
||||
nextAiringEpisode = nextAiringEpisode,
|
||||
posterImage = img(
|
||||
posterUrl, posterHeaders
|
||||
) ?: img(R.drawable.default_cover),
|
||||
titleText = txt(name),
|
||||
url = url,
|
||||
tags = tags ?: emptyList(),
|
||||
comingSoon = comingSoon,
|
||||
actors = if (hasActorImages) actors else null,
|
||||
actorsText = if (hasActorImages) null else txt(
|
||||
R.string.cast_format,
|
||||
actors?.joinToString { it.actor.name }),
|
||||
plotText =
|
||||
if (plot.isNullOrBlank()) txt(if (this is TorrentLoadResponse) R.string.torrent_no_plot else R.string.normal_no_plot) else txt(
|
||||
plot!!
|
||||
),
|
||||
backgroundPosterUrl = backgroundPosterUrl,
|
||||
title = name,
|
||||
typeText = txt(
|
||||
when (type) {
|
||||
TvType.TvSeries -> R.string.tv_series_singular
|
||||
TvType.Anime -> R.string.anime_singular
|
||||
TvType.OVA -> R.string.ova_singular
|
||||
TvType.AnimeMovie -> R.string.movies_singular
|
||||
TvType.Cartoon -> R.string.cartoons_singular
|
||||
TvType.Documentary -> R.string.documentaries_singular
|
||||
TvType.Movie -> R.string.movies_singular
|
||||
TvType.Torrent -> R.string.torrent_singular
|
||||
TvType.AsianDrama -> R.string.asian_drama_singular
|
||||
TvType.Live -> R.string.live_singular
|
||||
}
|
||||
),
|
||||
yearText = txt(year),
|
||||
apiName = txt(apiName),
|
||||
ratingText = rating?.div(1000f)?.let { UiText.StringResource(R.string.rating_format, it) },
|
||||
vpnText = txt(
|
||||
when (repo.vpnStatus) {
|
||||
VPNStatus.None -> null
|
||||
VPNStatus.Torrent -> R.string.vpn_torrent
|
||||
VPNStatus.MightBeNeeded -> R.string.vpn_might_be_needed
|
||||
}
|
||||
),
|
||||
metaText =
|
||||
if (repo.providerType == ProviderType.MetaProvider) txt(R.string.provider_info_meta) else null,
|
||||
durationText = txt(R.string.duration_format, duration),
|
||||
onGoingText = if (this is EpisodeResponse) {
|
||||
txt(
|
||||
when (showStatus) {
|
||||
ShowStatus.Ongoing -> R.string.status_ongoing
|
||||
ShowStatus.Completed -> R.string.status_completed
|
||||
else -> null
|
||||
}
|
||||
)
|
||||
} else null,
|
||||
noEpisodesFoundText =
|
||||
if ((this is TvSeriesLoadResponse && this.episodes.isEmpty()) || (this is AnimeLoadResponse && !this.episodes.any { it.value.isNotEmpty() })) txt(
|
||||
R.string.no_episodes_found
|
||||
) else null
|
||||
)
|
||||
}
|
||||
|
||||
class ResultViewModel2 : ViewModel() {
|
||||
private var currentResponse: LoadResponse? = null
|
||||
|
||||
data class EpisodeIndexer(
|
||||
val dubStatus: DubStatus,
|
||||
val season: Int,
|
||||
)
|
||||
|
||||
/** map<dub, map<season, List<episode>>> */
|
||||
private var currentEpisodes: Map<EpisodeIndexer, List<ResultEpisode>> = mapOf()
|
||||
private var currentRanges: Map<EpisodeIndexer, List<EpisodeRange>> = mapOf()
|
||||
private var currentIndex: EpisodeIndexer? = null
|
||||
private var currentRange: EpisodeRange? = null
|
||||
private var currentShowFillers: Boolean = false
|
||||
private var currentRepo: APIRepository? = null
|
||||
private var currentId: Int? = null
|
||||
private var fillers: Map<Int, Boolean> = emptyMap()
|
||||
private var generator: IGenerator? = null
|
||||
private var preferDubStatus: DubStatus? = null
|
||||
private var preferStartEpisode: Int? = null
|
||||
private var preferStartSeason: Int? = null
|
||||
|
||||
private val _page: MutableLiveData<Resource<ResultData>> =
|
||||
MutableLiveData(Resource.Loading())
|
||||
val page: LiveData<Resource<ResultData>> = _page
|
||||
|
||||
private val _episodes: MutableLiveData<Resource<List<ResultEpisode>>> =
|
||||
MutableLiveData(Resource.Loading())
|
||||
val episodes: LiveData<Resource<List<ResultEpisode>>> = _episodes
|
||||
|
||||
private val _episodesCount: MutableLiveData<Int> =
|
||||
MutableLiveData(0)
|
||||
val episodesCount: LiveData<Int> = _episodesCount
|
||||
|
||||
private val _trailers: MutableLiveData<List<TrailerData>> = MutableLiveData(mutableListOf())
|
||||
val trailers: LiveData<List<TrailerData>> = _trailers
|
||||
|
||||
private val _dubStatus: MutableLiveData<DubStatus?> = MutableLiveData(null)
|
||||
val dubStatus: LiveData<DubStatus?> = _dubStatus
|
||||
|
||||
private val _dubSubSelections: MutableLiveData<List<DubStatus>> = MutableLiveData(emptyList())
|
||||
val dubSubSelections: LiveData<List<DubStatus>> = _dubSubSelections
|
||||
|
||||
companion object {
|
||||
private const val EPISODE_RANGE_SIZE = 50
|
||||
private const val EPISODE_RANGE_OVERLOAD = 60
|
||||
|
||||
private fun filterName(name: String?): String? {
|
||||
if (name == null) return null
|
||||
Regex("[eE]pisode [0-9]*(.*)").find(name)?.groupValues?.get(1)?.let {
|
||||
if (it.isEmpty())
|
||||
return null
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
fun singleMap(ep: ResultEpisode): Map<EpisodeIndexer, List<ResultEpisode>> =
|
||||
mapOf(
|
||||
EpisodeIndexer(DubStatus.None, 0) to listOf(
|
||||
ep
|
||||
)
|
||||
)
|
||||
|
||||
private fun getRanges(allEpisodes: Map<EpisodeIndexer, List<ResultEpisode>>): Map<EpisodeIndexer, List<EpisodeRange>> {
|
||||
return allEpisodes.keys.mapNotNull { index ->
|
||||
val episodes =
|
||||
allEpisodes[index] ?: return@mapNotNull null // this should never happened
|
||||
|
||||
// fast case
|
||||
if (episodes.size <= EPISODE_RANGE_OVERLOAD) {
|
||||
return@mapNotNull index to listOf(
|
||||
EpisodeRange(
|
||||
0,
|
||||
episodes.size,
|
||||
episodes.minOf { it.episode },
|
||||
episodes.maxOf { it.episode })
|
||||
)
|
||||
}
|
||||
|
||||
if (episodes.isEmpty()) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
val list = mutableListOf<EpisodeRange>()
|
||||
|
||||
val currentEpisode = episodes.first()
|
||||
var currentIndex = 0
|
||||
val maxIndex = episodes.size
|
||||
var targetEpisode = 0
|
||||
var currentMin = currentEpisode.episode
|
||||
var currentMax = currentEpisode.episode
|
||||
|
||||
while (currentIndex < maxIndex) {
|
||||
val startIndex = currentIndex
|
||||
targetEpisode += EPISODE_RANGE_SIZE
|
||||
while (currentIndex < maxIndex && episodes[currentIndex].episode <= targetEpisode) {
|
||||
val episodeNumber = episodes[currentIndex].episode
|
||||
if (episodeNumber < currentMin) {
|
||||
currentMin = episodeNumber
|
||||
} else if (episodeNumber > currentMax) {
|
||||
currentMax = episodeNumber
|
||||
}
|
||||
++currentIndex
|
||||
}
|
||||
|
||||
val length = currentIndex - startIndex
|
||||
if (length <= 0) continue
|
||||
|
||||
list.add(
|
||||
EpisodeRange(
|
||||
startIndex,
|
||||
length,
|
||||
currentMin,
|
||||
currentMax
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/*var currentMin = Int.MAX_VALUE
|
||||
var currentMax = Int.MIN_VALUE
|
||||
var currentStartIndex = 0
|
||||
var currentLength = 0
|
||||
for (ep in episodes) {
|
||||
val episodeNumber = ep.episode
|
||||
if (episodeNumber < currentMin) {
|
||||
currentMin = episodeNumber
|
||||
} else if (episodeNumber > currentMax) {
|
||||
currentMax = episodeNumber
|
||||
}
|
||||
|
||||
if (++currentLength >= EPISODE_RANGE_SIZE) {
|
||||
list.add(
|
||||
EpisodeRange(
|
||||
currentStartIndex,
|
||||
currentLength,
|
||||
currentMin,
|
||||
currentMax
|
||||
)
|
||||
)
|
||||
currentMin = Int.MAX_VALUE
|
||||
currentMax = Int.MIN_VALUE
|
||||
currentStartIndex += currentLength
|
||||
currentLength = 0
|
||||
}
|
||||
}
|
||||
if (currentLength > 0) {
|
||||
list.add(
|
||||
EpisodeRange(
|
||||
currentStartIndex,
|
||||
currentLength,
|
||||
currentMin,
|
||||
currentMax
|
||||
)
|
||||
)
|
||||
}*/
|
||||
|
||||
index to list
|
||||
}.toMap()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateFillers(name: String) {
|
||||
fillers =
|
||||
try {
|
||||
FillerEpisodeCheck.getFillerEpisodes(name)
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
null
|
||||
} ?: emptyMap()
|
||||
|
||||
}
|
||||
|
||||
fun changeDubStatus(status: DubStatus) {
|
||||
postEpisodeRange(currentIndex?.copy(dubStatus = status), currentRange)
|
||||
}
|
||||
|
||||
fun changeRange(range: EpisodeRange) {
|
||||
postEpisodeRange(currentIndex, range)
|
||||
}
|
||||
|
||||
private fun getEpisodes(indexer: EpisodeIndexer, range: EpisodeRange): List<ResultEpisode> {
|
||||
//TODO ADD GENERATOR
|
||||
|
||||
val startIndex = range.startIndex
|
||||
val length = range.length
|
||||
|
||||
return currentEpisodes[indexer]
|
||||
?.let { list ->
|
||||
val start = minOf(list.size, startIndex)
|
||||
val end = minOf(list.size, start + length)
|
||||
list.subList(start, end).map {
|
||||
val posDur = DataStoreHelper.getViewPos(it.id)
|
||||
it.copy(position = posDur?.position ?: 0, duration = posDur?.duration ?: 0)
|
||||
}
|
||||
}
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
fun reloadEpisodes() {
|
||||
_episodes.postValue(
|
||||
Resource.Success(
|
||||
getEpisodes(
|
||||
currentIndex ?: return,
|
||||
currentRange ?: return
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) {
|
||||
if (range == null || indexer == null) {
|
||||
return
|
||||
}
|
||||
|
||||
currentIndex = indexer
|
||||
currentRange = range
|
||||
|
||||
//TODO SET KEYS
|
||||
preferStartEpisode = range.startEpisode
|
||||
preferStartSeason = indexer.season
|
||||
preferDubStatus = indexer.dubStatus
|
||||
|
||||
val ret = getEpisodes(indexer, range)
|
||||
_episodes.postValue(Resource.Success(ret))
|
||||
}
|
||||
|
||||
private suspend fun postSuccessful(
|
||||
loadResponse: LoadResponse,
|
||||
apiRepository: APIRepository,
|
||||
updateEpisodes: Boolean,
|
||||
updateFillers: Boolean,
|
||||
) {
|
||||
currentResponse = loadResponse
|
||||
postPage(loadResponse, apiRepository)
|
||||
if (updateEpisodes)
|
||||
postEpisodes(loadResponse, updateFillers)
|
||||
}
|
||||
|
||||
private suspend fun postEpisodes(loadResponse: LoadResponse, updateFillers: Boolean) {
|
||||
_episodes.postValue(Resource.Loading())
|
||||
|
||||
val mainId = loadResponse.getId()
|
||||
currentId = mainId
|
||||
|
||||
if (updateFillers && loadResponse is AnimeLoadResponse) {
|
||||
updateFillers(loadResponse.name)
|
||||
}
|
||||
|
||||
val allEpisodes = when (loadResponse) {
|
||||
is AnimeLoadResponse -> {
|
||||
val existingEpisodes = HashSet<Int>()
|
||||
val episodes: MutableMap<EpisodeIndexer, MutableList<ResultEpisode>> =
|
||||
mutableMapOf()
|
||||
loadResponse.episodes.map { ep ->
|
||||
val idIndex = ep.key.id
|
||||
for ((index, i) in ep.value.withIndex()) {
|
||||
val episode = i.episode ?: (index + 1)
|
||||
val id = mainId + episode + idIndex * 1000000
|
||||
if (!existingEpisodes.contains(episode)) {
|
||||
existingEpisodes.add(id)
|
||||
val eps =
|
||||
buildResultEpisode(
|
||||
loadResponse.name,
|
||||
filterName(i.name),
|
||||
i.posterUrl,
|
||||
episode,
|
||||
null,
|
||||
i.season,
|
||||
i.data,
|
||||
loadResponse.apiName,
|
||||
id,
|
||||
index,
|
||||
i.rating,
|
||||
i.description,
|
||||
fillers.getOrDefault(episode, false),
|
||||
loadResponse.type,
|
||||
mainId
|
||||
)
|
||||
|
||||
val season = eps.season ?: 0
|
||||
val indexer = EpisodeIndexer(ep.key, season)
|
||||
episodes[indexer]?.add(eps) ?: run {
|
||||
episodes[indexer] = mutableListOf(eps)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
episodes
|
||||
}
|
||||
is TvSeriesLoadResponse -> {
|
||||
val episodes: MutableMap<EpisodeIndexer, MutableList<ResultEpisode>> =
|
||||
mutableMapOf()
|
||||
val existingEpisodes = HashSet<Int>()
|
||||
for ((index, episode) in loadResponse.episodes.sortedBy {
|
||||
(it.season?.times(10000) ?: 0) + (it.episode ?: 0)
|
||||
}.withIndex()) {
|
||||
val episodeIndex = episode.episode ?: (index + 1)
|
||||
val id =
|
||||
mainId + (episode.season?.times(100000) ?: 0) + episodeIndex + 1
|
||||
if (!existingEpisodes.contains(id)) {
|
||||
existingEpisodes.add(id)
|
||||
val seasonIndex = episode.season?.minus(1)
|
||||
val currentSeason =
|
||||
loadResponse.seasonNames?.getOrNull(seasonIndex ?: -1)
|
||||
|
||||
val ep =
|
||||
buildResultEpisode(
|
||||
loadResponse.name,
|
||||
filterName(episode.name),
|
||||
episode.posterUrl,
|
||||
episodeIndex,
|
||||
seasonIndex,
|
||||
currentSeason?.season ?: episode.season,
|
||||
episode.data,
|
||||
loadResponse.apiName,
|
||||
id,
|
||||
index,
|
||||
episode.rating,
|
||||
episode.description,
|
||||
null,
|
||||
loadResponse.type,
|
||||
mainId
|
||||
)
|
||||
|
||||
val season = episode.season ?: 0
|
||||
val indexer = EpisodeIndexer(DubStatus.None, season)
|
||||
|
||||
episodes[indexer]?.add(ep) ?: kotlin.run {
|
||||
episodes[indexer] = mutableListOf(ep)
|
||||
}
|
||||
}
|
||||
}
|
||||
episodes
|
||||
}
|
||||
is MovieLoadResponse -> {
|
||||
singleMap(
|
||||
buildResultEpisode(
|
||||
loadResponse.name,
|
||||
loadResponse.name,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
loadResponse.dataUrl,
|
||||
loadResponse.apiName,
|
||||
(mainId), // HAS SAME ID
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
loadResponse.type,
|
||||
mainId
|
||||
)
|
||||
)
|
||||
}
|
||||
is LiveStreamLoadResponse -> {
|
||||
singleMap(
|
||||
buildResultEpisode(
|
||||
loadResponse.name,
|
||||
loadResponse.name,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
loadResponse.dataUrl,
|
||||
loadResponse.apiName,
|
||||
(mainId), // HAS SAME ID
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
loadResponse.type,
|
||||
mainId
|
||||
)
|
||||
)
|
||||
}
|
||||
is TorrentLoadResponse -> {
|
||||
singleMap(
|
||||
buildResultEpisode(
|
||||
loadResponse.name,
|
||||
loadResponse.name,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
loadResponse.torrent ?: loadResponse.magnet ?: "",
|
||||
loadResponse.apiName,
|
||||
(mainId), // HAS SAME ID
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
loadResponse.type,
|
||||
mainId
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
mapOf()
|
||||
}
|
||||
}
|
||||
|
||||
currentEpisodes = allEpisodes
|
||||
val ranges = getRanges(allEpisodes)
|
||||
currentRanges = ranges
|
||||
|
||||
// this takes the indexer most preferable by the user given the current sorting
|
||||
val min = ranges.keys.minByOrNull { index ->
|
||||
kotlin.math.abs(
|
||||
index.season - (preferStartSeason ?: 0)
|
||||
) + if (index.dubStatus == preferDubStatus) 0 else 100000
|
||||
}
|
||||
|
||||
// this takes the range most preferable by the user given the current sorting
|
||||
val ranger = ranges[min]
|
||||
val range = ranger?.firstOrNull {
|
||||
it.startEpisode >= (preferStartEpisode ?: 0)
|
||||
} ?: ranger?.lastOrNull()
|
||||
|
||||
postEpisodeRange(min, range)
|
||||
}
|
||||
|
||||
// this instantly updates the metadata on the page
|
||||
private fun postPage(loadResponse: LoadResponse, apiRepository: APIRepository) {
|
||||
_page.postValue(Resource.Success(loadResponse.toResultData(apiRepository)))
|
||||
_trailers.postValue(loadResponse.trailers)
|
||||
}
|
||||
|
||||
fun load(
|
||||
url: String,
|
||||
apiName: String,
|
||||
showFillers: Boolean,
|
||||
dubStatus: DubStatus,
|
||||
startEpisode: Int,
|
||||
startSeason: Int
|
||||
) =
|
||||
viewModelScope.launch {
|
||||
_page.postValue(Resource.Loading(url))
|
||||
_episodes.postValue(Resource.Loading(url))
|
||||
|
||||
preferDubStatus = dubStatus
|
||||
currentShowFillers = showFillers
|
||||
preferStartEpisode = startEpisode
|
||||
preferStartSeason = startSeason
|
||||
|
||||
// set api
|
||||
val api = APIHolder.getApiFromNameNull(apiName) ?: APIHolder.getApiFromUrlNull(url)
|
||||
if (api == null) {
|
||||
_page.postValue(
|
||||
Resource.Failure(
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
"This provider does not exist"
|
||||
)
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
||||
// validate url
|
||||
val validUrlResource = safeApiCall {
|
||||
SyncRedirector.redirect(
|
||||
url,
|
||||
api.mainUrl.replace(NineAnimeProvider().mainUrl, "9anime")
|
||||
.replace(GogoanimeProvider().mainUrl, "gogoanime")
|
||||
)
|
||||
}
|
||||
if (validUrlResource !is Resource.Success) {
|
||||
if (validUrlResource is Resource.Failure) {
|
||||
_page.postValue(validUrlResource)
|
||||
}
|
||||
|
||||
return@launch
|
||||
}
|
||||
val validUrl = validUrlResource.value
|
||||
val repo = APIRepository(api)
|
||||
currentRepo = repo
|
||||
|
||||
when (val data = repo.load(validUrl)) {
|
||||
is Resource.Failure -> {
|
||||
_page.postValue(data)
|
||||
}
|
||||
is Resource.Success -> {
|
||||
val loadResponse = data.value
|
||||
val mainId = loadResponse.getId()
|
||||
|
||||
AcraApplication.setKey(
|
||||
DOWNLOAD_HEADER_CACHE,
|
||||
mainId.toString(),
|
||||
VideoDownloadHelper.DownloadHeaderCached(
|
||||
apiName,
|
||||
validUrl,
|
||||
loadResponse.type,
|
||||
loadResponse.name,
|
||||
loadResponse.posterUrl,
|
||||
mainId,
|
||||
System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
|
||||
postSuccessful(
|
||||
data.value,
|
||||
updateEpisodes = true,
|
||||
updateFillers = showFillers,
|
||||
apiRepository = repo
|
||||
)
|
||||
}
|
||||
is Resource.Loading -> {
|
||||
debugException { "Invalid load result" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
126
app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt
Normal file
126
app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt
Normal file
|
@ -0,0 +1,126 @@
|
|||
package com.lagradost.cloudstream3.ui.result
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
|
||||
sealed class UiText {
|
||||
data class DynamicString(val value: String) : UiText()
|
||||
class StringResource(
|
||||
@StringRes val resId: Int,
|
||||
vararg val args: Any
|
||||
) : UiText()
|
||||
|
||||
fun asStringNull(context: Context?): String? {
|
||||
return asString(context ?: return null)
|
||||
}
|
||||
|
||||
fun asString(context: Context): String {
|
||||
return when (this) {
|
||||
is DynamicString -> value
|
||||
is StringResource -> context.getString(resId, *args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class UiImage {
|
||||
data class Image(
|
||||
val url: String,
|
||||
val headers: Map<String, String>? = null,
|
||||
@DrawableRes val errorDrawable: Int? = null
|
||||
) : UiImage()
|
||||
|
||||
data class Drawable(@DrawableRes val resId: Int) : UiImage()
|
||||
}
|
||||
|
||||
fun ImageView?.setImage(value: UiImage?) {
|
||||
when (value) {
|
||||
is UiImage.Image -> setImageImage(value)
|
||||
is UiImage.Drawable -> setImageDrawable(value)
|
||||
null -> {
|
||||
this?.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ImageView?.setImageImage(value: UiImage.Image) {
|
||||
if (this == null) return
|
||||
this.isVisible = setImage(value.url, value.headers, value.errorDrawable)
|
||||
}
|
||||
|
||||
fun ImageView?.setImageDrawable(value: UiImage.Drawable) {
|
||||
if (this == null) return
|
||||
this.isVisible = true
|
||||
setImageResource(value.resId)
|
||||
}
|
||||
|
||||
@JvmName("imgNull")
|
||||
fun img(
|
||||
url: String?,
|
||||
headers: Map<String, String>? = null,
|
||||
@DrawableRes errorDrawable: Int? = null
|
||||
): UiImage? {
|
||||
if (url.isNullOrBlank()) return null
|
||||
return UiImage.Image(url, headers, errorDrawable)
|
||||
}
|
||||
|
||||
fun img(
|
||||
url: String,
|
||||
headers: Map<String, String>? = null,
|
||||
@DrawableRes errorDrawable: Int? = null
|
||||
): UiImage {
|
||||
return UiImage.Image(url, headers, errorDrawable)
|
||||
}
|
||||
|
||||
fun img(@DrawableRes drawable: Int): UiImage {
|
||||
return UiImage.Drawable(drawable)
|
||||
}
|
||||
|
||||
fun txt(value: String): UiText {
|
||||
return UiText.DynamicString(value)
|
||||
}
|
||||
|
||||
@JvmName("txtNull")
|
||||
fun txt(value: String?): UiText? {
|
||||
return UiText.DynamicString(value ?: return null)
|
||||
}
|
||||
|
||||
fun txt(@StringRes resId: Int, vararg args: Any): UiText {
|
||||
return UiText.StringResource(resId, args)
|
||||
}
|
||||
|
||||
@JvmName("txtNull")
|
||||
fun txt(@StringRes resId: Int?, vararg args: Any?): UiText? {
|
||||
if (resId == null || args.any { it == null }) {
|
||||
return null
|
||||
}
|
||||
return UiText.StringResource(resId, args)
|
||||
}
|
||||
|
||||
fun TextView?.setText(text: UiText?) {
|
||||
if (this == null) return
|
||||
if (text == null) {
|
||||
this.isVisible = false
|
||||
} else {
|
||||
val str = text.asStringNull(context)
|
||||
this.isGone = str.isNullOrBlank()
|
||||
this.text = str
|
||||
}
|
||||
}
|
||||
|
||||
fun TextView?.setTextHtml(text: UiText?) {
|
||||
if (this == null) return
|
||||
if (text == null) {
|
||||
this.isVisible = false
|
||||
} else {
|
||||
val str = text.asStringNull(context)
|
||||
this.isGone = str.isNullOrBlank()
|
||||
this.text = str.html()
|
||||
}
|
||||
}
|
|
@ -464,6 +464,14 @@
|
|||
android:textColor="?attr/grayTextColor"
|
||||
android:textSize="15sp"
|
||||
tools:text="@string/provider_info_meta" />
|
||||
<TextView
|
||||
android:id="@+id/result_no_episodes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:textSize="15sp"
|
||||
tools:text="@string/no_episodes_found" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_tag_holder"
|
||||
|
|
Loading…
Reference in a new issue