mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-29 09:39:03 +03:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
20e6431fe0
23 changed files with 340 additions and 269 deletions
|
@ -1,6 +1,7 @@
|
|||
package eu.kanade.data.anime
|
||||
|
||||
import eu.kanade.domain.anime.model.Anime
|
||||
import eu.kanade.domain.episode.model.Episode
|
||||
|
||||
val animeMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long) -> Anime =
|
||||
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, _, initialized, viewer, episodeFlags, coverLastModified, dateAdded ->
|
||||
|
@ -24,3 +25,40 @@ val animeMapper: (Long, Long, String, String?, String?, String?, List<String>?,
|
|||
initialized = initialized,
|
||||
)
|
||||
}
|
||||
|
||||
val animeEpisodeMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, Long, Long, String, String, String?, Boolean, Boolean, Long, Long, Float, Long, Long, Long) -> Pair<Anime, Episode> =
|
||||
{ _id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, next_update, initialized, viewerFlags, episodeFlags, coverLastModified, dateAdded, episodeId, animeId, chapterUrl, name, scanlator, seen, bookmark, lastSecondSeen, totalSeconds, episodeNumber, sourceOrder, dateFetch, dateUpload ->
|
||||
Anime(
|
||||
id = _id,
|
||||
source = source,
|
||||
favorite = favorite,
|
||||
lastUpdate = lastUpdate ?: 0,
|
||||
dateAdded = dateAdded,
|
||||
viewerFlags = viewerFlags,
|
||||
episodeFlags = episodeFlags,
|
||||
coverLastModified = coverLastModified,
|
||||
url = url,
|
||||
title = title,
|
||||
artist = artist,
|
||||
author = author,
|
||||
description = description,
|
||||
genre = genre,
|
||||
status = status,
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
initialized = initialized,
|
||||
) to Episode(
|
||||
id = episodeId,
|
||||
animeId = animeId,
|
||||
seen = seen,
|
||||
bookmark = bookmark,
|
||||
lastSecondSeen = lastSecondSeen,
|
||||
totalSeconds = totalSeconds,
|
||||
dateFetch = dateFetch,
|
||||
sourceOrder = sourceOrder,
|
||||
url = chapterUrl,
|
||||
name = name,
|
||||
dateUpload = dateUpload,
|
||||
episodeNumber = episodeNumber,
|
||||
scanlator = scanlator,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package eu.kanade.data.manga
|
||||
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
|
||||
val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long) -> Manga =
|
||||
|
@ -24,3 +25,39 @@ val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?,
|
|||
initialized = initialized,
|
||||
)
|
||||
}
|
||||
|
||||
val mangaChapterMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, Long, Long, String, String, String?, Boolean, Boolean, Long, Float, Long, Long, Long) -> Pair<Manga, Chapter> =
|
||||
{ _id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, next_update, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, chapterId, mangaId, chapterUrl, name, scanlator, read, bookmark, lastPageRead, chapterNumber, sourceOrder, dateFetch, dateUpload ->
|
||||
Manga(
|
||||
id = _id,
|
||||
source = source,
|
||||
favorite = favorite,
|
||||
lastUpdate = lastUpdate ?: 0,
|
||||
dateAdded = dateAdded,
|
||||
viewerFlags = viewerFlags,
|
||||
chapterFlags = chapterFlags,
|
||||
coverLastModified = coverLastModified,
|
||||
url = url,
|
||||
title = title,
|
||||
artist = artist,
|
||||
author = author,
|
||||
description = description,
|
||||
genre = genre,
|
||||
status = status,
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
initialized = initialized,
|
||||
) to Chapter(
|
||||
id = chapterId,
|
||||
mangaId = mangaId,
|
||||
read = read,
|
||||
bookmark = bookmark,
|
||||
lastPageRead = lastPageRead,
|
||||
dateFetch = dateFetch,
|
||||
sourceOrder = sourceOrder,
|
||||
url = chapterUrl,
|
||||
name = name,
|
||||
dateUpload = dateUpload,
|
||||
chapterNumber = chapterNumber,
|
||||
scanlator = scanlator,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -361,6 +361,7 @@ private fun AnimeScreenSmallImpl(
|
|||
WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()
|
||||
VerticalFastScroller(
|
||||
listState = episodeListState,
|
||||
thumbAllowed = { scrollBehavior.state.offset == scrollBehavior.state.offsetLimit },
|
||||
topContentPadding = withNavBarContentPadding.calculateTopPadding(),
|
||||
endContentPadding = withNavBarContentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
||||
) {
|
||||
|
|
|
@ -47,6 +47,7 @@ import kotlin.math.roundToInt
|
|||
fun VerticalFastScroller(
|
||||
listState: LazyListState,
|
||||
modifier: Modifier = Modifier,
|
||||
thumbAllowed: () -> Boolean = { true },
|
||||
thumbColor: Color = MaterialTheme.colorScheme.primary,
|
||||
topContentPadding: Dp = Dp.Hairline,
|
||||
endContentPadding: Dp = Dp.Hairline,
|
||||
|
@ -106,8 +107,12 @@ fun VerticalFastScroller(
|
|||
val isThumbVisible = alpha.value > 0f
|
||||
LaunchedEffect(scrolled, alpha) {
|
||||
scrolled.collectLatest {
|
||||
alpha.snapTo(1f)
|
||||
alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec)
|
||||
if (thumbAllowed()) {
|
||||
alpha.snapTo(1f)
|
||||
alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec)
|
||||
} else {
|
||||
alpha.animateTo(0f, animationSpec = ImmediateFadeOutAnimationSpec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,6 +192,9 @@ private val FadeOutAnimationSpec = tween<Float>(
|
|||
durationMillis = ViewConfiguration.getScrollBarFadeDuration(),
|
||||
delayMillis = 2000,
|
||||
)
|
||||
private val ImmediateFadeOutAnimationSpec = tween<Float>(
|
||||
durationMillis = ViewConfiguration.getScrollBarFadeDuration(),
|
||||
)
|
||||
|
||||
private val LazyListItemInfo.top: Int
|
||||
get() = offset
|
||||
|
|
|
@ -353,6 +353,7 @@ private fun MangaScreenSmallImpl(
|
|||
WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()
|
||||
VerticalFastScroller(
|
||||
listState = chapterListState,
|
||||
thumbAllowed = { scrollBehavior.state.offset == scrollBehavior.state.offsetLimit },
|
||||
topContentPadding = withNavBarContentPadding.calculateTopPadding(),
|
||||
endContentPadding = withNavBarContentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
||||
) {
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
package eu.kanade.tachiyomi.data.database.queries
|
||||
|
||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||
import java.util.Date
|
||||
|
||||
interface ChapterQueries : DbProvider {
|
||||
|
||||
|
@ -24,18 +20,6 @@ interface ChapterQueries : DbProvider {
|
|||
)
|
||||
.prepare()
|
||||
|
||||
fun getRecentChapters(date: Date) = db.get()
|
||||
.listOfObjects(MangaChapter::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentsQuery())
|
||||
.args(date.time)
|
||||
.observesTables(ChapterTable.TABLE)
|
||||
.build(),
|
||||
)
|
||||
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getChapter(id: Long) = db.get()
|
||||
.`object`(Chapter::class.java)
|
||||
.withQuery(
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
package eu.kanade.tachiyomi.data.database.queries
|
||||
|
||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||
import eu.kanade.tachiyomi.data.database.models.Anime
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeEpisode
|
||||
import eu.kanade.tachiyomi.data.database.models.Episode
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.AnimeEpisodeGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.EpisodeProgressPutResolver
|
||||
import eu.kanade.tachiyomi.data.database.tables.EpisodeTable
|
||||
import java.util.Date
|
||||
|
||||
interface EpisodeQueries : DbProvider {
|
||||
|
||||
|
@ -24,18 +20,6 @@ interface EpisodeQueries : DbProvider {
|
|||
)
|
||||
.prepare()
|
||||
|
||||
fun getRecentEpisodes(date: Date) = db.get()
|
||||
.listOfObjects(AnimeEpisode::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentsQueryAnime())
|
||||
.args(date.time)
|
||||
.observesTables(EpisodeTable.TABLE)
|
||||
.build(),
|
||||
)
|
||||
.withGetResolver(AnimeEpisodeGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getEpisode(id: Long) = db.get()
|
||||
.`object`(Episode::class.java)
|
||||
.withQuery(
|
||||
|
|
|
@ -71,32 +71,6 @@ val animelibQuery =
|
|||
ON MC.${AnimeCategory.COL_ANIME_ID} = M.${Anime.COL_ID}
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the recent chapters of manga from the library up to a date.
|
||||
*/
|
||||
fun getRecentsQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${Manga.COL_FAVORITE} = 1
|
||||
AND ${Chapter.COL_DATE_UPLOAD} > ?
|
||||
AND ${Chapter.COL_DATE_FETCH} > ${Manga.COL_DATE_ADDED}
|
||||
ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the recent chapters of manga from the library up to a date.
|
||||
*/
|
||||
fun getRecentsQueryAnime() =
|
||||
"""
|
||||
SELECT ${Anime.TABLE}.${Anime.COL_URL} as animeUrl, * FROM ${Anime.TABLE} JOIN ${Episode.TABLE}
|
||||
ON ${Anime.TABLE}.${Anime.COL_ID} = ${Episode.TABLE}.${Episode.COL_ANIME_ID}
|
||||
WHERE ${Anime.COL_FAVORITE} = 1
|
||||
AND ${Episode.COL_DATE_UPLOAD} > ?
|
||||
AND ${Episode.COL_DATE_FETCH} > ${Anime.COL_DATE_ADDED}
|
||||
ORDER BY ${Episode.COL_DATE_UPLOAD} DESC
|
||||
"""
|
||||
|
||||
fun getLastReadMangaQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
|
||||
|
|
|
@ -2,15 +2,13 @@ package eu.kanade.tachiyomi.ui.anime.episode.base
|
|||
|
||||
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
||||
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
|
||||
import eu.kanade.tachiyomi.data.database.models.Episode
|
||||
import eu.kanade.domain.episode.model.Episode
|
||||
import eu.kanade.tachiyomi.data.download.model.AnimeDownload
|
||||
|
||||
abstract class BaseEpisodeItem<T : BaseEpisodeHolder, H : AbstractHeaderItem<*>>(
|
||||
val episode: Episode,
|
||||
header: H? = null,
|
||||
) :
|
||||
AbstractSectionableItem<T, H?>(header),
|
||||
Episode by episode {
|
||||
) : AbstractSectionableItem<T, H?>(header) {
|
||||
|
||||
private var _status: AnimeDownload.State = AnimeDownload.State.NOT_DOWNLOADED
|
||||
|
||||
|
@ -35,12 +33,14 @@ abstract class BaseEpisodeItem<T : BaseEpisodeHolder, H : AbstractHeaderItem<*>>
|
|||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other is BaseEpisodeItem<*, *>) {
|
||||
return episode.id!! == other.episode.id!!
|
||||
return episode.id == other.episode.id && episode.seen == other.episode.seen
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return episode.id!!.hashCode()
|
||||
var result = episode.id.hashCode()
|
||||
result = 31 * result + episode.seen.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,14 @@ package eu.kanade.tachiyomi.ui.manga.chapter.base
|
|||
|
||||
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
||||
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
|
||||
abstract class BaseChapterItem<T : BaseChapterHolder, H : AbstractHeaderItem<*>>(
|
||||
val chapter: Chapter,
|
||||
header: H? = null,
|
||||
) :
|
||||
AbstractSectionableItem<T, H?>(header),
|
||||
Chapter by chapter {
|
||||
) : AbstractSectionableItem<T, H?>(header) {
|
||||
|
||||
private var _status: Download.State = Download.State.NOT_DOWNLOADED
|
||||
|
||||
|
@ -36,12 +34,14 @@ abstract class BaseChapterItem<T : BaseChapterHolder, H : AbstractHeaderItem<*>>
|
|||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other is BaseChapterItem<*, *>) {
|
||||
return chapter.id!! == other.chapter.id!!
|
||||
return chapter.id == other.chapter.id && chapter.read == other.chapter.read
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return chapter.id!!.hashCode()
|
||||
var result = chapter.id.hashCode()
|
||||
result = 31 * result + chapter.read.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@ package eu.kanade.tachiyomi.ui.reader
|
|||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.ScaleXSpan
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.core.text.set
|
||||
import eu.kanade.tachiyomi.widget.OutlineSpan
|
||||
|
||||
/**
|
||||
|
@ -31,10 +31,10 @@ class PageIndicatorTextView(
|
|||
// Also add a bit of spacing between each character, as the stroke overlaps them
|
||||
val finalText = SpannableString(currText.asIterable().joinToString("\u00A0")).apply {
|
||||
// Apply text outline
|
||||
set(1, length - 1, spanOutline)
|
||||
setSpan(spanOutline, 1, length - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
||||
for (i in 1..lastIndex step 2) {
|
||||
set(i, i + 1, ScaleXSpan(0.2f))
|
||||
setSpan(ScaleXSpan(0.2f), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
|||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.core.view.updatePadding
|
||||
import com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
|
||||
|
@ -56,7 +55,7 @@ class PagerTransitionHolder(
|
|||
orientation = VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
val sidePadding = 64.dpToPx
|
||||
updatePadding(left = sidePadding, right = sidePadding)
|
||||
setPadding(sidePadding, 0, sidePadding, 0)
|
||||
|
||||
val transitionView = ReaderTransitionView(context)
|
||||
addView(transitionView)
|
||||
|
|
|
@ -12,12 +12,11 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.SelectableAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.domain.anime.model.toDbAnime
|
||||
import eu.kanade.domain.episode.model.toDbEpisode
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
|
||||
import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateService
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainAnime
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainEpisode
|
||||
import eu.kanade.tachiyomi.data.download.AnimeDownloadService
|
||||
import eu.kanade.tachiyomi.data.download.model.AnimeDownload
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
|
@ -40,12 +39,13 @@ import eu.kanade.tachiyomi.util.system.notificationManager
|
|||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
|
||||
import eu.kanade.tachiyomi.widget.ActionModeWithToolbar
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import reactivecircus.flowbinding.recyclerview.scrollStateChanges
|
||||
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
/**
|
||||
|
@ -123,6 +123,24 @@ class AnimeUpdatesController :
|
|||
binding.swipeRefresh.isRefreshing = false
|
||||
}
|
||||
.launchIn(viewScope)
|
||||
|
||||
viewScope.launch {
|
||||
presenter.updates.collectLatest { updatesItems ->
|
||||
destroyActionModeIfNeeded()
|
||||
if (adapter == null) {
|
||||
adapter = AnimeUpdatesAdapter(this@AnimeUpdatesController, binding.recycler.context, updatesItems)
|
||||
binding.recycler.adapter = adapter
|
||||
adapter!!.fastScroller = binding.fastScroller
|
||||
} else {
|
||||
adapter?.updateDataSet(updatesItems)
|
||||
}
|
||||
binding.swipeRefresh.isRefreshing = false
|
||||
binding.fastScroller.isVisible = true
|
||||
binding.recycler.onAnimationsFinished {
|
||||
(activity as? MainActivity)?.ready = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
|
@ -211,7 +229,7 @@ class AnimeUpdatesController :
|
|||
if (useExternal) {
|
||||
openEpisodeExternal(item)
|
||||
} else {
|
||||
val intent = PlayerActivity.newIntent(activity, item.anime, item.episode)
|
||||
val intent = PlayerActivity.newIntent(activity, item.anime.id, item.episode.id)
|
||||
if (hasAnimation) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
||||
}
|
||||
|
@ -222,23 +240,22 @@ class AnimeUpdatesController :
|
|||
private fun openEpisodeExternal(item: AnimeUpdatesItem) {
|
||||
val context = activity ?: return
|
||||
val anime = item.anime
|
||||
val domainAnime = item.anime.toDomainAnime() ?: return
|
||||
val domainAnime = item.anime
|
||||
val episode = item.episode
|
||||
val domainEpisode = item.episode.toDomainEpisode() ?: return
|
||||
val source = sourceManager.get(item.anime.source) ?: return
|
||||
launchIO {
|
||||
val video = try {
|
||||
EpisodeLoader.getLink(episode, anime, source).awaitSingle()
|
||||
EpisodeLoader.getLink(episode.toDbEpisode(), anime.toDbAnime(), source).awaitSingle()
|
||||
} catch (e: Exception) {
|
||||
launchUI { context.toast(e.message) }
|
||||
return@launchIO
|
||||
}
|
||||
if (video != null) {
|
||||
AnimeController.EXT_EPISODE = domainEpisode
|
||||
AnimeController.EXT_EPISODE = episode
|
||||
AnimeController.EXT_ANIME = domainAnime
|
||||
|
||||
val extIntent = ExternalIntents(domainAnime, source).getExternalIntent(
|
||||
domainEpisode,
|
||||
episode,
|
||||
video,
|
||||
context,
|
||||
)
|
||||
|
@ -278,26 +295,6 @@ class AnimeUpdatesController :
|
|||
destroyActionModeIfNeeded()
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate adapter with episodes
|
||||
* @param episodes list of [Any]
|
||||
*/
|
||||
fun onNextRecentEpisodes(episodes: List<IFlexible<*>>) {
|
||||
destroyActionModeIfNeeded()
|
||||
if (adapter == null) {
|
||||
adapter = AnimeUpdatesAdapter(this@AnimeUpdatesController, binding.recycler.context, episodes)
|
||||
binding.recycler.adapter = adapter
|
||||
adapter!!.fastScroller = binding.fastScroller
|
||||
} else {
|
||||
adapter?.updateDataSet(episodes)
|
||||
}
|
||||
binding.swipeRefresh.isRefreshing = false
|
||||
binding.fastScroller.isVisible = true
|
||||
binding.recycler.onAnimationsFinished {
|
||||
(activity as? MainActivity)?.ready = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUpdateEmptyView(size: Int) {
|
||||
if (size > 0) {
|
||||
binding.emptyView.hide()
|
||||
|
@ -356,7 +353,7 @@ class AnimeUpdatesController :
|
|||
}
|
||||
|
||||
private fun openAnime(episode: AnimeUpdatesItem) {
|
||||
parentController!!.router.pushController(AnimeController(episode.anime.id!!))
|
||||
parentController!!.router.pushController(AnimeController(episode.anime.id))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -395,8 +392,8 @@ class AnimeUpdatesController :
|
|||
}
|
||||
|
||||
override fun startDownloadNow(position: Int) {
|
||||
val episode = adapter?.getItem(position) as? AnimeUpdatesItem ?: return
|
||||
presenter.startDownloadingNow(episode)
|
||||
val item = adapter?.getItem(position) as? AnimeUpdatesItem ?: return
|
||||
presenter.startDownloadingNow(item.episode)
|
||||
}
|
||||
|
||||
private fun bookmarkEpisodes(episodes: List<AnimeUpdatesItem>, bookmarked: Boolean) {
|
||||
|
@ -441,8 +438,8 @@ class AnimeUpdatesController :
|
|||
if (episodes.isEmpty()) return
|
||||
toolbar.findToolbarItem(R.id.action_download)?.isVisible = episodes.any { !it.isDownloaded }
|
||||
toolbar.findToolbarItem(R.id.action_delete)?.isVisible = episodes.any { it.isDownloaded }
|
||||
toolbar.findToolbarItem(R.id.action_bookmark)?.isVisible = episodes.any { !it.bookmark }
|
||||
toolbar.findToolbarItem(R.id.action_remove_bookmark)?.isVisible = episodes.all { it.bookmark }
|
||||
toolbar.findToolbarItem(R.id.action_bookmark)?.isVisible = episodes.any { !it.episode.bookmark }
|
||||
toolbar.findToolbarItem(R.id.action_remove_bookmark)?.isVisible = episodes.all { it.episode.bookmark }
|
||||
toolbar.findToolbarItem(R.id.action_mark_as_seen)?.isVisible = episodes.any { !it.episode.seen }
|
||||
toolbar.findToolbarItem(R.id.action_mark_as_unseen)?.isVisible = episodes.all { it.episode.seen }
|
||||
toolbar.findToolbarItem(R.id.action_play_externally)?.isVisible = !preferences.alwaysUseExternalPlayer() && episodes.size == 1
|
||||
|
|
|
@ -43,12 +43,12 @@ class AnimeUpdatesHolder(private val view: View, private val adapter: AnimeUpdat
|
|||
} else {
|
||||
binding.mangaTitle.setTextColor(adapter.unseenColor)
|
||||
binding.chapterTitle.setTextColor(
|
||||
if (item.bookmark) adapter.bookmarkedColor else adapter.unseenColorSecondary,
|
||||
if (item.episode.bookmark) adapter.bookmarkedColor else adapter.unseenColorSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
// Set bookmark status
|
||||
binding.bookmarkIcon.isVisible = item.bookmark
|
||||
binding.bookmarkIcon.isVisible = item.episode.bookmark
|
||||
|
||||
// Set episode status
|
||||
binding.download.isVisible = item.anime.source != LocalAnimeSource.ID
|
||||
|
|
|
@ -4,9 +4,9 @@ import android.view.View
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.domain.anime.model.Anime
|
||||
import eu.kanade.domain.episode.model.Episode
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Anime
|
||||
import eu.kanade.tachiyomi.data.database.models.Episode
|
||||
import eu.kanade.tachiyomi.ui.anime.episode.base.BaseEpisodeItem
|
||||
import eu.kanade.tachiyomi.ui.recent.DateSectionItem
|
||||
|
||||
|
|
|
@ -1,17 +1,28 @@
|
|||
package eu.kanade.tachiyomi.ui.recent.animeupdates
|
||||
|
||||
import android.os.Bundle
|
||||
import eu.kanade.data.AnimeDatabaseHandler
|
||||
import eu.kanade.data.anime.animeEpisodeMapper
|
||||
import eu.kanade.domain.anime.model.Anime
|
||||
import eu.kanade.domain.anime.model.toDbAnime
|
||||
import eu.kanade.domain.episode.interactor.UpdateEpisode
|
||||
import eu.kanade.domain.episode.model.Episode
|
||||
import eu.kanade.domain.episode.model.EpisodeUpdate
|
||||
import eu.kanade.domain.episode.model.toDbEpisode
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
|
||||
import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeEpisode
|
||||
import eu.kanade.tachiyomi.data.database.models.Episode
|
||||
import eu.kanade.tachiyomi.data.download.AnimeDownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.model.AnimeDownload
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.ui.recent.DateSectionItem
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.toDateKey
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import logcat.LogPriority
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
|
@ -25,24 +36,22 @@ import java.util.TreeMap
|
|||
class AnimeUpdatesPresenter : BasePresenter<AnimeUpdatesController>() {
|
||||
|
||||
val preferences: PreferencesHelper by injectLazy()
|
||||
private val db: AnimeDatabaseHelper by injectLazy()
|
||||
private val downloadManager: AnimeDownloadManager by injectLazy()
|
||||
private val sourceManager: AnimeSourceManager by injectLazy()
|
||||
|
||||
private val handler: AnimeDatabaseHandler by injectLazy()
|
||||
private val updateEpisode: UpdateEpisode by injectLazy()
|
||||
|
||||
private val relativeTime: Int = preferences.relativeTime().get()
|
||||
private val dateFormat: DateFormat = preferences.dateFormat()
|
||||
|
||||
/**
|
||||
* List containing episode and anime information
|
||||
*/
|
||||
private var episodes: List<AnimeUpdatesItem> = emptyList()
|
||||
private val _updates: MutableStateFlow<List<AnimeUpdatesItem>> = MutableStateFlow(listOf())
|
||||
val updates: StateFlow<List<AnimeUpdatesItem>> = _updates.asStateFlow()
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
|
||||
getUpdatesObservable()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeLatestCache(AnimeUpdatesController::onNextRecentEpisodes)
|
||||
|
||||
downloadManager.queue.getStatusObservable()
|
||||
.observeOn(Schedulers.io())
|
||||
|
@ -80,43 +89,49 @@ class AnimeUpdatesPresenter : BasePresenter<AnimeUpdatesController>() {
|
|||
*
|
||||
* @return observable containing recent episodes and date
|
||||
*/
|
||||
private fun getUpdatesObservable(): Observable<List<AnimeUpdatesItem>> {
|
||||
private fun getUpdatesObservable() {
|
||||
// Set date limit for recent episodes
|
||||
val cal = Calendar.getInstance().apply {
|
||||
time = Date()
|
||||
add(Calendar.MONTH, -3)
|
||||
}
|
||||
|
||||
return db.getRecentEpisodes(cal.time).asRxObservable()
|
||||
// Convert to a list of recent episodes.
|
||||
.map { animeEpisodes ->
|
||||
val map = TreeMap<Date, MutableList<AnimeEpisode>> { d1, d2 -> d2.compareTo(d1) }
|
||||
val byDay = animeEpisodes
|
||||
.groupByTo(map) { it.episode.date_fetch.toDateKey() }
|
||||
byDay.flatMap { entry ->
|
||||
val dateItem = DateSectionItem(entry.key, relativeTime, dateFormat)
|
||||
entry.value
|
||||
.sortedWith(compareBy({ it.episode.date_fetch }, { it.episode.episode_number })).asReversed()
|
||||
.map { AnimeUpdatesItem(it.episode, it.anime, dateItem) }
|
||||
}
|
||||
presenterScope.launchIO {
|
||||
val cal = Calendar.getInstance().apply {
|
||||
time = Date()
|
||||
add(Calendar.MONTH, -3)
|
||||
}
|
||||
.doOnNext { list ->
|
||||
list.forEach { item ->
|
||||
// Find an active download for this episode.
|
||||
val download = downloadManager.queue.find { it.episode.id == item.episode.id }
|
||||
|
||||
// If there's an active download, assign it, otherwise ask the manager if
|
||||
// the episode is downloaded and assign it to the status.
|
||||
if (download != null) {
|
||||
item.download = download
|
||||
handler
|
||||
.subscribeToList {
|
||||
animesQueries.getRecentlyUpdated(after = cal.timeInMillis, animeEpisodeMapper)
|
||||
}
|
||||
.map { animeEpisode ->
|
||||
val map = TreeMap<Date, MutableList<Pair<Anime, Episode>>> { d1, d2 -> d2.compareTo(d1) }
|
||||
val byDate = animeEpisode.groupByTo(map) { it.second.dateFetch.toDateKey() }
|
||||
byDate.flatMap { entry ->
|
||||
val dateItem = DateSectionItem(entry.key, relativeTime, dateFormat)
|
||||
entry.value
|
||||
.sortedWith(compareBy({ it.second.dateFetch }, { it.second.episodeNumber })).asReversed()
|
||||
.map { AnimeUpdatesItem(it.second, it.first, dateItem) }
|
||||
}
|
||||
}
|
||||
setDownloadedEpisodes(list)
|
||||
episodes = list
|
||||
.collectLatest { list ->
|
||||
// TODO: remove this workaround when updates aren't cluttered anymore
|
||||
_updates.value = list
|
||||
list.forEach { item ->
|
||||
// Find an active download for this episode.
|
||||
val download = downloadManager.queue.find { it.episode.id == item.episode.id }
|
||||
|
||||
// Set unseen episode count for bottom bar badge
|
||||
preferences.unseenUpdatesCount().set(list.count { !it.seen })
|
||||
}
|
||||
// If there's an active download, assign it, otherwise ask the manager if
|
||||
// the chapter is downloaded and assign it to the status.
|
||||
if (download != null) {
|
||||
item.download = download
|
||||
}
|
||||
}
|
||||
setDownloadedEpisodes(list)
|
||||
|
||||
_updates.value = list
|
||||
|
||||
// Set unseen episode count for bottom bar badge
|
||||
preferences.unseenUpdatesCount().set(list.count { !it.episode.seen })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,6 +158,7 @@ class AnimeUpdatesPresenter : BasePresenter<AnimeUpdatesController>() {
|
|||
private fun onDownloadStatusChange(download: AnimeDownload) {
|
||||
// Assign the download to the model object.
|
||||
if (download.status == AnimeDownload.State.QUEUE) {
|
||||
val episodes = (view?.adapter?.currentItems ?: emptyList()).filterIsInstance<AnimeUpdatesItem>()
|
||||
val episode = episodes.find { it.episode.id == download.episode.id }
|
||||
if (episode != null && episode.download == null) {
|
||||
episode.download = download
|
||||
|
@ -161,17 +177,16 @@ class AnimeUpdatesPresenter : BasePresenter<AnimeUpdatesController>() {
|
|||
* @param seen seen/unseen status
|
||||
*/
|
||||
fun markEpisodeRead(items: List<AnimeUpdatesItem>, seen: Boolean) {
|
||||
val episodes = items.map { it.episode }
|
||||
episodes.forEach {
|
||||
it.seen = seen
|
||||
if (!seen) {
|
||||
it.last_second_seen = 0
|
||||
presenterScope.launchIO {
|
||||
val toUpdate = items.map {
|
||||
EpisodeUpdate(
|
||||
seen = seen,
|
||||
lastSecondSeen = if (!seen) 0 else null,
|
||||
id = it.episode.id,
|
||||
)
|
||||
}
|
||||
updateEpisode.awaitAll(toUpdate)
|
||||
}
|
||||
|
||||
Observable.fromCallable { db.updateEpisodesProgress(episodes).executeAsBlocking() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -198,14 +213,15 @@ class AnimeUpdatesPresenter : BasePresenter<AnimeUpdatesController>() {
|
|||
* @param bookmarked bookmark status
|
||||
*/
|
||||
fun bookmarkEpisodes(items: List<AnimeUpdatesItem>, bookmarked: Boolean) {
|
||||
val episodes = items.map { it.episode }
|
||||
episodes.forEach {
|
||||
it.bookmark = bookmarked
|
||||
presenterScope.launchIO {
|
||||
val toUpdate = items.map {
|
||||
EpisodeUpdate(
|
||||
bookmark = bookmarked,
|
||||
id = it.episode.id,
|
||||
)
|
||||
}
|
||||
updateEpisode.awaitAll(toUpdate)
|
||||
}
|
||||
|
||||
Observable.fromCallable { db.updateEpisodesProgress(episodes).executeAsBlocking() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -213,7 +229,7 @@ class AnimeUpdatesPresenter : BasePresenter<AnimeUpdatesController>() {
|
|||
* @param items list of recent episodes seleted.
|
||||
*/
|
||||
fun downloadEpisodes(items: List<AnimeUpdatesItem>) {
|
||||
items.forEach { downloadManager.downloadEpisodes(it.anime, listOf(it.episode)) }
|
||||
items.forEach { downloadManager.downloadEpisodes(it.anime.toDbAnime(), listOf(it.episode.toDbEpisode())) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,7 +237,7 @@ class AnimeUpdatesPresenter : BasePresenter<AnimeUpdatesController>() {
|
|||
* @param items list of recent episodes seleted.
|
||||
*/
|
||||
fun downloadEpisodesExternally(items: List<AnimeUpdatesItem>) {
|
||||
items.forEach { downloadManager.downloadEpisodesAlt(it.anime, listOf(it.episode)) }
|
||||
items.forEach { downloadManager.downloadEpisodesAlt(it.anime.toDbAnime(), listOf(it.episode.toDbEpisode())) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,9 +248,9 @@ class AnimeUpdatesPresenter : BasePresenter<AnimeUpdatesController>() {
|
|||
private fun deleteEpisodesInternal(episodeItems: List<AnimeUpdatesItem>) {
|
||||
val itemsByAnime = episodeItems.groupBy { it.anime.id }
|
||||
for ((_, items) in itemsByAnime) {
|
||||
val anime = items.first().anime
|
||||
val anime = items.first().anime.toDbAnime()
|
||||
val source = sourceManager.get(anime.source) ?: continue
|
||||
val episodes = items.map { it.episode }
|
||||
val episodes = items.map { it.episode.toDbEpisode() }
|
||||
|
||||
downloadManager.deleteEpisodes(episodes, anime, source)
|
||||
items.forEach {
|
||||
|
|
|
@ -11,7 +11,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.SelectableAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
|
@ -30,8 +29,10 @@ import eu.kanade.tachiyomi.util.system.notificationManager
|
|||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
|
||||
import eu.kanade.tachiyomi.widget.ActionModeWithToolbar
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import reactivecircus.flowbinding.recyclerview.scrollStateChanges
|
||||
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
|
||||
|
@ -107,6 +108,24 @@ class UpdatesController :
|
|||
binding.swipeRefresh.isRefreshing = false
|
||||
}
|
||||
.launchIn(viewScope)
|
||||
|
||||
viewScope.launch {
|
||||
presenter.updates.collectLatest { updatesItems ->
|
||||
destroyActionModeIfNeeded()
|
||||
if (adapter == null) {
|
||||
adapter = UpdatesAdapter(this@UpdatesController, binding.recycler.context, updatesItems)
|
||||
binding.recycler.adapter = adapter
|
||||
adapter!!.fastScroller = binding.fastScroller
|
||||
} else {
|
||||
adapter?.updateDataSet(updatesItems)
|
||||
}
|
||||
binding.swipeRefresh.isRefreshing = false
|
||||
binding.fastScroller.isVisible = true
|
||||
binding.recycler.onAnimationsFinished {
|
||||
(activity as? MainActivity)?.ready = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
|
@ -191,7 +210,7 @@ class UpdatesController :
|
|||
*/
|
||||
private fun openChapter(item: UpdatesItem) {
|
||||
val activity = activity ?: return
|
||||
val intent = ReaderActivity.newIntent(activity, item.manga, item.chapter)
|
||||
val intent = ReaderActivity.newIntent(activity, item.manga.id, item.chapter.id)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
|
@ -204,26 +223,6 @@ class UpdatesController :
|
|||
destroyActionModeIfNeeded()
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate adapter with chapters
|
||||
* @param chapters list of [Any]
|
||||
*/
|
||||
fun onNextRecentChapters(chapters: List<IFlexible<*>>) {
|
||||
destroyActionModeIfNeeded()
|
||||
if (adapter == null) {
|
||||
adapter = UpdatesAdapter(this@UpdatesController, binding.recycler.context, chapters)
|
||||
binding.recycler.adapter = adapter
|
||||
adapter!!.fastScroller = binding.fastScroller
|
||||
} else {
|
||||
adapter?.updateDataSet(chapters)
|
||||
}
|
||||
binding.swipeRefresh.isRefreshing = false
|
||||
binding.fastScroller.isVisible = true
|
||||
binding.recycler.onAnimationsFinished {
|
||||
(activity as? MainActivity)?.ready = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUpdateEmptyView(size: Int) {
|
||||
if (size > 0) {
|
||||
binding.emptyView.hide()
|
||||
|
@ -317,8 +316,8 @@ class UpdatesController :
|
|||
}
|
||||
|
||||
override fun startDownloadNow(position: Int) {
|
||||
val chapter = adapter?.getItem(position) as? UpdatesItem ?: return
|
||||
presenter.startDownloadingNow(chapter)
|
||||
val item = adapter?.getItem(position) as? UpdatesItem ?: return
|
||||
presenter.startDownloadingNow(item.chapter)
|
||||
}
|
||||
|
||||
private fun bookmarkChapters(chapters: List<UpdatesItem>, bookmarked: Boolean) {
|
||||
|
@ -357,8 +356,8 @@ class UpdatesController :
|
|||
if (chapters.isEmpty()) return
|
||||
toolbar.findToolbarItem(R.id.action_download)?.isVisible = chapters.any { !it.isDownloaded }
|
||||
toolbar.findToolbarItem(R.id.action_delete)?.isVisible = chapters.any { it.isDownloaded }
|
||||
toolbar.findToolbarItem(R.id.action_bookmark)?.isVisible = chapters.any { !it.bookmark }
|
||||
toolbar.findToolbarItem(R.id.action_remove_bookmark)?.isVisible = chapters.all { it.bookmark }
|
||||
toolbar.findToolbarItem(R.id.action_bookmark)?.isVisible = chapters.any { !it.chapter.bookmark }
|
||||
toolbar.findToolbarItem(R.id.action_remove_bookmark)?.isVisible = chapters.all { it.chapter.bookmark }
|
||||
toolbar.findToolbarItem(R.id.action_mark_as_read)?.isVisible = chapters.any { !it.chapter.read }
|
||||
toolbar.findToolbarItem(R.id.action_mark_as_unread)?.isVisible = chapters.all { it.chapter.read }
|
||||
}
|
||||
|
|
|
@ -44,12 +44,12 @@ class UpdatesHolder(private val view: View, private val adapter: UpdatesAdapter)
|
|||
} else {
|
||||
binding.mangaTitle.setTextColor(adapter.unreadColor)
|
||||
binding.chapterTitle.setTextColor(
|
||||
if (item.bookmark) adapter.bookmarkedColor else adapter.unreadColorSecondary,
|
||||
if (item.chapter.bookmark) adapter.bookmarkedColor else adapter.unreadColorSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
// Set bookmark status
|
||||
binding.bookmarkIcon.isVisible = item.bookmark
|
||||
binding.bookmarkIcon.isVisible = item.chapter.bookmark
|
||||
|
||||
// Set chapter status
|
||||
binding.download.isVisible = item.manga.source != LocalSource.ID
|
||||
|
|
|
@ -4,9 +4,9 @@ import android.view.View
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChapterItem
|
||||
import eu.kanade.tachiyomi.ui.recent.DateSectionItem
|
||||
|
||||
|
|
|
@ -1,17 +1,28 @@
|
|||
package eu.kanade.tachiyomi.ui.recent.updates
|
||||
|
||||
import android.os.Bundle
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.data.manga.mangaChapterMapper
|
||||
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.chapter.model.ChapterUpdate
|
||||
import eu.kanade.domain.chapter.model.toDbChapter
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.manga.model.toDbManga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.ui.recent.DateSectionItem
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.toDateKey
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import logcat.LogPriority
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
|
@ -25,24 +36,22 @@ import java.util.TreeMap
|
|||
class UpdatesPresenter : BasePresenter<UpdatesController>() {
|
||||
|
||||
val preferences: PreferencesHelper by injectLazy()
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
private val downloadManager: DownloadManager by injectLazy()
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
|
||||
private val handler: DatabaseHandler by injectLazy()
|
||||
private val updateChapter: UpdateChapter by injectLazy()
|
||||
|
||||
private val relativeTime: Int = preferences.relativeTime().get()
|
||||
private val dateFormat: DateFormat = preferences.dateFormat()
|
||||
|
||||
/**
|
||||
* List containing chapter and manga information
|
||||
*/
|
||||
private var chapters: List<UpdatesItem> = emptyList()
|
||||
private val _updates: MutableStateFlow<List<UpdatesItem>> = MutableStateFlow(listOf())
|
||||
val updates: StateFlow<List<UpdatesItem>> = _updates.asStateFlow()
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
|
||||
getUpdatesObservable()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeLatestCache(UpdatesController::onNextRecentChapters)
|
||||
|
||||
downloadManager.queue.getStatusObservable()
|
||||
.observeOn(Schedulers.io())
|
||||
|
@ -72,43 +81,47 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
|
|||
*
|
||||
* @return observable containing recent chapters and date
|
||||
*/
|
||||
private fun getUpdatesObservable(): Observable<List<UpdatesItem>> {
|
||||
private fun getUpdatesObservable() {
|
||||
// Set date limit for recent chapters
|
||||
val cal = Calendar.getInstance().apply {
|
||||
time = Date()
|
||||
add(Calendar.MONTH, -3)
|
||||
}
|
||||
|
||||
return db.getRecentChapters(cal.time).asRxObservable()
|
||||
// Convert to a list of recent chapters.
|
||||
.map { mangaChapters ->
|
||||
val map = TreeMap<Date, MutableList<MangaChapter>> { d1, d2 -> d2.compareTo(d1) }
|
||||
val byDay = mangaChapters
|
||||
.groupByTo(map) { it.chapter.date_fetch.toDateKey() }
|
||||
byDay.flatMap { entry ->
|
||||
val dateItem = DateSectionItem(entry.key, relativeTime, dateFormat)
|
||||
entry.value
|
||||
.sortedWith(compareBy({ it.chapter.date_fetch }, { it.chapter.chapter_number })).asReversed()
|
||||
.map { UpdatesItem(it.chapter, it.manga, dateItem) }
|
||||
}
|
||||
presenterScope.launchIO {
|
||||
val cal = Calendar.getInstance().apply {
|
||||
time = Date()
|
||||
add(Calendar.MONTH, -3)
|
||||
}
|
||||
.doOnNext { list ->
|
||||
list.forEach { item ->
|
||||
// Find an active download for this chapter.
|
||||
val download = downloadManager.queue.find { it.chapter.id == item.chapter.id }
|
||||
|
||||
// If there's an active download, assign it, otherwise ask the manager if
|
||||
// the chapter is downloaded and assign it to the status.
|
||||
if (download != null) {
|
||||
item.download = download
|
||||
handler
|
||||
.subscribeToList {
|
||||
mangasQueries.getRecentlyUpdated(after = cal.timeInMillis, mangaChapterMapper)
|
||||
}
|
||||
.map { mangaChapter ->
|
||||
val map = TreeMap<Date, MutableList<Pair<Manga, Chapter>>> { d1, d2 -> d2.compareTo(d1) }
|
||||
val byDate = mangaChapter.groupByTo(map) { it.second.dateFetch.toDateKey() }
|
||||
byDate.flatMap { entry ->
|
||||
val dateItem = DateSectionItem(entry.key, relativeTime, dateFormat)
|
||||
entry.value
|
||||
.sortedWith(compareBy({ it.second.dateFetch }, { it.second.chapterNumber })).asReversed()
|
||||
.map { UpdatesItem(it.second, it.first, dateItem) }
|
||||
}
|
||||
}
|
||||
setDownloadedChapters(list)
|
||||
chapters = list
|
||||
.collectLatest { list ->
|
||||
list.forEach { item ->
|
||||
// Find an active download for this chapter.
|
||||
val download = downloadManager.queue.find { it.chapter.id == item.chapter.id }
|
||||
|
||||
// Set unread chapter count for bottom bar badge
|
||||
preferences.unreadUpdatesCount().set(list.count { !it.read })
|
||||
}
|
||||
// If there's an active download, assign it, otherwise ask the manager if
|
||||
// the chapter is downloaded and assign it to the status.
|
||||
if (download != null) {
|
||||
item.download = download
|
||||
}
|
||||
}
|
||||
setDownloadedChapters(list)
|
||||
|
||||
_updates.value = list
|
||||
|
||||
// Set unread chapter count for bottom bar badge
|
||||
preferences.unreadUpdatesCount().set(list.count { !it.chapter.read })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,6 +148,7 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
|
|||
private fun onDownloadStatusChange(download: Download) {
|
||||
// Assign the download to the model object.
|
||||
if (download.status == Download.State.QUEUE) {
|
||||
val chapters = (view?.adapter?.currentItems ?: emptyList()).filterIsInstance<UpdatesItem>()
|
||||
val chapter = chapters.find { it.chapter.id == download.chapter.id }
|
||||
if (chapter != null && chapter.download == null) {
|
||||
chapter.download = download
|
||||
|
@ -153,17 +167,16 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
|
|||
* @param read read status
|
||||
*/
|
||||
fun markChapterRead(items: List<UpdatesItem>, read: Boolean) {
|
||||
val chapters = items.map { it.chapter }
|
||||
chapters.forEach {
|
||||
it.read = read
|
||||
if (!read) {
|
||||
it.last_page_read = 0
|
||||
presenterScope.launchIO {
|
||||
val toUpdate = items.map {
|
||||
ChapterUpdate(
|
||||
read = read,
|
||||
lastPageRead = if (!read) 0 else null,
|
||||
id = it.chapter.id,
|
||||
)
|
||||
}
|
||||
updateChapter.awaitAll(toUpdate)
|
||||
}
|
||||
|
||||
Observable.fromCallable { db.updateChaptersProgress(chapters).executeAsBlocking() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -190,14 +203,15 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
|
|||
* @param bookmarked bookmark status
|
||||
*/
|
||||
fun bookmarkChapters(items: List<UpdatesItem>, bookmarked: Boolean) {
|
||||
val chapters = items.map { it.chapter }
|
||||
chapters.forEach {
|
||||
it.bookmark = bookmarked
|
||||
presenterScope.launchIO {
|
||||
val toUpdate = items.map {
|
||||
ChapterUpdate(
|
||||
bookmark = bookmarked,
|
||||
id = it.chapter.id,
|
||||
)
|
||||
}
|
||||
updateChapter.awaitAll(toUpdate)
|
||||
}
|
||||
|
||||
Observable.fromCallable { db.updateChaptersProgress(chapters).executeAsBlocking() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -205,7 +219,7 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
|
|||
* @param items list of recent chapters seleted.
|
||||
*/
|
||||
fun downloadChapters(items: List<UpdatesItem>) {
|
||||
items.forEach { downloadManager.downloadChapters(it.manga, listOf(it.chapter)) }
|
||||
items.forEach { downloadManager.downloadChapters(it.manga.toDbManga(), listOf(it.chapter.toDbChapter())) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -216,9 +230,9 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
|
|||
private fun deleteChaptersInternal(chapterItems: List<UpdatesItem>) {
|
||||
val itemsByManga = chapterItems.groupBy { it.manga.id }
|
||||
for ((_, items) in itemsByManga) {
|
||||
val manga = items.first().manga
|
||||
val manga = items.first().manga.toDbManga()
|
||||
val source = sourceManager.get(manga.source) ?: continue
|
||||
val chapters = items.map { it.chapter }
|
||||
val chapters = items.map { it.chapter.toDbChapter() }
|
||||
|
||||
downloadManager.deleteChapters(chapters, manga, source)
|
||||
items.forEach {
|
||||
|
|
|
@ -12,7 +12,6 @@ import android.util.AttributeSet
|
|||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.core.graphics.drawable.updateBounds
|
||||
import androidx.core.graphics.withTranslation
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||
|
@ -93,7 +92,7 @@ class TachiyomiAppBarLayout @JvmOverloads constructor(
|
|||
|
||||
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||
super.onLayout(changed, l, t, r, b)
|
||||
statusBarForeground?.updateBounds(right = width, bottom = paddingTop)
|
||||
statusBarForeground?.setBounds(0, 0, width, paddingTop)
|
||||
}
|
||||
|
||||
override fun onOffsetChanged(offset: Int) {
|
||||
|
|
|
@ -76,6 +76,16 @@ FROM mangas
|
|||
WHERE favorite = 0
|
||||
GROUP BY source;
|
||||
|
||||
getRecentlyUpdated:
|
||||
SELECT *
|
||||
FROM mangas M
|
||||
JOIN chapters C
|
||||
ON M._id = C.manga_id
|
||||
WHERE M.favorite = 1
|
||||
AND C.date_upload > :after
|
||||
AND C.date_fetch > M.date_added
|
||||
ORDER BY C.date_upload DESC;
|
||||
|
||||
deleteMangasNotInLibraryBySourceIds:
|
||||
DELETE FROM mangas
|
||||
WHERE favorite = 0 AND source IN :sourceIds;
|
||||
|
|
|
@ -76,6 +76,16 @@ FROM animes
|
|||
WHERE favorite = 0
|
||||
GROUP BY source;
|
||||
|
||||
getRecentlyUpdated:
|
||||
SELECT *
|
||||
FROM animes A
|
||||
JOIN episodes EP
|
||||
ON A._id = EP.anime_id
|
||||
WHERE A.favorite = 1
|
||||
AND EP.date_upload > :after
|
||||
AND EP.date_fetch > A.date_added
|
||||
ORDER BY EP.date_upload DESC;
|
||||
|
||||
deleteAnimesNotInLibraryBySourceIds:
|
||||
DELETE FROM animes
|
||||
WHERE favorite = 0 AND source IN :sourceIds;
|
||||
|
|
Loading…
Reference in a new issue