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:
NarwhalHorns 2025-02-25 11:01:13 +00:00 committed by GitHub
parent badc229a23
commit 49b2b346b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 214 additions and 25 deletions

View File

@ -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))

View File

@ -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,
)
}

View File

@ -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,
) {
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 = {

View File

@ -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,
)
}

View File

@ -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,16 +166,32 @@ 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(),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
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),
)
}
}
}
}
},
) { innerPadding ->

View File

@ -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 {

View File

@ -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,
)
}

View File

@ -404,6 +404,7 @@ class MangaRestorer(
track.remoteUrl,
track.startDate,
track.finishDate,
track.private,
track.id,
)
}

View File

@ -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 {

View File

@ -29,4 +29,6 @@ class TrackImpl : Track {
override var finished_reading_date: Long = 0
override var tracking_url: String = ""
override var private: Boolean = false
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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) {

View File

@ -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

View File

@ -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) {

View File

@ -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,
)
}
}

View File

@ -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

View File

@ -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()

View File

@ -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 = ""

View File

@ -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()) {

View File

@ -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
}

View File

@ -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,
)
}

View File

@ -64,6 +64,7 @@ class TrackRepositoryImpl(
remoteUrl = mangaTrack.remoteUrl,
startDate = mangaTrack.startDate,
finishDate = mangaTrack.finishDate,
private = mangaTrack.private,
)
}
}

View File

@ -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;

View File

@ -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;

View File

@ -16,4 +16,5 @@ data class Track(
val remoteUrl: String,
val startDate: Long,
val finishDate: Long,
val private: Boolean,
) : Serializable

View File

@ -521,7 +521,6 @@
<string name="enhanced_services">Enhanced trackers</string>
<string name="enhanced_services_not_installed">Available but source not installed: %s</string>
<string name="enhanced_tracking_info">Provides enhanced features for specific sources. Entries are automatically tracked when added to your library.</string>
<string name="action_track">Track</string>
<string name="track_activity_name">Tracker login</string>
<!-- Browse section -->
@ -752,6 +751,7 @@
<!-- Tracking Screen -->
<string name="manga_tracking_tab">Tracking</string>
<string name="add_tracking">Add tracking</string>
<string name="action_track">Track</string>
<string name="unread">Unread</string>
<string name="reading">Reading</string>
<string name="completed">Completed</string>
@ -771,6 +771,9 @@
<string name="track_status">Status</string>
<string name="track_started_reading_date">Start date</string>
<string name="track_finished_reading_date">Finish date</string>
<string name="tracked_privately">Tracked privately</string>
<string name="action_toggle_private_on">Track privately</string>
<string name="action_toggle_private_off">Track publicly</string>
<string name="track_type">Type</string>
<string name="myanimelist_relogin">Please login to MAL again</string>
<string name="source_unsupported">Source is not supported</string>