mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-22 12:48:15 +03:00
feat(anime screen): Next Episode Countdown (#1045)
This commit is contained in:
parent
878b0b65a2
commit
424d587cfa
10 changed files with 299 additions and 20 deletions
|
@ -1,20 +1,55 @@
|
|||
package eu.kanade.domain.entries.anime.interactor
|
||||
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.entries.anime.model.AnimeUpdate
|
||||
import tachiyomi.domain.entries.anime.repository.AnimeRepository
|
||||
import kotlin.math.pow
|
||||
|
||||
class SetAnimeViewerFlags(
|
||||
private val animeRepository: AnimeRepository,
|
||||
) {
|
||||
|
||||
suspend fun awaitSetSkipIntroLength(id: Long, skipIntroLength: Long) {
|
||||
// TODO: Convert to proper flag format
|
||||
// val anime = animeRepository.getAnimeById(id)
|
||||
suspend fun awaitSetSkipIntroLength(id: Long, flag: Long) {
|
||||
val anime = animeRepository.getAnimeById(id)
|
||||
animeRepository.updateAnime(
|
||||
AnimeUpdate(
|
||||
id = id,
|
||||
viewerFlags = skipIntroLength,
|
||||
viewerFlags = anime.viewerFlags.setFlag(flag, Anime.ANIME_INTRO_MASK),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitSetNextEpisodeAiring(id: Long, flags: Pair<Int, Long>) {
|
||||
awaitSetNextEpisodeToAir(id, flags.first.toLong().addHexZeros(zeros = 2))
|
||||
awaitSetNextEpisodeAiringAt(id, flags.second.addHexZeros(zeros = 6))
|
||||
}
|
||||
|
||||
private suspend fun awaitSetNextEpisodeToAir(id: Long, flag: Long) {
|
||||
val anime = animeRepository.getAnimeById(id)
|
||||
animeRepository.updateAnime(
|
||||
AnimeUpdate(
|
||||
id = id,
|
||||
viewerFlags = anime.viewerFlags.setFlag(flag, Anime.ANIME_AIRING_EPISODE_MASK),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun awaitSetNextEpisodeAiringAt(id: Long, flag: Long) {
|
||||
val anime = animeRepository.getAnimeById(id)
|
||||
animeRepository.updateAnime(
|
||||
AnimeUpdate(
|
||||
id = id,
|
||||
viewerFlags = anime.viewerFlags.setFlag(flag, Anime.ANIME_AIRING_TIME_MASK),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Long.setFlag(flag: Long, mask: Long): Long {
|
||||
return this and mask.inv() or (flag and mask)
|
||||
}
|
||||
|
||||
private fun Long.addHexZeros(zeros: Int): Long {
|
||||
val hex = 16.0
|
||||
return this.times(hex.pow(zeros)).toLong()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,4 +20,5 @@ enum class EntryScreenItem {
|
|||
DESCRIPTION_WITH_TAG,
|
||||
ITEM_HEADER,
|
||||
ITEM,
|
||||
AIRING_TIME,
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import androidx.compose.material3.SnackbarHost
|
|||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
@ -59,16 +60,18 @@ import eu.kanade.presentation.entries.anime.components.AnimeEpisodeListItem
|
|||
import eu.kanade.presentation.entries.anime.components.AnimeInfoBox
|
||||
import eu.kanade.presentation.entries.anime.components.EpisodeDownloadAction
|
||||
import eu.kanade.presentation.entries.anime.components.ExpandableAnimeDescription
|
||||
import eu.kanade.presentation.entries.anime.components.NextEpisodeAiringListItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload
|
||||
import eu.kanade.tachiyomi.source.anime.AnimeSourceManager
|
||||
import eu.kanade.tachiyomi.source.anime.getNameForAnimeInfo
|
||||
import eu.kanade.tachiyomi.ui.entries.anime.AnimeScreenState
|
||||
import eu.kanade.tachiyomi.ui.entries.anime.EpisodeItem
|
||||
import eu.kanade.tachiyomi.ui.entries.manga.chapterDecimalFormat
|
||||
import eu.kanade.tachiyomi.ui.entries.anime.episodeDecimalFormat
|
||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import kotlinx.coroutines.delay
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.items.episode.model.Episode
|
||||
import tachiyomi.presentation.core.components.LazyColumn
|
||||
|
@ -416,6 +419,31 @@ private fun AnimeScreenSmallImpl(
|
|||
)
|
||||
}
|
||||
|
||||
if (state.airingTime > 0L) {
|
||||
item(
|
||||
key = EntryScreenItem.AIRING_TIME,
|
||||
contentType = EntryScreenItem.AIRING_TIME,
|
||||
) {
|
||||
// Handles the second by second countdown
|
||||
var timer by remember { mutableStateOf(state.airingTime) }
|
||||
LaunchedEffect(key1 = timer) {
|
||||
if (timer > 0L) {
|
||||
delay(1000L)
|
||||
timer -= 1000L
|
||||
}
|
||||
}
|
||||
if (timer > 0L) {
|
||||
NextEpisodeAiringListItem(
|
||||
title = stringResource(
|
||||
R.string.display_mode_episode,
|
||||
episodeDecimalFormat.format(state.airingEpisodeNumber),
|
||||
),
|
||||
date = formatTime(state.airingTime, useDayFormat = true),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sharedEpisodeItems(
|
||||
anime = state.anime,
|
||||
episodes = episodes,
|
||||
|
@ -633,6 +661,31 @@ fun AnimeScreenLargeImpl(
|
|||
)
|
||||
}
|
||||
|
||||
if (state.airingTime > 0L) {
|
||||
item(
|
||||
key = EntryScreenItem.AIRING_TIME,
|
||||
contentType = EntryScreenItem.AIRING_TIME,
|
||||
) {
|
||||
// Handles the second by second countdown
|
||||
var timer by remember { mutableStateOf(state.airingTime) }
|
||||
LaunchedEffect(key1 = timer) {
|
||||
if (timer > 0L) {
|
||||
delay(1000L)
|
||||
timer -= 1000L
|
||||
}
|
||||
}
|
||||
if (timer > 0L) {
|
||||
NextEpisodeAiringListItem(
|
||||
title = stringResource(
|
||||
R.string.display_mode_episode,
|
||||
episodeDecimalFormat.format(state.airingEpisodeNumber),
|
||||
),
|
||||
date = formatTime(state.airingTime, useDayFormat = true),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sharedEpisodeItems(
|
||||
anime = state.anime,
|
||||
episodes = episodes,
|
||||
|
@ -721,8 +774,8 @@ private fun LazyListScope.sharedEpisodeItems(
|
|||
AnimeEpisodeListItem(
|
||||
title = if (anime.displayMode == Anime.EPISODE_DISPLAY_NUMBER) {
|
||||
stringResource(
|
||||
R.string.display_mode_chapter,
|
||||
chapterDecimalFormat.format(episodeItem.episode.episodeNumber.toDouble()),
|
||||
R.string.display_mode_episode,
|
||||
episodeDecimalFormat.format(episodeItem.episode.episodeNumber.toDouble()),
|
||||
)
|
||||
} else {
|
||||
episodeItem.episode.name
|
||||
|
@ -741,8 +794,8 @@ private fun LazyListScope.sharedEpisodeItems(
|
|||
?.let {
|
||||
stringResource(
|
||||
R.string.episode_progress,
|
||||
formatProgress(it),
|
||||
formatProgress(episodeItem.episode.totalSeconds),
|
||||
formatTime(it),
|
||||
formatTime(episodeItem.episode.totalSeconds),
|
||||
)
|
||||
},
|
||||
scanlator = episodeItem.episode.scanlator.takeIf { !it.isNullOrBlank() },
|
||||
|
@ -786,8 +839,19 @@ private fun onEpisodeItemClick(
|
|||
}
|
||||
}
|
||||
|
||||
private fun formatProgress(milliseconds: Long): String {
|
||||
return if (milliseconds > 3600000L) {
|
||||
private fun formatTime(milliseconds: Long, useDayFormat: Boolean = false): String {
|
||||
return if (useDayFormat) {
|
||||
String.format(
|
||||
"Airing in %02dd %02dh %02dm %02ds",
|
||||
TimeUnit.MILLISECONDS.toDays(milliseconds),
|
||||
TimeUnit.MILLISECONDS.toHours(milliseconds) -
|
||||
TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS.toDays(milliseconds)),
|
||||
TimeUnit.MILLISECONDS.toMinutes(milliseconds) -
|
||||
TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(milliseconds)),
|
||||
TimeUnit.MILLISECONDS.toSeconds(milliseconds) -
|
||||
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(milliseconds)),
|
||||
)
|
||||
} else if (milliseconds > 3600000L) {
|
||||
String.format(
|
||||
"%d:%02d:%02d",
|
||||
TimeUnit.MILLISECONDS.toHours(milliseconds),
|
||||
|
|
|
@ -131,3 +131,42 @@ fun AnimeEpisodeListItem(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NextEpisodeAiringListItem(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
date: String,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
var textHeight by remember { mutableStateOf(0) }
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
onTextLayout = { textHeight = it.size.height },
|
||||
modifier = Modifier.alpha(SecondaryItemAlpha),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Row(modifier = Modifier.alpha(SecondaryItemAlpha)) {
|
||||
ProvideTextStyle(
|
||||
value = MaterialTheme.typography.bodyMedium.copy(fontSize = 12.sp),
|
||||
) {
|
||||
Text(
|
||||
text = date,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ data class BackupAnime(
|
|||
favorite = anime.favorite,
|
||||
source = anime.source,
|
||||
dateAdded = anime.dateAdded,
|
||||
viewer_flags = anime.viewerFlags.toInt(),
|
||||
viewer_flags = anime.skipIntroLength,
|
||||
episodeFlags = anime.episodeFlags.toInt(),
|
||||
updateStrategy = anime.updateStrategy,
|
||||
)
|
||||
|
|
|
@ -27,8 +27,8 @@ interface Anime : SAnime {
|
|||
}
|
||||
|
||||
var skipIntroLength: Int
|
||||
get() = viewer_flags and 0x000000FF
|
||||
set(skipIntro) = setViewerFlags(skipIntro, 0x000000FF)
|
||||
get() = viewer_flags and DomainAnime.ANIME_INTRO_MASK.toInt()
|
||||
set(flag) = setViewerFlags(flag, DomainAnime.ANIME_INTRO_MASK.toInt())
|
||||
}
|
||||
|
||||
fun Anime.toDomainAnime(): DomainAnime? {
|
||||
|
|
|
@ -401,8 +401,8 @@ fun ChangeIntroLength(
|
|||
onSelectionChanged = {
|
||||
newLength = it + 1
|
||||
},
|
||||
startIndex = if (anime.viewerFlags > 0) {
|
||||
anime.viewerFlags.toInt() - 1
|
||||
startIndex = if (anime.skipIntroLength > 0) {
|
||||
anime.skipIntroLength - 1
|
||||
} else {
|
||||
defaultIntroLength
|
||||
},
|
||||
|
|
|
@ -37,6 +37,7 @@ import eu.kanade.tachiyomi.network.HttpException
|
|||
import eu.kanade.tachiyomi.source.anime.AnimeSourceManager
|
||||
import eu.kanade.tachiyomi.ui.entries.anime.track.AnimeTrackItem
|
||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||
import eu.kanade.tachiyomi.util.AniChartApi
|
||||
import eu.kanade.tachiyomi.util.episode.getNextUnseen
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
import eu.kanade.tachiyomi.util.shouldDownloadNewEpisodes
|
||||
|
@ -76,6 +77,9 @@ import tachiyomi.domain.items.episode.service.getEpisodeSort
|
|||
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.Calendar
|
||||
|
||||
class AnimeInfoScreenModel(
|
||||
val context: Context,
|
||||
|
@ -179,7 +183,7 @@ class AnimeInfoScreenModel(
|
|||
dialog = null,
|
||||
)
|
||||
}
|
||||
// Start observe tracking since it only needs mangaId
|
||||
// Start observe tracking since it only needs animeId
|
||||
observeTrackers()
|
||||
|
||||
// Fetch info-episodes when needed
|
||||
|
@ -205,6 +209,7 @@ class AnimeInfoScreenModel(
|
|||
)
|
||||
fetchFromSourceTasks.awaitAll()
|
||||
updateSuccessState { it.copy(isRefreshingData = false) }
|
||||
successState?.let { updateAiringTime(it.anime, it.trackItems, manualFetch) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -896,7 +901,6 @@ class AnimeInfoScreenModel(
|
|||
|
||||
private fun observeTrackers() {
|
||||
val anime = successState?.anime ?: return
|
||||
|
||||
coroutineScope.launchIO {
|
||||
getTracks.subscribe(anime.id)
|
||||
.catch { logcat(LogPriority.ERROR, it) }
|
||||
|
@ -911,10 +915,16 @@ class AnimeInfoScreenModel(
|
|||
.distinctUntilChanged()
|
||||
.collectLatest { trackItems ->
|
||||
updateSuccessState { it.copy(trackItems = trackItems) }
|
||||
updateAiringTime(anime, trackItems, manualFetch = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateAiringTime(anime: Anime, trackItems: List<AnimeTrackItem>, manualFetch: Boolean) {
|
||||
val airingEpisode = AniChartApi().loadAiringTime(anime, trackItems, manualFetch)
|
||||
updateSuccessState { it.copy(nextAiringEpisode = airingEpisode) }
|
||||
}
|
||||
|
||||
// Track sheet - end
|
||||
|
||||
sealed class Dialog {
|
||||
|
@ -1006,6 +1016,7 @@ sealed class AnimeScreenState {
|
|||
val isRefreshingData: Boolean = false,
|
||||
val dialog: AnimeInfoScreenModel.Dialog? = null,
|
||||
val hasPromptedToAddBefore: Boolean = false,
|
||||
val nextAiringEpisode: Pair<Int, Long> = Pair(anime.nextEpisodeToAir, anime.nextEpisodeAiringAt),
|
||||
) : AnimeScreenState() {
|
||||
|
||||
val processedEpisodes: Sequence<EpisodeItem>
|
||||
|
@ -1017,6 +1028,12 @@ sealed class AnimeScreenState {
|
|||
val trackingCount: Int
|
||||
get() = trackItems.count { it.track != null }
|
||||
|
||||
val airingEpisodeNumber: Double
|
||||
get() = nextAiringEpisode.first.toDouble()
|
||||
|
||||
val airingTime: Long
|
||||
get() = nextAiringEpisode.second.times(1000L).minus(Calendar.getInstance().timeInMillis)
|
||||
|
||||
/**
|
||||
* Applies the view filters to the list of chapters obtained from the database.
|
||||
* @return an observable of the list of chapters filtered and sorted.
|
||||
|
@ -1045,6 +1062,12 @@ data class EpisodeItem(
|
|||
val isDownloaded = downloadState == AnimeDownload.State.DOWNLOADED
|
||||
}
|
||||
|
||||
val episodeDecimalFormat = DecimalFormat(
|
||||
"#.###",
|
||||
DecimalFormatSymbols()
|
||||
.apply { decimalSeparator = '.' },
|
||||
)
|
||||
|
||||
private val Throwable.snackbarMessage: String
|
||||
get() = when (val className = this::class.simpleName) {
|
||||
null -> message ?: ""
|
||||
|
|
101
app/src/main/java/eu/kanade/tachiyomi/util/AniChartApi.kt
Normal file
101
app/src/main/java/eu/kanade/tachiyomi/util/AniChartApi.kt
Normal file
|
@ -0,0 +1,101 @@
|
|||
package eu.kanade.tachiyomi.util
|
||||
import eu.kanade.domain.entries.anime.interactor.SetAnimeViewerFlags
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.jsonMime
|
||||
import eu.kanade.tachiyomi.ui.entries.anime.track.AnimeTrackItem
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class AniChartApi {
|
||||
private val client = OkHttpClient()
|
||||
private val setAnimeViewerFlags: SetAnimeViewerFlags = Injekt.get()
|
||||
|
||||
internal suspend fun loadAiringTime(anime: Anime, trackItems: List<AnimeTrackItem>, manualFetch: Boolean): Pair<Int, Long> {
|
||||
if (anime.status == SAnime.COMPLETED.toLong() && !manualFetch) return Pair(anime.nextEpisodeToAir, anime.nextEpisodeAiringAt)
|
||||
return withIOContext {
|
||||
var alId = 0L
|
||||
var airingTime = Pair(0, 0L)
|
||||
trackItems.forEach {
|
||||
if (it.track != null) {
|
||||
alId = when (it.service) {
|
||||
is Anilist -> it.track.media_id
|
||||
is MyAnimeList -> getAlIdFromMal(it.track.media_id)
|
||||
else -> 0L
|
||||
}
|
||||
}
|
||||
}
|
||||
if (alId != 0L) {
|
||||
airingTime = getAiringAt(alId)
|
||||
setAnimeViewerFlags.awaitSetNextEpisodeAiring(anime.id, airingTime)
|
||||
}
|
||||
return@withIOContext airingTime
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAlIdFromMal(idMal: Long): Long {
|
||||
return withIOContext {
|
||||
val query = """
|
||||
query {
|
||||
Media(idMal:$idMal,type: ANIME) {
|
||||
id
|
||||
}
|
||||
}
|
||||
""".trimMargin()
|
||||
|
||||
val response = try {
|
||||
client.newCall(
|
||||
POST(
|
||||
"https://graphql.anilist.co",
|
||||
body = buildJsonObject { put("query", query) }.toString()
|
||||
.toRequestBody(jsonMime),
|
||||
),
|
||||
).execute()
|
||||
} catch (e: Exception) {
|
||||
return@withIOContext 0L
|
||||
}
|
||||
return@withIOContext response.body.string().substringAfter("id\":")
|
||||
.substringBefore("}")
|
||||
.toLongOrNull() ?: 0L
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAiringAt(id: Long): Pair<Int, Long> {
|
||||
return withIOContext {
|
||||
val query = """
|
||||
query {
|
||||
Media(id:$id) {
|
||||
nextAiringEpisode {
|
||||
episode
|
||||
airingAt
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimMargin()
|
||||
val response = try {
|
||||
client.newCall(
|
||||
POST(
|
||||
"https://graphql.anilist.co",
|
||||
body = buildJsonObject { put("query", query) }.toString()
|
||||
.toRequestBody(jsonMime),
|
||||
),
|
||||
).execute()
|
||||
} catch (e: Exception) {
|
||||
return@withIOContext Pair(0, 0L)
|
||||
}
|
||||
val data = response.body.string()
|
||||
val episodeNumber = data.substringAfter("episode\":").substringBefore(",").toIntOrNull() ?: 0
|
||||
val airingAt = data.substringAfter("airingAt\":").substringBefore("}").toLongOrNull() ?: 0L
|
||||
|
||||
return@withIOContext Pair(episodeNumber, airingAt)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package tachiyomi.domain.entries.anime.model
|
|||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import java.io.Serializable
|
||||
import kotlin.math.pow
|
||||
|
||||
data class Anime(
|
||||
val id: Long,
|
||||
|
@ -40,8 +41,14 @@ data class Anime(
|
|||
val bookmarkedFilterRaw: Long
|
||||
get() = episodeFlags and EPISODE_BOOKMARKED_MASK
|
||||
|
||||
val skipIntroLength: Long
|
||||
get() = viewerFlags
|
||||
val skipIntroLength: Int
|
||||
get() = (viewerFlags and ANIME_INTRO_MASK).toInt()
|
||||
|
||||
val nextEpisodeToAir: Int
|
||||
get() = (viewerFlags and ANIME_AIRING_EPISODE_MASK).removeHexZeros(zeros = 2).toInt()
|
||||
|
||||
val nextEpisodeAiringAt: Long
|
||||
get() = (viewerFlags and ANIME_AIRING_TIME_MASK).removeHexZeros(zeros = 6)
|
||||
|
||||
val unseenFilter: TriStateFilter
|
||||
get() = when (unseenFilterRaw) {
|
||||
|
@ -61,6 +68,11 @@ data class Anime(
|
|||
return episodeFlags and EPISODE_SORT_DIR_MASK == EPISODE_SORT_DESC
|
||||
}
|
||||
|
||||
private fun Long.removeHexZeros(zeros: Int): Long {
|
||||
val hex = 16.0
|
||||
return this.div(hex.pow(zeros)).toLong()
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Generic filter that does not filter anything
|
||||
const val SHOW_ALL = 0x00000000L
|
||||
|
@ -90,6 +102,10 @@ data class Anime(
|
|||
const val EPISODE_DISPLAY_NUMBER = 0x00100000L
|
||||
const val EPISODE_DISPLAY_MASK = 0x00100000L
|
||||
|
||||
const val ANIME_INTRO_MASK = 0x000000000000FFL
|
||||
const val ANIME_AIRING_EPISODE_MASK = 0x00000000FFFF00L
|
||||
const val ANIME_AIRING_TIME_MASK = 0xFFFFFFFF000000L
|
||||
|
||||
fun create() = Anime(
|
||||
id = -1L,
|
||||
url = "",
|
||||
|
|
Loading…
Reference in a new issue