diff --git a/app/build.gradle b/app/build.gradle
index f0778fdf..1e0376f8 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,7 +4,7 @@ apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlinx-serialization'
-if (file("google-services.json").exists()) {
+if (file("google-services.json").exists() && file("src/debug/google-services.json").exists()) {
logger.lifecycle("Firebase Enabled")
apply plugin: 'com.google.gms.google-services'
apply plugin: 'io.fabric'
@@ -19,19 +19,27 @@ android {
applicationId "xyz.quaver.pupil"
minSdkVersion 16
targetSdkVersion 29
- versionCode 41
- versionName "4.5"
+ versionCode 42
+ versionName "4.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
}
buildTypes {
- release {
- minifyEnabled false
+ debug {
+ debuggable true
+ applicationIdSuffix ".debug"
+ versionNameSuffix "-DEBUG"
+
+ buildConfigField('Boolean', 'CENSOR', 'false')
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
- buildTypes.each {
- it.buildConfigField('boolean', 'CENSOR', 'false')
+ release {
+ minifyEnabled true
+ shrinkResources true
+
+ buildConfigField('Boolean', 'CENSOR', 'false')
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
kotlinOptions {
@@ -57,26 +65,27 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.gridlayout:gridlayout:1.0.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-alpha04'
+ implementation 'com.google.android.material:material:1.2.0-alpha05'
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.11.0'
- implementation ("com.github.bumptech.glide:recyclerview-integration:4.10.0") {
+ annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
+ kapt 'com.github.bumptech.glide:compiler:4.11.0'
+ implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
transitive = false
}
+
+ implementation 'net.rdrei.android.dirchooser:library:3.2@aar'
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.11.0'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test:rules:1.2.0'
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 481bb434..5a516874 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -18,4 +18,13 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
-#-renamesourcefileattribute SourceFile
\ No newline at end of file
+#-renamesourcefileattribute SourceFile
+
+-dontobfuscate
+
+-keep public class * implements com.bumptech.glide.module.GlideModule
+-keep public class * extends com.bumptech.glide.module.AppGlideModule
+-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
+ **[] $VALUES;
+ public *;
+}
\ No newline at end of file
diff --git a/app/release/output.json b/app/release/output.json
index dd112837..e58db7af 100644
--- a/app/release/output.json
+++ b/app/release/output.json
@@ -1 +1 @@
-[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":41,"versionName":"4.5","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
\ No newline at end of file
+[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":42,"versionName":"4.6","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
\ No newline at end of file
diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml
new file mode 100644
index 00000000..944c7613
--- /dev/null
+++ b/app/src/debug/res/values/strings.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ Pupil-Debug
+
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d445644f..f9bf5296 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,8 +6,8 @@
-
+
+
+ tools:replace="android:theme"
+ android:requestLegacyExternalStorage="true">
() {
+ val glide = Glide.with(context)
+
//region Glide.RecyclerView
- inner class SizeProvider : ListPreloader.PreloadSizeProvider {
-
- override fun getPreloadSize(item: File, adapterPosition: Int, itemPosition: Int): IntArray? {
- return Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.getOrNull(itemPosition)?.let {
- arrayOf(it.width, it.height).toIntArray()
- }
+ val sizeProvider = ListPreloader.PreloadSizeProvider { _, _, position ->
+ Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.files?.getOrNull(position)?.let {
+ arrayOf(it.width, it.height).toIntArray()
}
-
}
-
- inner class ModelProvider : ListPreloader.PreloadModelProvider {
-
+ val modelProvider = object: ListPreloader.PreloadModelProvider {
override fun getPreloadItems(position: Int): MutableList {
- return listOf(Cache(context).getImages(galleryID)?.get(position)).filterNotNullTo(mutableListOf())
+ return listOf(Cache(context).getImages(galleryID)?.getOrNull(position)).filterNotNullTo(mutableListOf())
}
override fun getPreloadRequestBuilder(item: File): RequestBuilder<*>? {
@@ -76,31 +72,17 @@ class ReaderAdapter(private val context: Context,
override(5, 8)
}
}
-
}
+ val preloader = RecyclerViewPreloader(glide, modelProvider, sizeProvider, 10)
//endregion
var reader: Reader? = null
- val glide = Glide.with(context)
val timer = Timer()
- val sizeProvider = SizeProvider()
- val modelProvider = ModelProvider()
- val preloader = RecyclerViewPreloader(glide, modelProvider, sizeProvider, 10)
-
var isFullScreen = false
var onItemClickListener : ((Int) -> (Unit))? = null
- init {
- CoroutineScope(Dispatchers.IO).launch {
- reader = Cache(context).getReader(galleryID)
- launch(Dispatchers.Main) {
- notifyDataSetChanged()
- }
- }
- }
-
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@@ -114,10 +96,13 @@ class ReaderAdapter(private val context: Context,
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.view as ConstraintLayout
- if (isFullScreen)
+ if (isFullScreen) {
holder.view.layoutParams.height = RecyclerView.LayoutParams.MATCH_PARENT
- else
+ holder.view.container.layoutParams.height = ConstraintLayout.LayoutParams.MATCH_PARENT
+ } else {
holder.view.layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT
+ holder.view.container.layoutParams.height = 0
+ }
holder.view.image.setOnPhotoTapListener { _, _, _ ->
onItemClickListener?.invoke(position)
@@ -127,57 +112,55 @@ class ReaderAdapter(private val context: Context,
onItemClickListener?.invoke(position)
}
- (holder.view.container.layoutParams as ConstraintLayout.LayoutParams)
- .dimensionRatio = "${reader!!.galleryInfo[position].width}:${reader!!.galleryInfo[position].height}"
+ if (!isFullScreen)
+ (holder.view.container.layoutParams as ConstraintLayout.LayoutParams)
+ .dimensionRatio = "${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
holder.view.reader_index.text = (position+1).toString()
- CoroutineScope(Dispatchers.IO).launch {
- val images = Cache(context).getImages(galleryID)
+ val images = Cache(context).getImage(galleryID, position)
- launch(Dispatchers.Main) {
- if (images?.get(position) != null) {
- glide
- .load(images[position])
- .diskCacheStrategy(DiskCacheStrategy.NONE)
- .skipMemoryCache(true)
- .error(R.drawable.image_broken_variant)
- .apply {
- if (BuildConfig.CENSOR)
- override(5, 8)
- }
- .into(holder.view.image)
- } else {
- val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position)
+ if (images != null) {
+ glide
+ .load(images)
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .skipMemoryCache(true)
+ .error(R.drawable.image_broken_variant)
+ .apply {
+ if (BuildConfig.CENSOR)
+ override(5, 8)
+ }
+ .into(holder.view.image)
+ } else {
+ val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position)
- if (progress?.isNaN() == true) {
+ if (progress?.isNaN() == true) {
+ if (Fabric.isInitialized())
+ Crashlytics.logException(DownloadWorker.getInstance(context).exception[galleryID]?.get(position))
- if (Fabric.isInitialized())
- Crashlytics.logException(DownloadWorker.getInstance(context).exception[galleryID]?.get(position))
+ glide
+ .load(R.drawable.image_broken_variant)
+ .into(holder.view.image)
- glide
- .load(R.drawable.image_broken_variant)
- .into(holder.view.image)
- } else {
- holder.view.reader_item_progressbar.progress =
- if (progress?.isInfinite() == true)
- 100
- else
- progress?.roundToInt() ?: 0
+ return
+ } else {
+ holder.view.reader_item_progressbar.progress =
+ if (progress?.isInfinite() == true)
+ 100
+ else
+ progress?.roundToInt() ?: 0
- holder.view.image.setImageDrawable(null)
- }
+ holder.view.image.setImageDrawable(null)
+ }
- timer.schedule(1000) {
- CoroutineScope(Dispatchers.Main).launch {
- notifyItemChanged(position)
- }
- }
+ timer.schedule(1000) {
+ CoroutineScope(Dispatchers.Main).launch {
+ notifyItemChanged(position)
}
}
}
}
- override fun getItemCount() = reader?.galleryInfo?.size ?: 0
+ override fun getItemCount() = reader?.galleryInfo?.files?.size ?: 0
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
index d5f33096..97f50840 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
@@ -45,15 +45,13 @@ import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.arlib.floatingsearchview.FloatingSearchView
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
import com.arlib.floatingsearchview.util.view.SearchInputView
+import com.crashlytics.android.Crashlytics
import com.google.android.material.appbar.AppBarLayout
+import io.fabric.sdk.android.Fabric
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main_content.*
import kotlinx.coroutines.*
-import kotlinx.serialization.ImplicitReflectionSerializer
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
-import kotlinx.serialization.stringify
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.doSearch
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
@@ -179,7 +177,7 @@ class MainActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
- (main_recyclerview.adapter as GalleryBlockAdapter).timer.cancel()
+ (main_recyclerview?.adapter as? GalleryBlockAdapter)?.timer?.cancel()
}
override fun onResume() {
@@ -693,7 +691,6 @@ class MainActivity : AppCompatActivity() {
}
private var suggestionJob : Job? = null
- @UseExperimental(ImplicitReflectionSerializer::class)
private fun setupSearchBar() {
val searchInputView = findViewById(R.id.search_bar_text)
//Change upper case letters to lower case
@@ -717,12 +714,11 @@ class MainActivity : AppCompatActivity() {
with(main_searchview as FloatingSearchView) {
val favoritesFile = File(ContextCompat.getDataDir(context), "favorites_tags.json")
- val json = Json(JsonConfiguration.Stable)
val serializer = Tag.serializer().list
if (!favoritesFile.exists()) {
favoritesFile.createNewFile()
- favoritesFile.writeText(json.stringify(Tags(listOf())))
+ favoritesFile.writeText(json.stringify(serializer, Tags(listOf())))
}
setOnMenuItemClickListener {
@@ -840,7 +836,7 @@ class MainActivity : AppCompatActivity() {
favorites.add(tag)
}
- favoritesFile.writeText(json.stringify(favorites))
+ favoritesFile.writeText(json.stringify(serializer, favorites))
}
}
@@ -939,26 +935,26 @@ class MainActivity : AppCompatActivity() {
when(sortMode) {
SortMode.POPULAR -> getGalleryIDsFromNozomi(null, "popular", "all")
else -> getGalleryIDsFromNozomi(null, "index", "all")
- }.apply {
- totalItems = size
+ }.also {
+ totalItems = it.size
}
}
- else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).apply {
- totalItems = size
+ else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).also {
+ totalItems = it.size
}
}
}
Mode.HISTORY -> {
when {
query.isEmpty() -> {
- histories.toList().apply {
- totalItems = size
+ histories.toList().also {
+ totalItems = it.size
}
}
else -> {
val result = doSearch(query).sorted()
- histories.filter { result.binarySearch(it) >= 0 }.apply {
- totalItems = size
+ histories.filter { result.binarySearch(it) >= 0 }.also {
+ totalItems = it.size
}
}
}
@@ -971,26 +967,26 @@ class MainActivity : AppCompatActivity() {
} ?: emptyList()
when {
- query.isEmpty() -> downloads.apply {
- totalItems = size
+ query.isEmpty() -> downloads.also {
+ totalItems = it.size
}
else -> {
val result = doSearch(query).sorted()
- downloads.filter { result.binarySearch(it) >= 0 }.apply {
- totalItems = size
+ downloads.filter { result.binarySearch(it) >= 0 }.also {
+ totalItems = it.size
}
}
}
}
Mode.FAVORITE -> {
when {
- query.isEmpty() -> favorites.toList().apply {
- totalItems = size
+ query.isEmpty() -> favorites.toList().also {
+ totalItems = it.size
}
else -> {
val result = doSearch(query).sorted()
- favorites.filter { result.binarySearch(it) >= 0 }.apply {
- totalItems = size
+ favorites.filter { result.binarySearch(it) >= 0 }.also {
+ totalItems = it.size
}
}
}
@@ -1004,9 +1000,16 @@ class MainActivity : AppCompatActivity() {
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
loadingJob = CoroutineScope(Dispatchers.IO).launch {
- val galleryIDs = galleryIDs?.await()
+ val galleryIDs = try {
+ galleryIDs!!.await().also {
+ if (it.isEmpty())
+ throw Exception("No result")
+ }
+ } catch (e: Exception) {
+
+ if (Fabric.isInitialized() && e.message != "No result")
+ Crashlytics.logException(e)
- if (galleryIDs.isNullOrEmpty()) { //No result
withContext(Dispatchers.Main) {
main_noresult.visibility = View.VISIBLE
main_progressbar.hide()
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
index 7af22aec..25fa0e4f 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
@@ -38,7 +38,6 @@ import io.fabric.sdk.android.Fabric
import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.android.synthetic.main.activity_reader.view.*
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
-import kotlinx.serialization.ImplicitReflectionSerializer
import xyz.quaver.Code
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
@@ -141,7 +140,6 @@ class ReaderActivity : AppCompatActivity() {
super.onResume()
}
- @UseExperimental(ImplicitReflectionSerializer::class)
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.reader, menu)
@@ -256,11 +254,17 @@ class ReaderActivity : AppCompatActivity() {
reader_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
if (title == getString(R.string.reader_loading)) {
- val reader = (reader_recyclerview.adapter as ReaderAdapter).reader
+ val reader = Cache(this@ReaderActivity).getReaderOrNull(galleryID)
if (reader != null) {
- title = reader.title
- menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.size}"
+
+ with (reader_recyclerview.adapter as ReaderAdapter) {
+ this.reader = reader
+ notifyDataSetChanged()
+ }
+
+ title = reader.galleryInfo.title
+ menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.files.size}"
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity,
when (reader.code) {
@@ -296,7 +300,7 @@ class ReaderActivity : AppCompatActivity() {
}
}
- //addOnScrollListener((adapter as ReaderAdapter).preloader)
+ addOnScrollListener((adapter as ReaderAdapter).preloader)
addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt
index aa09d8a4..ebcdbb77 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt
@@ -30,9 +30,8 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.settings_activity.*
-import kotlinx.serialization.ImplicitReflectionSerializer
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.parseList
+import kotlinx.serialization.list
+import kotlinx.serialization.serializer
import net.rdrei.android.dirchooser.DirectoryChooserActivity
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
@@ -79,7 +78,6 @@ class SettingsActivity : AppCompatActivity() {
return true
}
- @UseExperimental(ImplicitReflectionSerializer::class)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when(requestCode) {
REQUEST_LOCK -> {
@@ -96,13 +94,13 @@ class SettingsActivity : AppCompatActivity() {
val uri = data?.data ?: return
try {
- val json = contentResolver.openInputStream(uri).use { inputStream ->
+ val str = contentResolver.openInputStream(uri).use { inputStream ->
inputStream!!
inputStream.readBytes().toString(Charset.defaultCharset())
}
- (application as Pupil).favorites.addAll(Json.parseList(json).also {
+ (application as Pupil).favorites.addAll(json.parse(Int.serializer().list, str).also {
Snackbar.make(
window.decorView,
getString(R.string.settings_restore_successful, it.size),
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialog.kt
index 125fa48b..3d39c6e4 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialog.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialog.kt
@@ -46,16 +46,12 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
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?) {
- initDialog()
-
setTitle(R.string.default_query_dialog_title)
- setView(dialogView)
+ setView(build())
setButton(Dialog.BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ ->
val newTags = Tags.parse(default_query_dialog_edittext.text.toString())
@@ -79,15 +75,15 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
}
@SuppressLint("InflateParams")
- private fun initDialog() {
+ private fun build() : View {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val tags = Tags.parse(
preferences.getString("default_query", "") ?: ""
)
- dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_default_query, null)
+ val view = LayoutInflater.from(context).inflate(R.layout.dialog_default_query, null)
- with(dialogView.default_query_dialog_language_selector) {
+ with(view.default_query_dialog_language_selector) {
adapter =
ArrayAdapter(
context,
@@ -110,13 +106,13 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
}
}
- with(dialogView.default_query_dialog_BL_checkbox) {
+ with(view.default_query_dialog_BL_checkbox) {
isChecked = tags.contains(excludeBL)
if (tags.contains(excludeBL))
tags.remove(excludeBL)
}
- with(dialogView.default_query_dialog_guro_checkbox) {
+ with(view.default_query_dialog_guro_checkbox) {
isChecked = excludeGuro.all { tags.contains(it) }
if (excludeGuro.all { tags.contains(it) })
excludeGuro.forEach {
@@ -124,7 +120,7 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
}
}
- with(dialogView.default_query_dialog_edittext) {
+ with(view.default_query_dialog_edittext) {
setText(tags.toString(), android.widget.TextView.BufferType.EDITABLE)
addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(
@@ -149,6 +145,8 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
}
})
}
+
+ return view
}
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt
index 3f24f0b0..3de0c7d7 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt
@@ -26,6 +26,7 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
+import android.view.View
import android.widget.LinearLayout
import android.widget.RadioButton
import androidx.appcompat.app.AlertDialog
@@ -36,10 +37,7 @@ import kotlinx.android.synthetic.main.item_dl_location.view.*
import net.rdrei.android.dirchooser.DirectoryChooserActivity
import net.rdrei.android.dirchooser.DirectoryChooserConfig
import xyz.quaver.pupil.R
-import xyz.quaver.pupil.util.REQUEST_DOWNLOAD_FOLDER
-import xyz.quaver.pupil.util.REQUEST_DOWNLOAD_FOLDER_OLD
-import xyz.quaver.pupil.util.REQUEST_WRITE_PERMISSION_AND_SAF
-import xyz.quaver.pupil.util.byteToString
+import xyz.quaver.pupil.util.*
import java.io.File
@SuppressLint("InflateParams")
@@ -49,6 +47,16 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) {
private val buttons = mutableListOf>()
override fun onCreate(savedInstanceState: Bundle?) {
+ setTitle(R.string.settings_dl_location)
+
+ setView(build())
+
+ setButton(Dialog.BUTTON_POSITIVE, context.getText(android.R.string.ok)) { _, _ -> }
+
+ super.onCreate(savedInstanceState)
+ }
+
+ private fun build() : View {
val view = layoutInflater.inflate(R.layout.dialog_dl_location, null) as LinearLayout
val externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null)
@@ -115,25 +123,16 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) {
buttons.add(button to null)
})
- val pref = preference.getString("dl_location", null)
- val index = externalFilesDirs.indexOfFirst {
- it.canonicalPath == pref
+ externalFilesDirs.indexOfFirst {
+ it.canonicalPath == getDownloadDirectory(context).canonicalPath
+ }.let { index ->
+ if (index < 0)
+ buttons.first().first.isChecked = true
+ else
+ buttons[index].first.isChecked = true
}
- if (index < 0)
- buttons.last().first.isChecked = true
- else
- buttons[index].first.isChecked = true
-
- setTitle(R.string.settings_dl_location)
-
- setView(view)
-
- setButton(Dialog.BUTTON_POSITIVE, context.getText(android.R.string.ok)) { _, _ ->
- dismiss()
- }
-
- super.onCreate(savedInstanceState)
+ return view
}
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/MirrorDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/MirrorDialog.kt
index 1a2f0351..d20b61df 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/MirrorDialog.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/MirrorDialog.kt
@@ -22,6 +22,7 @@ import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.os.Bundle
+import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
@@ -56,21 +57,17 @@ class MirrorDialog(context: Context) : AlertDialog(context) {
}
}
- private lateinit var recyclerView: RecyclerView
-
@SuppressLint("InflateParams")
override fun onCreate(savedInstanceState: Bundle?) {
- initDialog()
-
setTitle(R.string.settings_mirror_title)
- setView(recyclerView)
+ setView(build())
setButton(Dialog.BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> }
super.onCreate(savedInstanceState)
}
- private fun initDialog() {
- recyclerView = RecyclerView(context).apply recyclerview@{
+ private fun build() : View {
+ return RecyclerView(context).apply recyclerview@{
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
layoutManager = LinearLayoutManager(context)
adapter = MirrorAdapter(context).apply adapter@{
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/ProxyDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/ProxyDialog.kt
new file mode 100644
index 00000000..68eb1e0a
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/ProxyDialog.kt
@@ -0,0 +1,133 @@
+/*
+ * 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 .
+ */
+
+package xyz.quaver.pupil.ui.dialog
+
+import android.annotation.SuppressLint
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import androidx.preference.PreferenceManager
+import kotlinx.android.synthetic.main.dialog_proxy.view.*
+import xyz.quaver.proxy
+import xyz.quaver.pupil.R
+import xyz.quaver.pupil.util.ProxyInfo
+import xyz.quaver.pupil.util.getProxyInfo
+import xyz.quaver.pupil.util.json
+import java.net.Proxy
+
+class ProxyDialog(context: Context) : Dialog(context) {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ val view = build()
+
+ setTitle(R.string.settings_proxy_title)
+ setContentView(view)
+
+ window?.attributes?.width = ViewGroup.LayoutParams.MATCH_PARENT
+
+ super.onCreate(savedInstanceState)
+ }
+
+ @SuppressLint("InflateParams")
+ private fun build() : View {
+ val proxyInfo = getProxyInfo(context)
+
+ val view = LayoutInflater.from(context).inflate(R.layout.dialog_proxy, null)
+
+ val enabler = { enable: Boolean ->
+ view?.proxy_addr?.isEnabled = enable
+ view?.proxy_port?.isEnabled = enable
+ view?.proxy_username?.isEnabled = enable
+ view?.proxy_password?.isEnabled = enable
+
+ if (!enable) {
+ view?.proxy_addr?.text = null
+ view?.proxy_port?.text = null
+ view?.proxy_username?.text = null
+ view?.proxy_password?.text = null
+ }
+ }
+
+ with(view.proxy_type_selector) {
+ adapter = ArrayAdapter(
+ context,
+ android.R.layout.simple_spinner_dropdown_item,
+ context.resources.getStringArray(R.array.proxy_type)
+ )
+
+ setSelection(proxyInfo.type.ordinal)
+
+ onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ enabler.invoke(position != 0)
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {}
+ }
+ }
+
+ view.proxy_addr.setText(proxyInfo.host)
+ view.proxy_port.setText(proxyInfo.port?.toString())
+ view.proxy_username.setText(proxyInfo.username)
+ view.proxy_password.setText(proxyInfo.password)
+
+ enabler.invoke(proxyInfo.type != Proxy.Type.DIRECT)
+
+ view.proxy_cancel.setOnClickListener {
+ dismiss()
+ }
+
+ view.proxy_ok.setOnClickListener {
+ val type = Proxy.Type.values()[view.proxy_type_selector.selectedItemPosition]
+ val addr = view.proxy_addr.text?.toString()
+ val port = view.proxy_port.text?.toString()?.toIntOrNull()
+ val username = view.proxy_username.text?.toString()
+ val password = view.proxy_password.text?.toString()
+
+ if (type != Proxy.Type.DIRECT) {
+ if (addr == null || addr.isEmpty())
+ view.proxy_addr.error = context.getText(R.string.proxy_dialog_error)
+ if (port == null)
+ view.proxy_port.error = context.getText(R.string.proxy_dialog_error)
+
+ if (addr == null || addr.isEmpty() || port == null)
+ return@setOnClickListener
+ }
+
+ ProxyInfo(type, addr, port, username, password).let {
+
+ PreferenceManager.getDefaultSharedPreferences(context).edit().putString("proxy",
+ json.stringify(ProxyInfo.serializer(), it)
+ ).apply()
+
+ proxy = it.proxy()
+ }
+
+ dismiss()
+ }
+
+ return view
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt
index f3d88dad..3a3bcdbd 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt
@@ -36,6 +36,7 @@ import xyz.quaver.pupil.ui.SettingsActivity
import xyz.quaver.pupil.ui.dialog.DefaultQueryDialog
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialog
import xyz.quaver.pupil.ui.dialog.MirrorDialog
+import xyz.quaver.pupil.ui.dialog.ProxyDialog
import xyz.quaver.pupil.util.*
import java.io.File
@@ -146,6 +147,10 @@ class SettingsFragment :
MirrorDialog(context)
.show()
}
+ "proxy" -> {
+ ProxyDialog(context)
+ .show()
+ }
"backup" -> {
File(ContextCompat.getDataDir(context), "favorites.json").copyTo(
File(getDownloadDirectory(context), "favorites.json"),
@@ -189,9 +194,18 @@ class SettingsFragment :
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
- when (key) {
- "dl_location" -> {
- findPreference(key)?.summary = getDownloadDirectory(context!!).canonicalPath
+ key ?: return
+
+ with(findPreference(key)) {
+ this ?: return
+
+ when (key) {
+ "proxy" -> {
+ summary = getProxyInfo(context).type.name
+ }
+ "dl_location" -> {
+ summary = getDownloadDirectory(context!!).canonicalPath
+ }
}
}
}
@@ -245,8 +259,7 @@ class SettingsFragment :
onPreferenceClickListener = this@SettingsFragment
}
"default_query" -> {
- val preferences = PreferenceManager.getDefaultSharedPreferences(context)
- summary = preferences.getString("default_query", "") ?: ""
+ summary = PreferenceManager.getDefaultSharedPreferences(context).getString("default_query", "") ?: ""
onPreferenceClickListener = this@SettingsFragment
}
@@ -270,6 +283,11 @@ class SettingsFragment :
"mirrors" -> {
onPreferenceClickListener = this@SettingsFragment
}
+ "proxy" -> {
+ summary = getProxyInfo(context).type.name
+
+ onPreferenceClickListener = this@SettingsFragment
+ }
"dark_mode" -> {
onPreferenceChangeListener = this@SettingsFragment
}
diff --git a/app/src/main/java/xyz/quaver/pupil/util/ConstValues.kt b/app/src/main/java/xyz/quaver/pupil/util/ConstValues.kt
index fecea791..38b24cf2 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/ConstValues.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/ConstValues.kt
@@ -18,8 +18,13 @@
package xyz.quaver.pupil.util
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonConfiguration
+
const val REQUEST_LOCK = 38238
const val REQUEST_RESTORE = 16546
const val REQUEST_DOWNLOAD_FOLDER = 3874
const val REQUEST_DOWNLOAD_FOLDER_OLD = 3425
-const val REQUEST_WRITE_PERMISSION_AND_SAF = 13900
\ No newline at end of file
+const val REQUEST_WRITE_PERMISSION_AND_SAF = 13900
+
+val json = Json(JsonConfiguration.Stable)
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt b/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt
index 6c75b55d..a4808a9a 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt
@@ -21,21 +21,18 @@ package xyz.quaver.pupil.util.download
import android.content.Context
import android.content.ContextWrapper
import android.util.Base64
-import android.util.Log
import androidx.preference.PreferenceManager
+import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
-import kotlinx.serialization.ImplicitReflectionSerializer
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.parse
-import kotlinx.serialization.stringify
import xyz.quaver.Code
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.util.getCachedGallery
import xyz.quaver.pupil.util.getDownloadDirectory
+import xyz.quaver.pupil.util.json
import java.io.File
import java.net.URL
@@ -50,7 +47,6 @@ class Cache(context: Context) : ContextWrapper(context) {
it.mkdirs()
}
- @UseExperimental(ImplicitReflectionSerializer::class)
fun getCachedMetadata(galleryID: Int) : Metadata? {
val file = File(getCachedGallery(galleryID), ".metadata")
@@ -58,7 +54,7 @@ class Cache(context: Context) : ContextWrapper(context) {
return null
return try {
- Json.parse(file.readText())
+ json.parse(Metadata.serializer(), file.readText())
} catch (e: Exception) {
//File corrupted
file.delete()
@@ -66,14 +62,13 @@ class Cache(context: Context) : ContextWrapper(context) {
}
}
- @UseExperimental(ImplicitReflectionSerializer::class)
fun setCachedMetadata(galleryID: Int, metadata: Metadata) {
val file = File(getCachedGallery(galleryID), ".metadata").also {
if (!it.exists())
it.createNewFile()
}
- file.writeText(Json.stringify(metadata))
+ file.writeText(json.stringify(Metadata.serializer(), metadata))
}
suspend fun getThumbnail(galleryID: Int): String? {
@@ -102,21 +97,29 @@ class Cache(context: Context) : ContextWrapper(context) {
suspend fun getGalleryBlock(galleryID: Int): GalleryBlock? {
val metadata = Cache(this).getCachedMetadata(galleryID)
- val source = mapOf(
- Code.HITOMI to { xyz.quaver.hitomi.getGalleryBlock(galleryID) },
- Code.HIYOBI to { xyz.quaver.hiyobi.getGalleryBlock(galleryID) }
+ val sources = listOf(
+ { xyz.quaver.hitomi.getGalleryBlock(galleryID) },
+ { xyz.quaver.hiyobi.getGalleryBlock(galleryID) }
)
- val galleryBlock = if (metadata?.galleryBlock == null)
- source.entries.map {
- CoroutineScope(Dispatchers.IO).async {
- kotlin.runCatching {
- it.value.invoke()
- }.getOrNull()
- }
- }.firstOrNull {
- it.await() != null
- }?.await()
+ val galleryBlock = if (metadata?.galleryBlock == null) {
+ CoroutineScope(Dispatchers.IO).async {
+ var galleryBlock: GalleryBlock? = null
+
+ for (source in sources) {
+ galleryBlock = try {
+ source.invoke()
+ } catch (e: Exception) {
+ null
+ }
+
+ if (galleryBlock != null)
+ break
+ }
+
+ galleryBlock
+ }.await() ?: return null
+ }
else
metadata.galleryBlock
@@ -155,40 +158,60 @@ class Cache(context: Context) : ContextWrapper(context) {
var retval: Reader? = null
for (source in sources) {
- retval = kotlin.runCatching {
+ retval = try {
source.value.invoke()
- }.getOrNull()
+ } catch (e: Exception) {
+ Crashlytics.logException(e)
+ null
+ }
if (retval != null)
break
}
retval
- }.await()
+ }.await() ?: return null
} else
metadata.reader
- if (reader != null)
- setCachedMetadata(
- galleryID,
- Metadata(Cache(this).getCachedMetadata(galleryID), readers = reader)
- )
+ setCachedMetadata(
+ galleryID,
+ Metadata(Cache(this).getCachedMetadata(galleryID), readers = reader)
+ )
return reader
}
+ val imageNameRegex = Regex("""^\d+\..+$""")
fun getImages(galleryID: Int): List? {
- val started = System.currentTimeMillis()
val gallery = getCachedGallery(galleryID)
- val reader = getReaderOrNull(galleryID) ?: return null
- val images = gallery.listFiles() ?: return null
- Log.i("PUPILD", "${System.currentTimeMillis() - started} ms")
- return reader.galleryInfo.indices.map { index ->
- images.firstOrNull { file -> file.name.startsWith("%05d".format(index)) }
+ return gallery.list { _, name ->
+ imageNameRegex.matches(name)
+ }?.map {
+ File(gallery, it)
}
}
+ val imageExtensions = listOf(
+ "png",
+ "jpg",
+ "webp",
+ "gif"
+ )
+ fun getImage(galleryID: Int, index: Int): File? {
+ val gallery = getCachedGallery(galleryID)
+
+ for (ext in imageExtensions) {
+ File(gallery, "%05d.$ext".format(index)).let {
+ if (it.exists())
+ return it
+ }
+ }
+
+ return null
+ }
+
fun putImage(galleryID: Int, name: String, data: ByteArray) {
val cache = File(getCachedGallery(galleryID), name).also {
if (!it.exists())
diff --git a/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt b/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt
index aa5dda63..bb3a2716 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt
@@ -40,6 +40,7 @@ import xyz.quaver.hitomi.urlFromUrlFromHash
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.createImgList
import xyz.quaver.hiyobi.user_agent
+import xyz.quaver.proxy
import xyz.quaver.pupil.R
import xyz.quaver.pupil.ui.ReaderActivity
import java.io.IOException
@@ -145,25 +146,22 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
private val loop = loop()
private val worker = SparseArray()
- @Volatile var nRunners = 0
+ val clients = SparseArray()
- private val client = OkHttpClient.Builder()
- .addInterceptor { chain ->
- val request = chain.request()
- var response = chain.proceed(request)
+ val interceptor = Interceptor { chain ->
+ val request = chain.request()
+ val response = chain.proceed(request)
- var retry = preferences.getInt("retry", 3)
- while (!response.isSuccessful && retry > 0) {
- response = chain.proceed(request)
- retry--
- }
-
- response.newBuilder()
- .body(ProgressResponseBody(request.tag(), response.body(), progressListener))
- .build()
- }
- .dispatcher(Dispatcher(Executors.newFixedThreadPool(4)))
+ response.newBuilder()
+ .body(ProgressResponseBody(request.tag(), response.body(), progressListener))
.build()
+ }
+ fun buildClient() =
+ OkHttpClient.Builder()
+ .addInterceptor(interceptor)
+ .dispatcher(Dispatcher(Executors.newFixedThreadPool(4)))
+ .proxy(proxy)
+ .build()
fun stop() {
queue.clear()
@@ -176,29 +174,23 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
worker[galleryID]?.cancel()
}
- client.dispatcher().cancelAll()
+ for (i in 0 until clients.size()) {
+ clients.valueAt(i).dispatcher().cancelAll()
+ }
+ clients.clear()
progress.clear()
exception.clear()
notification.clear()
notificationManager.cancelAll()
-
- nRunners = 0
-
}
fun cancel(galleryID: Int) {
queue.remove(galleryID)
worker[galleryID]?.cancel()
- client.dispatcher().queuedCalls()
- .filter {
- @Suppress("UNCHECKED_CAST")
- (it.request().tag() as? Pair)?.first == galleryID
- }
- .forEach {
- it.cancel()
- }
+ clients[galleryID]?.dispatcher()?.cancelAll()
+ clients.remove(galleryID)
progress.remove(galleryID)
exception.remove(galleryID)
@@ -207,7 +199,6 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
if (progress.indexOfKey(galleryID) >= 0) {
Cache(this@DownloadWorker).setDownloading(galleryID, false)
- nRunners--
}
}
@@ -222,7 +213,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
url(
urlFromUrlFromHash(
galleryID,
- reader.galleryInfo[index],
+ reader.galleryInfo.files[index],
if (lowQuality) "webp" else null
)
)
@@ -240,7 +231,10 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
tag(galleryID to index)
}.build()
- client.newCall(request).enqueue(callback)
+ if (clients.get(galleryID) == null)
+ clients.put(galleryID, buildClient())
+
+ clients[galleryID]?.newCall(request)?.enqueue(callback)
}
private fun download(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
@@ -252,24 +246,23 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
exception.put(galleryID, null)
Cache(this@DownloadWorker).setDownloading(galleryID, false)
- nRunners--
return@launch
}
val cache = Cache(this@DownloadWorker).getImages(galleryID)
- progress.put(galleryID, reader.galleryInfo.indices.map { index ->
- if (cache?.get(index) != null)
+ progress.put(galleryID, reader.galleryInfo.files.indices.map { index ->
+ if (cache?.getOrNull(index) != null)
Float.POSITIVE_INFINITY
else
0F
}.toMutableList())
- exception.put(galleryID, reader.galleryInfo.map { null }.toMutableList())
+ exception.put(galleryID, reader.galleryInfo.files.map { null }.toMutableList())
if (notification[galleryID] == null)
initNotification(galleryID)
- notification[galleryID].setContentTitle(reader.title)
+ notification[galleryID].setContentTitle(reader.galleryInfo.title)
notify(galleryID)
if (isCompleted(galleryID)) {
@@ -279,15 +272,14 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
setDownloading(galleryID, false)
}
}
- nRunners--
return@launch
}
- for (i in reader.galleryInfo.indices) {
+ for (i in reader.galleryInfo.files.indices) {
val callback = object : Callback {
override fun onFailure(call: Call, e: IOException) {
- if (Fabric.isInitialized())
+ if (Fabric.isInitialized() && e.message != "Canceled")
Crashlytics.logException(e)
progress[galleryID]?.set(i, Float.NaN)
@@ -302,7 +294,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
setDownloading(galleryID, false)
}
}
- nRunners--
+ clients.remove(galleryID)
}
}
@@ -325,7 +317,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
setDownloading(galleryID, false)
}
}
- nRunners--
+ clients.remove(galleryID)
}
}
}
@@ -342,7 +334,9 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
if (isCompleted(galleryID))
notification[galleryID]
?.setContentText(getString(R.string.reader_notification_complete))
+ ?.setSmallIcon(android.R.drawable.stat_sys_download_done)
?.setProgress(0, 0, false)
+ ?.setOngoing(false)
else
notification[galleryID]
?.setProgress(max, progress, false)
@@ -369,24 +363,24 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
setSmallIcon(android.R.drawable.stat_sys_download) // had to use this because old android doesn't support VectorDrawable on Notification :P
setContentIntent(pendingIntent)
setProgress(0, 0, true)
+ setOngoing(true)
})
}
private fun loop() = CoroutineScope(Dispatchers.Default).launch {
while (true) {
- if (queue.isEmpty() || nRunners > preferences.getInt("max_download", 4))
+ if (queue.isEmpty() || clients.size() > preferences.getInt("max_download", 4))
continue
val galleryID = queue.poll() ?: continue
- if (progress.indexOfKey(galleryID) >= 0) // Gallery already downloading!
+ if (clients.indexOfKey(galleryID) >= 0) // Gallery already downloading!
continue
initNotification(galleryID)
if (Cache(this@DownloadWorker).isDownloading(galleryID))
notificationManager.notify(galleryID, notification[galleryID].build())
worker.put(galleryID, download(galleryID))
- nRunners++
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/util/history.kt b/app/src/main/java/xyz/quaver/pupil/util/history.kt
index 665ccdfe..cb13c226 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/history.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/history.kt
@@ -18,15 +18,14 @@
package xyz.quaver.pupil.util
-import kotlinx.serialization.ImplicitReflectionSerializer
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.JsonConfiguration
-import kotlinx.serialization.parseList
-import kotlinx.serialization.stringify
+import kotlinx.serialization.list
+import kotlinx.serialization.serializer
import java.io.File
class Histories(private val file: File) : ArrayList() {
+ val serializer = Int.serializer().list
+
init {
if (!file.exists())
file.parentFile?.mkdirs()
@@ -38,21 +37,20 @@ class Histories(private val file: File) : ArrayList() {
}
}
- @UseExperimental(ImplicitReflectionSerializer::class)
fun load() : Histories {
return apply {
super.clear()
addAll(
- Json(JsonConfiguration.Stable).parseList(
+ json.parse(
+ serializer,
file.bufferedReader().use { it.readText() }
)
)
}
}
- @UseExperimental(ImplicitReflectionSerializer::class)
fun save() {
- file.writeText(Json(JsonConfiguration.Stable).stringify(this))
+ file.writeText(json.stringify(serializer, this))
}
override fun add(element: Int): Boolean {
diff --git a/app/src/main/java/xyz/quaver/pupil/util/lock.kt b/app/src/main/java/xyz/quaver/pupil/util/lock.kt
index cba085c4..4f48b784 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/lock.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/lock.kt
@@ -21,9 +21,10 @@ package xyz.quaver.pupil.util
import android.content.Context
import android.content.ContextWrapper
import androidx.core.content.ContextCompat
-import kotlinx.serialization.*
+import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
+import kotlinx.serialization.list
import java.io.File
import java.security.MessageDigest
@@ -73,7 +74,6 @@ class LockManager(base: Context): ContextWrapper(base) {
load()
}
- @UseExperimental(ImplicitReflectionSerializer::class)
private fun load() {
val lock = File(ContextCompat.getDataDir(this), "lock.json")
@@ -82,17 +82,16 @@ class LockManager(base: Context): ContextWrapper(base) {
lock.writeText("[]")
}
- locks = ArrayList(Json(JsonConfiguration.Stable).parseList(lock.readText()))
+ locks = ArrayList(json.parse(Lock.serializer().list, lock.readText()))
}
- @UseExperimental(ImplicitReflectionSerializer::class)
private fun save() {
val lock = File(ContextCompat.getDataDir(this), "lock.json")
if (!lock.exists())
lock.createNewFile()
- lock.writeText(Json(JsonConfiguration.Stable).stringify(locks?.toList() ?: listOf()))
+ lock.writeText(json.stringify(Lock.serializer().list, locks?.toList() ?: listOf()))
}
fun add(lock: Lock) {
diff --git a/app/src/main/java/xyz/quaver/pupil/util/proxy.kt b/app/src/main/java/xyz/quaver/pupil/util/proxy.kt
new file mode 100644
index 00000000..229c5e90
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/util/proxy.kt
@@ -0,0 +1,63 @@
+/*
+ * 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 .
+ */
+
+package xyz.quaver.pupil.util
+
+import android.content.Context
+import androidx.preference.PreferenceManager
+import kotlinx.serialization.Serializable
+import okhttp3.Authenticator
+import okhttp3.Credentials
+import java.net.InetSocketAddress
+import java.net.Proxy
+
+@Serializable
+data class ProxyInfo(
+ val type: Proxy.Type,
+ val host: String? = null,
+ val port: Int? = null,
+ val username: String? = null,
+ val password: String? = null
+) {
+ fun proxy() : Proxy {
+ return if (host == null || port == null)
+ return Proxy.NO_PROXY
+ else
+ Proxy(type, InetSocketAddress.createUnresolved(host, port))
+ }
+
+ fun authenticator() = Authenticator { _, response ->
+ val credential = Credentials.basic(username, password)
+
+ response.request().newBuilder()
+ .header("Proxy-Authorization", credential)
+ .build()
+ }
+
+}
+
+fun getProxy(context: Context) =
+ getProxyInfo(context).proxy()
+
+fun getProxyInfo(context: Context) =
+ PreferenceManager.getDefaultSharedPreferences(context).getString("proxy", null).let {
+ if (it == null)
+ ProxyInfo(Proxy.Type.DIRECT)
+ else
+ json.parse(ProxyInfo.serializer(), it)
+ }
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/util/update.kt b/app/src/main/java/xyz/quaver/pupil/util/update.kt
index 1d0c9091..94ae9ed1 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/update.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/update.kt
@@ -32,7 +32,10 @@ import androidx.preference.PreferenceManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import kotlinx.serialization.json.*
+import kotlinx.serialization.json.JsonArray
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.boolean
+import kotlinx.serialization.json.content
import ru.noties.markwon.Markwon
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R
@@ -43,7 +46,7 @@ import java.util.*
fun getReleases(url: String) : JsonArray {
return try {
URL(url).readText().let {
- Json(JsonConfiguration.Stable).parse(JsonArray.serializer(), it)
+ json.parse(JsonArray.serializer(), it)
}
} catch (e: Exception) {
JsonArray(emptyList())
@@ -145,6 +148,7 @@ fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
setContentTitle(context.getString(R.string.update_notification_description))
setSmallIcon(android.R.drawable.stat_sys_download)
priority = NotificationCompat.PRIORITY_LOW
+ setOngoing(true)
}
CoroutineScope(Dispatchers.IO).launch io@{
@@ -160,6 +164,7 @@ fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
setContentText(context.getString(R.string.update_failed))
setMessage(context.getString(R.string.update_failed_message))
setSmallIcon(android.R.drawable.stat_sys_download_done)
+ setOngoing(false)
}
notificationManager.cancel(UPDATE_NOTIFICATION_ID)
@@ -179,6 +184,7 @@ fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
setSmallIcon(android.R.drawable.stat_sys_download_done)
setContentTitle(context.getString(R.string.update_download_completed))
setContentText(context.getString(R.string.update_download_completed_description))
+ setOngoing(false)
}
notificationManager.cancel(UPDATE_NOTIFICATION_ID)
diff --git a/app/src/main/res/layout/dialog_proxy.xml b/app/src/main/res/layout/dialog_proxy.xml
new file mode 100644
index 00000000..5765d86e
--- /dev/null
+++ b/app/src/main/res/layout/dialog_proxy.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_reader.xml b/app/src/main/res/layout/item_reader.xml
index d91faa03..60a1f261 100644
--- a/app/src/main/res/layout/item_reader.xml
+++ b/app/src/main/res/layout/item_reader.xml
@@ -62,6 +62,8 @@
diff --git a/app/src/main/res/values-ja/arrays.xml b/app/src/main/res/values-ja/arrays.xml
new file mode 100644
index 00000000..568c6f35
--- /dev/null
+++ b/app/src/main/res/values-ja/arrays.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+ - ダイレクト
+ - HTTP
+ - SOCKS
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 39a0af29..5cb0d9a4 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -122,4 +122,12 @@
ロード速度とデータ使用料を改善するため低解像度イメージをロード
手動で設定
このフォルダにアクセスできません。他のフォルダを選択してください。
+ プロクシ
+ ID
+ プロクシタイプ
+ ポート
+ パスワード
+ エラー
+ サーバーアドレス
+ サーバー
\ No newline at end of file
diff --git a/app/src/main/res/values-ko/arrays.xml b/app/src/main/res/values-ko/arrays.xml
new file mode 100644
index 00000000..f7148ac2
--- /dev/null
+++ b/app/src/main/res/values-ko/arrays.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+ - 다이렉트
+ - HTTP
+ - SOCKS
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index e5829cb9..05c173f1 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -122,4 +122,12 @@
미러 설정
직접 설정
이 폴더에 접근할 수 없습니다. 다른 폴더를 선택해주세요.
+ 프록시
+ ID
+ 프록시 타입
+ 포트
+ 비밀번호
+ 잘못된 값
+ 서버 주소
+ 서버
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 0b62b1a3..0afe7479 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -62,4 +62,10 @@
- HIYOBI|hiyobi.me
+
+ - Direct
+ - HTTP
+ - SOCKS
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml
index 25399db6..68307f90 100644
--- a/app/src/main/res/values/dimen.xml
+++ b/app/src/main/res/values/dimen.xml
@@ -10,4 +10,7 @@
176dp
8dp
+
+ 50dp
+ 150dp
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e7e389b8..c79b2143 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -154,6 +154,7 @@
Miscellaneous
Load images from mirrors
+ Proxy
Enable security mode
Enable security mode to make the screen invisible on recent app window
Dark mode
@@ -189,4 +190,13 @@
Any
Mirrors
+
+ type
+ address
+ port
+ username
+ password
+ Wrong value
+ server
+
diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml
index fe77a2f1..eb898b09 100644
--- a/app/src/main/res/xml/root_preferences.xml
+++ b/app/src/main/res/xml/root_preferences.xml
@@ -48,7 +48,7 @@
app:title="@string/settings_dl_location"/>
@@ -71,6 +71,10 @@
app:title="@string/settings_mirror_title"
app:summary="@string/settings_mirror_summary"/>
+
+
hash
@@ -76,11 +77,20 @@ fun fullPathFromHash(hash: String?) : String? {
}
@Suppress("NAME_SHADOWING", "UNUSED_PARAMETER")
-fun urlFromHash(galleryID: Int, image: GalleryInfo, dir: String? = null, ext: String? = null) : String {
+fun urlFromHash(galleryID: Int, image: GalleryFiles, 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, dir: String? = null, ext: String? = null, base: String? = null) =
- urlFromURL(urlFromHash(galleryID, image, dir, ext), base)
\ No newline at end of file
+fun urlFromUrlFromHash(galleryID: Int, image: GalleryFiles, dir: String? = null, ext: String? = null, base: String? = null) =
+ urlFromURL(urlFromHash(galleryID, image, dir, ext), base)
+
+fun imageUrlFromImage(galleryID: Int, image: GalleryFiles, noWebp: Boolean) : String {
+ val webp = if (image.hash != null && image.haswebp != 0 && !noWebp)
+ "webp"
+ else
+ null
+
+ return urlFromUrlFromHash(galleryID, image, webp)
+}
\ No newline at end of file
diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/galleries.kt b/libpupil/src/main/java/xyz/quaver/hitomi/galleries.kt
index 63d15b93..c9b1fae8 100644
--- a/libpupil/src/main/java/xyz/quaver/hitomi/galleries.kt
+++ b/libpupil/src/main/java/xyz/quaver/hitomi/galleries.kt
@@ -18,6 +18,7 @@ package xyz.quaver.hitomi
import kotlinx.serialization.Serializable
import org.jsoup.Jsoup
+import xyz.quaver.proxy
import java.net.URLDecoder
@Serializable
@@ -36,7 +37,7 @@ data class Gallery(
val thumbnails: List
)
fun getGallery(galleryID: Int) : Gallery {
- val url = Jsoup.connect("https://hitomi.la/galleries/$galleryID.html").get()
+ val url = Jsoup.connect("https://hitomi.la/galleries/$galleryID.html").proxy(proxy).get()
.select("a").attr("href")
val doc = Jsoup.connect(url).get()
@@ -70,7 +71,7 @@ fun getGallery(galleryID: Int) : Gallery {
href.slice(5 until href.indexOf('-'))
}
- val thumbnails = getGalleryInfo(galleryID).map { galleryInfo ->
+ val thumbnails = getGalleryInfo(galleryID).files.map { galleryInfo ->
urlFromUrlFromHash(galleryID, galleryInfo, "smalltn", "jpg", "tn")
}
diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/galleryblock.kt b/libpupil/src/main/java/xyz/quaver/hitomi/galleryblock.kt
index f2b76f7e..6040251d 100644
--- a/libpupil/src/main/java/xyz/quaver/hitomi/galleryblock.kt
+++ b/libpupil/src/main/java/xyz/quaver/hitomi/galleryblock.kt
@@ -19,6 +19,7 @@ package xyz.quaver.hitomi
import kotlinx.serialization.Serializable
import org.jsoup.Jsoup
import xyz.quaver.Code
+import xyz.quaver.proxy
import java.net.URL
import java.net.URLDecoder
import java.nio.ByteBuffer
@@ -34,35 +35,31 @@ fun fetchNozomi(area: String? = null, tag: String = "index", language: String =
else -> "$protocol//$domain/$area/$tag-$language$nozomiextension"
}
- try {
- with(URL(url).openConnection() as HttpsURLConnection) {
- requestMethod = "GET"
+ with(URL(url).openConnection() as HttpsURLConnection) {
+ requestMethod = "GET"
- if (start != -1 && count != -1) {
- val startByte = start*4
- val endByte = (start+count)*4-1
+ if (start != -1 && count != -1) {
+ val startByte = start*4
+ val endByte = (start+count)*4-1
- setRequestProperty("Range", "bytes=$startByte-$endByte")
- }
-
- connect()
-
- val totalItems = getHeaderField("Content-Range")
- .replace(Regex("^[Bb]ytes \\d+-\\d+/"), "").toInt() / 4
-
- val nozomi = ArrayList()
-
- val arrayBuffer = ByteBuffer
- .wrap(inputStream.readBytes())
- .order(ByteOrder.BIG_ENDIAN)
-
- while (arrayBuffer.hasRemaining())
- nozomi.add(arrayBuffer.int)
-
- return Pair(nozomi, totalItems)
+ setRequestProperty("Range", "bytes=$startByte-$endByte")
}
- } catch (e: Exception) {
- return Pair(emptyList(), 0)
+
+ connect()
+
+ val totalItems = getHeaderField("Content-Range")
+ .replace(Regex("^[Bb]ytes \\d+-\\d+/"), "").toInt() / 4
+
+ val nozomi = ArrayList()
+
+ val arrayBuffer = ByteBuffer
+ .wrap(inputStream.readBytes())
+ .order(ByteOrder.BIG_ENDIAN)
+
+ while (arrayBuffer.hasRemaining())
+ nozomi.add(arrayBuffer.int)
+
+ return Pair(nozomi, totalItems)
}
}
@@ -82,30 +79,26 @@ data class GalleryBlock(
fun getGalleryBlock(galleryID: Int) : GalleryBlock? {
val url = "$protocol//$domain/$galleryblockdir/$galleryID$extension"
- try {
- val doc = Jsoup.connect(url).get()
+ val doc = Jsoup.connect(url).proxy(proxy).get()
- val galleryUrl = doc.selectFirst(".lillie").attr("href")
+ val galleryUrl = doc.selectFirst(".lillie").attr("href")
- val thumbnails = doc.select("img").map { protocol + it.attr("data-src") }
+ val thumbnails = doc.select("img").map { protocol + it.attr("data-src") }
- val title = doc.selectFirst("h1.lillie > a").text()
- val artists = doc.select("div.artist-list a").map{ it.text() }
- val series = doc.select("a[href~=^/series/]").map { it.text() }
- val type = doc.selectFirst("a[href~=^/type/]").text()
+ val title = doc.selectFirst("h1.lillie > a").text()
+ val artists = doc.select("div.artist-list a").map{ it.text() }
+ val series = doc.select("a[href~=^/series/]").map { it.text() }
+ val type = doc.selectFirst("a[href~=^/type/]").text()
- val language = {
- val href = doc.select("a[href~=^/index-.+-1.html]").attr("href")
- href.slice(7 until href.indexOf("-1"))
- }.invoke()
+ val language = {
+ val href = doc.select("a[href~=^/index-.+-1.html]").attr("href")
+ href.slice(7 until href.indexOf("-1"))
+ }.invoke()
- val relatedTags = doc.select(".relatedtags a").map {
- val href = URLDecoder.decode(it.attr("href"), "UTF-8")
- href.slice(5 until href.indexOf("-all"))
- }
-
- return GalleryBlock(Code.HITOMI, galleryID, galleryUrl, thumbnails, title, artists, series, type, language, relatedTags)
- } catch (e: Exception) {
- return null
+ val relatedTags = doc.select(".relatedtags a").map {
+ val href = URLDecoder.decode(it.attr("href"), "UTF-8")
+ href.slice(5 until href.indexOf("-all"))
}
+
+ return GalleryBlock(Code.HITOMI, galleryID, galleryUrl, thumbnails, title, artists, series, type, language, relatedTags)
}
\ No newline at end of file
diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/reader.kt b/libpupil/src/main/java/xyz/quaver/hitomi/reader.kt
index 228ce069..138a37a8 100644
--- a/libpupil/src/main/java/xyz/quaver/hitomi/reader.kt
+++ b/libpupil/src/main/java/xyz/quaver/hitomi/reader.kt
@@ -17,28 +17,35 @@
package xyz.quaver.hitomi
import kotlinx.serialization.Serializable
-import org.jsoup.Jsoup
import xyz.quaver.Code
fun getReferer(galleryID: Int) = "https://hitomi.la/reader/$galleryID.html"
@Serializable
data class GalleryInfo(
+ val language_localname: String? = null,
+ val language: String? = null,
+ val date: String? = null,
+ val files: List,
+ val id: Int? = null,
+ val type: String? = null,
+ val title: String? = null
+)
+
+@Serializable
+data class GalleryFiles(
val width: Int,
val hash: String? = null,
val haswebp: Int = 0,
val name: String,
- val height: Int
+ val height: Int,
+ val hasavif: Int = 0
)
@Serializable
-data class Reader(val code: Code, val title: String, val galleryInfo: List)
+data class Reader(val code: Code, val galleryInfo: GalleryInfo)
//Set header `Referer` to reader url to avoid 403 error
fun getReader(galleryID: Int) : Reader {
- val readerUrl = "https://hitomi.la/reader/$galleryID.html"
-
- val doc = Jsoup.connect(readerUrl).get()
-
- return Reader(Code.HITOMI, doc.title(), getGalleryInfo(galleryID))
+ return Reader(Code.HITOMI, getGalleryInfo(galleryID))
}
\ No newline at end of file
diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/search.kt b/libpupil/src/main/java/xyz/quaver/hitomi/search.kt
index fbcd4a66..1a9d6087 100644
--- a/libpupil/src/main/java/xyz/quaver/hitomi/search.kt
+++ b/libpupil/src/main/java/xyz/quaver/hitomi/search.kt
@@ -16,6 +16,7 @@
package xyz.quaver.hitomi
+import xyz.quaver.proxy
import java.net.URL
import java.nio.ByteBuffer
import java.nio.ByteOrder
@@ -49,8 +50,9 @@ fun sanitize(input: String) : String {
fun getIndexVersion(name: String) : String {
return try {
- URL("$protocol//$domain/$name/version?_=${System.currentTimeMillis()}")
- .readText()
+ URL("$protocol//$domain/$name/version?_=${System.currentTimeMillis()}").openConnection(proxy).getInputStream().use {
+ it.reader().readText()
+ }
} catch (e: Exception) {
""
}
@@ -173,22 +175,20 @@ fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : List
else -> "$protocol//$domain/$compressed_nozomi_prefix/$area/$tag-$language$nozomiextension"
}
- try {
- val bytes = URL(nozomiAddress).readBytes()
-
- val nozomi = ArrayList()
-
- val arrayBuffer = ByteBuffer
- .wrap(bytes)
- .order(ByteOrder.BIG_ENDIAN)
-
- while (arrayBuffer.hasRemaining())
- nozomi.add(arrayBuffer.int)
-
- return nozomi
- } catch (e: Exception) {
- return emptyList()
+ val bytes = URL(nozomiAddress).openConnection(proxy).getInputStream().use {
+ it.readBytes()
}
+
+ val nozomi = ArrayList()
+
+ val arrayBuffer = ByteBuffer
+ .wrap(bytes)
+ .order(ByteOrder.BIG_ENDIAN)
+
+ while (arrayBuffer.hasRemaining())
+ nozomi.add(arrayBuffer.int)
+
+ return nozomi
}
fun getGalleryIDsFromData(data: Pair) : List {
@@ -242,7 +242,7 @@ fun getNodeAtAddress(field: String, address: Long) : Node? {
fun getURLAtRange(url: String, range: LongRange) : ByteArray? {
try {
- with (URL(url).openConnection() as HttpsURLConnection) {
+ with (URL(url).openConnection(proxy) as HttpsURLConnection) {
requestMethod = "GET"
setRequestProperty("Range", "bytes=${range.first}-${range.last}")
diff --git a/libpupil/src/main/java/xyz/quaver/hiyobi/galleryblock.kt b/libpupil/src/main/java/xyz/quaver/hiyobi/galleryblock.kt
index 1529dbad..6b324ce9 100644
--- a/libpupil/src/main/java/xyz/quaver/hiyobi/galleryblock.kt
+++ b/libpupil/src/main/java/xyz/quaver/hiyobi/galleryblock.kt
@@ -20,30 +20,27 @@ import org.jsoup.Jsoup
import xyz.quaver.Code
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.protocol
+import xyz.quaver.proxy
fun getGalleryBlock(galleryID: Int) : GalleryBlock? {
- val url = "$protocol//$hiyobi/search/$galleryID"
+ val url = "$protocol//$hiyobi/info/$galleryID"
- try {
- val doc = Jsoup.connect(url).get()
+ val doc = Jsoup.connect(url).proxy(proxy).get()
- val galleryBlock = doc.selectFirst(".gallery-content")
+ val galleryBlock = doc.selectFirst(".gallery-content")
- val galleryUrl = galleryBlock.selectFirst("a").attr("href")
+ val galleryUrl = galleryBlock.selectFirst("a").attr("href")
- val thumbnails = listOf(galleryBlock.selectFirst("img").attr("abs:src"))
+ val thumbnails = listOf(galleryBlock.selectFirst("img").attr("abs:src"))
- val title = galleryBlock.selectFirst("b").text()
- val artists = galleryBlock.select("tr:matches(작가) a[href~=artist]").map { it.text() }
- val series = galleryBlock.select("tr:matches(원작) a").map { it.attr("href").substringAfter("series:").replace('_', ' ') }
- val type = galleryBlock.selectFirst("tr:matches(종류) a").attr("href").substringAfter("type:").replace('_', ' ')
+ val title = galleryBlock.selectFirst("b").text()
+ val artists = galleryBlock.select("tr:matches(작가) a[href~=artist]").map { it.text() }
+ val series = galleryBlock.select("tr:matches(원작) a").map { it.attr("href").substringAfter("series:").replace('_', ' ') }
+ val type = galleryBlock.selectFirst("tr:matches(종류) a").attr("href").substringAfter("type:").replace('_', ' ')
- val language = "korean"
+ val language = "korean"
- val relatedTags = galleryBlock.select("tr:matches(태그) a").map { it.attr("href").substringAfterLast('/').replace('_', ' ') }
+ val relatedTags = galleryBlock.select("tr:matches(태그) a").map { it.attr("href").substringAfterLast('/').replace('_', ' ') }
- return GalleryBlock(Code.HIYOBI, galleryID, galleryUrl, thumbnails, title, artists, series, type, language, relatedTags)
- } catch (e: Exception) {
- return null
- }
+ return GalleryBlock(Code.HIYOBI, galleryID, galleryUrl, thumbnails, title, artists, series, type, language, relatedTags)
}
\ No newline at end of file
diff --git a/libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt b/libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt
index 873f0ce1..72fa7f07 100644
--- a/libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt
+++ b/libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt
@@ -16,13 +16,16 @@
package xyz.quaver.hiyobi
+import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json
import kotlinx.serialization.list
import org.jsoup.Jsoup
import xyz.quaver.Code
+import xyz.quaver.hitomi.GalleryFiles
import xyz.quaver.hitomi.GalleryInfo
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.protocol
+import xyz.quaver.proxy
import java.net.URL
import javax.net.ssl.HttpsURLConnection
@@ -47,7 +50,7 @@ fun renewCookie() : String {
val url = "https://$hiyobi/"
try {
- with(URL(url).openConnection() as HttpsURLConnection) {
+ with(URL(url).openConnection(proxy) as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent)
connectTimeout = 2000
connect()
@@ -58,16 +61,16 @@ fun renewCookie() : String {
}
}
+@UseExperimental(UnstableDefault::class)
fun getReader(galleryID: Int) : Reader {
val reader = "https://$hiyobi/reader/$galleryID"
- val url = "https://$hiyobi/data/json/${galleryID}_list.json"
+ val url = "https://cdn.hiyobi.me/data/json/${galleryID}_list.json"
- val title = Jsoup.connect(reader).get().title()
+ val title = Jsoup.connect(reader).proxy(proxy).get().title()
- @Suppress("EXPERIMENTAL_API_USAGE")
- val galleryInfo = Json.parse(
- GalleryInfo.serializer().list,
- with(URL(url).openConnection() as HttpsURLConnection) {
+ val galleryFiles = Json.nonstrict.parse(
+ GalleryFiles.serializer().list,
+ with(URL(url).openConnection(proxy) as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie)
connectTimeout = 2000
@@ -77,14 +80,14 @@ fun getReader(galleryID: Int) : Reader {
}
)
- return Reader(Code.HIYOBI, title, galleryInfo)
+ return Reader(Code.HIYOBI, GalleryInfo(title = title, files = galleryFiles))
}
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)
+ reader.galleryInfo.files.map {
+ val name = it.name.replace(Regex("""\.[^/.]+$"""), "")
+ Images("$protocol//$hiyobi/data_r/$galleryID/$name.jpg", galleryID, it.name)
}
else
- reader.galleryInfo.map { Images("$protocol//$hiyobi/data/$galleryID/${it.name}", galleryID, it.name) }
\ No newline at end of file
+ reader.galleryInfo.files.map { Images("$protocol//$hiyobi/data/$galleryID/${it.name}", galleryID, it.name) }
\ No newline at end of file
diff --git a/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt b/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt
index 84c79a75..4d56ed56 100644
--- a/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt
+++ b/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt
@@ -82,14 +82,14 @@ class UnitTest {
@Test
fun test_hiyobi() {
- val reader = xyz.quaver.hiyobi.getReader(10000062)
+ val reader = xyz.quaver.hiyobi.getReader(1574736)
print(reader)
}
@Test
fun test_urlFromUrlFromHash() {
- val url = urlFromUrlFromHash(1531795, GalleryInfo(
+ val url = urlFromUrlFromHash(1531795, GalleryFiles(
212, "719d46a7556be0d0021c5105878507129b5b3308b02cf67f18901b69dbb3b5ef", 1, "00.jpg", 300
), "webp")