diff --git a/CHANGELOG.md b/CHANGELOG.md index c79ed43ae..364c3dc54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co - Added option to enable incognito per extension ([@sdaqo](https://github.com/sdaqo), [@AntsyLich](https://github.com/AntsyLich)) ([#157](https://github.com/mihonapp/mihon/pull/157)) - Add button to favorite manga from history screen ([@Animeboynz](https://github.com/Animeboynz)) ([#1733](https://github.com/mihonapp/mihon/pull/1733)) - Add Monochrome theme (made with e-ink displays in mind) ([@MajorTanya](https://github.com/MajorTanya)) ([#1752](https://github.com/mihonapp/mihon/pull/1752)) +- Support for private tracking with AniList and Bangumi ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#1736](https://github.com/mihonapp/mihon/pull/1736)) ### Changed - Apply "Downloaded only" filter to all entries regardless of favourite status ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#1603](https://github.com/mihonapp/mihon/pull/1603)) diff --git a/app/src/main/java/eu/kanade/domain/track/model/Track.kt b/app/src/main/java/eu/kanade/domain/track/model/Track.kt index e44133433..f29901fad 100644 --- a/app/src/main/java/eu/kanade/domain/track/model/Track.kt +++ b/app/src/main/java/eu/kanade/domain/track/model/Track.kt @@ -10,6 +10,7 @@ fun Track.copyPersonalFrom(other: Track): Track { status = other.status, startDate = other.startDate, finishDate = other.finishDate, + private = other.private, ) } @@ -26,6 +27,7 @@ fun Track.toDbTrack(): DbTrack = DbTrack.create(trackerId).also { it.tracking_url = remoteUrl it.started_reading_date = startDate it.finished_reading_date = finishDate + it.private = private } fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? { @@ -44,5 +46,6 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? { remoteUrl = tracking_url, startDate = started_reading_date, finishDate = finished_reading_date, + private = private, ) } diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt index 3d766c4cf..28bcd36e6 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt @@ -10,10 +10,12 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.absoluteOffset import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentSize @@ -22,6 +24,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -70,6 +75,7 @@ fun TrackInfoDialogHome( onOpenInBrowser: (TrackItem) -> Unit, onRemoved: (TrackItem) -> Unit, onCopyLink: (TrackItem) -> Unit, + onTogglePrivate: (TrackItem) -> Unit, ) { Column( modifier = Modifier @@ -84,6 +90,7 @@ fun TrackInfoDialogHome( if (item.track != null) { val supportsScoring = item.tracker.getScoreList().isNotEmpty() val supportsReadingDates = item.tracker.supportsReadingDates + val supportsPrivate = item.tracker.supportsPrivateTracking TrackInfoItem( title = item.track.title, tracker = item.tracker, @@ -115,6 +122,9 @@ fun TrackInfoDialogHome( onOpenInBrowser = { onOpenInBrowser(item) }, onRemoved = { onRemoved(item) }, onCopyLink = { onCopyLink(item) }, + private = item.track.private, + onTogglePrivate = { onTogglePrivate(item) } + .takeIf { supportsPrivate }, ) } else { TrackInfoItemEmpty( @@ -144,17 +154,37 @@ private fun TrackInfoItem( onOpenInBrowser: () -> Unit, onRemoved: () -> Unit, onCopyLink: () -> Unit, + private: Boolean, + onTogglePrivate: (() -> Unit)?, ) { val context = LocalContext.current Column { Row( verticalAlignment = Alignment.CenterVertically, ) { - TrackLogoIcon( - tracker = tracker, - onClick = onOpenInBrowser, - onLongClick = onCopyLink, - ) + BadgedBox( + badge = { + if (private) { + Badge( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + modifier = Modifier.absoluteOffset(x = (-5).dp), + ) { + Icon( + imageVector = Icons.Filled.VisibilityOff, + contentDescription = stringResource(MR.strings.tracked_privately), + modifier = Modifier.size(14.dp), + ) + } + } + }, + ) { + TrackLogoIcon( + tracker = tracker, + onClick = onOpenInBrowser, + onLongClick = onCopyLink, + ) + } Box( modifier = Modifier .height(48.dp) @@ -181,6 +211,8 @@ private fun TrackInfoItem( onOpenInBrowser = onOpenInBrowser, onRemoved = onRemoved, onCopyLink = onCopyLink, + private = private, + onTogglePrivate = onTogglePrivate, ) } @@ -291,6 +323,8 @@ private fun TrackInfoItemMenu( onOpenInBrowser: () -> Unit, onRemoved: () -> Unit, onCopyLink: () -> Unit, + private: Boolean, + onTogglePrivate: (() -> Unit)?, ) { var expanded by remember { mutableStateOf(false) } Box(modifier = Modifier.wrapContentSize(Alignment.TopStart)) { @@ -318,6 +352,25 @@ private fun TrackInfoItemMenu( expanded = false }, ) + if (onTogglePrivate != null) { + DropdownMenuItem( + text = { + Text( + stringResource( + if (private) { + MR.strings.action_toggle_private_off + } else { + MR.strings.action_toggle_private_on + }, + ), + ) + }, + onClick = { + onTogglePrivate() + expanded = false + }, + ) + } DropdownMenuItem( text = { Text(stringResource(MR.strings.action_remove)) }, onClick = { diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHomePreviewProvider.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHomePreviewProvider.kt index 3b60a9277..923dfd573 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHomePreviewProvider.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHomePreviewProvider.kt @@ -25,7 +25,9 @@ internal class TrackInfoDialogHomePreviewProvider : remoteUrl = "https://example.com", startDate = 0L, finishDate = 0L, + private = false, ) + private val privateTrack = aTrack.copy(private = true) private val trackItemWithoutTrack = TrackItem( track = null, tracker = DummyTracker( @@ -40,6 +42,13 @@ internal class TrackInfoDialogHomePreviewProvider : name = "Example Tracker 2", ), ) + private val trackItemWithPrivateTrack = TrackItem( + track = privateTrack, + tracker = DummyTracker( + id = 2L, + name = "Example Tracker 2", + ), + ) private val trackersWithAndWithoutTrack = @Composable { TrackInfoDialogHome( @@ -57,6 +66,7 @@ internal class TrackInfoDialogHomePreviewProvider : onOpenInBrowser = {}, onRemoved = {}, onCopyLink = {}, + onTogglePrivate = {}, ) } @@ -73,6 +83,24 @@ internal class TrackInfoDialogHomePreviewProvider : onOpenInBrowser = {}, onRemoved = {}, onCopyLink = {}, + onTogglePrivate = {}, + ) + } + + private val trackerWithPrivateTracking = @Composable { + TrackInfoDialogHome( + trackItems = listOf(trackItemWithPrivateTrack), + dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM), + onStatusClick = {}, + onChapterClick = {}, + onScoreClick = {}, + onStartDateEdit = {}, + onEndDateEdit = {}, + onNewSearch = {}, + onOpenInBrowser = {}, + onRemoved = {}, + onCopyLink = {}, + onTogglePrivate = {}, ) } @@ -80,5 +108,6 @@ internal class TrackInfoDialogHomePreviewProvider : get() = sequenceOf( trackersWithAndWithoutTrack, noTrackers, + trackerWithPrivateTracking, ) } diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt b/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt index 512d7e268..61fa61667 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt @@ -33,6 +33,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.DropdownMenuItem @@ -90,8 +91,9 @@ fun TrackerSearch( queryResult: Result>?, selected: TrackSearch?, onSelectedChange: (TrackSearch) -> Unit, - onConfirmSelection: () -> Unit, + onConfirmSelection: (private: Boolean) -> Unit, onDismissRequest: () -> Unit, + supportsPrivateTracking: Boolean, ) { val focusManager = LocalFocusManager.current val focusRequester = remember { FocusRequester() } @@ -164,15 +166,31 @@ fun TrackerSearch( enter = fadeIn() + slideInVertically { it / 2 }, exit = slideOutVertically { it / 2 } + fadeOut(), ) { - Button( - onClick = { onConfirmSelection() }, + Row( modifier = Modifier - .padding(12.dp) + .padding(MaterialTheme.padding.small) .windowInsetsPadding(WindowInsets.navigationBars) .fillMaxWidth(), - elevation = ButtonDefaults.elevatedButtonElevation(), + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), ) { - Text(text = stringResource(MR.strings.action_track)) + Button( + onClick = { onConfirmSelection(false) }, + modifier = Modifier.weight(1f), + elevation = ButtonDefaults.elevatedButtonElevation(), + ) { + Text(text = stringResource(MR.strings.action_track)) + } + if (supportsPrivateTracking) { + Button( + onClick = { onConfirmSelection(true) }, + elevation = ButtonDefaults.elevatedButtonElevation(), + ) { + Icon( + imageVector = Icons.Filled.VisibilityOff, + contentDescription = stringResource(MR.strings.action_toggle_private_on), + ) + } + } } } }, diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackerSearchPreviewProvider.kt b/app/src/main/java/eu/kanade/presentation/track/TrackerSearchPreviewProvider.kt index d79762a2a..9a5937dd7 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackerSearchPreviewProvider.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackerSearchPreviewProvider.kt @@ -20,6 +20,7 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab onSelectedChange = {}, onConfirmSelection = {}, onDismissRequest = {}, + supportsPrivateTracking = false, ) } private val fullPageWithoutSelected = @Composable { @@ -31,6 +32,7 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab onSelectedChange = {}, onConfirmSelection = {}, onDismissRequest = {}, + supportsPrivateTracking = false, ) } private val loading = @Composable { @@ -42,12 +44,27 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab onSelectedChange = {}, onConfirmSelection = {}, onDismissRequest = {}, + supportsPrivateTracking = false, + ) + } + private val fullPageWithPrivateTracking = @Composable { + val items = someTrackSearches().take(30).toList() + TrackerSearch( + state = TextFieldState(initialText = "search text"), + onDispatchQuery = {}, + queryResult = Result.success(items), + selected = items[1], + onSelectedChange = {}, + onConfirmSelection = {}, + onDismissRequest = {}, + supportsPrivateTracking = true, ) } override val values: Sequence<@Composable () -> Unit> = sequenceOf( fullPageWithSecondSelected, fullPageWithoutSelected, loading, + fullPageWithPrivateTracking, ) private fun someTrackSearches(): Sequence = sequence { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt index 910a8adac..56108e68e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt @@ -25,6 +25,7 @@ data class BackupTracking( @ProtoNumber(10) var startedReadingDate: Long = 0, // finishedReadingDate is called endReadTime in 1.x @ProtoNumber(11) var finishedReadingDate: Long = 0, + @ProtoNumber(12) var private: Boolean = false, @ProtoNumber(100) var mediaId: Long = 0, ) { @@ -48,6 +49,7 @@ data class BackupTracking( startDate = this@BackupTracking.startedReadingDate, finishDate = this@BackupTracking.finishedReadingDate, remoteUrl = this@BackupTracking.trackingUrl, + private = this@BackupTracking.private, ) } } @@ -66,6 +68,7 @@ val backupTrackMapper = { remoteUrl: String, startDate: Long, finishDate: Long, + private: Boolean, -> BackupTracking( syncId = syncId.toInt(), @@ -80,5 +83,6 @@ val backupTrackMapper = { startedReadingDate = startDate, finishedReadingDate = finishDate, trackingUrl = remoteUrl, + private = private, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt index 213fc4bf7..11c11e220 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt @@ -404,6 +404,7 @@ class MangaRestorer( track.remoteUrl, track.startDate, track.finishDate, + track.private, track.id, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt index 6a4d5d741..4c64f83b7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt @@ -32,12 +32,15 @@ interface Track : Serializable { var tracking_url: String - fun copyPersonalFrom(other: Track) { + var private: Boolean + + fun copyPersonalFrom(other: Track, copyRemotePrivate: Boolean = true) { last_chapter_read = other.last_chapter_read score = other.score status = other.status started_reading_date = other.started_reading_date finished_reading_date = other.finished_reading_date + if (copyRemotePrivate) private = other.private } companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt index 91c4c4fe7..02449b7b2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt @@ -29,4 +29,6 @@ class TrackImpl : Track { override var finished_reading_date: Long = 0 override var tracking_url: String = "" + + override var private: Boolean = false } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt index 33caf6555..ef5411a0b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt @@ -37,6 +37,8 @@ abstract class BaseTracker( // Application and remote support for reading dates override val supportsReadingDates: Boolean = false + override val supportsPrivateTracking: Boolean = false + // TODO: Store all scores as 10 point in the future maybe? override fun get10PointScore(track: DomainTrack): Double { return track.score @@ -120,6 +122,11 @@ abstract class BaseTracker( updateRemote(track) } + override suspend fun setRemotePrivate(track: Track, private: Boolean) { + track.private = private + updateRemote(track) + } + private suspend fun updateRemote(track: Track): Unit = withIOContext { try { update(track) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt index fd3a9f45e..fc9e62ca2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt @@ -22,6 +22,8 @@ interface Tracker { // Application and remote support for reading dates val supportsReadingDates: Boolean + val supportsPrivateTracking: Boolean + @ColorInt fun getLogoColor(): Int @@ -82,4 +84,6 @@ interface Tracker { suspend fun setRemoteStartDate(track: Track, epochMillis: Long) suspend fun setRemoteFinishDate(track: Track, epochMillis: Long) + + suspend fun setRemotePrivate(track: Track, private: Boolean) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt index db25cc763..40eda8208 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import tachiyomi.i18n.MR import uy.kohesive.injekt.injectLazy @@ -43,6 +42,8 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker { override val supportsReadingDates: Boolean = true + override val supportsPrivateTracking: Boolean = true + private val scorePreference = trackPreferences.anilistScoreType() init { @@ -183,7 +184,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker { override suspend fun bind(track: Track, hasReadChapters: Boolean): Track { val remoteTrack = api.findLibManga(track, getUsername().toInt()) return if (remoteTrack != null) { - track.copyPersonalFrom(remoteTrack) + track.copyPersonalFrom(remoteTrack, copyRemotePrivate = false) track.library_id = remoteTrack.library_id if (track.status != COMPLETED) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt index 3695b6f25..4d4c630f9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt @@ -42,8 +42,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { suspend fun addLibManga(track: Track): Track { return withIOContext { val query = """ - |mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) { - |SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) { + |mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}private: Boolean) { + |SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status, private: ${'$'}private) { | id | status |} @@ -56,6 +56,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { put("mangaId", track.remote_id) put("progress", track.last_chapter_read.toInt()) put("status", track.toApiStatus()) + put("private", track.private) } } with(json) { @@ -79,11 +80,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { return withIOContext { val query = """ |mutation UpdateManga( - |${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, + |${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}private: Boolean, |${'$'}score: Int, ${'$'}startedAt: FuzzyDateInput, ${'$'}completedAt: FuzzyDateInput |) { |SaveMediaListEntry( - |id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, + |id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, private: ${'$'}private, |scoreRaw: ${'$'}score, startedAt: ${'$'}startedAt, completedAt: ${'$'}completedAt |) { |id @@ -102,6 +103,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { put("score", track.score.toInt()) put("startedAt", createDate(track.started_reading_date)) put("completedAt", createDate(track.finished_reading_date)) + put("private", track.private) } } authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) @@ -190,6 +192,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { |status |scoreRaw: score(format: POINT_100) |progress + |private |startedAt { |year |month diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/dto/ALManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/dto/ALManga.kt index b8f98ce63..5c69381ce 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/dto/ALManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/dto/ALManga.kt @@ -49,6 +49,7 @@ data class ALUserManga( val startDateFuzzy: Long, val completedDateFuzzy: Long, val manga: ALManga, + val private: Boolean, ) { fun toTrack() = Track.create(TrackerManager.ANILIST).apply { remote_id = manga.remoteId @@ -60,6 +61,7 @@ data class ALUserManga( last_chapter_read = chaptersRead.toDouble() library_id = libraryId total_chapters = manga.totalChapters + private = this@ALUserManga.private } private fun toTrackStatus() = when (listStatus) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/dto/ALUserList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/dto/ALUserList.kt index 4ccec7aa4..552177a25 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/dto/ALUserList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/dto/ALUserList.kt @@ -28,6 +28,7 @@ data class ALUserListItem( val startedAt: ALFuzzyDate, val completedAt: ALFuzzyDate, val media: ALSearchItem, + val private: Boolean, ) { fun toALUserManga(): ALUserManga { return ALUserManga( @@ -38,6 +39,7 @@ data class ALUserListItem( startDateFuzzy = startedAt.toEpochMilli(), completedDateFuzzy = completedAt.toEpochMilli(), manga = media.toALManga(), + private = private, ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt index cefbdf18b..cd819950b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt @@ -22,6 +22,8 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") { private val api by lazy { BangumiApi(id, client, interceptor) } + override val supportsPrivateTracking: Boolean = true + override fun getScoreList(): ImmutableList = SCORE_LIST override fun displayScore(track: DomainTrack): String { @@ -49,7 +51,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") { override suspend fun bind(track: Track, hasReadChapters: Boolean): Track { val statusTrack = api.statusLibManga(track, getUsername()) return if (statusTrack != null) { - track.copyPersonalFrom(statusTrack) + track.copyPersonalFrom(statusTrack, copyRemotePrivate = false) track.library_id = statusTrack.library_id track.score = statusTrack.score track.last_chapter_read = statusTrack.last_chapter_read diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt index e041886d8..24c78b549 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt @@ -45,6 +45,7 @@ class BangumiApi( put("type", track.toApiStatus()) put("rate", track.score.toInt().coerceIn(0, 10)) put("ep_status", track.last_chapter_read.toInt()) + put("private", track.private) } .toString() .toRequestBody() @@ -62,6 +63,7 @@ class BangumiApi( put("type", track.toApiStatus()) put("rate", track.score.toInt().coerceIn(0, 10)) put("ep_status", track.last_chapter_read.toInt()) + put("private", track.private) } .toString() .toRequestBody() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt index fabaa2f69..5f44e9aa2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt @@ -30,6 +30,8 @@ class TrackSearch : Track { override var finished_reading_date: Long = 0 + override var private: Boolean = false + override lateinit var tracking_url: String var cover_url: String = "" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt index 3541be7ac..06a4c8378 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt @@ -172,6 +172,7 @@ data class TrackInfoDialogHomeScreen( ) }, onCopyLink = { context.copyTrackerLink(it) }, + onTogglePrivate = screenModel::togglePrivate, ) } @@ -247,6 +248,12 @@ data class TrackInfoDialogHomeScreen( } } + fun togglePrivate(item: TrackItem) { + screenModelScope.launchNonCancellable { + item.tracker.setRemotePrivate(item.track!!.toDbTrack(), !item.track.private) + } + } + private fun List.mapToTrackItem(): List { val loggedInTrackers = Injekt.get().loggedInTrackers() val source = Injekt.get().getOrStub(sourceId) @@ -673,11 +680,14 @@ data class TrackerSearchScreen( queryResult = state.queryResult, selected = state.selected, onSelectedChange = screenModel::updateSelection, - onConfirmSelection = { - screenModel.registerTracking(state.selected!!) + onConfirmSelection = f@{ private: Boolean -> + val selected = state.selected ?: return@f + selected.private = private + screenModel.registerTracking(selected) navigator.pop() }, onDismissRequest = navigator::pop, + supportsPrivateTracking = screenModel.supportsPrivateTracking, ) } @@ -688,6 +698,8 @@ data class TrackerSearchScreen( private val tracker: Tracker, ) : StateScreenModel(State()) { + val supportsPrivateTracking = tracker.supportsPrivateTracking + init { // Run search on first launch if (initialQuery.isNotBlank()) { diff --git a/app/src/main/java/eu/kanade/test/DummyTracker.kt b/app/src/main/java/eu/kanade/test/DummyTracker.kt index 4c77e660d..397301131 100644 --- a/app/src/main/java/eu/kanade/test/DummyTracker.kt +++ b/app/src/main/java/eu/kanade/test/DummyTracker.kt @@ -17,6 +17,7 @@ data class DummyTracker( override val id: Long, override val name: String, override val supportsReadingDates: Boolean = false, + override val supportsPrivateTracking: Boolean = false, override val isLoggedIn: Boolean = false, override val isLoggedInFlow: Flow = flowOf(false), val valLogoColor: Int = Color.rgb(18, 25, 35), @@ -119,4 +120,9 @@ data class DummyTracker( track: eu.kanade.tachiyomi.data.database.models.Track, epochMillis: Long, ) = Unit + + override suspend fun setRemotePrivate( + track: eu.kanade.tachiyomi.data.database.models.Track, + private: Boolean, + ) = Unit } diff --git a/data/src/main/java/tachiyomi/data/track/TrackMapper.kt b/data/src/main/java/tachiyomi/data/track/TrackMapper.kt index ee941d49c..83a3f5b86 100644 --- a/data/src/main/java/tachiyomi/data/track/TrackMapper.kt +++ b/data/src/main/java/tachiyomi/data/track/TrackMapper.kt @@ -17,6 +17,7 @@ object TrackMapper { remoteUrl: String, startDate: Long, finishDate: Long, + private: Boolean, ): Track = Track( id = id, mangaId = mangaId, @@ -31,5 +32,6 @@ object TrackMapper { remoteUrl = remoteUrl, startDate = startDate, finishDate = finishDate, + private = private, ) } diff --git a/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt index 6cd8396b1..70ab78c37 100644 --- a/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt @@ -64,6 +64,7 @@ class TrackRepositoryImpl( remoteUrl = mangaTrack.remoteUrl, startDate = mangaTrack.startDate, finishDate = mangaTrack.finishDate, + private = mangaTrack.private, ) } } diff --git a/data/src/main/sqldelight/tachiyomi/data/manga_sync.sq b/data/src/main/sqldelight/tachiyomi/data/manga_sync.sq index bf8d0cba3..5c19f365f 100644 --- a/data/src/main/sqldelight/tachiyomi/data/manga_sync.sq +++ b/data/src/main/sqldelight/tachiyomi/data/manga_sync.sq @@ -1,3 +1,5 @@ +import kotlin.Boolean; + CREATE TABLE manga_sync( _id INTEGER NOT NULL PRIMARY KEY, manga_id INTEGER NOT NULL, @@ -12,6 +14,7 @@ CREATE TABLE manga_sync( remote_url TEXT NOT NULL, start_date INTEGER NOT NULL, finish_date INTEGER NOT NULL, + private INTEGER AS Boolean DEFAULT 0 NOT NULL, UNIQUE (manga_id, sync_id) ON CONFLICT REPLACE, FOREIGN KEY(manga_id) REFERENCES mangas (_id) ON DELETE CASCADE @@ -36,8 +39,8 @@ FROM manga_sync WHERE manga_id = :mangaId; insert: -INSERT INTO manga_sync(manga_id,sync_id,remote_id,library_id,title,last_chapter_read,total_chapters,status,score,remote_url,start_date,finish_date) -VALUES (:mangaId,:syncId,:remoteId,:libraryId,:title,:lastChapterRead,:totalChapters,:status,:score,:remoteUrl,:startDate,:finishDate); +INSERT INTO manga_sync(manga_id,sync_id,remote_id,library_id,title,last_chapter_read,total_chapters,status,score,remote_url,start_date,finish_date,private) +VALUES (:mangaId,:syncId,:remoteId,:libraryId,:title,:lastChapterRead,:totalChapters,:status,:score,:remoteUrl,:startDate,:finishDate,:private); update: UPDATE manga_sync @@ -53,5 +56,6 @@ SET score = coalesce(:score, score), remote_url = coalesce(:trackingUrl, remote_url), start_date = coalesce(:startDate, start_date), - finish_date = coalesce(:finishDate, finish_date) + finish_date = coalesce(:finishDate, finish_date), + private = coalesce(:private, private) WHERE _id = :id; diff --git a/data/src/main/sqldelight/tachiyomi/migrations/4.sqm b/data/src/main/sqldelight/tachiyomi/migrations/4.sqm new file mode 100644 index 000000000..0f7c93264 --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/migrations/4.sqm @@ -0,0 +1,4 @@ +import kotlin.Boolean; + +-- Add private field for tracking +ALTER TABLE manga_sync ADD COLUMN private INTEGER AS Boolean DEFAULT 0 NOT NULL; diff --git a/domain/src/main/java/tachiyomi/domain/track/model/Track.kt b/domain/src/main/java/tachiyomi/domain/track/model/Track.kt index a92390494..54765790c 100644 --- a/domain/src/main/java/tachiyomi/domain/track/model/Track.kt +++ b/domain/src/main/java/tachiyomi/domain/track/model/Track.kt @@ -16,4 +16,5 @@ data class Track( val remoteUrl: String, val startDate: Long, val finishDate: Long, + val private: Boolean, ) : Serializable diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 00f21b425..68ddae3e4 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -521,7 +521,6 @@ Enhanced trackers Available but source not installed: %s Provides enhanced features for specific sources. Entries are automatically tracked when added to your library. - Track Tracker login @@ -752,6 +751,7 @@ Tracking Add tracking + Track Unread Reading Completed @@ -771,6 +771,9 @@ Status Start date Finish date + Tracked privately + Track privately + Track publicly Type Please login to MAL again Source is not supported