Compare commits
22 Commits
4.2
...
4.3-hotfix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20bc5423cf | ||
|
|
b84cddffdc | ||
|
|
e46d1123df | ||
|
|
2c9c8e223c | ||
|
|
e81b5a4e3a | ||
|
|
0b87c57fbf | ||
|
|
5fd985ba39 | ||
|
|
8c64548513 | ||
|
|
a6de64ceb9 | ||
|
|
16ebb437a3 | ||
|
|
683118a3f4 | ||
|
|
08e38ed45c | ||
|
|
7abf08f1fb | ||
|
|
f3019e9b84 | ||
|
|
c468764234 | ||
|
|
31c3178430 | ||
|
|
e81c189afc | ||
|
|
e0ccac13c1 | ||
|
|
93228459d7 | ||
|
|
63e07f56e0 | ||
|
|
ee87122bb2 | ||
|
|
290dda9018 |
1
.idea/vcs.xml
generated
1
.idea/vcs.xml
generated
@@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/gh-pages" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -5,9 +5,12 @@ apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
if (file("google-services.json").exists()) {
|
||||
logger.lifecycle("Firebase Enabled")
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'io.fabric'
|
||||
apply plugin: 'com.google.firebase.firebase-perf'
|
||||
} else {
|
||||
logger.lifecycle("Firebase Disabled")
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -16,8 +19,8 @@ android {
|
||||
applicationId "xyz.quaver.pupil"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode 31
|
||||
versionName "4.2"
|
||||
versionCode 32
|
||||
versionName "4.3-hotfix1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@@ -47,34 +50,34 @@ dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.preference:preference:1.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation "androidx.biometric:biometric:1.0.1"
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
||||
implementation 'com.google.android.material:material:1.2.0-alpha03'
|
||||
implementation 'com.google.firebase:firebase-core:17.2.1'
|
||||
implementation 'com.google.firebase:firebase-perf:19.0.4'
|
||||
implementation 'com.google.android.material:material:1.2.0-alpha04'
|
||||
implementation 'com.google.firebase:firebase-core:17.2.2'
|
||||
implementation 'com.google.firebase:firebase-perf:19.0.5'
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
||||
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
||||
implementation 'com.github.clans:fab:1.6.4'
|
||||
implementation 'com.github.bumptech.glide:glide:4.9.0'
|
||||
implementation ("com.github.bumptech.glide:recyclerview-integration:4.9.0") {
|
||||
implementation 'com.github.bumptech.glide:glide:4.10.0'
|
||||
implementation ("com.github.bumptech.glide:recyclerview-integration:4.10.0") {
|
||||
transitive = false
|
||||
}
|
||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
||||
implementation "ru.noties.markwon:core:${markwonVersion}"
|
||||
kapt 'com.github.bumptech.glide:compiler:4.9.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
kapt 'com.github.bumptech.glide:compiler:4.10.0'
|
||||
testImplementation 'junit:junit:4.13'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
|
||||
@@ -1 +1 @@
|
||||
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":31,"versionName":"4.2","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
|
||||
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":32,"versionName":"4.3-hotfix1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
|
||||
@@ -220,6 +220,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
else -> null
|
||||
}
|
||||
text = tag.tag.wordCapitalize()
|
||||
setEnsureMinTouchTargetSize(false)
|
||||
setOnClickListener {
|
||||
for (callback in onChipClickedHandler)
|
||||
callback.invoke(tag)
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.activity_lock.*
|
||||
import kotlinx.android.synthetic.main.fragment_pattern_lock.*
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.ui.fragment.PatternLockFragment
|
||||
import xyz.quaver.pupil.util.Lock
|
||||
import xyz.quaver.pupil.util.LockManager
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
@@ -46,7 +47,6 @@ import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
||||
import com.arlib.floatingsearchview.util.view.SearchInputView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.activity_main_content.*
|
||||
import kotlinx.coroutines.*
|
||||
@@ -365,21 +365,14 @@ class MainActivity : AppCompatActivity() {
|
||||
setTitle(R.string.main_open_gallery_by_id)
|
||||
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
try {
|
||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
||||
val gallery =
|
||||
getGalleryBlock(editText.text.toString().toInt()) ?: throw Exception()
|
||||
intent.putExtra("galleryID", gallery.id)
|
||||
|
||||
startActivity(intent)
|
||||
|
||||
histories.add(gallery.id)
|
||||
} catch (e: Exception) {
|
||||
Snackbar.make(main_layout,
|
||||
R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show()
|
||||
val galleryID = editText.text.toString().toInt()
|
||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java).apply {
|
||||
putExtra("galleryID", galleryID)
|
||||
}
|
||||
}
|
||||
|
||||
startActivity(intent)
|
||||
|
||||
histories.add(galleryID)
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
@@ -729,6 +722,7 @@ class MainActivity : AppCompatActivity() {
|
||||
s.replace(0, s.length, s.toString().toLowerCase(Locale.getDefault()))
|
||||
}
|
||||
})
|
||||
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
|
||||
|
||||
with(main_searchview as FloatingSearchView) {
|
||||
val favoritesFile = File(ContextCompat.getDataDir(context), "favorites_tags.json")
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
package xyz.quaver.pupil.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.Drawable
|
||||
@@ -50,7 +49,6 @@ import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||
import xyz.quaver.pupil.util.GalleryDownloader
|
||||
import xyz.quaver.pupil.util.Histories
|
||||
import xyz.quaver.pupil.util.ItemClickSupport
|
||||
import xyz.quaver.pupil.util.hasPermission
|
||||
|
||||
class ReaderActivity : AppCompatActivity() {
|
||||
|
||||
@@ -373,17 +371,6 @@ class ReaderActivity : AppCompatActivity() {
|
||||
with(reader_fab_download) {
|
||||
setImageResource(R.drawable.ic_download)
|
||||
setOnClickListener {
|
||||
|
||||
if (!this@ReaderActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
AlertDialog.Builder(this@ReaderActivity).apply {
|
||||
setTitle(R.string.warning)
|
||||
setMessage(R.string.update_no_permission)
|
||||
setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
}.show()
|
||||
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
downloader.download = !downloader.download
|
||||
|
||||
if (!downloader.download)
|
||||
|
||||
@@ -21,34 +21,19 @@ package xyz.quaver.pupil.ui
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.WindowManager
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.dialog_default_query.view.*
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.parseList
|
||||
import xyz.quaver.pupil.Pupil
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.types.Tags
|
||||
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialog
|
||||
import xyz.quaver.pupil.util.*
|
||||
import java.io.File
|
||||
import xyz.quaver.pupil.ui.fragment.LockFragment
|
||||
import xyz.quaver.pupil.ui.fragment.SettingsFragment
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
|
||||
class SettingsActivity : AppCompatActivity() {
|
||||
|
||||
@@ -82,380 +67,6 @@ class SettingsActivity : AppCompatActivity() {
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
val lockManager = LockManager(context!!)
|
||||
|
||||
findPreference<Preference>("app_lock")?.summary = if (lockManager.locks.isNullOrEmpty()) {
|
||||
getString(R.string.settings_lock_none)
|
||||
} else {
|
||||
lockManager.locks?.joinToString(", ") {
|
||||
when(it.type) {
|
||||
Lock.Type.PATTERN -> getString(R.string.settings_lock_pattern)
|
||||
Lock.Type.PIN -> getString(R.string.settings_lock_pin)
|
||||
Lock.Type.PASSWORD -> getString(R.string.settings_lock_password)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDirSize(dir: File) : String {
|
||||
val size = dir.walk().map { it.length() }.sum()
|
||||
|
||||
return getString(R.string.settings_clear_summary, byteToString(size))
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||
|
||||
with(findPreference<Preference>("app_version")) {
|
||||
this!!
|
||||
|
||||
val manager = context.packageManager
|
||||
val info = manager.getPackageInfo(context.packageName, 0)
|
||||
|
||||
summary = context.getString(R.string.settings_app_version_description, info.versionName)
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
checkUpdate(activity as SettingsActivity, true)
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
with(findPreference<Preference>("delete_cache")) {
|
||||
this!!
|
||||
|
||||
val dir = File(context.cacheDir, "imageCache")
|
||||
|
||||
summary = getDirSize(dir)
|
||||
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.warning)
|
||||
setMessage(R.string.settings_clear_cache_alert_message)
|
||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
if (dir.exists())
|
||||
dir.deleteRecursively()
|
||||
|
||||
summary = getDirSize(dir)
|
||||
}
|
||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||
}.show()
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
with(findPreference<Preference>("delete_downloads")) {
|
||||
this!!
|
||||
|
||||
val dir = getDownloadDirectory(context)
|
||||
|
||||
summary = getDirSize(dir)
|
||||
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.warning)
|
||||
setMessage(R.string.settings_clear_downloads_alert_message)
|
||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
if (dir.exists())
|
||||
dir.deleteRecursively()
|
||||
|
||||
val downloads = (activity!!.application as Pupil).downloads
|
||||
|
||||
downloads.clear()
|
||||
|
||||
summary = getDirSize(dir)
|
||||
}
|
||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||
}.show()
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
with(findPreference<Preference>("clear_history")) {
|
||||
this!!
|
||||
|
||||
val histories = (activity!!.application as Pupil).histories
|
||||
|
||||
summary = getString(R.string.settings_clear_history_summary, histories.size)
|
||||
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.warning)
|
||||
setMessage(R.string.settings_clear_history_alert_message)
|
||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
histories.clear()
|
||||
summary = getString(R.string.settings_clear_history_summary, histories.size)
|
||||
}
|
||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||
}.show()
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
with(findPreference<Preference>("dl_location")) {
|
||||
this!!
|
||||
|
||||
summary = getDownloadDirectory(context).absolutePath
|
||||
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
DownloadLocationDialog(context).apply {
|
||||
onDownloadLocationChangedListener = { value ->
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
.putInt(key, value)
|
||||
.apply()
|
||||
summary = getDownloadDirectory(context).absolutePath
|
||||
}
|
||||
}.show()
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
with(findPreference<Preference>("default_query")) {
|
||||
this!!
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
summary = preferences.getString("default_query", "") ?: ""
|
||||
|
||||
val languages = resources.getStringArray(R.array.languages).map {
|
||||
it.split("|").let { split ->
|
||||
Pair(split[0], split[1])
|
||||
}
|
||||
}.toMap()
|
||||
val reverseLanguages = languages.entries.associate { (k, v) -> v to k }
|
||||
|
||||
val excludeBL = "-male:yaoi"
|
||||
val excludeGuro = listOf("-female:guro", "-male:guro")
|
||||
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
val dialogView = LayoutInflater.from(context).inflate(
|
||||
R.layout.dialog_default_query,
|
||||
LinearLayout(context),
|
||||
false
|
||||
)
|
||||
|
||||
val tags = Tags.parse(
|
||||
preferences.getString("default_query", "") ?: ""
|
||||
)
|
||||
|
||||
summary = tags.toString()
|
||||
|
||||
with(dialogView.default_query_dialog_language_selector) {
|
||||
adapter =
|
||||
ArrayAdapter(
|
||||
context,
|
||||
android.R.layout.simple_spinner_dropdown_item,
|
||||
arrayListOf(
|
||||
getString(R.string.default_query_dialog_language_selector_none)
|
||||
).apply {
|
||||
addAll(languages.values)
|
||||
}
|
||||
)
|
||||
if (tags.any { it.area == "language" && !it.isNegative }) {
|
||||
val tag = languages[tags.first { it.area == "language" }.tag]
|
||||
if (tag != null) {
|
||||
setSelection(
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(adapter as ArrayAdapter<String>).getPosition(tag)
|
||||
)
|
||||
tags.removeByArea("language", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with(dialogView.default_query_dialog_BL_checkbox) {
|
||||
isChecked = tags.contains(excludeBL)
|
||||
if (tags.contains(excludeBL))
|
||||
tags.remove(excludeBL)
|
||||
}
|
||||
|
||||
with(dialogView.default_query_dialog_guro_checkbox) {
|
||||
isChecked = excludeGuro.all { tags.contains(it) }
|
||||
if (excludeGuro.all { tags.contains(it) })
|
||||
excludeGuro.forEach {
|
||||
tags.remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
with(dialogView.default_query_dialog_edittext) {
|
||||
setText(tags.toString(), TextView.BufferType.EDITABLE)
|
||||
addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
s ?: return
|
||||
|
||||
if (s.any { it.isUpperCase() })
|
||||
s.replace(0, s.length, s.toString().toLowerCase(Locale.getDefault()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val dialog = AlertDialog.Builder(context!!).apply {
|
||||
setView(dialogView)
|
||||
}.create()
|
||||
|
||||
dialogView.default_query_dialog_ok.setOnClickListener {
|
||||
val newTags = Tags.parse(dialogView.default_query_dialog_edittext.text.toString())
|
||||
|
||||
with(dialogView.default_query_dialog_language_selector) {
|
||||
if (selectedItemPosition != 0)
|
||||
newTags.add("language:${reverseLanguages[selectedItem]}")
|
||||
}
|
||||
|
||||
if (dialogView.default_query_dialog_BL_checkbox.isChecked)
|
||||
newTags.add(excludeBL)
|
||||
|
||||
if (dialogView.default_query_dialog_guro_checkbox.isChecked)
|
||||
excludeGuro.forEach { tag ->
|
||||
newTags.add(tag)
|
||||
}
|
||||
|
||||
preferenceManager.sharedPreferences.edit().putString("default_query", newTags.toString()).apply()
|
||||
summary = preferences.getString("default_query", "") ?: ""
|
||||
tags.clear()
|
||||
tags.addAll(newTags)
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
with(findPreference<Preference>("app_lock")) {
|
||||
this!!
|
||||
|
||||
val lockManager = LockManager(context)
|
||||
|
||||
summary = if (lockManager.locks.isNullOrEmpty()) {
|
||||
getString(R.string.settings_lock_none)
|
||||
} else {
|
||||
lockManager.locks?.joinToString(", ") {
|
||||
when(it.type) {
|
||||
Lock.Type.PATTERN -> getString(R.string.settings_lock_pattern)
|
||||
Lock.Type.PIN -> getString(R.string.settings_lock_pin)
|
||||
Lock.Type.PASSWORD -> getString(R.string.settings_lock_password)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(context, LockActivity::class.java)
|
||||
activity?.startActivityForResult(intent, (activity as SettingsActivity).REQUEST_LOCK)
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
with(findPreference<Preference>("dark_mode")) {
|
||||
this!!
|
||||
|
||||
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
AppCompatDelegate.setDefaultNightMode(when (newValue as Boolean) {
|
||||
true -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
false -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
})
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
with(findPreference<Preference>("backup")) {
|
||||
this!!
|
||||
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
File(ContextCompat.getDataDir(context), "favorites.json").copyTo(
|
||||
File(getDownloadDirectory(context), "favorites.json"),
|
||||
true
|
||||
)
|
||||
|
||||
Snackbar.make(this@SettingsFragment.listView, R.string.settings_backup_snackbar, Snackbar.LENGTH_LONG)
|
||||
.show()
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
with(findPreference<Preference>("restore")) {
|
||||
this!!
|
||||
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/*"
|
||||
}
|
||||
|
||||
activity?.startActivityForResult(intent, (activity as SettingsActivity).REQUEST_RESTORE)
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LockFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
val lockManager = LockManager(context!!)
|
||||
|
||||
findPreference<Preference>("lock_pattern")?.summary =
|
||||
if (lockManager.contains(Lock.Type.PATTERN))
|
||||
getString(R.string.settings_lock_enabled)
|
||||
else
|
||||
""
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.lock_preferences, rootKey)
|
||||
|
||||
with(findPreference<Preference>("lock_pattern")) {
|
||||
this!!
|
||||
|
||||
if (LockManager(context!!).contains(Lock.Type.PATTERN))
|
||||
summary = getString(R.string.settings_lock_enabled)
|
||||
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
val lockManager = LockManager(context!!)
|
||||
|
||||
if (lockManager.contains(Lock.Type.PATTERN)) {
|
||||
AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.warning)
|
||||
setMessage(R.string.settings_lock_remove_message)
|
||||
|
||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
lockManager.remove(Lock.Type.PATTERN)
|
||||
onResume()
|
||||
}
|
||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||
}.show()
|
||||
} else {
|
||||
val intent = Intent(context, LockActivity::class.java).apply {
|
||||
putExtra("mode", "add_lock")
|
||||
putExtra("type", "pattern")
|
||||
}
|
||||
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||
when (item?.itemId) {
|
||||
android.R.id.home -> onBackPressed()
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.dialog
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlinx.android.synthetic.main.dialog_default_query.view.*
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.types.Tags
|
||||
|
||||
class DefaultQueryDialog(context : Context) : AlertDialog(context) {
|
||||
|
||||
private val languages = context.resources.getStringArray(R.array.languages).map {
|
||||
it.split("|").let { split ->
|
||||
Pair(split[0], split[1])
|
||||
}
|
||||
}.toMap()
|
||||
private val reverseLanguages = languages.entries.associate { (k, v) -> v to k }
|
||||
|
||||
private val excludeBL = "-male:yaoi"
|
||||
private val excludeGuro = listOf("-female:guro", "-male:guro")
|
||||
|
||||
private lateinit var dialogView : View
|
||||
|
||||
var onPositiveButtonClickListener : ((Tags) -> (Unit))? = null
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_default_query, null)
|
||||
|
||||
initView()
|
||||
|
||||
setContentView(dialogView)
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val tags = Tags.parse(
|
||||
preferences.getString("default_query", "") ?: ""
|
||||
)
|
||||
|
||||
with(dialogView.default_query_dialog_language_selector) {
|
||||
adapter =
|
||||
ArrayAdapter(
|
||||
context,
|
||||
android.R.layout.simple_spinner_dropdown_item,
|
||||
arrayListOf(
|
||||
context.getString(R.string.default_query_dialog_language_selector_none)
|
||||
).apply {
|
||||
addAll(languages.values)
|
||||
}
|
||||
)
|
||||
if (tags.any { it.area == "language" && !it.isNegative }) {
|
||||
val tag = languages[tags.first { it.area == "language" }.tag]
|
||||
if (tag != null) {
|
||||
setSelection(
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(adapter as ArrayAdapter<String>).getPosition(tag)
|
||||
)
|
||||
tags.removeByArea("language", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with(dialogView.default_query_dialog_BL_checkbox) {
|
||||
isChecked = tags.contains(excludeBL)
|
||||
if (tags.contains(excludeBL))
|
||||
tags.remove(excludeBL)
|
||||
}
|
||||
|
||||
with(dialogView.default_query_dialog_guro_checkbox) {
|
||||
isChecked = excludeGuro.all { tags.contains(it) }
|
||||
if (excludeGuro.all { tags.contains(it) })
|
||||
excludeGuro.forEach {
|
||||
tags.remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
with(dialogView.default_query_dialog_edittext) {
|
||||
setText(tags.toString(), android.widget.TextView.BufferType.EDITABLE)
|
||||
addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
s ?: return
|
||||
|
||||
if (s.any { it.isUpperCase() })
|
||||
s.replace(0, s.length, s.toString().toLowerCase(java.util.Locale.getDefault()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
dialogView.default_query_dialog_ok.setOnClickListener {
|
||||
val newTags = Tags.parse(dialogView.default_query_dialog_edittext.text.toString())
|
||||
|
||||
with(dialogView.default_query_dialog_language_selector) {
|
||||
if (selectedItemPosition != 0)
|
||||
newTags.add("language:${reverseLanguages[selectedItem]}")
|
||||
}
|
||||
|
||||
if (dialogView.default_query_dialog_BL_checkbox.isChecked)
|
||||
newTags.add(excludeBL)
|
||||
|
||||
if (dialogView.default_query_dialog_guro_checkbox.isChecked)
|
||||
excludeGuro.forEach { tag ->
|
||||
newTags.add(tag)
|
||||
}
|
||||
|
||||
onPositiveButtonClickListener?.invoke(newTags)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 androidx.appcompat.app.AlertDialog
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.ui.LockActivity
|
||||
import xyz.quaver.pupil.util.Lock
|
||||
import xyz.quaver.pupil.util.LockManager
|
||||
|
||||
class LockFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
val lockManager = LockManager(context!!)
|
||||
|
||||
findPreference<Preference>("lock_pattern")?.summary =
|
||||
if (lockManager.contains(Lock.Type.PATTERN))
|
||||
getString(R.string.settings_lock_enabled)
|
||||
else
|
||||
""
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.lock_preferences, rootKey)
|
||||
|
||||
with(findPreference<Preference>("lock_pattern")) {
|
||||
this!!
|
||||
|
||||
if (LockManager(context!!).contains(Lock.Type.PATTERN))
|
||||
summary = getString(R.string.settings_lock_enabled)
|
||||
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
val lockManager = LockManager(context!!)
|
||||
|
||||
if (lockManager.contains(Lock.Type.PATTERN)) {
|
||||
AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.warning)
|
||||
setMessage(R.string.settings_lock_remove_message)
|
||||
|
||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
lockManager.remove(Lock.Type.PATTERN)
|
||||
onResume()
|
||||
}
|
||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||
}.show()
|
||||
} else {
|
||||
val intent = Intent(context, LockActivity::class.java).apply {
|
||||
putExtra("mode", "add_lock")
|
||||
putExtra("type", "pattern")
|
||||
}
|
||||
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.quaver.pupil.ui
|
||||
package xyz.quaver.pupil.ui.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* 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 androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import xyz.quaver.pupil.Pupil
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.ui.LockActivity
|
||||
import xyz.quaver.pupil.ui.SettingsActivity
|
||||
import xyz.quaver.pupil.ui.dialog.DefaultQueryDialog
|
||||
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialog
|
||||
import xyz.quaver.pupil.util.*
|
||||
import java.io.File
|
||||
|
||||
|
||||
class SettingsFragment :
|
||||
PreferenceFragmentCompat(),
|
||||
Preference.OnPreferenceClickListener,
|
||||
Preference.OnPreferenceChangeListener {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
val lockManager = LockManager(context!!)
|
||||
|
||||
findPreference<Preference>("app_lock")?.summary = if (lockManager.locks.isNullOrEmpty()) {
|
||||
getString(R.string.settings_lock_none)
|
||||
} else {
|
||||
lockManager.locks?.joinToString(", ") {
|
||||
when(it.type) {
|
||||
Lock.Type.PATTERN -> getString(R.string.settings_lock_pattern)
|
||||
Lock.Type.PIN -> getString(R.string.settings_lock_pin)
|
||||
Lock.Type.PASSWORD -> getString(R.string.settings_lock_password)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDirSize(dir: File) : String {
|
||||
val size = dir.walk().map { it.length() }.sum()
|
||||
|
||||
return getString(R.string.settings_clear_summary, byteToString(size))
|
||||
}
|
||||
|
||||
override fun onPreferenceClick(preference: Preference?): Boolean {
|
||||
with (preference) {
|
||||
this ?: return false
|
||||
|
||||
when (key) {
|
||||
"app_version" -> {
|
||||
checkUpdate(activity as SettingsActivity, true)
|
||||
}
|
||||
"delete_cache" -> {
|
||||
val dir = File(context.cacheDir, "imageCache")
|
||||
|
||||
AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.warning)
|
||||
setMessage(R.string.settings_clear_cache_alert_message)
|
||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
if (dir.exists())
|
||||
dir.deleteRecursively()
|
||||
|
||||
summary = getDirSize(dir)
|
||||
}
|
||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||
}.show()
|
||||
}
|
||||
"delete_downloads" -> {
|
||||
val dir = getDownloadDirectory(context)
|
||||
|
||||
AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.warning)
|
||||
setMessage(R.string.settings_clear_downloads_alert_message)
|
||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
if (dir.exists())
|
||||
dir.deleteRecursively()
|
||||
|
||||
val downloads = (activity!!.application as Pupil).downloads
|
||||
|
||||
downloads.clear()
|
||||
|
||||
summary = getDirSize(dir)
|
||||
}
|
||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||
}.show()
|
||||
}
|
||||
"clear_history" -> {
|
||||
val histories = (context.applicationContext as Pupil).histories
|
||||
|
||||
AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.warning)
|
||||
setMessage(R.string.settings_clear_history_alert_message)
|
||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
histories.clear()
|
||||
summary = getString(R.string.settings_clear_history_summary, histories.size)
|
||||
}
|
||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||
}.show()
|
||||
}
|
||||
"dl_location" -> {
|
||||
DownloadLocationDialog(context).apply {
|
||||
onDownloadLocationChangedListener = { value ->
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
.putInt(key, value)
|
||||
.apply()
|
||||
summary = getDownloadDirectory(context).absolutePath
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
"default_query" -> {
|
||||
DefaultQueryDialog(context).apply {
|
||||
onPositiveButtonClickListener = { newTags ->
|
||||
sharedPreferences.edit().putString("default_query", newTags.toString()).apply()
|
||||
summary = newTags.toString()
|
||||
dismiss() //This sucks
|
||||
// TODO: make dialog dissmiss itself :P
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
"app_lock" -> {
|
||||
val intent = Intent(context, LockActivity::class.java)
|
||||
activity?.startActivityForResult(intent, (activity as SettingsActivity).REQUEST_LOCK)
|
||||
}
|
||||
"backup" -> {
|
||||
File(ContextCompat.getDataDir(context), "favorites.json").copyTo(
|
||||
File(getDownloadDirectory(context), "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, (activity as SettingsActivity).REQUEST_RESTORE)
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean {
|
||||
with (preference) {
|
||||
this ?: return false
|
||||
|
||||
when (key) {
|
||||
"dark_mode" -> {
|
||||
AppCompatDelegate.setDefaultNightMode(when (newValue as Boolean) {
|
||||
true -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
false -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
})
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||
|
||||
initPreferences()
|
||||
}
|
||||
|
||||
private fun initPreferences() {
|
||||
for (i in 0 until preferenceScreen.preferenceCount) {
|
||||
|
||||
preferenceScreen.getPreference(i).run {
|
||||
if (this is PreferenceCategory)
|
||||
(0 until preferenceCount).map { getPreference(it) }
|
||||
else
|
||||
listOf(this)
|
||||
}.forEach { preference ->
|
||||
with (preference) {
|
||||
|
||||
when (key) {
|
||||
"app_version" -> {
|
||||
val manager = context.packageManager
|
||||
val info = manager.getPackageInfo(context.packageName, 0)
|
||||
summary = context.getString(R.string.settings_app_version_description, info.versionName)
|
||||
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
"delete_cache" -> {
|
||||
val dir = File(context.cacheDir, "imageCache")
|
||||
summary = getDirSize(dir)
|
||||
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
"delete_downloads" -> {
|
||||
val dir = getDownloadDirectory(context)
|
||||
summary = getDirSize(dir)
|
||||
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
"clear_history" -> {
|
||||
val histories = (activity!!.application as Pupil).histories
|
||||
summary = getString(R.string.settings_clear_history_summary, histories.size)
|
||||
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
"dl_location" -> {
|
||||
summary = getDownloadDirectory(context).absolutePath
|
||||
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
"default_query" -> {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
summary = preferences.getString("default_query", "") ?: ""
|
||||
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
"app_lock" -> {
|
||||
val lockManager = LockManager(context)
|
||||
summary =
|
||||
if (lockManager.locks.isNullOrEmpty()) {
|
||||
getString(R.string.settings_lock_none)
|
||||
} else {
|
||||
lockManager.locks?.joinToString(", ") {
|
||||
when (it.type) {
|
||||
Lock.Type.PATTERN -> getString(R.string.settings_lock_pattern)
|
||||
Lock.Type.PIN -> getString(R.string.settings_lock_pin)
|
||||
Lock.Type.PASSWORD -> getString(R.string.settings_lock_password)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
"dark_mode" -> {
|
||||
onPreferenceChangeListener = this@SettingsFragment
|
||||
}
|
||||
"backup" -> {
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
"restore" -> {
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,6 +166,8 @@ class GalleryDownloader(
|
||||
fun start() {
|
||||
downloadJob = CoroutineScope(Dispatchers.Default).launch {
|
||||
val reader = reader!!.await() ?: return@launch
|
||||
val lowQuality = PreferenceManager.getDefaultSharedPreferences(this@GalleryDownloader)
|
||||
.getBoolean("low_quality", false)
|
||||
|
||||
notificationBuilder.setContentTitle(reader.title)
|
||||
|
||||
@@ -177,15 +179,15 @@ class GalleryDownloader(
|
||||
.setProgress(reader.galleryInfo.size, 0, false)
|
||||
.setContentText("0/${reader.galleryInfo.size}")
|
||||
|
||||
reader.galleryInfo.chunked(4).forEachIndexed { chunkIndex, chunked ->
|
||||
chunked.mapIndexed { i, galleryInfo ->
|
||||
reader.galleryInfo.chunked(4).forEachIndexed { chunkIndex, chunk ->
|
||||
chunk.mapIndexed { i, galleryInfo ->
|
||||
val index = chunkIndex*4+i
|
||||
|
||||
async(Dispatchers.IO) {
|
||||
val url = when(useHiyobi) {
|
||||
true -> createImgList(galleryID, reader)[index].path
|
||||
true -> createImgList(galleryID, reader, lowQuality)[index].path
|
||||
false -> when {
|
||||
(!galleryInfo.hash.isNullOrBlank()) and (galleryInfo.haswebp == 1) ->
|
||||
(!galleryInfo.hash.isNullOrBlank()) && (galleryInfo.haswebp == 1) && lowQuality ->
|
||||
urlFromUrlFromHash(galleryID, galleryInfo, "webp")
|
||||
else ->
|
||||
urlFromUrlFromHash(galleryID, galleryInfo)
|
||||
|
||||
@@ -18,36 +18,21 @@
|
||||
|
||||
package xyz.quaver.pupil.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat
|
||||
import android.annotation.SuppressLint
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.round
|
||||
|
||||
//Android Q+ uses scoped storage thus not requiring permission
|
||||
fun Context.hasPermission(permission: String) =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q || ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
@UseExperimental(ExperimentalStdlibApi::class)
|
||||
fun String.wordCapitalize() : String {
|
||||
val result = ArrayList<String>()
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
for (word in this.split(" "))
|
||||
result.add(word.capitalize(Locale.getDefault()))
|
||||
result.add(word.capitalize(Locale.US))
|
||||
|
||||
return result.joinToString(" ")
|
||||
}
|
||||
|
||||
|
||||
//https://discuss.kotlinlang.org/t/how-do-you-round-a-number-to-n-decimal-places/8843(fvasco)
|
||||
fun Double.round(decimals: Int): Double {
|
||||
var multiplier = 1.0
|
||||
repeat(decimals) { multiplier *= 10 }
|
||||
return round(this * multiplier) / multiplier
|
||||
}
|
||||
|
||||
fun byteToString(byte: Long, precision : Int = 1) : String {
|
||||
|
||||
val suffix = listOf(
|
||||
@@ -57,14 +42,13 @@ fun byteToString(byte: Long, precision : Int = 1) : String {
|
||||
"GB",
|
||||
"TB" //really?
|
||||
)
|
||||
var size = byte.toDouble()
|
||||
var suffixIndex = 0
|
||||
var size = byte.toDouble(); var suffixIndex = 0
|
||||
|
||||
while (size >= 1024) {
|
||||
size /= 1024
|
||||
suffixIndex++
|
||||
}
|
||||
|
||||
return "${size.round(precision)} ${suffix[suffixIndex]}"
|
||||
return "%.${precision}f ${suffix[suffixIndex]}".format(size)
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package xyz.quaver.pupil.util
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@@ -93,9 +94,9 @@ fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
|
||||
fun extractReleaseNote(update: JsonObject, locale: Locale) : String {
|
||||
val markdown = update["body"]!!.content
|
||||
|
||||
val target = when(locale) {
|
||||
Locale.KOREAN -> "한국어"
|
||||
Locale.JAPANESE -> "日本語"
|
||||
val target = when(locale.language) {
|
||||
"ko" -> "한국어"
|
||||
"ja" -> "日本語"
|
||||
else -> "English"
|
||||
}
|
||||
|
||||
@@ -151,12 +152,25 @@ fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
|
||||
priority = NotificationCompat.PRIORITY_LOW
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
CoroutineScope(Dispatchers.IO).launch io@{
|
||||
val target = File(getDownloadDirectory(context), "Pupil.apk")
|
||||
|
||||
URL(url).download(target) { progress, fileSize ->
|
||||
builder.setProgress(fileSize.toInt(), progress.toInt(), false)
|
||||
try {
|
||||
URL(url).download(target) { progress, fileSize ->
|
||||
builder.setProgress(fileSize.toInt(), progress.toInt(), false)
|
||||
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
builder.apply {
|
||||
setContentText(context.getString(R.string.update_failed))
|
||||
setMessage(context.getString(R.string.update_failed_message))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
}
|
||||
|
||||
notificationManager.cancel(UPDATE_NOTIFICATION_ID)
|
||||
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build())
|
||||
|
||||
return@io
|
||||
}
|
||||
|
||||
val install = Intent(Intent.ACTION_VIEW).apply {
|
||||
@@ -165,7 +179,11 @@ fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
|
||||
context,
|
||||
context.applicationContext.packageName + ".fileprovider",
|
||||
target
|
||||
), MimeTypeMap.getSingleton().getExtensionFromMimeType(".apk"))
|
||||
), MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk"))
|
||||
|
||||
if (resolveActivity(context.packageManager) == null)
|
||||
setDataAndType(Uri.fromFile(target),
|
||||
MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk"))
|
||||
}
|
||||
|
||||
builder.apply {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".ui.PatternLockFragment">
|
||||
tools:context=".ui.fragment.PatternLockFragment">
|
||||
|
||||
<com.andrognito.patternlockview.PatternLockView
|
||||
android:id="@+id/lock_pattern_view"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<com.github.chrisbanes.photoview.PhotoView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:contentDescription="@string/reader_imageview_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
<ImageView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/nav_header_height"
|
||||
android:background="@drawable/side_nav_bar"
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
<string name="main_menu_sort_newest">投稿日時順</string>
|
||||
<string name="main_menu_sort_popular">人気順</string>
|
||||
<string name="update_failed">アップデートに失敗しました</string>
|
||||
<string name="update_failed_message">マニュアルインストールが必要です。APKファイルは</string>
|
||||
<string name="update_failed_message">アップデート中エラーが発生しました</string>
|
||||
<string name="ignore_update">無視</string>
|
||||
<string name="lock_corrupted">ロックファイルが破損されています。Pupilを再再インストールしてください。</string>
|
||||
<string name="update_no_permission">権限がないため自動アップデートを行えません。ホームページで直接ダウンロードしてください。</string>
|
||||
@@ -118,4 +118,6 @@
|
||||
<string name="update_download_completed_description">ここをクリックしてアップデートを行えます</string>
|
||||
<string name="settings_beta">ベータチャンネルでアップデートを受信</string>
|
||||
<string name="settings_app_version_description">v%s</string>
|
||||
<string name="settings_low_quality">低解像度イメージ</string>
|
||||
<string name="settings_low_quality_summary">ロード速度とデータ使用料を改善するため低解像度イメージをロード</string>
|
||||
</resources>
|
||||
@@ -83,8 +83,8 @@
|
||||
<string name="main_menu_sort">정렬</string>
|
||||
<string name="main_menu_sort_popular">인기순</string>
|
||||
<string name="main_menu_sort_newest">시간순</string>
|
||||
<string name="update_failed">"업데이트 "</string>
|
||||
<string name="update_failed_message">수동 업데이트가 필요합니다. APK 파일은 다운로드 폴더에 있습니다.</string>
|
||||
<string name="update_failed">"업데이트 에러</string>
|
||||
<string name="update_failed_message">업데이트 중 에러가 발생했습니다</string>
|
||||
<string name="ignore_update">무시</string>
|
||||
<string name="lock_corrupted">잠금 파일이 손상되었습니다! 앱을 재설치 해 주시기 바랍니다.</string>
|
||||
<string name="update_no_permission">권한이 부여되어 있지 않아 자동 업데이트를 진행할 수 없습니다. 홈페이지에서 직접 다운로드 받으시기 바랍니다.</string>
|
||||
@@ -118,4 +118,6 @@
|
||||
<string name="update_download_completed_description">여기를 클릭해서 업데이트를 진행할 수 있습니다</string>
|
||||
<string name="settings_beta">베타 채널에서 업데이트</string>
|
||||
<string name="settings_app_version_description">v%s</string>
|
||||
<string name="settings_low_quality">저해상도 이미지</string>
|
||||
<string name="settings_low_quality_summary">로드 속도와 데이터 사용량을 줄이기 위해 저해상도 이미지를 로드</string>
|
||||
</resources>
|
||||
@@ -28,7 +28,7 @@
|
||||
<string name="https_block_alert">(Korean only)</string>
|
||||
|
||||
<string name="update_failed">Update failed</string>
|
||||
<string name="update_failed_message">Please install manually. APK file is in the Downloads folder.</string>
|
||||
<string name="update_failed_message">Please install manually by visiting github release page :{ (or try again!)</string>
|
||||
<string name="update_no_permission">Cannot auto update because permission is denied. Please download manually from the webpage.</string>
|
||||
<string name="ignore_update">Ignore</string>
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
<string name="settings_dl_location_removable">Removable Storage</string>
|
||||
<string name="settings_dl_location_internal">Internal Storage</string>
|
||||
<string name="settings_dl_location_available">%s available</string>
|
||||
<string name="settings_low_quality">Low quality images</string>
|
||||
<string name="settings_low_quality_summary">Load low quality images to improve load speed and data usage</string>
|
||||
|
||||
<!-- SETTINGS/APP LOCK -->
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<Preference
|
||||
app:title="@string/settings_app_version_title"
|
||||
app:key="app_version"/>
|
||||
app:key="app_version"
|
||||
app:title="@string/settings_app_version_title"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:title="@string/settings_beta"
|
||||
app:key="beta"/>
|
||||
app:key="beta"
|
||||
app:title="@string/settings_beta"/>
|
||||
|
||||
<PreferenceCategory
|
||||
app:title="@string/settings_search_title">
|
||||
@@ -32,20 +32,25 @@
|
||||
app:title="@string/settings_storage">
|
||||
|
||||
<Preference
|
||||
app:title="@string/settings_clear_cache"
|
||||
app:key="delete_cache"/>
|
||||
app:key="delete_cache"
|
||||
app:title="@string/settings_clear_cache"/>
|
||||
|
||||
<Preference
|
||||
app:title="@string/settings_clear_downloads"
|
||||
app:key="delete_downloads"/>
|
||||
app:key="delete_downloads"
|
||||
app:title="@string/settings_clear_downloads"/>
|
||||
|
||||
<Preference
|
||||
app:title="@string/settings_clear_history"
|
||||
app:key="clear_history"/>
|
||||
app:key="clear_history"
|
||||
app:title="@string/settings_clear_history"/>
|
||||
|
||||
<Preference
|
||||
app:title="@string/settings_dl_location"
|
||||
app:key="dl_location"/>
|
||||
app:key="dl_location"
|
||||
app:title="@string/settings_dl_location"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:key="dl_low_quality"
|
||||
app:title="@string/settings_low_quality"
|
||||
app:summary="@string/settings_low_quality_summary"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
@@ -53,8 +58,8 @@
|
||||
app:title="@string/settings_app_lock">
|
||||
|
||||
<Preference
|
||||
app:title="@string/settings_app_lock_type"
|
||||
app:key="app_lock"/>
|
||||
app:key="app_lock"
|
||||
app:title="@string/settings_app_lock_type"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
||||
@@ -17,20 +17,19 @@
|
||||
package xyz.quaver.hitomi
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlinx.serialization.list
|
||||
import java.net.URL
|
||||
|
||||
const val protocol = "https:"
|
||||
|
||||
fun getGalleryInfo(galleryID: Int): List<GalleryInfo> {
|
||||
return Json(JsonConfiguration.Stable).parse(
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
fun getGalleryInfo(galleryID: Int) =
|
||||
Json.nonstrict.parse(
|
||||
GalleryInfo.serializer().list,
|
||||
Regex("""\[.+]""").find(
|
||||
URL("$protocol//$domain/galleries/$galleryID.js").readText()
|
||||
)?.value ?: "[]"
|
||||
)
|
||||
}
|
||||
|
||||
//common.js
|
||||
var adapose = false
|
||||
@@ -54,17 +53,9 @@ fun subdomainFromURL(url: String, base: String? = null) : String {
|
||||
if (!base.isNullOrBlank())
|
||||
retval = base
|
||||
|
||||
val r = Regex("""/galleries/\d*(\d)/""")
|
||||
var m = r.find(url)
|
||||
var b = 10
|
||||
|
||||
if (m == null) {
|
||||
b = 16
|
||||
val r2 = Regex("""/[0-9a-f]/([0-9a-f]{2})/""")
|
||||
m = r2.find(url)
|
||||
if (m == null)
|
||||
return retval
|
||||
}
|
||||
val b = 16
|
||||
val r = Regex("""/[0-9a-f]/([0-9a-f]{2})/""")
|
||||
val m = r.find(url) ?: return retval
|
||||
|
||||
val g = m.groupValues[1].toIntOrNull(b) ?: return retval
|
||||
|
||||
@@ -84,15 +75,12 @@ fun fullPathFromHash(hash: String?) : String? {
|
||||
}
|
||||
}
|
||||
|
||||
fun urlFromHash(galleryID: Int, image: GalleryInfo, webp: String? = null) : String {
|
||||
val ext = webp ?: image.name.split('.').last()
|
||||
return when {
|
||||
image.hash.isNullOrBlank() ->
|
||||
"$protocol//a.hitomi.la/galleries/$galleryID/${image.name}"
|
||||
else ->
|
||||
"$protocol//a.hitomi.la/${webp?:"images"}/${fullPathFromHash(image.hash)}.$ext"
|
||||
}
|
||||
@Suppress("NAME_SHADOWING", "UNUSED_PARAMETER")
|
||||
fun urlFromHash(galleryID: Int, image: GalleryInfo, dir: String? = null, ext: String? = null) : String {
|
||||
val ext = ext ?: dir ?: image.name.split('.').last()
|
||||
val dir = dir ?: "images"
|
||||
return "$protocol//a.hitomi.la/$dir/${fullPathFromHash(image.hash)}.$ext"
|
||||
}
|
||||
|
||||
fun urlFromUrlFromHash(galleryID: Int, image: GalleryInfo, webp: String? = null) =
|
||||
urlFromURL(urlFromHash(galleryID, image, webp))
|
||||
fun urlFromUrlFromHash(galleryID: Int, image: GalleryInfo, dir: String? = null, ext: String? = null, base: String? = null) =
|
||||
urlFromURL(urlFromHash(galleryID, image, dir, ext), base)
|
||||
@@ -68,8 +68,8 @@ fun getGallery(galleryID: Int) : Gallery {
|
||||
href.slice(5 until href.indexOf('-'))
|
||||
}
|
||||
|
||||
val thumbnails = getGalleryInfo(galleryID).map {
|
||||
"$protocol//tn.hitomi.la/smalltn/$galleryID/${it.name}.jpg"
|
||||
val thumbnails = getGalleryInfo(galleryID).map { galleryInfo ->
|
||||
urlFromUrlFromHash(galleryID, galleryInfo, "smalltn", "jpg", "tn")
|
||||
}
|
||||
|
||||
return Gallery(related, langList, cover, title, artists, groups, type, language, series, characters, tags, thumbnails)
|
||||
|
||||
@@ -20,7 +20,6 @@ import kotlinx.serialization.Serializable
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
fun getReferer(galleryID: Int) = "https://hitomi.la/reader/$galleryID.html"
|
||||
fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
|
||||
|
||||
@Serializable
|
||||
data class GalleryInfo(
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package xyz.quaver.hiyobi
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlinx.serialization.list
|
||||
import org.jsoup.Jsoup
|
||||
import xyz.quaver.hitomi.GalleryInfo
|
||||
@@ -64,7 +63,8 @@ fun getReader(galleryID: Int) : Reader {
|
||||
|
||||
val title = Jsoup.connect(reader).get().title()
|
||||
|
||||
val galleryInfo = Json(JsonConfiguration.Stable).parse(
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
val galleryInfo = Json.parse(
|
||||
GalleryInfo.serializer().list,
|
||||
with(URL(url).openConnection() as HttpsURLConnection) {
|
||||
setRequestProperty("User-Agent", user_agent)
|
||||
@@ -79,5 +79,11 @@ fun getReader(galleryID: Int) : Reader {
|
||||
return Reader(Reader.Code.HIYOBI, title, galleryInfo)
|
||||
}
|
||||
|
||||
fun createImgList(galleryID: Int, reader: Reader) =
|
||||
reader.galleryInfo.map { Images("$protocol//$hiyobi/data/$galleryID/${it.name}", galleryID, it.name) }
|
||||
fun createImgList(galleryID: Int, reader: Reader, lowQuality: Boolean = false) =
|
||||
if (lowQuality)
|
||||
reader.galleryInfo.map {
|
||||
val name = it.name.replace(Regex("/.[^/.]+$"), "") + ".jpg"
|
||||
Images("$protocol//$hiyobi/data/$galleryID/$name.jpg", galleryID, it.name)
|
||||
}
|
||||
else
|
||||
reader.galleryInfo.map { Images("$protocol//$hiyobi/data/$galleryID/${it.name}", galleryID, it.name) }
|
||||
@@ -68,7 +68,7 @@ class UnitTest {
|
||||
|
||||
@Test
|
||||
fun test_getGallery() {
|
||||
val gallery = getGallery(1510566)
|
||||
val gallery = getGallery(1552751)
|
||||
|
||||
print(gallery)
|
||||
}
|
||||
@@ -82,7 +82,9 @@ class UnitTest {
|
||||
|
||||
@Test
|
||||
fun test_hiyobi() {
|
||||
xyz.quaver.hiyobi.getReader(1510567)
|
||||
val reader = xyz.quaver.hiyobi.getReader(10000062)
|
||||
|
||||
print(reader)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user