mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-15 15:02:49 +01:00
feat: added triggers for syncing.
Experimental for now, but works fine so far, I don't know about google api limit but I think it's pretty generous. Signed-off-by: KaiserBh <kaiserbh@proton.me>
This commit is contained in:
parent
cc5e14088c
commit
178b00280b
@ -34,6 +34,7 @@ import cafe.adriel.voyager.navigator.currentOrThrow
|
|||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
||||||
|
import eu.kanade.presentation.more.settings.screen.data.SyncOptionsScreen
|
||||||
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
||||||
import eu.kanade.presentation.util.relativeTimeSpanString
|
import eu.kanade.presentation.util.relativeTimeSpanString
|
||||||
@ -559,6 +560,7 @@ private fun getSyncNowPref(): Preference.PreferenceGroup {
|
|||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(MR.strings.pref_sync_now_group_title),
|
title = stringResource(MR.strings.pref_sync_now_group_title),
|
||||||
preferenceItems = listOf(
|
preferenceItems = listOf(
|
||||||
|
getSyncOptionsPref(),
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(MR.strings.pref_sync_now),
|
title = stringResource(MR.strings.pref_sync_now),
|
||||||
subtitle = stringResource(MR.strings.pref_sync_now_subtitle),
|
subtitle = stringResource(MR.strings.pref_sync_now_subtitle),
|
||||||
@ -570,6 +572,16 @@ private fun getSyncNowPref(): Preference.PreferenceGroup {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getSyncOptionsPref(): Preference.PreferenceItem.TextPreference {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
return Preference.PreferenceItem.TextPreference(
|
||||||
|
title = stringResource(MR.strings.pref_sync_options),
|
||||||
|
subtitle = stringResource(MR.strings.pref_sync_options_summ),
|
||||||
|
onClick = { navigator.push(SyncOptionsScreen()) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getAutomaticSyncGroup(syncPreferences: SyncPreferences): Preference.PreferenceGroup {
|
private fun getAutomaticSyncGroup(syncPreferences: SyncPreferences): Preference.PreferenceGroup {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
@ -0,0 +1,125 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.screen.data
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.util.Screen
|
||||||
|
import kotlinx.collections.immutable.PersistentSet
|
||||||
|
import kotlinx.collections.immutable.minus
|
||||||
|
import kotlinx.collections.immutable.plus
|
||||||
|
import kotlinx.collections.immutable.toPersistentSet
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import tachiyomi.domain.sync.SyncPreferences
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class SyncOptionsScreen : Screen() {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val model = rememberScreenModel { SyncOptionsScreenModel() }
|
||||||
|
val state by model.state.collectAsState()
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = stringResource(MR.strings.pref_sync_options),
|
||||||
|
navigateUp = navigator::pop,
|
||||||
|
scrollBehavior = it,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { contentPadding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(contentPadding)
|
||||||
|
.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(horizontal = MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
SyncChoices.forEach { (k, v) ->
|
||||||
|
item {
|
||||||
|
LabeledCheckbox(
|
||||||
|
label = stringResource(v),
|
||||||
|
checked = state.flags.contains(k),
|
||||||
|
onCheckedChange = {
|
||||||
|
model.toggleOptionFlag(k)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SyncOptionsScreenModel : StateScreenModel<SyncOptionsScreenModel.State>(State()) {
|
||||||
|
private val syncPreferences = Injekt.get<SyncPreferences>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
loadInitialFlags()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadInitialFlags() {
|
||||||
|
val savedFlags = syncPreferences.syncFlags().get()
|
||||||
|
val flagSet = SyncPreferences.Flags.values().filter { flag ->
|
||||||
|
savedFlags and flag > 0
|
||||||
|
}.toSet().toPersistentSet()
|
||||||
|
|
||||||
|
mutableState.update { State(flags = flagSet) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleOptionFlag(option: Int) {
|
||||||
|
mutableState.update { currentState ->
|
||||||
|
val newFlags = if (currentState.flags.contains(option)) {
|
||||||
|
currentState.flags - option
|
||||||
|
} else {
|
||||||
|
currentState.flags + option
|
||||||
|
}
|
||||||
|
saveFlags(newFlags)
|
||||||
|
currentState.copy(flags = newFlags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveFlags(flags: PersistentSet<Int>) {
|
||||||
|
val flagsInt = flags.fold(0) { acc, flag -> acc or flag }
|
||||||
|
syncPreferences.syncFlags().set(flagsInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class State(
|
||||||
|
val flags: PersistentSet<Int> = SyncChoices.keys.toPersistentSet(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val SyncChoices = mapOf(
|
||||||
|
SyncPreferences.Flags.SYNC_ON_CHAPTER_READ to MR.strings.sync_on_chapter_read,
|
||||||
|
SyncPreferences.Flags.SYNC_ON_CHAPTER_OPEN to MR.strings.sync_on_chapter_open,
|
||||||
|
SyncPreferences.Flags.SYNC_ON_APP_START to MR.strings.sync_on_app_start,
|
||||||
|
)
|
||||||
|
|
@ -32,6 +32,7 @@ import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
|
|||||||
import eu.kanade.tachiyomi.data.coil.MangaKeyer
|
import eu.kanade.tachiyomi.data.coil.MangaKeyer
|
||||||
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
|
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.data.sync.SyncDataJob
|
||||||
import eu.kanade.tachiyomi.di.AppModule
|
import eu.kanade.tachiyomi.di.AppModule
|
||||||
import eu.kanade.tachiyomi.di.PreferenceModule
|
import eu.kanade.tachiyomi.di.PreferenceModule
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
@ -56,6 +57,7 @@ import org.acra.sender.HttpSender
|
|||||||
import org.conscrypt.Conscrypt
|
import org.conscrypt.Conscrypt
|
||||||
import tachiyomi.core.i18n.stringResource
|
import tachiyomi.core.i18n.stringResource
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.sync.SyncPreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.widget.WidgetManager
|
import tachiyomi.presentation.widget.WidgetManager
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -168,6 +170,13 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
|
|||||||
|
|
||||||
override fun onStart(owner: LifecycleOwner) {
|
override fun onStart(owner: LifecycleOwner) {
|
||||||
SecureActivityDelegate.onApplicationStart()
|
SecureActivityDelegate.onApplicationStart()
|
||||||
|
|
||||||
|
val syncPreferences: SyncPreferences by injectLazy()
|
||||||
|
val syncFlags = syncPreferences.syncFlags().get()
|
||||||
|
if (syncPreferences.syncService().get() != 0 && syncFlags and SyncPreferences.Flags.SYNC_ON_APP_START == SyncPreferences.Flags.SYNC_ON_APP_START) {
|
||||||
|
SyncDataJob.startNow(this@App)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop(owner: LifecycleOwner) {
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
|
@ -21,6 +21,7 @@ import eu.kanade.tachiyomi.data.download.model.Download
|
|||||||
import eu.kanade.tachiyomi.data.saver.Image
|
import eu.kanade.tachiyomi.data.saver.Image
|
||||||
import eu.kanade.tachiyomi.data.saver.ImageSaver
|
import eu.kanade.tachiyomi.data.saver.ImageSaver
|
||||||
import eu.kanade.tachiyomi.data.saver.Location
|
import eu.kanade.tachiyomi.data.saver.Location
|
||||||
|
import eu.kanade.tachiyomi.data.sync.SyncDataJob
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.reader.loader.ChapterLoader
|
import eu.kanade.tachiyomi.ui.reader.loader.ChapterLoader
|
||||||
@ -71,6 +72,7 @@ import tachiyomi.domain.history.model.HistoryUpdate
|
|||||||
import tachiyomi.domain.manga.interactor.GetManga
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
|
import tachiyomi.domain.sync.SyncPreferences
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -98,6 +100,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
private val upsertHistory: UpsertHistory = Injekt.get(),
|
private val upsertHistory: UpsertHistory = Injekt.get(),
|
||||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||||
private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(),
|
private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(),
|
||||||
|
private val syncPreferences: SyncPreferences = Injekt.get()
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val mutableState = MutableStateFlow(State())
|
private val mutableState = MutableStateFlow(State())
|
||||||
@ -513,6 +516,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
*/
|
*/
|
||||||
private suspend fun updateChapterProgress(readerChapter: ReaderChapter, page: Page) {
|
private suspend fun updateChapterProgress(readerChapter: ReaderChapter, page: Page) {
|
||||||
val pageIndex = page.index
|
val pageIndex = page.index
|
||||||
|
val syncFlags = syncPreferences.syncFlags().get()
|
||||||
|
|
||||||
mutableState.update {
|
mutableState.update {
|
||||||
it.copy(currentPage = pageIndex + 1)
|
it.copy(currentPage = pageIndex + 1)
|
||||||
@ -527,6 +531,12 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
readerChapter.chapter.read = true
|
readerChapter.chapter.read = true
|
||||||
updateTrackChapterRead(readerChapter)
|
updateTrackChapterRead(readerChapter)
|
||||||
deleteChapterIfNeeded(readerChapter)
|
deleteChapterIfNeeded(readerChapter)
|
||||||
|
|
||||||
|
// Check if syncing is enabled for chapter read:
|
||||||
|
if (syncPreferences.syncService().get() != 0 &&
|
||||||
|
syncFlags and SyncPreferences.Flags.SYNC_ON_CHAPTER_READ == SyncPreferences.Flags.SYNC_ON_CHAPTER_READ) {
|
||||||
|
SyncDataJob.startNow(Injekt.get<Application>())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateChapter.await(
|
updateChapter.await(
|
||||||
@ -536,6 +546,12 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
lastPageRead = readerChapter.chapter.last_page_read.toLong(),
|
lastPageRead = readerChapter.chapter.last_page_read.toLong(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Check if syncing is enabled for chapter open:
|
||||||
|
if (syncPreferences.syncService().get() != 0 &&
|
||||||
|
syncFlags and SyncPreferences.Flags.SYNC_ON_CHAPTER_OPEN == SyncPreferences.Flags.SYNC_ON_CHAPTER_OPEN) {
|
||||||
|
SyncDataJob.startNow(Injekt.get<Application>())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,18 @@ import tachiyomi.core.preference.PreferenceStore
|
|||||||
class SyncPreferences(
|
class SyncPreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
object Flags {
|
||||||
|
const val NONE = 0x0
|
||||||
|
const val SYNC_ON_CHAPTER_READ = 0x1
|
||||||
|
const val SYNC_ON_CHAPTER_OPEN = 0x2
|
||||||
|
const val SYNC_ON_APP_START = 0x4
|
||||||
|
|
||||||
|
const val Defaults = NONE
|
||||||
|
|
||||||
|
fun values() = listOf(NONE, SYNC_ON_CHAPTER_READ, SYNC_ON_CHAPTER_OPEN, SYNC_ON_APP_START)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun syncHost() = preferenceStore.getString("sync_host", "https://sync.tachiyomi.org")
|
fun syncHost() = preferenceStore.getString("sync_host", "https://sync.tachiyomi.org")
|
||||||
fun syncAPIKey() = preferenceStore.getString("sync_api_key", "")
|
fun syncAPIKey() = preferenceStore.getString("sync_api_key", "")
|
||||||
fun lastSyncTimestamp() = preferenceStore.getLong(Preference.appStateKey("last_sync_timestamp"), 0L)
|
fun lastSyncTimestamp() = preferenceStore.getLong(Preference.appStateKey("last_sync_timestamp"), 0L)
|
||||||
@ -22,4 +34,6 @@ class SyncPreferences(
|
|||||||
Preference.appStateKey("google_drive_refresh_token"),
|
Preference.appStateKey("google_drive_refresh_token"),
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun syncFlags() = preferenceStore.getInt("sync_flags", Flags.Defaults)
|
||||||
}
|
}
|
||||||
|
@ -566,7 +566,11 @@
|
|||||||
<string name="google_drive_not_signed_in">Not signed in to Google Drive</string>
|
<string name="google_drive_not_signed_in">Not signed in to Google Drive</string>
|
||||||
<string name="pref_purge_confirmation_title">Purge confirmation</string>
|
<string name="pref_purge_confirmation_title">Purge confirmation</string>
|
||||||
<string name="pref_purge_confirmation_message">Purging sync data will delete all your sync data from Google Drive. Are you sure you want to continue?</string>
|
<string name="pref_purge_confirmation_message">Purging sync data will delete all your sync data from Google Drive. Are you sure you want to continue?</string>
|
||||||
|
<string name="pref_sync_options">Create sync triggers</string>
|
||||||
|
<string name="pref_sync_options_summ">Can be used to set sync triggers</string>
|
||||||
|
<string name="sync_on_chapter_read">On chapter read</string>
|
||||||
|
<string name="sync_on_chapter_open">On every open chapter page (EXPERIMENTAL NOT RECOMMENDED, everytime you go to next page it or previous it will sync.)</string>
|
||||||
|
<string name="sync_on_app_start">On app start</string>
|
||||||
|
|
||||||
<!-- Advanced section -->
|
<!-- Advanced section -->
|
||||||
<string name="label_network">Networking</string>
|
<string name="label_network">Networking</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user