Compare commits

...

6 Commits
4.14 ... 4.17

Author SHA1 Message Date
Pupil
bbe29941df Merge pull request #75 from tom5079/dev
Version 4.17
2020-06-15 08:20:29 +09:00
tom5079
2720e445ea Changed low quality settting to true by default 2020-06-15 08:19:16 +09:00
tom5079
49ba579a59 Random Gallery Added
Changed tag search behavior
Loading time improved for hitomi.la
Bug fixed
2020-06-14 16:53:30 +09:00
Pupil
3198c6cbfd Merge pull request #74 from tom5079/dev
Version 4.15
2020-03-25 19:02:50 -07:00
pupil
b3feee6d9d Fast scroll Added
Not able to download after finished downloading to cache Fixed
Trying to move files from different threads causing exceptions Fixed
Import old galleries Added
2020-03-25 10:29:05 -07:00
Pupil
f0f53e6bce Fixed - Jump to page in Reader not working 2020-03-02 20:30:26 +09:00
40 changed files with 585 additions and 87 deletions

View File

@@ -19,8 +19,8 @@ android {
applicationId "xyz.quaver.pupil" applicationId "xyz.quaver.pupil"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 50 versionCode 52
versionName "4.14" versionName "4.17"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@@ -46,7 +46,7 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
buildToolsVersion = '29.0.2' buildToolsVersion = '29.0.3'
} }
dependencies { dependencies {
@@ -54,20 +54,19 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 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.3.3' 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-coroutines-android:1.3.3'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0"
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation "androidx.biometric:biometric:1.0.1" implementation "androidx.biometric:biometric:1.0.1"
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation "com.daimajia.swipelayout:library:1.2.0@aar" implementation "com.daimajia.swipelayout:library:1.2.0@aar"
implementation 'com.google.android.material:material:1.2.0-alpha05' implementation 'com.google.android.material:material:1.3.0-alpha01'
implementation 'com.google.firebase:firebase-core:17.2.2' implementation 'com.google.firebase:firebase-core:17.4.3'
implementation 'com.google.firebase:firebase-perf:19.0.5' implementation 'com.google.firebase:firebase-perf:19.0.7'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.github.arimorty:floatingsearchview:2.1.1' implementation 'com.github.arimorty:floatingsearchview:2.1.1'
implementation 'com.github.clans:fab:1.6.4' implementation 'com.github.clans:fab:1.6.4'

View File

@@ -1 +1 @@
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":50,"versionName":"4.14","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release","dirName":""},"path":"app-release.apk","properties":{}}] [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":52,"versionName":"4.17","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release","dirName":""},"path":"app-release.apk","properties":{}}]

View File

@@ -28,6 +28,7 @@ import androidx.test.rule.ActivityTestRule
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
import xyz.quaver.hiyobi.cookie import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.createImgList import xyz.quaver.hiyobi.createImgList
import xyz.quaver.hiyobi.getReader import xyz.quaver.hiyobi.getReader
@@ -62,6 +63,13 @@ class ExampleInstrumentedTest {
} }
} }
@Test
fun test_nozomi() {
val nozomi = getGalleryIDsFromNozomi(null, "index", "all")
Log.i("PUPILD", nozomi.size.toString())
}
@Test @Test
fun test_doSearch() { fun test_doSearch() {
val reader = getReader( 1426382) val reader = getReader( 1426382)

View File

@@ -30,10 +30,17 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import xyz.quaver.pupil.util.NOTIFICATION_ID_UPDATE import xyz.quaver.pupil.util.NOTIFICATION_ID_UPDATE
import xyz.quaver.pupil.util.cancelImport
import java.io.File import java.io.File
class BroadcastReciever : BroadcastReceiver() { class BroadcastReciever : BroadcastReceiver() {
companion object {
const val ACTION_CANCEL_IMPORT = "ACTION_CANCEL_IMPORT"
const val EXTRA_IMPORT_NOTIFICATION_ID = "EXTRA_IMPORT_NOTIFICATION_ID"
}
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
context ?: return context ?: return
@@ -87,6 +94,9 @@ class BroadcastReciever : BroadcastReceiver() {
notificationManager.notify(NOTIFICATION_ID_UPDATE, notification) notificationManager.notify(NOTIFICATION_ID_UPDATE, notification)
} }
ACTION_CANCEL_IMPORT -> {
cancelImport = true
}
} }
} }

View File

@@ -59,6 +59,13 @@ class Pupil : MultiDexApplication() {
preference.edit().remove("dl_location").apply() preference.edit().remove("dl_location").apply()
} }
if (!preference.getBoolean("low_quality_reset", false)) {
preference.edit()
.putBoolean("low_quality", true)
.putBoolean("low_quality_reset", true)
.apply()
}
histories = Histories(File(ContextCompat.getDataDir(this), "histories.json")) histories = Histories(File(ContextCompat.getDataDir(this), "histories.json"))
favorites = Histories(File(ContextCompat.getDataDir(this), "favorites.json")) favorites = Histories(File(ContextCompat.getDataDir(this), "favorites.json"))
@@ -89,6 +96,13 @@ class Pupil : MultiDexApplication() {
enableVibration(true) enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_SECRET lockscreenVisibility = Notification.VISIBILITY_SECRET
}) })
manager.createNotificationChannel(NotificationChannel("import", getString(R.string.channel_update), NotificationManager.IMPORTANCE_HIGH).apply {
description = getString(R.string.channel_update_description)
enableLights(false)
enableVibration(false)
lockscreenVisibility = Notification.VISIBILITY_SECRET
})
} }
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)

View File

@@ -52,7 +52,7 @@ import io.fabric.sdk.android.Fabric
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main_content.* import kotlinx.android.synthetic.main.activity_main_content.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.list import kotlinx.serialization.builtins.list
import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.doSearch import xyz.quaver.hitomi.doSearch
import xyz.quaver.hitomi.getGalleryIDsFromNozomi import xyz.quaver.hitomi.getGalleryIDsFromNozomi
@@ -367,6 +367,29 @@ class MainActivity : AppCompatActivity() {
} }
} }
with(main_fab_random) {
setImageResource(R.drawable.shuffle_variant)
setOnClickListener {
runBlocking {
withTimeoutOrNull(100) {
galleryIDs?.await()
}
}.let {
if (it?.isEmpty() == false) {
val galleryID = it.random()
val intent = Intent(this@MainActivity, ReaderActivity::class.java).apply {
putExtra("galleryID", galleryID)
}
startActivity(intent)
histories.add(galleryID)
}
}
}
}
with(main_fab_id) { with(main_fab_id) {
setImageResource(R.drawable.numeric) setImageResource(R.drawable.numeric)
setOnClickListener { setOnClickListener {
@@ -414,8 +437,6 @@ class MainActivity : AppCompatActivity() {
} }
onDownloadClickedHandler = { position -> onDownloadClickedHandler = { position ->
val galleryID = galleries[position].id val galleryID = galleries[position].id
if (!completeFlag.get(galleryID, false)) {
val worker = DownloadWorker.getInstance(context) val worker = DownloadWorker.getInstance(context)
if (Cache(context).isDownloading(galleryID)) //download in progress if (Cache(context).isDownloading(galleryID)) //download in progress
@@ -423,10 +444,8 @@ class MainActivity : AppCompatActivity() {
else { else {
Cache(context).setDownloading(galleryID, true) Cache(context).setDownloading(galleryID, true)
if (!worker.queue.contains(galleryID))
worker.queue.add(galleryID) worker.queue.add(galleryID)
} }
}
closeAllItems() closeAllItems()
} }
@@ -477,6 +496,7 @@ class MainActivity : AppCompatActivity() {
GalleryDialog( GalleryDialog(
this@MainActivity, this@MainActivity,
Glide.with(this@MainActivity),
galleryID galleryID
).apply { ).apply {
onChipClickedHandler.add { onChipClickedHandler.add {
@@ -731,6 +751,16 @@ class MainActivity : AppCompatActivity() {
favoritesFile.writeText(json.stringify(serializer, Tags(listOf()))) favoritesFile.writeText(json.stringify(serializer, Tags(listOf())))
} }
setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener {
override fun onMenuOpened() {
(this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
}
override fun onMenuClosed() {
//Do Nothing
}
})
setOnMenuItemClickListener { setOnMenuItemClickListener {
when(it.itemId) { when(it.itemId) {
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS) R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS)

View File

@@ -158,10 +158,10 @@ class ReaderActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem?): Boolean { override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when(item?.itemId) { when(item?.itemId) {
R.id.reader_menu_page_indicator -> { R.id.reader_menu_page_indicator -> {
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, findViewById(android.R.id.content), false) val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, reader_layout, false)
with(view.dialog_number_picker) { with(view.dialog_number_picker) {
minValue=1 minValue=1
maxValue=reader_recyclerview?.adapter?.itemCount ?: 0 maxValue=Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.files?.size ?: 0
value=currentPage value=currentPage
} }
val dialog = AlertDialog.Builder(this).apply { val dialog = AlertDialog.Builder(this).apply {

View File

@@ -30,8 +30,8 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.settings_activity.* import kotlinx.android.synthetic.main.settings_activity.*
import kotlinx.serialization.list import kotlinx.serialization.builtins.list
import kotlinx.serialization.serializer import kotlinx.serialization.builtins.serializer
import net.rdrei.android.dirchooser.DirectoryChooserActivity import net.rdrei.android.dirchooser.DirectoryChooserActivity
import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
@@ -156,6 +156,43 @@ class SettingsActivity : AppCompatActivity() {
.apply() .apply()
} }
} }
REQUEST_IMPORT_OLD_GALLERIES -> {
if (resultCode == Activity.RESULT_OK) {
data?.data?.also { uri ->
val takeFlags: Int =
intent.flags and (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
contentResolver.takePersistableUriPermission(uri, takeFlags)
val file = uri.toFile(this)
if (file?.canRead() != true)
Snackbar.make(
settings,
resources.getText(R.string.import_old_galleries_folder_not_readable),
Snackbar.LENGTH_LONG
).show()
else
importOldGalleries(this, file)
}
}
}
REQUEST_IMPORT_OLD_GALLERIES_OLD -> {
if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
val directory = data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!!
if (!File(directory).canRead())
Snackbar.make(
settings,
resources.getText(R.string.import_old_galleries_folder_not_readable),
Snackbar.LENGTH_LONG
).show()
else {
importOldGalleries(this, File(directory))
}
}
}
else -> super.onActivityResult(requestCode, resultCode, data) else -> super.onActivityResult(requestCode, resultCode, data)
} }
} }

View File

@@ -18,6 +18,7 @@
package xyz.quaver.pupil.ui.dialog package xyz.quaver.pupil.ui.dialog
import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@@ -29,7 +30,7 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.RequestManager
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.dialog_gallery.* import kotlinx.android.synthetic.main.dialog_gallery.*
@@ -53,7 +54,7 @@ import xyz.quaver.pupil.util.ItemClickSupport
import xyz.quaver.pupil.util.download.Cache import xyz.quaver.pupil.util.download.Cache
import xyz.quaver.pupil.util.wordCapitalize import xyz.quaver.pupil.util.wordCapitalize
class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(context) { class GalleryDialog(context: Context, private val glide: RequestManager, private val galleryID: Int) : Dialog(context) {
private val languages = context.resources.getStringArray(R.array.languages).map { private val languages = context.resources.getStringArray(R.array.languages).map {
it.split("|").let { split -> it.split("|").let { split ->
@@ -61,8 +62,6 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
} }
}.toMap() }.toMap()
private val glide = Glide.with(context)
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>() val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -90,7 +89,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
try { try {
val gallery = getGallery(galleryID) val gallery = getGallery(galleryID)
launch(Dispatchers.Main) { gallery_cover.post {
gallery_progressbar.visibility = View.GONE gallery_progressbar.visibility = View.GONE
gallery_title.text = gallery.title gallery_title.text = gallery.title
gallery_artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() } gallery_artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() }
@@ -112,7 +111,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
} }
} }
Glide.with(context) glide
.load(gallery.cover) .load(gallery.cover)
.apply { .apply {
if (BuildConfig.CENSOR) if (BuildConfig.CENSOR)
@@ -226,7 +225,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
val inflater = LayoutInflater.from(context) val inflater = LayoutInflater.from(context)
val galleries = ArrayList<GalleryBlock>() val galleries = ArrayList<GalleryBlock>()
val adapter = GalleryBlockAdapter(Glide.with(ownerActivity ?: return), galleries).apply { val adapter = GalleryBlockAdapter(glide, galleries).apply {
onChipClickedHandler.add { tag -> onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { handler -> this@GalleryDialog.onChipClickedHandler.forEach { handler ->
handler.invoke(tag) handler.invoke(tag)
@@ -264,6 +263,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
.setOnItemLongClickListener { _, position, _ -> .setOnItemLongClickListener { _, position, _ ->
GalleryDialog( GalleryDialog(
context, context,
glide,
galleries[position].id galleries[position].id
).apply { ).apply {
onChipClickedHandler.add { tag -> onChipClickedHandler.add { tag ->

View File

@@ -18,17 +18,23 @@
package xyz.quaver.pupil.ui.fragment package xyz.quaver.pupil.ui.fragment
import android.Manifest
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceCategory import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import net.rdrei.android.dirchooser.DirectoryChooserActivity
import net.rdrei.android.dirchooser.DirectoryChooserConfig
import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.ui.LockActivity import xyz.quaver.pupil.ui.LockActivity
@@ -168,6 +174,31 @@ class SettingsFragment :
activity?.startActivityForResult(intent, REQUEST_RESTORE) activity?.startActivityForResult(intent, REQUEST_RESTORE)
} }
"old_import_galleries" -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(activity!!, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_WRITE_PERMISSION_AND_SAF)
else {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
putExtra("android.content.extra.SHOW_ADVANCED", true)
}
activity?.startActivityForResult(intent, REQUEST_IMPORT_OLD_GALLERIES)
}
} else { // Can't use SAF on old Androids!
val config = DirectoryChooserConfig.builder()
.newDirectoryName("Pupil")
.allowNewDirectoryNameModification(true)
.build()
val intent = Intent(context, DirectoryChooserActivity::class.java).apply {
putExtra(DirectoryChooserActivity.EXTRA_CONFIG, config)
}
activity?.startActivityForResult(intent, REQUEST_IMPORT_OLD_GALLERIES_OLD)
}
}
else -> return false else -> return false
} }
} }
@@ -297,6 +328,9 @@ class SettingsFragment :
"restore" -> { "restore" -> {
onPreferenceClickListener = this@SettingsFragment onPreferenceClickListener = this@SettingsFragment
} }
"old_import_galleries" -> {
onPreferenceClickListener = this@SettingsFragment
}
} }
} }

View File

@@ -20,9 +20,16 @@ package xyz.quaver.pupil.util
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import okhttp3.Dispatcher
import okhttp3.OkHttpClient
import xyz.quaver.proxy
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
const val REQUEST_LOCK = 38238 const val REQUEST_LOCK = 38238
const val REQUEST_RESTORE = 16546 const val REQUEST_RESTORE = 16546
const val REQUEST_IMPORT_OLD_GALLERIES = 6458
const val REQUEST_IMPORT_OLD_GALLERIES_OLD = 5946
const val REQUEST_DOWNLOAD_FOLDER = 3874 const val REQUEST_DOWNLOAD_FOLDER = 3874
const val REQUEST_DOWNLOAD_FOLDER_OLD = 3425 const val REQUEST_DOWNLOAD_FOLDER_OLD = 3425
const val REQUEST_WRITE_PERMISSION_AND_SAF = 13900 const val REQUEST_WRITE_PERMISSION_AND_SAF = 13900

View File

@@ -26,7 +26,6 @@ import android.util.SparseArray
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.io.InputStream
import xyz.quaver.Code import xyz.quaver.Code
import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader import xyz.quaver.hitomi.Reader
@@ -35,14 +34,20 @@ import xyz.quaver.pupil.util.getCachedGallery
import xyz.quaver.pupil.util.getDownloadDirectory import xyz.quaver.pupil.util.getDownloadDirectory
import xyz.quaver.pupil.util.isParentOf import xyz.quaver.pupil.util.isParentOf
import xyz.quaver.pupil.util.json import xyz.quaver.pupil.util.json
import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.InputStream
import java.net.URL import java.net.URL
import java.util.concurrent.locks.Lock import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
class Cache(context: Context) : ContextWrapper(context) { class Cache(context: Context) : ContextWrapper(context) {
companion object {
private val moving = mutableListOf<Int>()
}
private val locks = SparseArray<Lock>() private val locks = SparseArray<Lock>()
private fun lock(galleryID: Int) { private fun lock(galleryID: Int) {
synchronized(locks) { synchronized(locks) {
@@ -240,12 +245,16 @@ class Cache(context: Context) : ContextWrapper(context) {
it.createNewFile() it.createNewFile()
} }
data.use { BufferedInputStream(data).use {
it.copyTo(FileOutputStream(cache)) it.copyTo(FileOutputStream(cache))
} }
} }
fun moveToDownload(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch { fun moveToDownload(galleryID: Int) {
if (moving.contains(galleryID))
return
CoroutineScope(Dispatchers.IO).launch {
val cache = getCachedGallery(galleryID).also { val cache = getCachedGallery(galleryID).also {
if (!it.exists()) if (!it.exists())
return@launch return@launch
@@ -267,6 +276,7 @@ class Cache(context: Context) : ContextWrapper(context) {
cache.deleteRecursively() cache.deleteRecursively()
Log.i("PUPILD", "DELETED ${cache.canonicalPath}") Log.i("PUPILD", "DELETED ${cache.canonicalPath}")
} }
}
fun isDownloading(galleryID: Int) = getCachedMetadata(galleryID)?.isDownloading == true fun isDownloading(galleryID: Int) = getCachedMetadata(galleryID)?.isDownloading == true

View File

@@ -46,11 +46,10 @@ import xyz.quaver.pupil.R
import xyz.quaver.pupil.ui.ReaderActivity import xyz.quaver.pupil.ui.ReaderActivity
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.concurrent.Executors
import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@UseExperimental(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class DownloadWorker private constructor(context: Context) : ContextWrapper(context) { class DownloadWorker private constructor(context: Context) : ContextWrapper(context) {
private val preferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) private val preferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
@@ -158,12 +157,16 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
.body(ProgressResponseBody(request.tag(), response.body(), progressListener)) .body(ProgressResponseBody(request.tag(), response.body(), progressListener))
.build() .build()
} }
val client = val client =
OkHttpClient.Builder() OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(0, TimeUnit.SECONDS) .connectTimeout(0, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.readTimeout(0, TimeUnit.SECONDS) .readTimeout(0, TimeUnit.SECONDS)
.dispatcher(Dispatcher(Executors.newFixedThreadPool(4))) .dispatcher(Dispatcher().apply {
maxRequests = 4
maxRequestsPerHost = 4
})
.proxy(proxy) .proxy(proxy)
.build() .build()
@@ -178,7 +181,11 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
worker[galleryID]?.cancel() worker[galleryID]?.cancel()
} }
client.dispatcher().cancelAll() client.dispatcher().queuedCalls().filter {
it.request().tag() is Pair<*, *>
}.forEach {
it.cancel()
}
progress.clear() progress.clear()
exception.clear() exception.clear()
@@ -217,7 +224,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
imageUrlFromImage( imageUrlFromImage(
galleryID, galleryID,
reader.galleryInfo.files[index], reader.galleryInfo.files[index],
lowQuality !lowQuality
) )
) )
addHeader("Referer", getReferer(galleryID)) addHeader("Referer", getReferer(galleryID))
@@ -235,6 +242,8 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
}.build() }.build()
client.newCall(request).enqueue(callback) client.newCall(request).enqueue(callback)
Log.i("PUPILD", "DOWNLOADING ($galleryID, $index) from ${request.url()}")
} }
private fun download(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch { private fun download(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
@@ -411,7 +420,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
val galleryID = queue.peek() ?: continue val galleryID = queue.peek() ?: continue
if (progress.indexOfKey(galleryID) >= 0) // Gallery already downloading! if (progress.indexOfKey(galleryID) >= 0) // Gallery already downloading!
continue cancel(galleryID)
if (notification[galleryID] == null) if (notification[galleryID] == null)
initNotification(galleryID) initNotification(galleryID)

View File

@@ -18,13 +18,14 @@
package xyz.quaver.pupil.util package xyz.quaver.pupil.util
import kotlinx.serialization.list import kotlinx.serialization.KSerializer
import kotlinx.serialization.serializer import kotlinx.serialization.builtins.list
import kotlinx.serialization.builtins.serializer
import java.io.File import java.io.File
class Histories(private val file: File) : ArrayList<Int>() { class Histories(private val file: File) : ArrayList<Int>() {
val serializer = Int.serializer().list val serializer: KSerializer<List<Int>> = Int.serializer().list
init { init {
if (!file.exists()) if (!file.exists())

View File

@@ -22,9 +22,7 @@ import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.builtins.list
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
import java.io.File import java.io.File
import java.security.MessageDigest import java.security.MessageDigest

View File

@@ -22,7 +22,7 @@ import android.annotation.SuppressLint
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@UseExperimental(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
fun String.wordCapitalize() : String { fun String.wordCapitalize() : String {
val result = ArrayList<String>() val result = ArrayList<String>()

View File

@@ -18,24 +18,40 @@
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 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.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.boolean import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.content import kotlinx.serialization.json.content
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.net.URL import java.net.URL
import java.util.* import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
fun getReleases(url: String) : JsonArray { fun getReleases(url: String) : JsonArray {
return try { return try {
@@ -173,3 +189,148 @@ fun checkUpdate(context: Context, force: Boolean = false) {
} }
} }
} }
var cancelImport = false
@SuppressLint("RestrictedApi")
fun importOldGalleries(context: Context, folder: File) = CoroutineScope(Dispatchers.IO).launch {
val client = OkHttpClient.Builder()
.connectTimeout(0, TimeUnit.SECONDS)
.readTimeout(0, TimeUnit.SECONDS)
.proxy(proxy)
.build()
val cancelIntent = Intent(context, BroadcastReciever::class.java).apply {
action = BroadcastReciever.ACTION_CANCEL_IMPORT
putExtra(BroadcastReciever.EXTRA_IMPORT_NOTIFICATION_ID, 0)
}
val pendingIntent = PendingIntent.getBroadcast(context, 0, cancelIntent, 0)
val notificationManager = NotificationManagerCompat.from(context)
val notificationBuilder = NotificationCompat.Builder(context, "import").apply {
setContentTitle(context.getText(R.string.import_old_galleries_notification))
setProgress(0, 0, true)
setSmallIcon(R.drawable.ic_notification)
addAction(0, context.getText(android.R.string.cancel), pendingIntent)
setOngoing(true)
}
notificationManager.notify(0, notificationBuilder.build())
if (!folder.isDirectory)
return@launch
val galleryRegex = Regex("""[0-9]+$""")
val imageRegex = Regex("""^[0-9]+\..+$""")
var size = 0
fun setProgress(progress: Int) {
notificationBuilder.apply {
setContentText(
context.getString(
R.string.import_old_galleries_notification_text,
progress,
size
)
)
setProgress(size, progress, false)
}
notificationManager.notify(0, notificationBuilder.build())
}
folder.listFiles { _, name ->
galleryRegex.matches(name)
}?.also {
size = it.size
setProgress(0)
}?.forEachIndexed { index, gallery ->
if (cancelImport)
return@forEachIndexed
setProgress(index)
val galleryID = gallery.name.toIntOrNull() ?: return@forEachIndexed
File(getDownloadDirectory(context), galleryID.toString()).mkdirs()
val reader = async {
kotlin.runCatching {
json.parse(Reader.serializer(), File(gallery, "reader.json").readText())
}.getOrElse {
getReader(galleryID)
}
}
val galleryBlock = async {
kotlin.runCatching {
json.parse(GalleryBlock.serializer(), File(gallery, "galleryBlock.json").readText())
}.getOrElse {
getGalleryBlock(galleryID)
}
}
@Suppress("NAME_SHADOWING")
val thumbnail = async thumbnail@{
val galleryBlock = galleryBlock.await()
Base64.encodeToString(try {
File(gallery, "thumbnail.jpg").readBytes()
} catch (e: Exception) {
val url = galleryBlock?.thumbnails?.firstOrNull()
if (url == null)
null
else {
val request = Request.Builder().url(url).build()
var done = false
var result: ByteArray? = null
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException?) {
done = true
}
override fun onResponse(call: Call?, response: Response?) {
result = response?.body()?.use {
it.bytes()
}
done = true
}
})
if (!done)
yield()
result
}
} ?: return@thumbnail null, Base64.DEFAULT)
}
Cache(context).setCachedMetadata(galleryID,
Metadata(
thumbnail.await(),
galleryBlock.await(),
reader.await()
)
)
File(gallery, "images").listFiles { _, name ->
imageRegex.matches(name)
}?.forEach {
if (cancelImport)
return@forEach
@Suppress("NAME_SHADOWING")
val index = it.nameWithoutExtension.toIntOrNull() ?: return@forEach
Cache(context).putImage(galleryID, index, it.extension, it.inputStream())
}
}
notificationBuilder.apply {
setContentText(context.getText(R.string.import_old_galleries_notification_done))
setProgress(0, 0, false)
setOngoing(false)
mActions.clear()
}
notificationManager.notify(0, notificationBuilder.build())
cancelImport = false
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

View File

@@ -0,0 +1,8 @@
<!-- drawable/shuffle_variant.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#fff" android:pathData="M17,3L22.25,7.5L17,12L22.25,16.5L17,21V18H14.26L11.44,15.18L13.56,13.06L15.5,15H17V12L17,9H15.5L6.5,18H2V15H5.26L14.26,6H17V3M2,6H6.5L9.32,8.82L7.2,10.94L5.26,9H2V6Z" />
</vector>

View File

@@ -0,0 +1,34 @@
<?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/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="44dp"
android:topRightRadius="44dp"
android:bottomLeftRadius="44dp" />
<padding
android:paddingLeft="22dp"
android:paddingRight="22dp" />
<solid android:color="@color/colorPrimary" />
</shape>

View File

@@ -0,0 +1,27 @@
<?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/>.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@drawable/thumb"/>
<item
android:drawable="@drawable/thumb"/>
</selector>

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/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<padding
android:top="10dp"
android:left="10dp"
android:right="10dp"
android:bottom="10dp"/>
</shape>

View File

@@ -0,0 +1,27 @@
<?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/>.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@drawable/track"/>
<item
android:drawable="@drawable/track"/>
</selector>

View File

@@ -71,7 +71,11 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingTop="64dp" android:paddingTop="64dp"
android:clipToPadding="false" android:clipToPadding="false"
android:scrollbars="vertical" app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/track_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/track_drawable"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
<com.github.clans.fab.FloatingActionMenu <com.github.clans.fab.FloatingActionMenu
@@ -96,6 +100,13 @@
app:fab_label="@string/main_jump_title" app:fab_label="@string/main_jump_title"
app:fab_size="mini"/> app:fab_size="mini"/>
<com.github.clans.fab.FloatingActionButton
android:id="@+id/main_fab_random"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fab_label="@string/main_fab_random"
app:fab_size="mini"/>
<com.github.clans.fab.FloatingActionButton <com.github.clans.fab.FloatingActionButton
android:id="@+id/main_fab_id" android:id="@+id/main_fab_id"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -30,6 +30,11 @@
android:id="@+id/reader_recyclerview" android:id="@+id/reader_recyclerview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/track_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/track_drawable"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
<LinearLayout <LinearLayout

View File

@@ -134,4 +134,11 @@
<string name="main_fab_cancel">すべてのダウンロードキャンセル</string> <string name="main_fab_cancel">すべてのダウンロードキャンセル</string>
<string name="channel_update">アップデート</string> <string name="channel_update">アップデート</string>
<string name="channel_update_description">アップデートの進行状態を表示</string> <string name="channel_update_description">アップデートの進行状態を表示</string>
<string name="channel_import">インポート</string>
<string name="channel_import_description">インポート状態を表示</string>
<string name="settings_import_old_galleries">旧ギャラリーインポート</string>
<string name="import_old_galleries_folder_not_readable">フォルダを読めません</string>
<string name="import_old_galleries_notification">旧ギャラリーインポート中…</string>
<string name="import_old_galleries_notification_done">インポート完了</string>
<string name="main_fab_random">ランダムギャラリーを開く</string>
</resources> </resources>

View File

@@ -134,4 +134,11 @@
<string name="main_fab_cancel">다운로드 모두 취소</string> <string name="main_fab_cancel">다운로드 모두 취소</string>
<string name="channel_update">업데이트</string> <string name="channel_update">업데이트</string>
<string name="channel_update_description">업데이트 진행상황 표시</string> <string name="channel_update_description">업데이트 진행상황 표시</string>
<string name="channel_import">가져오기</string>
<string name="channel_import_description">가져오기 상태 표시</string>
<string name="settings_import_old_galleries">이전 버전 갤러리 가져오기</string>
<string name="import_old_galleries_folder_not_readable">폴더를 읽을 수 없습니다</string>
<string name="import_old_galleries_notification">이전 버전 갤러리 가져오는 중…</string>
<string name="import_old_galleries_notification_done">가져오기 완료</string>
<string name="main_fab_random">무작위 갤러리 열기</string>
</resources> </resources>

View File

@@ -38,6 +38,9 @@
<string name="channel_update">Update</string> <string name="channel_update">Update</string>
<string name="channel_update_description">Shows update progress</string> <string name="channel_update_description">Shows update progress</string>
<string name="channel_import">Import</string>
<string name="channel_import_description">Shows progress of import</string>
<string name="unable_to_connect">Unable to connect to hitomi.la</string> <string name="unable_to_connect">Unable to connect to hitomi.la</string>
<string name="lock_corrupted">Lock file corrupted! Please re-install Pupil</string> <string name="lock_corrupted">Lock file corrupted! Please re-install Pupil</string>
@@ -66,6 +69,7 @@
<string name="main_jump_message">Current page: %1$d\nMaximum page: %2$d</string> <string name="main_jump_message">Current page: %1$d\nMaximum page: %2$d</string>
<string name="main_open_gallery_by_id">Open Gallery by ID</string> <string name="main_open_gallery_by_id">Open Gallery by ID</string>
<string name="reader_failed_to_find_gallery">Failed to open gallery</string> <string name="reader_failed_to_find_gallery">Failed to open gallery</string>
<string name="main_fab_random">Open a random gallery</string>
<string name="main_fab_cancel">Cancel all downloads</string> <string name="main_fab_cancel">Cancel all downloads</string>
<string name="main_move">Move to page %1$d</string> <string name="main_move">Move to page %1$d</string>
@@ -173,6 +177,7 @@
<string name="settings_restore_title">Restore favorites</string> <string name="settings_restore_title">Restore favorites</string>
<string name="settings_restore_failed">Restore failed</string> <string name="settings_restore_failed">Restore failed</string>
<string name="settings_restore_successful">%1$d entries restored</string> <string name="settings_restore_successful">%1$d entries restored</string>
<string name="settings_import_old_galleries">Import old galleries</string>
<!-- SETTINGS/APP LOCK ACTIVITY --> <!-- SETTINGS/APP LOCK ACTIVITY -->
@@ -205,4 +210,10 @@
<string name="proxy_dialog_error">Wrong value</string> <string name="proxy_dialog_error">Wrong value</string>
<string name="proxy_dialog_server">server</string> <string name="proxy_dialog_server">server</string>
<!-- IMPORT OLD GALLERIES -->
<string name="import_old_galleries_folder_not_readable">This folder is not readable</string>
<string name="import_old_galleries_notification">Importing old galleries…</string>
<string name="import_old_galleries_notification_text" translatable="false">%1$d/%2$d</string>
<string name="import_old_galleries_notification_done">Importing completed</string>
</resources> </resources>

View File

@@ -50,7 +50,8 @@
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="low_quality" app:key="low_quality"
app:title="@string/settings_low_quality" app:title="@string/settings_low_quality"
app:summary="@string/settings_low_quality_summary"/> app:summary="@string/settings_low_quality_summary"
app:defaultValue="true"/>
</PreferenceCategory> </PreferenceCategory>
@@ -99,6 +100,10 @@
app:key="restore" app:key="restore"
app:title="@string/settings_restore_title"/> app:title="@string/settings_restore_title"/>
<Preference
app:key="old_import_galleries"
app:title="@string/settings_import_old_galleries"/>
</PreferenceCategory> </PreferenceCategory>
</androidx.preference.PreferenceScreen> </androidx.preference.PreferenceScreen>

View File

@@ -34,8 +34,6 @@ class ExampleUnitTest {
@Test @Test
fun test() { fun test() {
val arr = SparseArray<Float>() val arr = SparseArray<Float>()
print(arr.indexOfKey(34))
} }
} }

View File

@@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.61' ext.kotlin_version = '1.3.72'
repositories { repositories {
google() google()
jcenter() jcenter()
maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.fabric.io/public' }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.1' classpath 'com.android.tools.build:gradle:3.6.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"

View File

@@ -6,7 +6,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0"
implementation 'org.jsoup:jsoup:1.12.1' implementation 'org.jsoup:jsoup:1.12.1'
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13'
} }

View File

@@ -16,10 +16,20 @@
package xyz.quaver package xyz.quaver
import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json
import java.net.Proxy import java.net.Proxy
var proxy = Proxy.NO_PROXY var proxy = Proxy.NO_PROXY
@OptIn(UnstableDefault::class)
var json = Json {
isLenient = true
ignoreUnknownKeys = true
serializeSpecialFloatingPointValues = true
useArrayPolymorphism = true
}
fun availableInHiyobi(galleryID: Int) : Boolean { fun availableInHiyobi(galleryID: Int) : Boolean {
return try { return try {
xyz.quaver.hiyobi.getReader(galleryID) xyz.quaver.hiyobi.getReader(galleryID)

View File

@@ -16,7 +16,7 @@
package xyz.quaver.hitomi package xyz.quaver.hitomi
import kotlinx.serialization.json.Json import xyz.quaver.json
import xyz.quaver.proxy import xyz.quaver.proxy
import java.net.URL import java.net.URL
@@ -24,7 +24,7 @@ const val protocol = "https:"
@Suppress("EXPERIMENTAL_API_USAGE") @Suppress("EXPERIMENTAL_API_USAGE")
fun getGalleryInfo(galleryID: Int) = fun getGalleryInfo(galleryID: Int) =
Json.nonstrict.parse( json.parse(
GalleryInfo.serializer(), GalleryInfo.serializer(),
URL("$protocol//$domain/galleries/$galleryID.js").openConnection(proxy).getInputStream().use { URL("$protocol//$domain/galleries/$galleryID.js").openConnection(proxy).getInputStream().use {
it.reader().readText() it.reader().readText()

View File

@@ -39,7 +39,7 @@ fun sha256(data: ByteArray) : ByteArray {
return MessageDigest.getInstance("SHA-256").digest(data) return MessageDigest.getInstance("SHA-256").digest(data)
} }
@UseExperimental(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
fun hashTerm(term: String) : UByteArray { fun hashTerm(term: String) : UByteArray {
return sha256(term.toByteArray()).toUByteArray().sliceArray(0 until 4) return sha256(term.toByteArray()).toUByteArray().sliceArray(0 until 4)
} }
@@ -258,9 +258,9 @@ fun getURLAtRange(url: String, range: LongRange) : ByteArray? {
} }
} }
@UseExperimental(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
data class Node(val keys: List<UByteArray>, val datas: List<Pair<Long, Int>>, val subNodeAddresses: List<Long>) data class Node(val keys: List<UByteArray>, val datas: List<Pair<Long, Int>>, val subNodeAddresses: List<Long>)
@UseExperimental(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
fun decodeNode(data: ByteArray) : Node { fun decodeNode(data: ByteArray) : Node {
val buffer = ByteBuffer val buffer = ByteBuffer
.wrap(data) .wrap(data)
@@ -302,7 +302,7 @@ fun decodeNode(data: ByteArray) : Node {
return Node(keys, datas, subNodeAddresses) return Node(keys, datas, subNodeAddresses)
} }
@UseExperimental(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
fun bSearch(field: String, key: UByteArray, node: Node) : Pair<Long, Int>? { fun bSearch(field: String, key: UByteArray, node: Node) : Pair<Long, Int>? {
fun compareArrayBuffers(dv1: UByteArray, dv2: UByteArray) : Int { fun compareArrayBuffers(dv1: UByteArray, dv2: UByteArray) : Int {
val top = Math.min(dv1.size, dv2.size) val top = Math.min(dv1.size, dv2.size)

View File

@@ -17,14 +17,14 @@
package xyz.quaver.hiyobi package xyz.quaver.hiyobi
import kotlinx.serialization.UnstableDefault import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json import kotlinx.serialization.builtins.list
import kotlinx.serialization.list
import org.jsoup.Jsoup import org.jsoup.Jsoup
import xyz.quaver.Code import xyz.quaver.Code
import xyz.quaver.hitomi.GalleryFiles import xyz.quaver.hitomi.GalleryFiles
import xyz.quaver.hitomi.GalleryInfo import xyz.quaver.hitomi.GalleryInfo
import xyz.quaver.hitomi.Reader import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.protocol import xyz.quaver.hitomi.protocol
import xyz.quaver.json
import xyz.quaver.proxy import xyz.quaver.proxy
import java.net.URL import java.net.URL
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
@@ -62,14 +62,14 @@ fun renewCookie() : String {
} }
} }
@UseExperimental(UnstableDefault::class) @OptIn(UnstableDefault::class)
fun getReader(galleryID: Int) : Reader { fun getReader(galleryID: Int) : Reader {
val reader = "https://$hiyobi/reader/$galleryID" val reader = "https://$hiyobi/reader/$galleryID"
val url = "https://cdn.hiyobi.me/data/json/${galleryID}_list.json" val url = "https://cdn.hiyobi.me/data/json/${galleryID}_list.json"
val title = Jsoup.connect(reader).proxy(proxy).get().title() val title = Jsoup.connect(reader).proxy(proxy).get().title()
val galleryFiles = Json.nonstrict.parse( val galleryFiles = json.parse(
GalleryFiles.serializer().list, GalleryFiles.serializer().list,
with(URL(url).openConnection(proxy) as HttpsURLConnection) { with(URL(url).openConnection(proxy) as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent) setRequestProperty("User-Agent", user_agent)