Implemented new favorite backup/restore feature

This commit is contained in:
tom5079
2020-08-26 23:40:36 +09:00
parent 68c94d1d8b
commit 3558d826fb
21 changed files with 292 additions and 106 deletions

View File

@@ -62,7 +62,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC" //implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC"
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.preference:preference:1.1.1'
@@ -92,7 +92,9 @@ dependencies {
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0' implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
//implementation 'com.andrognito.pinlockview:pinlockview:2.1.0' //implementation 'com.andrognito.pinlockview:pinlockview:2.1.0'
implementation "ru.noties.markwon:core:3.1.0" implementation "ru.noties.markwon:core:3.1.0"
implementation 'xyz.quaver:libpupil:1.0' implementation ("xyz.quaver:libpupil:1.0") {
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm'
}
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation 'androidx.test:rules:1.3.0'

Binary file not shown.

View File

@@ -40,9 +40,10 @@
-keepattributes *Annotation*, InnerClasses -keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.SerializationKt -dontnote kotlinx.serialization.SerializationKt
-keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; } # <-- change package name to your app's -keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; } # <-- change package name to your app's
-keepclassmembers class xyz.quaver.pupil** { # <-- change package name to your app's -keepclassmembers class xyz.quaver.pupil.** { # <-- change package name to your app's
*** Companion; *** Companion;
} }
-keepclasseswithmembers class xyz.quaver.pupil** { # <-- change package name to your app's -keepclasseswithmembers class xyz.quaver.pupil.** { # <-- change package name to your app's
kotlinx.serialization.KSerializer serializer(...); kotlinx.serialization.KSerializer serializer(...);
} }
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment

View File

@@ -6,9 +6,9 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
@@ -20,6 +20,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config"
tools:replace="android:theme" tools:replace="android:theme"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
@@ -36,9 +37,11 @@
</provider> </provider>
<receiver android:name=".BroadcastReciever" android:exported="true"> <receiver
android:name=".BroadcastReciever"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE"/> <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter> </intent-filter>
</receiver> </receiver>
@@ -204,7 +207,9 @@
</activity> </activity>
<activity <activity
android:name=".ui.SettingsActivity" android:name=".ui.SettingsActivity"
android:label="@string/settings_title" /> android:label="@string/settings_title">
<tools:validation testUrl="http://ix.io/eer" />
</activity>
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
@@ -215,6 +220,17 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="http"
android:host="ix.io"
android:pathPattern="/..*" />
</intent-filter>
</activity> </activity>
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" /> <activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
</application> </application>

View File

@@ -83,6 +83,10 @@ class Pupil : MultiDexApplication() {
histories.clear() histories.clear()
histories.addAll(it) histories.addAll(it)
} }
favorites.reversed().let {
favorites.clear()
favorites.addAll(it)
}
} }
preference.edit().putBoolean("old_history", false).apply() preference.edit().putBoolean("old_history", false).apply()
} }

View File

@@ -67,7 +67,7 @@ data class Tag(val area: String?, val tag: String, val isNegative: Boolean = fal
} }
} }
class Tags(tag: List<Tag?>?) : ArrayList<Tag>() { class Tags(val tags: MutableSet<Tag> = mutableSetOf()) : MutableSet<Tag> by tags {
companion object { companion object {
fun parse(tags: String) : Tags { fun parse(tags: String) : Tags {
@@ -77,20 +77,13 @@ class Tags(tag: List<Tag?>?) : ArrayList<Tag>() {
Tag.parse(it) Tag.parse(it)
else else
null null
} }.filterNotNull().toMutableSet()
) )
} }
} }
init {
tag?.forEach {
if (it != null)
add(it)
}
}
fun contains(element: String): Boolean { fun contains(element: String): Boolean {
forEach { tags.forEach {
if (it.toString() == element) if (it.toString() == element)
return true return true
} }
@@ -99,23 +92,25 @@ class Tags(tag: List<Tag?>?) : ArrayList<Tag>() {
} }
fun add(element: String): Boolean { fun add(element: String): Boolean {
return super.add(Tag.parse(element)) return tags.add(Tag.parse(element))
} }
fun remove(element: String) { fun remove(element: String) {
filter { it.toString() == element }.forEach { tags.filter { it.toString() == element }.forEach {
remove(it) tags.remove(it)
} }
} }
fun removeByArea(area: String, isNegative: Boolean? = null) { fun removeByArea(area: String, isNegative: Boolean? = null) {
filter { it.area == area && (if(isNegative == null) true else (it.isNegative == isNegative)) }.forEach { tags.filter { it.area == area && (if(isNegative == null) true else (it.isNegative == isNegative)) }.forEach {
remove(it) tags.remove(it)
} }
} }
override fun toString(): String { override fun toString(): String {
return joinToString(" ") { it.toString() } return tags.joinToString(" ") { it.toString() }
} }
} }

View File

@@ -62,7 +62,6 @@ import xyz.quaver.hitomi.getSuggestionsForQuery
import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.GalleryBlockAdapter import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.types.TagSuggestion import xyz.quaver.pupil.types.TagSuggestion
import xyz.quaver.pupil.types.Tags import xyz.quaver.pupil.types.Tags
import xyz.quaver.pupil.ui.dialog.GalleryDialog import xyz.quaver.pupil.ui.dialog.GalleryDialog
@@ -153,6 +152,18 @@ class MainActivity : AppCompatActivity() {
this@MainActivity.favorites = favorites this@MainActivity.favorites = favorites
} }
if (intent.action == Intent.ACTION_VIEW) {
intent.dataString?.let { url ->
restore(favorites, url,
onFailure = {
Snackbar.make(this.main_recyclerview, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
}, onSuccess = {
Snackbar.make(this.main_recyclerview, getString(R.string.settings_restore_success, it.size), Snackbar.LENGTH_LONG).show()
}
)
}
}
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
checkUpdate(this) checkUpdate(this)
@@ -771,7 +782,7 @@ class MainActivity : AppCompatActivity() {
if (!favoritesFile.exists()) { if (!favoritesFile.exists()) {
favoritesFile.createNewFile() favoritesFile.createNewFile()
favoritesFile.writeText(Json.encodeToString(Tags(listOf()))) favoritesFile.writeText(Json.encodeToString(Tags()))
} }
setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener { setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener {
@@ -833,7 +844,7 @@ class MainActivity : AppCompatActivity() {
clearSuggestions() clearSuggestions()
if (query.isEmpty() or query.endsWith(' ')) { if (query.isEmpty() or query.endsWith(' ')) {
swapSuggestions(Json.decodeFromString<Tags>(favoritesFile.readText()).map { swapSuggestions(Tags(Json.decodeFromString(favoritesFile.readText())).map {
TagSuggestion(it.tag, -1, "", it.area ?: "tag") TagSuggestion(it.tag, -1, "", it.area ?: "tag")
}) })
@@ -911,7 +922,7 @@ class MainActivity : AppCompatActivity() {
favorites.add(tag) favorites.add(tag)
} }
favoritesFile.writeText(Json.encodeToString(favorites)) favoritesFile.writeText(Json.encodeToString(favorites.tags))
} }
} }
@@ -951,7 +962,7 @@ class MainActivity : AppCompatActivity() {
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener { setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
override fun onFocus() { override fun onFocus() {
if (query.isEmpty() or query.endsWith(' ')) if (query.isEmpty() or query.endsWith(' '))
swapSuggestions(Tags(Json.decodeFromString<List<Tag>>(favoritesFile.readText())).map { swapSuggestions(Tags(Json.decodeFromString(favoritesFile.readText())).map {
TagSuggestion(it.tag, -1, "", it.area ?: "tag") TagSuggestion(it.tag, -1, "", it.area ?: "tag")
}) })
} }
@@ -1076,7 +1087,7 @@ class MainActivity : AppCompatActivity() {
} }
Mode.FAVORITE -> { Mode.FAVORITE -> {
when { when {
query.isEmpty() -> favorites.toList().also { query.isEmpty() -> favorites.reversed().also {
totalItems = it.size totalItems = it.size
} }
else -> { else -> {

View File

@@ -80,7 +80,7 @@ class SettingsActivity : AppCompatActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when(requestCode) { when(requestCode) {
R.id.request_lock -> { R.id.request_lock.normalizeID() -> {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
supportFragmentManager supportFragmentManager
.beginTransaction() .beginTransaction()
@@ -89,7 +89,7 @@ class SettingsActivity : AppCompatActivity() {
.commitAllowingStateLoss() .commitAllowingStateLoss()
} }
} }
R.id.request_restore -> { R.id.request_restore.normalizeID() -> {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
val uri = data?.data ?: return val uri = data?.data ?: return
@@ -103,7 +103,7 @@ class SettingsActivity : AppCompatActivity() {
(application as Pupil).favorites.addAll(Json.decodeFromString<List<Int>>(str).also { (application as Pupil).favorites.addAll(Json.decodeFromString<List<Int>>(str).also {
Snackbar.make( Snackbar.make(
window.decorView, window.decorView,
getString(R.string.settings_restore_successful, it.size), getString(R.string.settings_restore_success, it.size),
Snackbar.LENGTH_LONG Snackbar.LENGTH_LONG
).show() ).show()
}) })
@@ -116,7 +116,7 @@ class SettingsActivity : AppCompatActivity() {
} }
} }
} }
R.id.request_download_folder -> { R.id.request_download_folder.normalizeID() -> {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
data?.data?.also { uri -> data?.data?.also { uri ->
val takeFlags: Int = val takeFlags: Int =
@@ -140,7 +140,7 @@ class SettingsActivity : AppCompatActivity() {
} }
} }
} }
R.id.request_download_folder_old -> { R.id.request_download_folder_old.normalizeID() -> {
if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
val directory = data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!! val directory = data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!!
@@ -163,7 +163,7 @@ class SettingsActivity : AppCompatActivity() {
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
when (requestCode) { when (requestCode) {
R.id.request_write_permission_and_saf -> { R.id.request_write_permission_and_saf.normalizeID() -> {
if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) { if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
putExtra("android.content.extra.SHOW_ADVANCED", true) putExtra("android.content.extra.SHOW_ADVANCED", true)

View File

@@ -31,8 +31,7 @@ import xyz.quaver.pupil.ui.LockActivity
import xyz.quaver.pupil.util.Lock import xyz.quaver.pupil.util.Lock
import xyz.quaver.pupil.util.LockManager import xyz.quaver.pupil.util.LockManager
class LockSettingsFragment : class LockSettingsFragment : PreferenceFragmentCompat() {
PreferenceFragmentCompat() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()

View File

@@ -0,0 +1,99 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2020 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui.fragment
import android.content.Intent
import android.os.Bundle
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.snackbar.Snackbar
import okhttp3.*
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.restore
import java.io.File
import java.io.IOException
class ManageFavoritesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.manage_favorites_preferences, rootKey)
initPreferences()
}
private fun initPreferences() {
findPreference<Preference>("backup")?.setOnPreferenceClickListener {
val request = Request.Builder()
.url(getString(R.string.backup_url))
.post(
FormBody.Builder()
.add("f:1", File(ContextCompat.getDataDir(requireContext()), "favorites.json").readText())
.build()
).build()
OkHttpClient().newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
val view = view ?: return
Snackbar.make(view, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
}
override fun onResponse(call: Call, response: Response) {
Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, response.body()?.use { it.string() }?.replace("\n", ""))
}.let {
context?.startActivity(Intent.createChooser(it, getString(R.string.settings_backup_share)))
}
}
})
true
}
findPreference<Preference>("restore")?.setOnPreferenceClickListener {
val editText = EditText(requireContext()).apply {
setText(getString(R.string.backup_url), TextView.BufferType.EDITABLE)
}
AlertDialog.Builder(requireContext())
.setTitle(R.string.settings_restore_title)
.setView(editText)
.setPositiveButton(android.R.string.ok) { _, _ ->
val favorites = (activity?.application as? Pupil)?.favorites ?: return@setPositiveButton
restore(favorites, editText.text.toString(),
onFailure = onFailure@{
val view = view ?: return@onFailure
Snackbar.make(view, R.string.settings_restore_failed, Snackbar.LENGTH_LONG).show()
}, onSuccess = onSuccess@{
val view = view ?: return@onSuccess
Snackbar.make(view, getString(R.string.settings_restore_success, it.size), Snackbar.LENGTH_LONG).show()
})
}.setNegativeButton(android.R.string.cancel) { _, _ ->
// Do Nothing
}.show()
true
}
}
}

View File

@@ -181,23 +181,6 @@ class SettingsFragment :
"nomedia" -> { "nomedia" -> {
File(getDownloadDirectory(context), ".nomedia").createNewFile() File(getDownloadDirectory(context), ".nomedia").createNewFile()
} }
"backup" -> {
File(ContextCompat.getDataDir(requireContext()), "favorites.json").copyTo(
File(getDownloadDirectory(requireContext()), "favorites.json"),
true
)
Snackbar.make(this@SettingsFragment.listView, R.string.settings_backup_snackbar, Snackbar.LENGTH_LONG)
.show()
}
"restore" -> {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
activity?.startActivityForResult(intent, R.id.request_restore.normalizeID())
}
"user_id" -> { "user_id" -> {
(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip( (context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(
ClipData.newPlainText("user_id", sharedPreference.getString("user_id", "")) ClipData.newPlainText("user_id", sharedPreference.getString("user_id", ""))
@@ -349,10 +332,7 @@ class SettingsFragment :
"nomedia" -> { "nomedia" -> {
onPreferenceClickListener = this@SettingsFragment onPreferenceClickListener = this@SettingsFragment
} }
"backup" -> { "old_import_galleries" -> {
onPreferenceClickListener = this@SettingsFragment
}
"restore" -> {
onPreferenceClickListener = this@SettingsFragment onPreferenceClickListener = this@SettingsFragment
} }
"user_id" -> { "user_id" -> {

View File

@@ -26,14 +26,15 @@ import java.io.File
class GalleryList(private val file: File, private val list: MutableSet<Int> = mutableSetOf()) : MutableSet<Int> by list { class GalleryList(private val file: File, private val list: MutableSet<Int> = mutableSetOf()) : MutableSet<Int> by list {
init { init {
if (!file.exists()) {
file.parentFile?.mkdirs()
save()
}
load() load()
} }
fun load() { fun load() {
synchronized(this) { synchronized(this) {
if (!file.exists())
file.parentFile?.mkdirs()
list.clear() list.clear()
list.addAll( list.addAll(
Json.decodeFromString<List<Int>>(file.bufferedReader().use { it.readText() }) Json.decodeFromString<List<Int>>(file.bufferedReader().use { it.readText() })

View File

@@ -18,42 +18,32 @@
package xyz.quaver.pupil.util package xyz.quaver.pupil.util
import android.annotation.SuppressLint
import android.app.DownloadManager import android.app.DownloadManager
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.Base64 import android.webkit.URLUtil
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import kotlinx.coroutines.* import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_main_content.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import okhttp3.* import okhttp3.*
import ru.noties.markwon.Markwon import ru.noties.markwon.Markwon
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.getGalleryBlock
import xyz.quaver.hitomi.getReader
import xyz.quaver.proxy
import xyz.quaver.pupil.BroadcastReciever
import xyz.quaver.pupil.BuildConfig import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.download.Cache
import xyz.quaver.pupil.util.download.Metadata
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit
fun getReleases(url: String) : JsonArray { fun getReleases(url: String) : JsonArray {
return try { return try {
URL(url).readText().let { URL(url).readText().let {
Json.decodeFromString(it) Json.parseToJsonElement(it).jsonArray
} }
} catch (e: Exception) { } catch (e: Exception) {
JsonArray(emptyList()) JsonArray(emptyList())
@@ -185,3 +175,28 @@ fun checkUpdate(context: Context, force: Boolean = false) {
} }
} }
} }
fun restore(favorites: GalleryList, url: String, onFailure: ((Exception) -> Unit)? = null, onSuccess: ((List<Int>) -> Unit)? = null) {
if (!URLUtil.isValidUrl(url)) {
onFailure?.invoke(IllegalArgumentException())
return
}
val request = Request.Builder()
.url(url)
.get()
.build()
OkHttpClient().newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
onFailure?.invoke(e)
}
override fun onResponse(call: Call, response: Response) {
Json.decodeFromString<List<Int>>(response.body().use { it?.string() } ?: "[]").let {
favorites.addAll(it)
onSuccess?.invoke(it)
}
}
})
}

View File

@@ -54,7 +54,7 @@
<string name="settings_clear_downloads">ダウンロード削除</string> <string name="settings_clear_downloads">ダウンロード削除</string>
<string name="settings_clear_downloads_alert_message">ダウンロードしたギャラリーを全て削除します。\n実行しますか</string> <string name="settings_clear_downloads_alert_message">ダウンロードしたギャラリーを全て削除します。\n実行しますか</string>
<string name="settings_mirror_summary">ミラーサーバからイメージをロード</string> <string name="settings_mirror_summary">ミラーサーバからイメージをロード</string>
<string name="main_drawer_favorite">お気に入り</string> <string name="main_drawer_favorite">ブックマーク</string>
<string name="main_open_gallery_by_id">ギャラリー番号で見る</string> <string name="main_open_gallery_by_id">ギャラリー番号で見る</string>
<string name="reader_failed_to_find_gallery">エラーが発生しました</string> <string name="reader_failed_to_find_gallery">エラーが発生しました</string>
<string name="settings_storage">ストレージ</string> <string name="settings_storage">ストレージ</string>
@@ -91,11 +91,11 @@
<string name="settings_nomedia_title">イメージを隠す</string> <string name="settings_nomedia_title">イメージを隠す</string>
<string name="main_delete">削除</string> <string name="main_delete">削除</string>
<string name="main_download">ダウンロード</string> <string name="main_download">ダウンロード</string>
<string name="settings_backup_title">お気に入りバックアップ</string> <string name="settings_backup_title">ブックマークバックアップ</string>
<string name="settings_restore_title">お気に入り復元</string> <string name="settings_restore_title">ブックマーク復元</string>
<string name="settings_backup_snackbar">バックアップファイルを作成しました</string> <string name="settings_backup_file_created">バックアップファイルを作成しました</string>
<string name="settings_restore_failed">復元に失敗しました</string> <string name="settings_restore_failed">復元に失敗しました</string>
<string name="settings_restore_successful">%1$d項目を復元しました</string> <string name="settings_restore_success">%1$d項目を復元しました</string>
<string name="settings_dl_location">ダウンロード場所</string> <string name="settings_dl_location">ダウンロード場所</string>
<string name="settings_dl_location_internal">内部ストレージ</string> <string name="settings_dl_location_internal">内部ストレージ</string>
<string name="settings_dl_location_removable">外部SDカード</string> <string name="settings_dl_location_removable">外部SDカード</string>
@@ -137,4 +137,7 @@
<string name="reader_fab_auto">自動スクロール</string> <string name="reader_fab_auto">自動スクロール</string>
<string name="search_all">全てのギャラリーを対象に検索</string> <string name="search_all">全てのギャラリーを対象に検索</string>
<string name="settings_rtl">綴じ方向を左にする</string> <string name="settings_rtl">綴じ方向を左にする</string>
<string name="settings_manage_favorites">ブックマーク管理</string>
<string name="settings_backup_failed">エラーが発生しました</string>
<string name="settings_backup_share">バックアップ共有</string>
</resources> </resources>

View File

@@ -91,9 +91,9 @@
<string name="main_download">다운로드</string> <string name="main_download">다운로드</string>
<string name="settings_backup_title">즐겨찾기 백업</string> <string name="settings_backup_title">즐겨찾기 백업</string>
<string name="settings_restore_title">즐겨찾기 복원</string> <string name="settings_restore_title">즐겨찾기 복원</string>
<string name="settings_backup_snackbar">백업 파일을 생성하였습니다</string> <string name="settings_backup_file_created">백업 파일을 생성하였습니다</string>
<string name="settings_restore_failed">복원에 실패했습니다</string> <string name="settings_restore_failed">복원에 실패했습니다</string>
<string name="settings_restore_successful">%1$d개 항목을 복원했습니다</string> <string name="settings_restore_success">%1$d개 항목을 복원했습니다</string>
<string name="settings_dl_location">다운로드 위치</string> <string name="settings_dl_location">다운로드 위치</string>
<string name="settings_dl_location_internal">내부 저장공간</string> <string name="settings_dl_location_internal">내부 저장공간</string>
<string name="settings_dl_location_removable">외부 SD카드</string> <string name="settings_dl_location_removable">외부 SD카드</string>
@@ -137,4 +137,7 @@
<string name="reader_fab_auto">자동 스크롤</string> <string name="reader_fab_auto">자동 스크롤</string>
<string name="search_all">모든 갤러리 검색</string> <string name="search_all">모든 갤러리 검색</string>
<string name="settings_rtl">좌측으로 페이지 넘기기</string> <string name="settings_rtl">좌측으로 페이지 넘기기</string>
<string name="settings_manage_favorites">즐겨찾기 관리</string>
<string name="settings_backup_failed">업로드 실패</string>
<string name="settings_backup_share">백업 공유</string>
</resources> </resources>

View File

@@ -5,8 +5,6 @@
<item name="request_lock" type="id" /> <item name="request_lock" type="id" />
<item name="request_restore" type="id" /> <item name="request_restore" type="id" />
<item name="request_import_old_galleries" type="id" />
<item name="request_import_old_galleries_old" type="id" />
<item name="request_download_folder" type="id" /> <item name="request_download_folder" type="id" />
<item name="request_download_folder_old" type="id" /> <item name="request_download_folder_old" type="id" />
<item name="request_write_permission_and_saf" type="id" /> <item name="request_write_permission_and_saf" type="id" />

View File

@@ -9,6 +9,8 @@
<string name="email" translatable="false">mailto:pupil.hentai@gmail.com</string> <string name="email" translatable="false">mailto:pupil.hentai@gmail.com</string>
<string name="discord" translatable="false">https://discord.gg/Stj4b5v</string> <string name="discord" translatable="false">https://discord.gg/Stj4b5v</string>
<string name="backup_url" translatable="false">http://ix.io/</string>
<string name="main_settings" translatable="false">Settings</string> <string name="main_settings" translatable="false">Settings</string>
<string name="galleryblock_thumbnail_description" translatable="false">Thumbnail</string> <string name="galleryblock_thumbnail_description" translatable="false">Thumbnail</string>
@@ -154,15 +156,21 @@
<string name="settings_dark_mode_title">Dark mode</string> <string name="settings_dark_mode_title">Dark mode</string>
<string name="settings_dark_mode_summary">Protect yourself against light attacks!</string> <string name="settings_dark_mode_summary">Protect yourself against light attacks!</string>
<string name="settings_nomedia_title">Hide image from gallery</string> <string name="settings_nomedia_title">Hide image from gallery</string>
<string name="settings_backup_title">Backup favorites</string>
<string name="settings_backup_snackbar">Backup file created</string>
<string name="settings_restore_title">Restore favorites</string>
<string name="settings_restore_failed">Restore failed</string>
<string name="settings_restore_successful">%1$d entries restored</string>
<string name="settings_import_old_galleries">Import old galleries</string> <string name="settings_import_old_galleries">Import old galleries</string>
<string name="settings_user_id">User ID</string> <string name="settings_user_id">User ID</string>
<string name="settings_user_id_toast">User ID is copied to clipboard</string> <string name="settings_user_id_toast">User ID is copied to clipboard</string>
<!-- MANAGE FAVORITES -->
<string name="settings_manage_favorites">Manage favorites</string>
<string name="settings_backup_title">Backup favorites</string>
<string name="settings_backup_failed">Upload Failed</string>
<string name="settings_backup_share">Share Backup</string>
<string name="settings_backup_file_created">Backup file created</string>
<string name="settings_restore_title">Restore favorites</string>
<string name="settings_restore_failed">Restore failed</string>
<string name="settings_restore_success">%1$d entries restored</string>
<!-- SETTINGS/APP LOCK ACTIVITY --> <!-- SETTINGS/APP LOCK ACTIVITY -->
<string name="settings_lock_none">None</string> <string name="settings_lock_none">None</string>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen <PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference <Preference
@@ -23,4 +23,4 @@
</PreferenceCategory> </PreferenceCategory>
</androidx.preference.PreferenceScreen> </PreferenceScreen>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2020 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
app:key="backup"
app:title="@string/settings_backup_title"/>
<Preference
app:key="restore"
app:title="@string/settings_restore_title"/>
</PreferenceScreen>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2020 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">ix.io</domain>
</domain-config>
</network-security-config>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen <PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference <Preference
@@ -101,12 +101,8 @@
app:title="@string/settings_nomedia_title"/> app:title="@string/settings_nomedia_title"/>
<Preference <Preference
app:key="backup" app:fragment="xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment"
app:title="@string/settings_backup_title"/> app:title="@string/settings_manage_favorites"/>
<Preference
app:key="restore"
app:title="@string/settings_restore_title"/>
<Preference <Preference
app:key="user_id" app:key="user_id"
@@ -114,4 +110,4 @@
</PreferenceCategory> </PreferenceCategory>
</androidx.preference.PreferenceScreen> </PreferenceScreen>