feat: add a time since chapter item (#316)

* feat: add a time since chapter item

* fix: this is the song that never ends
This commit is contained in:
TwistedUmbrellaX 2024-04-04 05:45:03 -04:00 committed by GitHub
parent 6bfadfa962
commit e1b968bfe0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 178 additions and 6 deletions

View file

@ -92,6 +92,7 @@ import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.BuildConfig.APPLICATION_ID import ani.dantotsu.BuildConfig.APPLICATION_ID
import ani.dantotsu.connections.anilist.Genre import ani.dantotsu.connections.anilist.Genre
import ani.dantotsu.connections.anilist.api.FuzzyDate import ani.dantotsu.connections.anilist.api.FuzzyDate
import ani.dantotsu.connections.bakaupdates.MangaUpdates
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.databinding.ItemCountDownBinding import ani.dantotsu.databinding.ItemCountDownBinding
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
@ -102,6 +103,7 @@ import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.settings.saving.internal.PreferenceKeystore import ani.dantotsu.settings.saving.internal.PreferenceKeystore
import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt
import ani.dantotsu.util.CountUpTimer
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
@ -134,9 +136,12 @@ import io.noties.markwon.html.TagHandlerNoOp
import io.noties.markwon.image.AsyncDrawable import io.noties.markwon.image.AsyncDrawable
import io.noties.markwon.image.glide.GlideImagesPlugin import io.noties.markwon.image.glide.GlideImagesPlugin
import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import nl.joery.animatedbottombar.AnimatedBottomBar import nl.joery.animatedbottombar.AnimatedBottomBar
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -985,6 +990,54 @@ fun countDown(media: Media, view: ViewGroup) {
} }
} }
fun sinceWhen(media: Media, view: ViewGroup) {
CoroutineScope(Dispatchers.IO).launch {
MangaUpdates().search(media.name ?: media.nameRomaji, media.startDate)?.let {
val latestChapter = it.metadata.series.latestChapter ?: it.record.chapter?.let { chapter ->
if (chapter.contains("-"))
chapter.split("-")[1].trim()
else
chapter
}?.toInt()
val timeSince = (System.currentTimeMillis() -
(it.metadata.series.lastUpdated!!.timestamp * 1000)) / 1000
withContext(Dispatchers.Main) {
val v =
ItemCountDownBinding.inflate(LayoutInflater.from(view.context), view, false)
view.addView(v.root, 0)
v.mediaCountdownText.text =
currActivity()?.getString(R.string.chapter_release_timeout, latestChapter)
object : CountUpTimer(86400000) {
override fun onTick(second: Int) {
val a = second + timeSince
v.mediaCountdown.text = currActivity()?.getString(
R.string.time_format,
a / 86400,
a % 86400 / 3600,
a % 86400 % 3600 / 60,
a % 86400 % 3600 % 60
)
}
override fun onFinish() {
// The legend will never die.
}
}.start()
}
}
}
}
fun displayTimer(media: Media, view: ViewGroup) {
when {
media.anime != null -> countDown(media, view)
media.format == "MANGA" || media.format == "ONE_SHOT" -> sinceWhen(media, view)
else -> { } // No timer yet
}
}
fun MutableMap<String, Genre>.checkId(id: Int): Boolean { fun MutableMap<String, Genre>.checkId(id: Int): Boolean {
this.forEach { this.forEach {
if (it.value.id == id) { if (it.value.id == id) {

View file

@ -0,0 +1,98 @@
package ani.dantotsu.connections.bakaupdates
import ani.dantotsu.client
import ani.dantotsu.connections.anilist.api.FuzzyDate
import ani.dantotsu.tryWithSuspend
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import okio.ByteString.Companion.encode
import org.json.JSONException
import org.json.JSONObject
import java.nio.charset.Charset
class MangaUpdates {
private val Int?.dateFormat get() = String.format("%02d", this)
private val apiUrl = "https://api.mangaupdates.com/v1/releases/search"
suspend fun search(title: String, startDate: FuzzyDate?) : MangaUpdatesResponse.Results? {
return tryWithSuspend {
val query = JSONObject().apply {
try {
put("search", title.encode(Charset.forName("UTF-8")))
startDate?.let {
put(
"start_date",
"${it.year}-${it.month.dateFormat}-${it.day.dateFormat}"
)
}
put("include_metadata", true)
} catch (e: JSONException) {
e.printStackTrace()
}
}
val res = client.post(apiUrl, json = query).parsed<MangaUpdatesResponse>()
res.results?.forEach{ println("MangaUpdates: $it") }
res.results?.first { it.metadata.series.lastUpdated?.timestamp != null }
}
}
@Serializable
data class MangaUpdatesResponse(
@SerialName("total_hits")
val totalHits: Int?,
@SerialName("page")
val page: Int?,
@SerialName("per_page")
val perPage: Int?,
val results: List<Results>? = null
) {
@Serializable
data class Results(
val record: Record,
val metadata: MetaData
) {
@Serializable
data class Record(
@SerialName("id")
val id: Int,
@SerialName("title")
val title: String,
@SerialName("volume")
val volume: String?,
@SerialName("chapter")
val chapter: String?,
@SerialName("release_date")
val releaseDate: String
)
@Serializable
data class MetaData(
val series: Series
) {
@Serializable
data class Series(
@SerialName("series_id")
val seriesId: Long?,
@SerialName("title")
val title: String?,
@SerialName("latest_chapter")
val latestChapter: Int?,
@SerialName("last_updated")
val lastUpdated: LastUpdated?
) {
@Serializable
data class LastUpdated(
@SerialName("timestamp")
val timestamp: Long,
@SerialName("as_rfc3339")
val asRfc3339: String,
@SerialName("as_string")
val asString: String
)
}
}
}
}
}

View file

@ -27,7 +27,6 @@ import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.GenresViewModel import ani.dantotsu.connections.anilist.GenresViewModel
import ani.dantotsu.copyToClipboard import ani.dantotsu.copyToClipboard
import ani.dantotsu.countDown
import ani.dantotsu.currActivity import ani.dantotsu.currActivity
import ani.dantotsu.databinding.ActivityGenreBinding import ani.dantotsu.databinding.ActivityGenreBinding
import ani.dantotsu.databinding.FragmentMediaInfoBinding import ani.dantotsu.databinding.FragmentMediaInfoBinding
@ -38,6 +37,7 @@ import ani.dantotsu.databinding.ItemTitleRecyclerBinding
import ani.dantotsu.databinding.ItemTitleSearchBinding import ani.dantotsu.databinding.ItemTitleSearchBinding
import ani.dantotsu.databinding.ItemTitleTextBinding import ani.dantotsu.databinding.ItemTitleTextBinding
import ani.dantotsu.databinding.ItemTitleTrailerBinding import ani.dantotsu.databinding.ItemTitleTrailerBinding
import ani.dantotsu.displayTimer
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.px import ani.dantotsu.px
@ -225,8 +225,7 @@ class MediaInfoFragment : Fragment() {
.setDuration(400).start() .setDuration(400).start()
} }
} }
displayTimer(media, binding.mediaInfoContainer)
countDown(media, binding.mediaInfoContainer)
val parent = _binding?.mediaInfoContainer!! val parent = _binding?.mediaInfoContainer!!
val screenWidth = resources.displayMetrics.run { widthPixels / density } val screenWidth = resources.displayMetrics.run { widthPixels / density }

View file

@ -17,11 +17,11 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.FileUrl import ani.dantotsu.FileUrl
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.countDown
import ani.dantotsu.currActivity import ani.dantotsu.currActivity
import ani.dantotsu.databinding.DialogLayoutBinding import ani.dantotsu.databinding.DialogLayoutBinding
import ani.dantotsu.databinding.ItemAnimeWatchBinding import ani.dantotsu.databinding.ItemAnimeWatchBinding
import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.databinding.ItemChipBinding
import ani.dantotsu.displayTimer
import ani.dantotsu.isOnline import ani.dantotsu.isOnline
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
@ -500,8 +500,7 @@ class AnimeWatchAdapter(
inner class ViewHolder(val binding: ItemAnimeWatchBinding) : inner class ViewHolder(val binding: ItemAnimeWatchBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
init { init {
//Timer displayTimer(media, binding.animeSourceContainer)
countDown(media, binding.animeSourceContainer)
} }
} }
} }

View file

@ -0,0 +1,22 @@
package ani.dantotsu.util
import android.os.CountDownTimer
// https://stackoverflow.com/a/40422151/461982
abstract class CountUpTimer protected constructor(
private val duration: Long
) : CountDownTimer(duration, INTERVAL_MS) {
abstract fun onTick(second: Int)
override fun onTick(msUntilFinished: Long) {
val second = ((duration - msUntilFinished) / 1000).toInt()
onTick(second)
}
override fun onFinish() {
onTick(duration / 1000)
}
companion object {
private const val INTERVAL_MS: Long = 1000
}
}

View file

@ -502,6 +502,7 @@
<string name="refresh_token_load_failed">Refresh Token : Failed to load Saved Token</string> <string name="refresh_token_load_failed">Refresh Token : Failed to load Saved Token</string>
<string name="refreshing_token_failed">Refreshing Token Failed</string> <string name="refreshing_token_failed">Refreshing Token Failed</string>
<string name="episode_release_countdown">Episode %1$d will be released in</string> <string name="episode_release_countdown">Episode %1$d will be released in</string>
<string name="chapter_release_timeout">Chapter %1$d has been available for</string>
<string name="time_format">%1$d days %2$d hrs %3$d mins %4$d secs</string> <string name="time_format">%1$d days %2$d hrs %3$d mins %4$d secs</string>
<string-array name="sort_by"> <string-array name="sort_by">
<item>Score</item> <item>Score</item>