Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/src/main/java/eu/kanade/domain/DomainModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class DomainModule : InjektModule {

addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
addFactory { TrackEpisode(get(), get(), get(), get()) }
addFactory { AddTracks(get(), get(), get(), get()) }
addFactory { AddTracks(get(), get(), get(), get(), get(), get()) }
addFactory { RefreshTracks(get(), get(), get(), get()) }
addFactory { DeleteTrack(get()) }
addFactory { GetTracksPerAnime(get()) }
Expand Down
139 changes: 86 additions & 53 deletions app/src/main/java/eu/kanade/domain/track/interactor/AddTracks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package eu.kanade.domain.track.interactor
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.model.FetchType
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.Tracker
Expand All @@ -15,6 +16,8 @@ import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.anime.model.Anime
import tachiyomi.domain.episode.interactor.GetEpisodesByAnimeId
import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.season.interactor.GetAnimeSeasonsByParentId
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.InsertTrack
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
Expand All @@ -25,83 +28,113 @@ class AddTracks(
private val syncEpisodeProgressWithTrack: SyncEpisodeProgressWithTrack,
private val getEpisodesByAnimeId: GetEpisodesByAnimeId,
private val trackerManager: TrackerManager,
// AM -->
private val getAnimeSeasonsByParentId: GetAnimeSeasonsByParentId,
private val sourceManager: SourceManager,
// <-- AM
) {

// TODO: update all trackers based on common data
suspend fun bind(tracker: Tracker, item: Track, animeId: Long) = withNonCancellableContext {
suspend fun bind(tracker: Tracker, item: Track, anime: Anime) = withNonCancellableContext {
withIOContext {
val allEpisodes = getEpisodesByAnimeId.await(animeId)
val allEpisodes = getEpisodesByAnimeId.await(anime.id)
val hasSeenEpisodes = allEpisodes.any { it.seen }
tracker.bind(item, hasSeenEpisodes)

var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext

insertTrack.await(track)

// TODO: merge into [SyncEpisodeProgressWithTrack]?
// Update episode progress if newer episodes marked seen locally
if (hasSeenEpisodes) {
val latestLocalSeenEpisodeNumber = allEpisodes
.sortedBy { it.episodeNumber }
.takeWhile { it.seen }
.lastOrNull()
?.episodeNumber ?: -1.0
// AM -->
when (anime.fetchType) {
FetchType.Seasons -> { }
// <-- AM
FetchType.Episodes -> {
// TODO: merge into [SyncEpisodeProgressWithTrack]?
// Update episode progress if newer episodes marked seen locally
if (hasSeenEpisodes) {
val latestLocalSeenEpisodeNumber = allEpisodes
.sortedBy { it.episodeNumber }
.takeWhile { it.seen }
.lastOrNull()
?.episodeNumber ?: -1.0

if (latestLocalSeenEpisodeNumber > track.lastEpisodeSeen) {
track = track.copy(
lastEpisodeSeen = latestLocalSeenEpisodeNumber,
)
tracker.setRemoteLastEpisodeSeen(track.toDbTrack(), latestLocalSeenEpisodeNumber.toInt())
}
if (latestLocalSeenEpisodeNumber > track.lastEpisodeSeen) {
track = track.copy(
lastEpisodeSeen = latestLocalSeenEpisodeNumber,
)
tracker.setRemoteLastEpisodeSeen(track.toDbTrack(), latestLocalSeenEpisodeNumber.toInt())
}

if (track.startDate <= 0) {
val firstSeenEpisodeDate = Injekt.get<GetHistory>().await(animeId)
.sortedBy { it.seenAt }
.firstOrNull()
?.seenAt
if (track.startDate <= 0) {
val firstSeenEpisodeDate = Injekt.get<GetHistory>().await(anime.id)
.sortedBy { it.seenAt }
.firstOrNull()
?.seenAt

firstSeenEpisodeDate?.let {
val startDate = firstSeenEpisodeDate.time.convertEpochMillisZone(
ZoneOffset.systemDefault(),
ZoneOffset.UTC,
)
track = track.copy(
startDate = startDate,
)
tracker.setRemoteStartDate(track.toDbTrack(), startDate)
firstSeenEpisodeDate?.let {
val startDate = firstSeenEpisodeDate.time.convertEpochMillisZone(
ZoneOffset.systemDefault(),
ZoneOffset.UTC,
)
track = track.copy(
startDate = startDate,
)
tracker.setRemoteStartDate(track.toDbTrack(), startDate)
}
}
}

syncEpisodeProgressWithTrack.await(anime.id, track, tracker)
}
}

syncEpisodeProgressWithTrack.await(animeId, track, tracker)
// AM -->
val source = sourceManager.getOrStub(anime.source)
bindEnhancedTrackers(anime, source)
// <-- AM
}
}

suspend fun bindEnhancedTrackers(anime: Anime, source: AnimeSource) = withNonCancellableContext {
withIOContext {
trackerManager.loggedInTrackers()
.filterIsInstance<EnhancedTracker>()
.filter { it.accept(source) }
.forEach { service ->
try {
service.match(anime)?.let { track ->
track.anime_id = anime.id
(service as Tracker).bind(track)
insertTrack.await(track.toDomainTrack(idRequired = false)!!)
suspend fun bindEnhancedTrackers(anime: Anime, source: AnimeSource) {
withNonCancellableContext {
withIOContext {
trackerManager.loggedInTrackers()
.filterIsInstance<EnhancedTracker>()
.filter { it.accept(source) }
.forEach { service ->
try {
service.match(anime)?.let { track ->
track.anime_id = anime.id
(service as Tracker).bind(track)
insertTrack.await(track.toDomainTrack(idRequired = false)!!)

syncEpisodeProgressWithTrack.await(
anime.id,
track.toDomainTrack(idRequired = false)!!,
service,
)
when (anime.fetchType) {
// AM -->
FetchType.Seasons -> {
val seasons = getAnimeSeasonsByParentId.await(anime.id)
seasons.filter { it.anime.fetchType == FetchType.Episodes }.forEach { s ->
bindEnhancedTrackers(s.anime, source)
}
}
// <-- AM
FetchType.Episodes -> {
syncEpisodeProgressWithTrack.await(
anime.id,
track.toDomainTrack(idRequired = false)!!,
service,
)
}
}
}
} catch (e: Exception) {
logcat(
LogPriority.WARN,
e,
) { "Could not match anime: ${anime.title} with service $service" }
}
} catch (e: Exception) {
logcat(
LogPriority.WARN,
e,
) { "Could not match anime: ${anime.title} with service $service" }
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ import java.time.format.DateTimeFormatter
fun TrackInfoDialogHome(
trackItems: List<TrackItem>,
dateFormat: DateTimeFormatter,
// AM -->
isSeason: Boolean,
// <-- AM
onStatusClick: (TrackItem) -> Unit,
onEpisodeClick: (TrackItem) -> Unit,
onScoreClick: (TrackItem) -> Unit,
Expand Down Expand Up @@ -94,6 +97,9 @@ fun TrackInfoDialogHome(
TrackInfoItem(
title = item.track.title,
tracker = item.tracker,
// AM -->
isSeason = isSeason,
// <-- AM
status = item.tracker.getStatus(item.track.status),
onStatusClick = { onStatusClick(item) },
episodes = "${item.track.lastEpisodeSeen.toInt()}".let {
Expand Down Expand Up @@ -140,6 +146,9 @@ fun TrackInfoDialogHome(
private fun TrackInfoItem(
title: String,
tracker: Tracker,
// AM -->
isSeason: Boolean,
// <-- AM
status: StringResource?,
onStatusClick: () -> Unit,
episodes: String,
Expand Down Expand Up @@ -216,54 +225,58 @@ private fun TrackInfoItem(
)
}

Box(
modifier = Modifier
.padding(top = 12.dp)
.clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.surfaceContainerHighest)
.padding(8.dp)
.clip(RoundedCornerShape(6.dp)),
) {
Column {
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
TrackDetailsItem(
modifier = Modifier.weight(1f),
text = status?.let { stringResource(it) } ?: "",
onClick = onStatusClick,
)
VerticalDivider()
TrackDetailsItem(
modifier = Modifier.weight(1f),
text = episodes,
onClick = onEpisodesClick,
)
if (onScoreClick != null) {
VerticalDivider()
TrackDetailsItem(
modifier = Modifier.weight(1f),
text = score,
placeholder = stringResource(MR.strings.score),
onClick = onScoreClick,
)
}
}

if (onStartDateClick != null && onEndDateClick != null) {
HorizontalDivider()
// AM -->
if (!isSeason) {
// <-- AM
Box(
modifier = Modifier
.padding(top = 12.dp)
.clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.surfaceContainerHighest)
.padding(8.dp)
.clip(RoundedCornerShape(6.dp)),
) {
Column {
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
TrackDetailsItem(
modifier = Modifier.weight(1F),
text = startDate,
placeholder = stringResource(MR.strings.track_started_reading_date),
onClick = onStartDateClick,
modifier = Modifier.weight(1f),
text = status?.let { stringResource(it) } ?: "",
onClick = onStatusClick,
)
VerticalDivider()
TrackDetailsItem(
modifier = Modifier.weight(1F),
text = endDate,
placeholder = stringResource(MR.strings.track_finished_reading_date),
onClick = onEndDateClick,
modifier = Modifier.weight(1f),
text = episodes,
onClick = onEpisodesClick,
)
if (onScoreClick != null) {
VerticalDivider()
TrackDetailsItem(
modifier = Modifier.weight(1f),
text = score,
placeholder = stringResource(MR.strings.score),
onClick = onScoreClick,
)
}
}

if (onStartDateClick != null && onEndDateClick != null) {
HorizontalDivider()
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
TrackDetailsItem(
modifier = Modifier.weight(1F),
text = startDate,
placeholder = stringResource(MR.strings.track_started_reading_date),
onClick = onStartDateClick,
)
VerticalDivider()
TrackDetailsItem(
modifier = Modifier.weight(1F),
text = endDate,
placeholder = stringResource(MR.strings.track_finished_reading_date),
onClick = onEndDateClick,
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ internal class TrackInfoDialogHomePreviewProvider :
trackItemWithTrack,
),
dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM),
isSeason = false,
onStatusClick = {},
onEpisodeClick = {},
onScoreClick = {},
Expand All @@ -74,6 +75,7 @@ internal class TrackInfoDialogHomePreviewProvider :
TrackInfoDialogHome(
trackItems = listOf(),
dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM),
isSeason = false,
onStatusClick = {},
onEpisodeClick = {},
onScoreClick = {},
Expand All @@ -91,6 +93,7 @@ internal class TrackInfoDialogHomePreviewProvider :
TrackInfoDialogHome(
trackItems = listOf(trackItemWithPrivateTrack),
dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM),
isSeason = false,
onStatusClick = {},
onEpisodeClick = {},
onScoreClick = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import okhttp3.OkHttpClient
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.anime.model.Anime
import tachiyomi.domain.track.interactor.InsertTrack
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
Expand Down Expand Up @@ -74,10 +75,10 @@ abstract class BaseTracker(
trackPreferences.setCredentials(this, username, password)
}

override suspend fun register(item: Track, animeId: Long) {
item.anime_id = animeId
override suspend fun register(item: Track, anime: Anime) {
item.anime_id = anime.id
try {
addTracks.bind(this, item, animeId)
addTracks.bind(this, item, anime)
} catch (e: Throwable) {
withUIContext { Injekt.get<Application>().toast(e.message) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ interface EnhancedTracker {
*/
suspend fun match(anime: Anime): TrackSearch?

// AM -->

/**
* Similar to [Tracker].search, but only returns zero or one match for seasons.
*/
suspend fun matchSeason(anime: Anime): TrackSearch?
// <-- AM

/**
* Checks whether the provided source/track/anime triplet is from this [Tracker]
*/
Expand Down
Loading
Loading