mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Support for private tracking with AniList and Bangumi (#1736)
Co-authored-by: MajorTanya <39014446+MajorTanya@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
		| @@ -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, | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -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 = { | ||||
|   | ||||
| @@ -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, | ||||
|         ) | ||||
| } | ||||
|   | ||||
| @@ -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<List<TrackSearch>>?, | ||||
|     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), | ||||
|                             ) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|   | ||||
| @@ -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<TrackSearch> = sequence { | ||||
|   | ||||
| @@ -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, | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -404,6 +404,7 @@ class MangaRestorer( | ||||
|                         track.remoteUrl, | ||||
|                         track.startDate, | ||||
|                         track.finishDate, | ||||
|                         track.private, | ||||
|                         track.id, | ||||
|                     ) | ||||
|                 } | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -29,4 +29,6 @@ class TrackImpl : Track { | ||||
|     override var finished_reading_date: Long = 0 | ||||
|  | ||||
|     override var tracking_url: String = "" | ||||
|  | ||||
|     override var private: Boolean = false | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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<String> = 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 | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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 = "" | ||||
|   | ||||
| @@ -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<Track>.mapToTrackItem(): List<TrackItem> { | ||||
|             val loggedInTrackers = Injekt.get<TrackerManager>().loggedInTrackers() | ||||
|             val source = Injekt.get<SourceManager>().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<Model.State>(State()) { | ||||
|  | ||||
|         val supportsPrivateTracking = tracker.supportsPrivateTracking | ||||
|  | ||||
|         init { | ||||
|             // Run search on first launch | ||||
|             if (initialQuery.isNotBlank()) { | ||||
|   | ||||
| @@ -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<Boolean> = 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 | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user