Include reader tap settings into backups
This commit is contained in:
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.alternatives.domain
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.sync.withPermit
|
import kotlinx.coroutines.sync.withPermit
|
||||||
@@ -40,13 +39,16 @@ class AlternativesUseCase @Inject constructor(
|
|||||||
searchHelper(manga.title, SearchKind.TITLE)?.manga
|
searchHelper(manga.title, SearchKind.TITLE)?.manga
|
||||||
}
|
}
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
list?.forEach { send(it) }
|
list?.forEach {
|
||||||
|
launch {
|
||||||
|
val details = runCatchingCancellable {
|
||||||
|
mangaRepositoryFactory.create(it.source).getDetails(it)
|
||||||
|
}.getOrDefault(it)
|
||||||
|
send(details)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.map {
|
|
||||||
runCatchingCancellable {
|
|
||||||
mangaRepositoryFactory.create(it.source).getDetails(it)
|
|
||||||
}.getOrDefault(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class BackupEntry(
|
|||||||
CATEGORIES("categories"),
|
CATEGORIES("categories"),
|
||||||
FAVOURITES("favourites"),
|
FAVOURITES("favourites"),
|
||||||
SETTINGS("settings"),
|
SETTINGS("settings"),
|
||||||
|
SETTINGS_READER_GRID("reader_grid"),
|
||||||
BOOKMARKS("bookmarks"),
|
BOOKMARKS("bookmarks"),
|
||||||
SOURCES("sources"),
|
SOURCES("sources"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.koitharu.kotatsu.parsers.util.json.asTypedList
|
|||||||
import org.koitharu.kotatsu.parsers.util.json.getLongOrDefault
|
import org.koitharu.kotatsu.parsers.util.json.getLongOrDefault
|
||||||
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
|
import org.koitharu.kotatsu.reader.data.TapGridSettings
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ private const val PAGE_SIZE = 10
|
|||||||
class BackupRepository @Inject constructor(
|
class BackupRepository @Inject constructor(
|
||||||
private val db: MangaDatabase,
|
private val db: MangaDatabase,
|
||||||
private val settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
|
private val tapGridSettings: TapGridSettings,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun dumpHistory(): BackupEntry {
|
suspend fun dumpHistory(): BackupEntry {
|
||||||
@@ -105,6 +107,14 @@ class BackupRepository @Inject constructor(
|
|||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun dumpReaderGridSettings(): BackupEntry {
|
||||||
|
val entry = BackupEntry(BackupEntry.Name.SETTINGS_READER_GRID, JSONArray())
|
||||||
|
val settingsDump = tapGridSettings.getAllValues()
|
||||||
|
val json = JsonSerializer(settingsDump).toJson()
|
||||||
|
entry.data.put(json)
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun dumpSources(): BackupEntry {
|
suspend fun dumpSources(): BackupEntry {
|
||||||
val entry = BackupEntry(BackupEntry.Name.SOURCES, JSONArray())
|
val entry = BackupEntry(BackupEntry.Name.SOURCES, JSONArray())
|
||||||
val all = db.getSourcesDao().findAll()
|
val all = db.getSourcesDao().findAll()
|
||||||
@@ -229,4 +239,14 @@ class BackupRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun restoreReaderGridSettings(entry: BackupEntry): CompositeResult {
|
||||||
|
val result = CompositeResult()
|
||||||
|
for (item in entry.data.asTypedList<JSONObject>()) {
|
||||||
|
result += runCatchingCancellable {
|
||||||
|
tapGridSettings.upsertAll(JsonDeserializer(item).toMap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ import androidx.core.os.LocaleListCompat
|
|||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import org.json.JSONArray
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||||
import org.koitharu.kotatsu.core.network.DoHProvider
|
import org.koitharu.kotatsu.core.network.DoHProvider
|
||||||
import org.koitharu.kotatsu.core.util.ext.connectivityManager
|
import org.koitharu.kotatsu.core.util.ext.connectivityManager
|
||||||
import org.koitharu.kotatsu.core.util.ext.getEnumValue
|
import org.koitharu.kotatsu.core.util.ext.getEnumValue
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.putAll
|
||||||
import org.koitharu.kotatsu.core.util.ext.putEnumValue
|
import org.koitharu.kotatsu.core.util.ext.putEnumValue
|
||||||
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
|
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
|
||||||
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
|
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
|
||||||
@@ -569,20 +569,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
|
|
||||||
fun getAllValues(): Map<String, *> = prefs.all
|
fun getAllValues(): Map<String, *> = prefs.all
|
||||||
|
|
||||||
fun upsertAll(m: Map<String, *>) {
|
fun upsertAll(m: Map<String, *>) = prefs.edit { putAll(m) }
|
||||||
prefs.edit {
|
|
||||||
m.forEach { e ->
|
|
||||||
when (val v = e.value) {
|
|
||||||
is Boolean -> putBoolean(e.key, v)
|
|
||||||
is Int -> putInt(e.key, v)
|
|
||||||
is Long -> putLong(e.key, v)
|
|
||||||
is Float -> putFloat(e.key, v)
|
|
||||||
is String -> putString(e.key, v)
|
|
||||||
is JSONArray -> putStringSet(e.key, v.toStringSet())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isBackgroundNetworkRestricted(): Boolean {
|
private fun isBackgroundNetworkRestricted(): Boolean {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
@@ -592,15 +579,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun JSONArray.toStringSet(): Set<String> {
|
|
||||||
val len = length()
|
|
||||||
val result = ArraySet<String>(len)
|
|
||||||
for (i in 0 until len) {
|
|
||||||
result.add(getString(i))
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val TRACK_HISTORY = "history"
|
const val TRACK_HISTORY = "history"
|
||||||
|
|||||||
@@ -13,19 +13,16 @@ import android.content.Context.POWER_SERVICE
|
|||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.OperationApplicationException
|
import android.content.OperationApplicationException
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.SyncResult
|
import android.content.SyncResult
|
||||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||||
import android.content.pm.ResolveInfo
|
import android.content.pm.ResolveInfo
|
||||||
import android.database.SQLException
|
import android.database.SQLException
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Color
|
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.view.ViewPropertyAnimator
|
import android.view.ViewPropertyAnimator
|
||||||
import android.view.Window
|
|
||||||
import android.webkit.CookieManager
|
import android.webkit.CookieManager
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
@@ -37,7 +34,6 @@ import androidx.appcompat.app.AppCompatDialog
|
|||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.ColorUtils
|
|
||||||
import androidx.core.os.LocaleListCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -46,15 +42,8 @@ import androidx.lifecycle.coroutineScope
|
|||||||
import androidx.webkit.WebViewCompat
|
import androidx.webkit.WebViewCompat
|
||||||
import androidx.webkit.WebViewFeature
|
import androidx.webkit.WebViewFeature
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
import com.google.android.material.elevation.ElevationOverlayProvider
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
|
||||||
import kotlinx.coroutines.channels.trySendBlocking
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runInterruptible
|
import kotlinx.coroutines.runInterruptible
|
||||||
import okio.IOException
|
import okio.IOException
|
||||||
@@ -68,7 +57,6 @@ import org.xmlpull.v1.XmlPullParser
|
|||||||
import org.xmlpull.v1.XmlPullParserException
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
import com.google.android.material.R as materialR
|
|
||||||
|
|
||||||
val Context.activityManager: ActivityManager?
|
val Context.activityManager: ActivityManager?
|
||||||
get() = getSystemService(ACTIVITY_SERVICE) as? ActivityManager
|
get() = getSystemService(ACTIVITY_SERVICE) as? ActivityManager
|
||||||
@@ -101,25 +89,6 @@ fun <I> ActivityResultLauncher<I>.tryLaunch(
|
|||||||
e.printStackTraceDebug()
|
e.printStackTraceDebug()
|
||||||
}.isSuccess
|
}.isSuccess
|
||||||
|
|
||||||
fun SharedPreferences.observe(): Flow<String?> = callbackFlow {
|
|
||||||
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
|
||||||
trySendBlocking(key)
|
|
||||||
}
|
|
||||||
registerOnSharedPreferenceChangeListener(listener)
|
|
||||||
awaitClose {
|
|
||||||
unregisterOnSharedPreferenceChangeListener(listener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> SharedPreferences.observe(key: String, valueProducer: suspend () -> T): Flow<T> = flow {
|
|
||||||
emit(valueProducer())
|
|
||||||
observe().collect { upstreamKey ->
|
|
||||||
if (upstreamKey == key) {
|
|
||||||
emit(valueProducer())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.distinctUntilChanged()
|
|
||||||
|
|
||||||
fun Lifecycle.postDelayed(delay: Long, runnable: Runnable) {
|
fun Lifecycle.postDelayed(delay: Long, runnable: Runnable) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
delay(delay)
|
delay(delay)
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
package org.koitharu.kotatsu.core.util.ext
|
package org.koitharu.kotatsu.core.util.ext
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import androidx.collection.ArraySet
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.MultiSelectListPreference
|
import androidx.preference.MultiSelectListPreference
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.channels.trySendBlocking
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import org.json.JSONArray
|
||||||
|
|
||||||
fun ListPreference.setDefaultValueCompat(defaultValue: String) {
|
fun ListPreference.setDefaultValueCompat(defaultValue: String) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@@ -28,3 +36,44 @@ fun <E : Enum<E>> SharedPreferences.getEnumValue(key: String, defaultValue: E):
|
|||||||
fun <E : Enum<E>> SharedPreferences.Editor.putEnumValue(key: String, value: E?) {
|
fun <E : Enum<E>> SharedPreferences.Editor.putEnumValue(key: String, value: E?) {
|
||||||
putString(key, value?.name)
|
putString(key, value?.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun SharedPreferences.observe(): Flow<String?> = callbackFlow {
|
||||||
|
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||||
|
trySendBlocking(key)
|
||||||
|
}
|
||||||
|
registerOnSharedPreferenceChangeListener(listener)
|
||||||
|
awaitClose {
|
||||||
|
unregisterOnSharedPreferenceChangeListener(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> SharedPreferences.observe(key: String, valueProducer: suspend () -> T): Flow<T> = flow {
|
||||||
|
emit(valueProducer())
|
||||||
|
observe().collect { upstreamKey ->
|
||||||
|
if (upstreamKey == key) {
|
||||||
|
emit(valueProducer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
|
||||||
|
fun SharedPreferences.Editor.putAll(values: Map<String, *>) {
|
||||||
|
values.forEach { e ->
|
||||||
|
when (val v = e.value) {
|
||||||
|
is Boolean -> putBoolean(e.key, v)
|
||||||
|
is Int -> putInt(e.key, v)
|
||||||
|
is Long -> putLong(e.key, v)
|
||||||
|
is Float -> putFloat(e.key, v)
|
||||||
|
is String -> putString(e.key, v)
|
||||||
|
is JSONArray -> putStringSet(e.key, v.toStringSet())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JSONArray.toStringSet(): Set<String> {
|
||||||
|
val len = length()
|
||||||
|
val result = ArraySet<String>(len)
|
||||||
|
for (i in 0 until len) {
|
||||||
|
result.add(getString(i))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ data class Progress(
|
|||||||
val isFull: Boolean
|
val isFull: Boolean
|
||||||
get() = progress == total
|
get() = progress == total
|
||||||
|
|
||||||
|
val isIndeterminate: Boolean
|
||||||
|
get() = total < 0
|
||||||
|
|
||||||
override fun compareTo(other: Progress): Int = if (total == other.total) {
|
override fun compareTo(other: Progress): Int = if (total == other.total) {
|
||||||
progress.compareTo(other.progress)
|
progress.compareTo(other.progress)
|
||||||
} else {
|
} else {
|
||||||
@@ -44,4 +47,9 @@ data class Progress(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun percentSting() = (percent * 100f).toInt().toString()
|
fun percentSting() = (percent * 100f).toInt().toString()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val INDETERMINATE = Progress(0, -1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,19 @@ package org.koitharu.kotatsu.reader.data
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
import dagger.Reusable
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import org.koitharu.kotatsu.core.util.ext.getEnumValue
|
import org.koitharu.kotatsu.core.util.ext.getEnumValue
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.putAll
|
||||||
import org.koitharu.kotatsu.core.util.ext.putEnumValue
|
import org.koitharu.kotatsu.core.util.ext.putEnumValue
|
||||||
import org.koitharu.kotatsu.reader.domain.TapGridArea
|
import org.koitharu.kotatsu.reader.domain.TapGridArea
|
||||||
import org.koitharu.kotatsu.reader.ui.tapgrid.TapAction
|
import org.koitharu.kotatsu.reader.ui.tapgrid.TapAction
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Reusable
|
||||||
class TapGridSettings @Inject constructor(@ApplicationContext context: Context) {
|
class TapGridSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||||
|
|
||||||
private val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
private val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
@@ -43,6 +46,10 @@ class TapGridSettings @Inject constructor(@ApplicationContext context: Context)
|
|||||||
|
|
||||||
fun observe() = prefs.observe().flowOn(Dispatchers.IO)
|
fun observe() = prefs.observe().flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
|
fun getAllValues(): Map<String, *> = prefs.all
|
||||||
|
|
||||||
|
fun upsertAll(m: Map<String, *>) = prefs.edit { putAll(m) }
|
||||||
|
|
||||||
private fun initPrefs(withDefaultValues: Boolean) {
|
private fun initPrefs(withDefaultValues: Boolean) {
|
||||||
prefs.edit {
|
prefs.edit {
|
||||||
clear()
|
clear()
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.backup.BackupZipOutput
|
|||||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
import org.koitharu.kotatsu.core.exceptions.BadBackupFormatException
|
import org.koitharu.kotatsu.core.exceptions.BadBackupFormatException
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.reader.data.TapGridSettings
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileDescriptor
|
import java.io.FileDescriptor
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
@@ -38,7 +39,14 @@ class AppBackupAgent : BackupAgent() {
|
|||||||
override fun onFullBackup(data: FullBackupDataOutput) {
|
override fun onFullBackup(data: FullBackupDataOutput) {
|
||||||
super.onFullBackup(data)
|
super.onFullBackup(data)
|
||||||
val file =
|
val file =
|
||||||
createBackupFile(this, BackupRepository(MangaDatabase(applicationContext), AppSettings(applicationContext)))
|
createBackupFile(
|
||||||
|
this,
|
||||||
|
BackupRepository(
|
||||||
|
MangaDatabase(context = applicationContext),
|
||||||
|
AppSettings(applicationContext),
|
||||||
|
TapGridSettings(applicationContext),
|
||||||
|
),
|
||||||
|
)
|
||||||
try {
|
try {
|
||||||
fullBackupFile(file, data)
|
fullBackupFile(file, data)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -58,7 +66,11 @@ class AppBackupAgent : BackupAgent() {
|
|||||||
restoreBackupFile(
|
restoreBackupFile(
|
||||||
data.fileDescriptor,
|
data.fileDescriptor,
|
||||||
size,
|
size,
|
||||||
BackupRepository(MangaDatabase(applicationContext), AppSettings(applicationContext)),
|
BackupRepository(
|
||||||
|
db = MangaDatabase(applicationContext),
|
||||||
|
settings = AppSettings(applicationContext),
|
||||||
|
tapGridSettings = TapGridSettings(applicationContext),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
destination.delete()
|
destination.delete()
|
||||||
} else {
|
} else {
|
||||||
@@ -76,6 +88,7 @@ class AppBackupAgent : BackupAgent() {
|
|||||||
backup.put(repository.dumpBookmarks())
|
backup.put(repository.dumpBookmarks())
|
||||||
backup.put(repository.dumpSources())
|
backup.put(repository.dumpSources())
|
||||||
backup.put(repository.dumpSettings())
|
backup.put(repository.dumpSettings())
|
||||||
|
backup.put(repository.dumpReaderGridSettings())
|
||||||
backup.finish()
|
backup.finish()
|
||||||
backup.file
|
backup.file
|
||||||
}
|
}
|
||||||
@@ -103,6 +116,7 @@ class AppBackupAgent : BackupAgent() {
|
|||||||
backup.getEntry(BackupEntry.Name.BOOKMARKS)?.let { repository.restoreBookmarks(it) }
|
backup.getEntry(BackupEntry.Name.BOOKMARKS)?.let { repository.restoreBookmarks(it) }
|
||||||
backup.getEntry(BackupEntry.Name.SOURCES)?.let { repository.restoreSources(it) }
|
backup.getEntry(BackupEntry.Name.SOURCES)?.let { repository.restoreSources(it) }
|
||||||
backup.getEntry(BackupEntry.Name.SETTINGS)?.let { repository.restoreSettings(it) }
|
backup.getEntry(BackupEntry.Name.SETTINGS)?.let { repository.restoreSettings(it) }
|
||||||
|
backup.getEntry(BackupEntry.Name.SETTINGS_READER_GRID)?.let { repository.restoreReaderGridSettings(it) }
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
backup.close()
|
backup.close()
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
|||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
||||||
|
import org.koitharu.kotatsu.core.util.progress.Progress
|
||||||
import org.koitharu.kotatsu.databinding.DialogProgressBinding
|
import org.koitharu.kotatsu.databinding.DialogProgressBinding
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
|
class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
|
||||||
@@ -68,13 +68,14 @@ class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
|
|||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onProgressChanged(value: Float) {
|
private fun onProgressChanged(value: Progress) {
|
||||||
with(requireViewBinding().progressBar) {
|
with(requireViewBinding().progressBar) {
|
||||||
isVisible = true
|
isVisible = true
|
||||||
val wasIndeterminate = isIndeterminate
|
val wasIndeterminate = isIndeterminate
|
||||||
isIndeterminate = value < 0
|
isIndeterminate = value.isIndeterminate
|
||||||
if (value >= 0) {
|
if (!value.isIndeterminate) {
|
||||||
setProgressCompat((value * max).roundToInt(), !wasIndeterminate)
|
max = value.total
|
||||||
|
setProgressCompat(value.progress, !wasIndeterminate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ data class BackupEntryModel(
|
|||||||
BackupEntry.Name.CATEGORIES -> R.string.favourites_categories
|
BackupEntry.Name.CATEGORIES -> R.string.favourites_categories
|
||||||
BackupEntry.Name.FAVOURITES -> R.string.favourites
|
BackupEntry.Name.FAVOURITES -> R.string.favourites
|
||||||
BackupEntry.Name.SETTINGS -> R.string.settings
|
BackupEntry.Name.SETTINGS -> R.string.settings
|
||||||
|
BackupEntry.Name.SETTINGS_READER_GRID -> R.string.reader_actions
|
||||||
BackupEntry.Name.BOOKMARKS -> R.string.bookmarks
|
BackupEntry.Name.BOOKMARKS -> R.string.bookmarks
|
||||||
BackupEntry.Name.SOURCES -> R.string.remote_sources
|
BackupEntry.Name.SOURCES -> R.string.remote_sources
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.koitharu.kotatsu.core.backup.BackupZipOutput
|
|||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
|
import org.koitharu.kotatsu.core.util.progress.Progress
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -18,35 +19,37 @@ class BackupViewModel @Inject constructor(
|
|||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val progress = MutableStateFlow(-1f)
|
val progress = MutableStateFlow(Progress.INDETERMINATE)
|
||||||
val onBackupDone = MutableEventFlow<File>()
|
val onBackupDone = MutableEventFlow<File>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
launchLoadingJob {
|
launchLoadingJob {
|
||||||
val file = BackupZipOutput.createTemp(context).use { backup ->
|
val file = BackupZipOutput.createTemp(context).use { backup ->
|
||||||
val step = 1f / 6f
|
progress.value = Progress(0, 7)
|
||||||
backup.put(repository.createIndex())
|
backup.put(repository.createIndex())
|
||||||
|
|
||||||
progress.value = 0f
|
|
||||||
backup.put(repository.dumpHistory())
|
backup.put(repository.dumpHistory())
|
||||||
|
progress.value = progress.value.inc()
|
||||||
|
|
||||||
progress.value += step
|
|
||||||
backup.put(repository.dumpCategories())
|
backup.put(repository.dumpCategories())
|
||||||
|
progress.value = progress.value.inc()
|
||||||
|
|
||||||
progress.value += step
|
|
||||||
backup.put(repository.dumpFavourites())
|
backup.put(repository.dumpFavourites())
|
||||||
|
progress.value = progress.value.inc()
|
||||||
|
|
||||||
progress.value += step
|
|
||||||
backup.put(repository.dumpBookmarks())
|
backup.put(repository.dumpBookmarks())
|
||||||
|
progress.value = progress.value.inc()
|
||||||
|
|
||||||
progress.value += step
|
|
||||||
backup.put(repository.dumpSources())
|
backup.put(repository.dumpSources())
|
||||||
|
progress.value = progress.value.inc()
|
||||||
|
|
||||||
progress.value += step
|
|
||||||
backup.put(repository.dumpSettings())
|
backup.put(repository.dumpSettings())
|
||||||
|
progress.value = progress.value.inc()
|
||||||
|
|
||||||
|
backup.put(repository.dumpReaderGridSettings())
|
||||||
|
progress.value = progress.value.inc()
|
||||||
|
|
||||||
backup.finish()
|
backup.finish()
|
||||||
progress.value = 1f
|
|
||||||
backup.file
|
backup.file
|
||||||
}
|
}
|
||||||
onBackupDone.call(file)
|
onBackupDone.call(file)
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class PeriodicalBackupService : CoroutineIntentService() {
|
|||||||
backup.put(repository.dumpBookmarks())
|
backup.put(repository.dumpBookmarks())
|
||||||
backup.put(repository.dumpSources())
|
backup.put(repository.dumpSources())
|
||||||
backup.put(repository.dumpSettings())
|
backup.put(repository.dumpSettings())
|
||||||
|
backup.put(repository.dumpReaderGridSettings())
|
||||||
backup.finish()
|
backup.finish()
|
||||||
}
|
}
|
||||||
externalBackupStorage.put(output.file)
|
externalBackupStorage.put(output.file)
|
||||||
|
|||||||
@@ -164,6 +164,15 @@ class RestoreService : CoroutineIntentService() {
|
|||||||
|
|
||||||
notify()
|
notify()
|
||||||
|
|
||||||
|
if (BackupEntry.Name.SETTINGS_READER_GRID in entries) {
|
||||||
|
input.getEntry(BackupEntry.Name.SETTINGS_READER_GRID)?.let {
|
||||||
|
result += repository.restoreReaderGridSettings(it)
|
||||||
|
}
|
||||||
|
progress++
|
||||||
|
}
|
||||||
|
|
||||||
|
notify()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
android:layout_marginEnd="@dimen/margin_normal"
|
android:layout_marginEnd="@dimen/margin_normal"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:gravity="center_vertical|start"
|
android:gravity="center_vertical|start"
|
||||||
|
android:maxLines="3"
|
||||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/guideline"
|
app:layout_constraintBottom_toBottomOf="@id/guideline"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|||||||
Reference in New Issue
Block a user