diff --git a/.idea/copyright/Apache.xml b/.idea/copyright/Apache.xml
new file mode 100644
index 00000000..3c72b283
--- /dev/null
+++ b/.idea/copyright/Apache.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/GPL.xml b/.idea/copyright/GPL.xml
new file mode 100644
index 00000000..aff4b88e
--- /dev/null
+++ b/.idea/copyright/GPL.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 00000000..b1a28ee4
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 3e352b83..219eea45 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -13,7 +13,6 @@
-
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 146ab09b..00000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 84da703c..37a75096 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,9 @@
-
-
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/scopes/Pupil.xml b/.idea/scopes/Pupil.xml
new file mode 100644
index 00000000..f607cbcf
--- /dev/null
+++ b/.idea/scopes/Pupil.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/scopes/libpupil.xml b/.idea/scopes/libpupil.xml
new file mode 100644
index 00000000..9ad573e4
--- /dev/null
+++ b/.idea/scopes/libpupil.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 94a25f7f..35eb1ddf 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 01c46b58..ba08fc0c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
License is specified in following module separately
app/
-libpupil/
+libpupil/
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 989ed597..ef3a25cf 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlinx-serialization'
apply plugin: 'com.google.gms.google-services'
@@ -12,8 +13,8 @@ android {
applicationId "xyz.quaver.pupil"
minSdkVersion 16
targetSdkVersion 29
- versionCode 20
- versionName "2.11.1"
+ versionCode 21
+ versionName "2.12"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
@@ -23,6 +24,9 @@ android {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
+ buildTypes.each {
+ it.buildConfigField('boolean', 'PRERELEASE', 'true')
+ }
}
kotlinOptions {
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
@@ -52,8 +56,13 @@ dependencies {
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
implementation 'com.github.clans:fab:1.6.4'
+ implementation 'com.github.bumptech.glide:glide:4.9.0'
+ implementation ("com.github.bumptech.glide:recyclerview-integration:4.9.0") {
+ transitive = false
+ }
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
implementation "ru.noties.markwon:core:${markwonVersion}"
+ kapt 'com.github.bumptech.glide:compiler:4.9.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test:rules:1.2.0'
diff --git a/app/release/output.json b/app/release/output.json
index de58b687..e36241c5 100644
--- a/app/release/output.json
+++ b/app/release/output.json
@@ -1 +1 @@
-[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":20,"versionName":"2.11.1","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":21,"versionName":"2.12","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/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
index f7decdb4..b283f4c0 100644
--- a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
@@ -1,3 +1,23 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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 .
+ */
+
+@file:Suppress("UNUSED_VARIABLE")
+
package xyz.quaver.pupil
import android.content.Intent
@@ -36,7 +56,7 @@ class ExampleInstrumentedTest {
@Test
fun checkCacheDir() {
- val activityTestRule = ActivityTestRule(LockActivity::class.java)
+ val activityTestRule = ActivityTestRule(LockActivity::class.java)
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
activityTestRule.launchActivity(Intent())
@@ -50,7 +70,7 @@ class ExampleInstrumentedTest {
val data: ByteArray
- with(URL(reader[0].url).openConnection() as HttpsURLConnection) {
+ with(URL(reader.readerItems[0].url).openConnection() as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 89e70b1e..08cc55a4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,7 +3,9 @@
package="xyz.quaver.pupil">
-
+
+
+
.
+ */
+
package xyz.quaver.pupil
+import android.app.DownloadManager
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
+import android.content.BroadcastReceiver
import android.content.Context
+import android.content.Intent
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt
index 68b23485..efbc0851 100644
--- a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt
+++ b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt
@@ -1,9 +1,26 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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.adapters
import android.app.AlertDialog
import android.graphics.BitmapFactory
import android.graphics.drawable.Drawable
-import android.util.Log
import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.View
@@ -15,6 +32,8 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.item_galleryblock.view.*
import kotlinx.coroutines.CoroutineScope
@@ -23,9 +42,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
-import kotlinx.serialization.list
import xyz.quaver.hitomi.GalleryBlock
-import xyz.quaver.hitomi.ReaderItem
+import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tag
@@ -47,8 +65,8 @@ class GalleryBlockAdapter(private val galleries: List>) {
+ inner class GalleryViewHolder(val view: CardView) : RecyclerView.ViewHolder(view) {
+ fun bind(holder: GalleryViewHolder, item: Pair>) {
with(view) {
val resources = context.resources
val languages = resources.getStringArray(R.array.languages).map {
@@ -62,17 +80,15 @@ class GalleryBlockAdapter(private val galleries: List.
+ */
+
package xyz.quaver.pupil.adapters
-import android.graphics.Bitmap
-import android.graphics.BitmapFactory
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
+import androidx.swiperefreshlayout.widget.CircularProgressDrawable
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
import xyz.quaver.pupil.R
class ReaderAdapter(private val images: List) : RecyclerView.Adapter() {
@@ -25,47 +43,19 @@ class ReaderAdapter(private val images: List) : RecyclerView.Adapter reqHeight || width > reqWidth) {
-
- val halfHeight: Int = height / 2
- val halfWidth: Int = width / 2
-
- // Calculate the largest inSampleSize value that is a power of 2 and keeps both
- // height and width larger than the requested height and width.
- while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
- inSampleSize *= 2
- }
- }
-
- return inSampleSize
+ val progressDrawable = CircularProgressDrawable(holder.view.context).apply {
+ strokeWidth = 10f
+ centerRadius = 100f
+ start()
}
- with(holder.view as ImageView) {
- val options = BitmapFactory.Options()
-
- options.inJustDecodeBounds = true
- BitmapFactory.decodeFile(images[position], options)
-
- val (reqWidth, reqHeight) = context.resources.displayMetrics.let {
- Pair(it.widthPixels, it.heightPixels)
- }
-
- options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
-
- options.inPreferredConfig = Bitmap.Config.RGB_565
-
- options.inJustDecodeBounds = false
-
- val image = BitmapFactory.decodeFile(images[position], options)
-
- setImageBitmap(image)
- }
+ Glide.with(holder.view)
+ .load(images[position])
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .skipMemoryCache(true)
+ .placeholder(progressDrawable)
+ .error(R.drawable.image_broken_variant)
+ .into(holder.view as ImageView)
}
override fun getItemCount() = images.size
diff --git a/app/src/main/java/xyz/quaver/pupil/types/TagSuggestion.kt b/app/src/main/java/xyz/quaver/pupil/types/TagSuggestion.kt
index 2b39db9f..f3795283 100644
--- a/app/src/main/java/xyz/quaver/pupil/types/TagSuggestion.kt
+++ b/app/src/main/java/xyz/quaver/pupil/types/TagSuggestion.kt
@@ -1,3 +1,21 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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.types
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
@@ -5,7 +23,7 @@ import kotlinx.android.parcel.Parcelize
import xyz.quaver.hitomi.Suggestion
@Parcelize
-data class TagSuggestion constructor(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion {
+data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion {
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
override fun getBody(): String {
diff --git a/app/src/main/java/xyz/quaver/pupil/types/Tags.kt b/app/src/main/java/xyz/quaver/pupil/types/Tags.kt
index 70d0a119..8bf8011f 100644
--- a/app/src/main/java/xyz/quaver/pupil/types/Tags.kt
+++ b/app/src/main/java/xyz/quaver/pupil/types/Tags.kt
@@ -1,3 +1,21 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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.types
import kotlinx.serialization.Serializable
@@ -90,8 +108,8 @@ class Tags(tag: List?) : ArrayList() {
}
}
- fun removeByArea(area: String) {
- filter { it.area == area }.forEach {
+ fun removeByArea(area: String, isNegative: Boolean? = null) {
+ filter { it.area == area && (if(isNegative == null) true else (it.isNegative == isNegative)) }.forEach {
remove(it)
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt
index 57f9ed9a..81f7622d 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt
@@ -1,3 +1,21 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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
import android.app.Activity
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 a19254a9..c4708b98 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
@@ -1,12 +1,35 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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
import android.Manifest
import android.app.Activity
+import android.app.DownloadManager
+import android.content.BroadcastReceiver
+import android.content.Context
import android.content.Intent
+import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.drawable.Animatable
import android.net.Uri
import android.os.Bundle
+import android.os.Environment
import android.text.*
import android.text.style.AlignmentSpan
import android.view.*
@@ -19,6 +42,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
+import androidx.core.content.FileProvider
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.GravityCompat
import androidx.preference.PreferenceManager
@@ -41,7 +65,6 @@ import kotlinx.serialization.list
import kotlinx.serialization.stringify
import ru.noties.markwon.Markwon
import xyz.quaver.hitomi.*
-import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
@@ -55,6 +78,8 @@ import java.net.URL
import java.util.*
import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList
+import kotlin.math.abs
+import kotlin.math.ceil
import kotlin.math.min
import kotlin.math.roundToInt
@@ -66,17 +91,25 @@ class MainActivity : AppCompatActivity() {
DOWNLOAD,
FAVORITE
}
+
+ enum class SortMode {
+ NEWEST,
+ POPULAR
+ }
private val galleries = ArrayList>>()
private var query = ""
set(value) {
field = value
- findViewById(R.id.search_bar_text)
- .setText(query, TextView.BufferType.EDITABLE)
+ with(findViewById(R.id.search_bar_text)) {
+ if (text.toString() != value)
+ setText(query, TextView.BufferType.EDITABLE)
+ }
}
private var mode = Mode.SEARCH
+ private var sortMode = SortMode.NEWEST
private val REQUEST_SETTINGS = 45162
private val REQUEST_LOCK = 561
@@ -132,7 +165,7 @@ class MainActivity : AppCompatActivity() {
cancelFetch()
clearGalleries()
- fetchGalleries(query)
+ fetchGalleries(query, sortMode)
loadBlocks()
}
else -> super.onBackPressed()
@@ -155,7 +188,7 @@ class MainActivity : AppCompatActivity() {
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
val preference = PreferenceManager.getDefaultSharedPreferences(this)
val perPage = preference.getString("per_page", "25")!!.toInt()
- val maxPage = Math.ceil(totalItems / perPage.toDouble()).roundToInt()
+ val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
return when(keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN -> {
@@ -165,7 +198,7 @@ class MainActivity : AppCompatActivity() {
cancelFetch()
clearGalleries()
- fetchGalleries(query)
+ fetchGalleries(query, sortMode)
loadBlocks()
}
}
@@ -179,7 +212,7 @@ class MainActivity : AppCompatActivity() {
cancelFetch()
clearGalleries()
- fetchGalleries(query)
+ fetchGalleries(query, sortMode)
loadBlocks()
}
}
@@ -197,7 +230,7 @@ class MainActivity : AppCompatActivity() {
runOnUiThread {
cancelFetch()
clearGalleries()
- fetchGalleries(query)
+ fetchGalleries(query, sortMode)
loadBlocks()
}
}
@@ -254,14 +287,36 @@ class MainActivity : AppCompatActivity() {
CoroutineScope(Dispatchers.Default).launch {
val update =
- checkUpdate(getString(R.string.release_url), BuildConfig.VERSION_NAME) ?: return@launch
+ checkUpdate(getString(R.string.release_url)) ?: return@launch
+
+ val (url, fileName) = getApkUrl(update) ?: return@launch
+ fileName ?: return@launch
val dialog = AlertDialog.Builder(this@MainActivity).apply {
setTitle(R.string.update_title)
val msg = extractReleaseNote(update, Locale.getDefault().language)
setMessage(Markwon.create(context).toMarkdown(msg))
setPositiveButton(android.R.string.yes) { _, _ ->
- startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.update))))
+ val request = DownloadManager.Request(Uri.parse(url)).apply {
+ setDescription(getString(R.string.update_notification_description))
+ setTitle(getString(R.string.app_name))
+ setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
+ }
+
+ val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
+ val id = manager.enqueue(request)
+
+ registerReceiver(object: BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ val install = Intent(Intent.ACTION_VIEW).apply {
+ flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION
+ setDataAndType(manager.getUriForDownloadedFile(id), manager.getMimeTypeForDownloadedFile(id))
+ }
+
+ startActivity(install)
+ unregisterReceiver(this)
+ }
+ }, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
setNegativeButton(android.R.string.no) { _, _ ->}
}
@@ -284,6 +339,13 @@ class MainActivity : AppCompatActivity() {
main_searchview.translationY = p1.toFloat()
main_recyclerview.scrollBy(0, prevP1 - p1)
+ with(main_fab) {
+ if (prevP1 > p1)
+ hideMenuButton(true)
+ else if (prevP1 < p1)
+ showMenuButton(true)
+ }
+
prevP1 = p1
}
)
@@ -300,7 +362,7 @@ class MainActivity : AppCompatActivity() {
currentPage = 0
query = ""
mode = Mode.SEARCH
- fetchGalleries(query)
+ fetchGalleries(query, sortMode)
loadBlocks()
}
R.id.main_drawer_history -> {
@@ -309,7 +371,7 @@ class MainActivity : AppCompatActivity() {
currentPage = 0
query = ""
mode = Mode.HISTORY
- fetchGalleries(query)
+ fetchGalleries(query, sortMode)
loadBlocks()
}
R.id.main_drawer_downloads -> {
@@ -318,7 +380,7 @@ class MainActivity : AppCompatActivity() {
currentPage = 0
query = ""
mode = Mode.DOWNLOAD
- fetchGalleries(query)
+ fetchGalleries(query, sortMode)
loadBlocks()
}
R.id.main_drawer_favorite -> {
@@ -327,7 +389,7 @@ class MainActivity : AppCompatActivity() {
currentPage = 0
query = ""
mode = Mode.FAVORITE
- fetchGalleries(query)
+ fetchGalleries(query, sortMode)
loadBlocks()
}
R.id.main_drawer_help -> {
@@ -351,9 +413,67 @@ class MainActivity : AppCompatActivity() {
true
}
+ with(main_fab_jump) {
+ setImageResource(R.drawable.ic_jump)
+ setOnClickListener {
+ val preference = PreferenceManager.getDefaultSharedPreferences(context)
+ val perPage = preference.getString("per_page", "25")!!.toInt()
+ val editText = EditText(context)
+
+ AlertDialog.Builder(context).apply {
+ setView(editText)
+ setTitle(R.string.main_jump_title)
+ setMessage(getString(
+ R.string.main_jump_message,
+ currentPage+1,
+ ceil(totalItems / perPage.toDouble()).roundToInt()
+ ))
+
+ setPositiveButton(android.R.string.ok) { _, _ ->
+ currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1
+
+ runOnUiThread {
+ cancelFetch()
+ clearGalleries()
+ fetchGalleries(query, sortMode)
+ loadBlocks()
+ }
+ }
+ }.show()
+ }
+ }
+
+ with(main_fab_id) {
+ setImageResource(R.drawable.numeric)
+ setOnClickListener {
+ val editText = EditText(context)
+
+ AlertDialog.Builder(context).apply {
+ setView(editText)
+ setTitle(R.string.main_open_gallery_by_id)
+
+ setPositiveButton(android.R.string.ok) { _, _ ->
+ CoroutineScope(Dispatchers.Default).launch {
+ try {
+ val intent = Intent(this@MainActivity, ReaderActivity::class.java)
+ val gallery =
+ getGalleryBlock(editText.text.toString().toInt()) ?: throw Exception()
+ intent.putExtra("galleryID", gallery.id)
+
+ startActivity(intent)
+ } catch (e: Exception) {
+ Snackbar.make(main_layout,
+ R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show()
+ }
+ }
+ }
+ }.show()
+ }
+ }
+
setupSearchBar()
setupRecyclerView()
- fetchGalleries(query)
+ fetchGalleries(query, sortMode)
loadBlocks()
}
@@ -367,7 +487,7 @@ class MainActivity : AppCompatActivity() {
cancelFetch()
clearGalleries()
- fetchGalleries(query)
+ fetchGalleries(query, sortMode)
loadBlocks()
}
}
@@ -380,9 +500,9 @@ class MainActivity : AppCompatActivity() {
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
val gallery = galleries[position].first
- intent.putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery))
+ intent.putExtra("galleryID", gallery.id)
- //TODO: Maybe sprinke some transitions will be nice :D
+ //TODO: Maybe sprinkling some transitions will be nice :D
startActivity(intent)
histories.add(gallery.id)
@@ -391,7 +511,7 @@ class MainActivity : AppCompatActivity() {
if (v !is CardView)
return@setOnItemLongClickListener true
- val galleryBlock = galleries[position].first
+ val gallery = galleries[position].first
val view = LayoutInflater.from(this@MainActivity)
.inflate(R.layout.dialog_galleryblock, recyclerView, false)
@@ -400,15 +520,15 @@ class MainActivity : AppCompatActivity() {
}.create()
with(view.main_dialog_download) {
- text = when(GalleryDownloader.get(galleryBlock.id)) {
+ text = when(GalleryDownloader.get(gallery.id)) {
null -> getString(R.string.reader_fab_download)
else -> getString(R.string.reader_fab_download_cancel)
}
- isEnabled = !(adapter as GalleryBlockAdapter).completeFlag.get(galleryBlock.id, false)
+ isEnabled = !(adapter as GalleryBlockAdapter).completeFlag.get(gallery.id, false)
setOnClickListener {
- val downloader = GalleryDownloader.get(galleryBlock.id)
+ val downloader = GalleryDownloader.get(gallery.id)
if (downloader == null)
- GalleryDownloader(context, galleryBlock, true).start()
+ GalleryDownloader(context, gallery.id, true).start()
else {
downloader.cancel()
downloader.clearNotification()
@@ -420,27 +540,27 @@ class MainActivity : AppCompatActivity() {
view.main_dialog_delete.setOnClickListener {
CoroutineScope(Dispatchers.Default).launch {
- with(GalleryDownloader[galleryBlock.id]) {
+ with(GalleryDownloader[gallery.id]) {
this?.cancelAndJoin()
this?.clearNotification()
}
- val cache = File(cacheDir, "imageCache/${galleryBlock.id}")
- val data = getCachedGallery(context, galleryBlock.id)
+ val cache = File(cacheDir, "imageCache/${gallery.id}")
+ val data = getCachedGallery(context, gallery.id)
cache.deleteRecursively()
data.deleteRecursively()
- downloads.remove(galleryBlock.id)
+ downloads.remove(gallery.id)
if (mode == Mode.DOWNLOAD) {
runOnUiThread {
cancelFetch()
clearGalleries()
- fetchGalleries(query)
+ fetchGalleries(query, sortMode)
loadBlocks()
}
}
- (adapter as GalleryBlockAdapter).completeFlag.put(galleryBlock.id, false)
+ (adapter as GalleryBlockAdapter).completeFlag.put(gallery.id, false)
}
dialog.dismiss()
}
@@ -503,7 +623,6 @@ class MainActivity : AppCompatActivity() {
runOnUiThread {
cancelFetch()
clearGalleries()
- fetchGalleries(query)
loadBlocks()
}
@@ -583,7 +702,7 @@ class MainActivity : AppCompatActivity() {
//BOTTOM
//Scrolling DOWN
- if (dist < 0 && currentPage != Math.ceil(totalItems.toDouble()/perPage).roundToInt()-1) {
+ if (dist < 0 && currentPage != ceil(totalItems.toDouble()/perPage).roundToInt()-1) {
with(main_recyclerview.adapter as GalleryBlockAdapter) {
if(!showNext) {
showNext = true
@@ -595,7 +714,7 @@ class MainActivity : AppCompatActivity() {
getChildAt(childCount-1)
}
- val absDist = Math.abs(dist)
+ val absDist = abs(dist)
if (next is LinearLayout) {
val icon = next.findViewById(R.id.icon_next)
@@ -690,72 +809,52 @@ class MainActivity : AppCompatActivity() {
setOnMenuItemClickListener {
when(it.itemId) {
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS)
- R.id.main_menu_jump -> {
- val preference = PreferenceManager.getDefaultSharedPreferences(context)
- val perPage = preference.getString("per_page", "25")!!.toInt()
- val editText = EditText(context)
+ R.id.main_menu_sort_newest -> {
+ sortMode = SortMode.NEWEST
+ it.isChecked = true
- AlertDialog.Builder(context).apply {
- setView(editText)
- setTitle(R.string.main_jump_title)
- setMessage(getString(
- R.string.main_jump_message,
- currentPage+1,
- Math.ceil(totalItems / perPage.toDouble()).roundToInt()
- ))
+ runOnUiThread {
+ currentPage = 0
- setPositiveButton(android.R.string.ok) { _, _ ->
- currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1
-
- runOnUiThread {
- cancelFetch()
- clearGalleries()
- fetchGalleries(query)
- loadBlocks()
- }
- }
- }.show()
+ cancelFetch()
+ clearGalleries()
+ fetchGalleries(query, sortMode)
+ loadBlocks()
+ }
}
- R.id.main_menu_id -> {
- val editText = EditText(context)
+ R.id.main_menu_sort_popular -> {
+ sortMode = SortMode.POPULAR
+ it.isChecked = true
- AlertDialog.Builder(context).apply {
- setView(editText)
- setTitle(R.string.main_open_gallery_by_id)
+ runOnUiThread {
+ currentPage = 0
- setPositiveButton(android.R.string.ok) { _, _ ->
- CoroutineScope(Dispatchers.Default).launch {
- try {
- val intent = Intent(this@MainActivity, ReaderActivity::class.java)
- val gallery =
- getGalleryBlock(editText.text.toString().toInt()) ?: throw Exception()
- intent.putExtra(
- "galleryblock",
- Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery)
- )
-
- startActivity(intent)
- } catch (e: Exception) {
- Snackbar.make(main_layout,
- R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show()
- }
- }
- }
- }.show()
+ cancelFetch()
+ clearGalleries()
+ fetchGalleries(query, sortMode)
+ loadBlocks()
+ }
}
}
}
setOnQueryChangeListener { _, query ->
- clearSuggestions()
-
- if (query.isEmpty() or query.endsWith(' '))
- return@setOnQueryChangeListener
-
- val currentQuery = query.split(" ").last().replace('_', ' ')
+ this@MainActivity.query = query
suggestionJob?.cancel()
+ clearSuggestions()
+
+ if (query.isEmpty() or query.endsWith(' ')) {
+ swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
+ TagSuggestion(it.tag, -1, "", it.area ?: "tag")
+ })
+
+ return@setOnQueryChangeListener
+ }
+
+ val currentQuery = query.split(" ").last().replace('_', ' ')
+
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
val suggestions = ArrayList(getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) })
@@ -774,13 +873,14 @@ class MainActivity : AppCompatActivity() {
}
setOnBindSuggestionCallback { suggestionView, leftIcon, textView, item, _ ->
- val suggestion = item as TagSuggestion
- val tag = "${suggestion.n}:${suggestion.s.replace(Regex("\\s"), "_")}"
+ item as TagSuggestion
+
+ val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}"
leftIcon.setImageDrawable(
ResourcesCompat.getDrawable(
resources,
- when(suggestion.n) {
+ when(item.n) {
"female" -> R.drawable.ic_gender_female
"male" -> R.drawable.ic_gender_male
"language" -> R.drawable.ic_translate
@@ -800,7 +900,6 @@ class MainActivity : AppCompatActivity() {
else
setImageResource(R.drawable.ic_star_empty)
- visibility = View.VISIBLE
rotation = 0f
isEnabled = true
@@ -827,13 +926,13 @@ class MainActivity : AppCompatActivity() {
}
}
- if (suggestion.t == -1) {
- textView.text = suggestion.s
+ if (item.t == -1) {
+ textView.text = item.s
} else {
- val text = "${suggestion.s}\n ${suggestion.t}"
+ val text = "${item.s}\n ${item.t}"
val len = text.length
- val left = suggestion.s.length
+ val left = item.s.length
textView.text = SpannableString(text).apply {
val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE)
@@ -846,14 +945,13 @@ class MainActivity : AppCompatActivity() {
setOnSearchListener(object : FloatingSearchView.OnSearchListener {
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
- val suggestion = searchSuggestion as TagSuggestion
+ if (searchSuggestion !is TagSuggestion)
+ return
with(searchInputView.text) {
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
- append("${suggestion.n}:${suggestion.s.replace(Regex("\\s"), "_")} ")
+ append("${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")} ")
}
-
- clearSuggestions()
}
override fun onSearchAction(currentQuery: String?) {
@@ -863,7 +961,7 @@ class MainActivity : AppCompatActivity() {
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
override fun onFocus() {
- if (searchInputView.text.isEmpty())
+ if (query.isEmpty() or query.endsWith(' '))
swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
})
@@ -872,18 +970,12 @@ class MainActivity : AppCompatActivity() {
override fun onFocusCleared() {
suggestionJob?.cancel()
- val query = searchInputView.text.toString()
-
- if (query != this@MainActivity.query) {
- this@MainActivity.query = query
-
- runOnUiThread {
- cancelFetch()
- clearGalleries()
- currentPage = 0
- fetchGalleries(query)
- loadBlocks()
- }
+ runOnUiThread {
+ cancelFetch()
+ clearGalleries()
+ currentPage = 0
+ fetchGalleries(query, sortMode)
+ loadBlocks()
}
}
})
@@ -912,9 +1004,8 @@ class MainActivity : AppCompatActivity() {
main_progressbar.show()
}
- private fun fetchGalleries(query: String) {
+ private fun fetchGalleries(query: String, sortMode: SortMode) {
val preference = PreferenceManager.getDefaultSharedPreferences(this)
- val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
val defaultQuery = preference.getString("default_query", "")!!
galleryIDs = null
@@ -927,12 +1018,14 @@ class MainActivity : AppCompatActivity() {
Mode.SEARCH -> {
when {
query.isEmpty() and defaultQuery.isEmpty() -> {
- fetchNozomi(start = currentPage*perPage, count = perPage).let {
- totalItems = it.second
- it.first
+ when(sortMode) {
+ SortMode.POPULAR -> getGalleryIDsFromNozomi(null, "popular", "all")
+ else -> getGalleryIDsFromNozomi(null, "index", "all")
+ }.apply {
+ totalItems = size
}
}
- else -> doSearch("$defaultQuery $query").apply {
+ else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).apply {
totalItems = size
}
}
@@ -985,7 +1078,6 @@ class MainActivity : AppCompatActivity() {
private fun loadBlocks() {
val preference = PreferenceManager.getDefaultSharedPreferences(this)
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
- val defaultQuery = preference.getString("default_query", "")!!
loadingJob = CoroutineScope(Dispatchers.IO).launch {
val galleryIDs = galleryIDs?.await()
@@ -999,12 +1091,7 @@ class MainActivity : AppCompatActivity() {
return@launch
}
- when {
- query.isEmpty() and defaultQuery.isEmpty() and (mode == Mode.SEARCH) ->
- galleryIDs
- else ->
- galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size))
- }.chunked(5).let { chunks ->
+ galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size)).chunked(5).let { chunks ->
for (chunk in chunks)
chunk.map { galleryID ->
async {
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/PatternLockFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/PatternLockFragment.kt
index 11d8a53b..f542a019 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/PatternLockFragment.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/PatternLockFragment.kt
@@ -1,3 +1,21 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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
import android.os.Bundle
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 e29b3111..3d3270ac 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
@@ -1,3 +1,21 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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
import android.content.Intent
@@ -21,13 +39,7 @@ import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import kotlinx.io.IOException
import kotlinx.serialization.ImplicitReflectionSerializer
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.JsonConfiguration
-import xyz.quaver.hitomi.GalleryBlock
-import xyz.quaver.hitomi.getGalleryBlock
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.ReaderAdapter
@@ -37,8 +49,8 @@ import xyz.quaver.pupil.util.ItemClickSupport
class ReaderActivity : AppCompatActivity() {
+ private var galleryID = 0
private val images = ArrayList()
- private lateinit var galleryBlock: GalleryBlock
private var gallerySize = 0
private var currentPage = 0
@@ -66,6 +78,9 @@ class ReaderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ title = getString(R.string.reader_loading)
+ supportActionBar?.setDisplayHomeAsUpEnabled(false)
+
favorites = (application as Pupil).favorites
window.setFlags(
@@ -76,16 +91,13 @@ class ReaderActivity : AppCompatActivity() {
handleIntent(intent)
- Crashlytics.setInt("GalleryID", galleryBlock.id)
+ Crashlytics.setInt("GalleryID", galleryID)
- if (!::galleryBlock.isInitialized) {
+ if (galleryID == 0) {
onBackPressed()
return
}
- supportActionBar?.title = galleryBlock.title
- supportActionBar?.setDisplayHomeAsUpEnabled(false)
-
initDownloader()
initView()
@@ -106,25 +118,16 @@ class ReaderActivity : AppCompatActivity() {
if (uri != null && lastPathSegment != null) {
val nonNumber = Regex("[^-?0-9]+")
- val galleryID = when (uri.host) {
+ galleryID = when (uri.host) {
"hitomi.la" -> lastPathSegment.replace(nonNumber, "").toInt()
"히요비.asia" -> lastPathSegment.toInt()
"xn--9w3b15m8vo.asia" -> lastPathSegment.toInt()
"e-hentai.org" -> uri.pathSegments[1].toInt()
else -> return
}
-
- runBlocking {
- CoroutineScope(Dispatchers.IO).launch {
- galleryBlock = getGalleryBlock(galleryID) ?: return@launch
- }.join()
- }
}
} else {
- galleryBlock = Json(JsonConfiguration.Stable).parse(
- GalleryBlock.serializer(),
- intent.getStringExtra("galleryblock")!!
- )
+ galleryID = intent.getIntExtra("galleryID", 0)
}
}
@@ -148,7 +151,7 @@ class ReaderActivity : AppCompatActivity() {
with(menu?.findItem(R.id.reader_menu_favorite)) {
this ?: return@with
- if (favorites.contains(galleryBlock.id))
+ if (favorites.contains(galleryID))
(icon as Animatable).start()
}
@@ -176,7 +179,7 @@ class ReaderActivity : AppCompatActivity() {
dialog.show()
}
R.id.reader_menu_favorite -> {
- val id = galleryBlock.id
+ val id = galleryID
val favorite = menu?.findItem(R.id.reader_menu_favorite) ?: return true
if (favorites.contains(id)) {
@@ -215,32 +218,26 @@ class ReaderActivity : AppCompatActivity() {
}
private fun initDownloader() {
- var d: GalleryDownloader? = GalleryDownloader.get(galleryBlock.id)
+ var d: GalleryDownloader? = GalleryDownloader.get(galleryID)
- if (d == null) {
- try {
- d = GalleryDownloader(this, galleryBlock)
- } catch (e: IOException) {
- Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
- finish()
- return
- }
- }
+ if (d == null)
+ d = GalleryDownloader(this, galleryID)
downloader = d.apply {
onReaderLoadedHandler = {
CoroutineScope(Dispatchers.Main).launch {
+ title = it.title
with(reader_download_progressbar) {
- max = it.size
+ max = it.readerItems.size
progress = 0
}
with(reader_progressbar) {
- max = it.size
+ max = it.readerItems.size
progress = 0
}
- gallerySize = it.size
- menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.size}"
+ gallerySize = it.readerItems.size
+ menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.readerItems.size}"
}
}
onProgressHandler = {
@@ -262,8 +259,7 @@ class ReaderActivity : AppCompatActivity() {
}
}
onErrorHandler = {
- if (it is IOException)
- Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
+ Snackbar.make(reader_layout, it.message ?: it.javaClass.name, Snackbar.LENGTH_INDEFINITE).show()
downloader.download = false
}
onCompleteHandler = {
@@ -317,6 +313,11 @@ class ReaderActivity : AppCompatActivity() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
+ if (dy < 0)
+ this@ReaderActivity.reader_fab.showMenuButton(true)
+ else if (dy > 0)
+ this@ReaderActivity.reader_fab.hideMenuButton(true)
+
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
if (layoutManager.findFirstVisibleItemPosition() == -1)
@@ -341,18 +342,24 @@ class ReaderActivity : AppCompatActivity() {
}
}
- reader_fab_fullscreen.setOnClickListener {
- isFullscreen = true
- fullscreen(isFullscreen)
+ with(reader_fab_download) {
+ setImageResource(R.drawable.ic_download)
+ setOnClickListener {
+ downloader.download = !downloader.download
- reader_fab.close(true)
+ if (!downloader.download)
+ downloader.clearNotification()
+ }
}
- reader_fab_download.setOnClickListener {
- downloader.download = !downloader.download
+ with(reader_fab_fullscreen) {
+ setImageResource(R.drawable.ic_fullscreen)
+ setOnClickListener {
+ isFullscreen = true
+ fullscreen(isFullscreen)
- if (!downloader.download)
- downloader.clearNotification()
+ this@ReaderActivity.reader_fab.close(true)
+ }
}
}
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 e4229365..0d0bc35c 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt
@@ -1,3 +1,21 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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
import android.app.Activity
@@ -222,14 +240,14 @@ class SettingsActivity : AppCompatActivity() {
addAll(languages.values)
}
)
- if (tags.any { it.area == "language" }) {
+ if (tags.any { it.area == "language" && !it.isNegative }) {
val tag = languages[tags.first { it.area == "language" }.tag]
if (tag != null) {
setSelection(
@Suppress("UNCHECKED_CAST")
(adapter as ArrayAdapter).getPosition(tag)
)
- tags.removeByArea("language")
+ tags.removeByArea("language", false)
}
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt b/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt
index 9be00515..d78a565f 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt
@@ -1,3 +1,21 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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.app.PendingIntent
@@ -13,12 +31,13 @@ import kotlinx.coroutines.*
import kotlinx.io.IOException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
-import kotlinx.serialization.list
-import xyz.quaver.hitomi.*
+import xyz.quaver.hitomi.Reader
+import xyz.quaver.hitomi.getReader
+import xyz.quaver.hitomi.getReferer
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.user_agent
-import xyz.quaver.pupil.R
import xyz.quaver.pupil.Pupil
+import xyz.quaver.pupil.R
import xyz.quaver.pupil.ui.ReaderActivity
import java.io.File
import java.io.FileOutputStream
@@ -30,7 +49,7 @@ import kotlin.concurrent.schedule
class GalleryDownloader(
base: Context,
- private val galleryBlock: GalleryBlock,
+ private val galleryID: Int,
_notify: Boolean = false
) : ContextWrapper(base) {
@@ -41,10 +60,10 @@ class GalleryDownloader(
set(value) {
if (value) {
field = true
- notificationManager.notify(galleryBlock.id, notificationBuilder.build())
+ notificationManager.notify(galleryID, notificationBuilder.build())
- val data = getCachedGallery(this, galleryBlock.id)
- val cache = File(cacheDir, "imageCache/${galleryBlock.id}")
+ val data = getCachedGallery(this, galleryID)
+ val cache = File(cacheDir, "imageCache/$galleryID")
if (File(cache, "images").exists() && !data.exists()) {
cache.copyRecursively(data, true)
@@ -54,7 +73,7 @@ class GalleryDownloader(
if (reader?.isActive == false && downloadJob?.isActive != true)
field = false
- downloads.add(galleryBlock.id)
+ downloads.add(galleryID)
} else {
field = false
}
@@ -78,60 +97,64 @@ class GalleryDownloader(
companion object : SparseArray()
init {
- put(galleryBlock.id, this)
+ put(galleryID, this)
initNotification()
reader = CoroutineScope(Dispatchers.IO).async {
- download = _notify
- val json = Json(JsonConfiguration.Stable)
- val serializer = ReaderItem.serializer().list
+ try {
+ download = _notify
+ val json = Json(JsonConfiguration.Stable)
+ val serializer = Reader.serializer()
- //Check cache
- val cache = File(getCachedGallery(this@GalleryDownloader, galleryBlock.id), "reader.json")
+ //Check cache
+ val cache = File(getCachedGallery(this@GalleryDownloader, galleryID), "reader.json")
- if (cache.exists()) {
- val cached = json.parse(serializer, cache.readText())
+ if (cache.exists()) {
+ val cached = json.parse(serializer, cache.readText())
- if (cached.isNotEmpty()) {
- useHiyobi = when {
- cached.first().url.contains("hitomi.la") -> false
- else -> true
+ if (cached.readerItems.isNotEmpty()) {
+ useHiyobi = when {
+ cached.readerItems[0].url.contains("hitomi.la") -> false
+ else -> true
+ }
+
+ onReaderLoadedHandler?.invoke(cached)
+
+ return@async cached
}
-
- onReaderLoadedHandler?.invoke(cached)
-
- return@async cached
}
- }
- //Cache doesn't exist. Load from internet
- val reader = when {
- useHiyobi -> {
- xyz.quaver.hiyobi.getReader(galleryBlock.id).let {
- when {
- it.isEmpty() -> {
- useHiyobi = false
- getReader(galleryBlock.id)
+ //Cache doesn't exist. Load from internet
+ val reader = when {
+ useHiyobi -> {
+ xyz.quaver.hiyobi.getReader(galleryID).let {
+ when {
+ it.readerItems.isEmpty() -> {
+ useHiyobi = false
+ getReader(galleryID)
+ }
+ else -> it
}
- else -> it
}
}
+ else -> {
+ getReader(galleryID)
+ }
}
- else -> {
- getReader(galleryBlock.id)
+
+ if (reader.readerItems.isNotEmpty()) {
+ //Save cache
+ if (cache.parentFile?.exists() == false)
+ cache.parentFile!!.mkdirs()
+
+ cache.writeText(json.stringify(serializer, reader))
}
+
+ reader
+ } catch (e: Exception) {
+ Reader("", listOf())
}
-
- if (reader.isNotEmpty()) {
- //Save cache
- if (cache.parentFile?.exists() == false)
- cache.parentFile!!.mkdirs()
-
- cache.writeText(json.stringify(serializer, reader))
- }
-
- reader
}
}
@@ -141,37 +164,30 @@ class GalleryDownloader(
downloadJob = CoroutineScope(Dispatchers.Default).launch {
val reader = reader!!.await()
- if (reader.isEmpty())
- onErrorHandler?.invoke(IOException("Couldn't retrieve Reader"))
+ if (reader.readerItems.isEmpty()) {
+ onErrorHandler?.invoke(IOException(getString(R.string.unable_to_connect)))
+ return@launch
+ }
val list = ArrayList()
onReaderLoadedHandler?.invoke(reader)
notificationBuilder
- .setProgress(reader.size, 0, false)
- .setContentText("0/${reader.size}")
+ .setProgress(reader.readerItems.size, 0, false)
+ .setContentText("0/${reader.readerItems.size}")
- reader.chunked(4).forEachIndexed { chunkIndex, chunked ->
+ reader.readerItems.chunked(4).forEachIndexed { chunkIndex, chunked ->
chunked.mapIndexed { i, it ->
val index = chunkIndex*4+i
- onProgressHandler?.invoke(index)
-
- notificationBuilder
- .setProgress(reader.size, index, false)
- .setContentText("$index/${reader.size}")
-
- if (download)
- notificationManager.notify(galleryBlock.id, notificationBuilder.build())
-
async(Dispatchers.IO) {
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
val name = "$index".padStart(4, '0')
val ext = url.split('.').last()
- val cache = File(getCachedGallery(this@GalleryDownloader, galleryBlock.id), "images/$name.$ext")
+ val cache = File(getCachedGallery(this@GalleryDownloader, galleryID), "images/$name.$ext")
if (!cache.exists())
try {
@@ -180,7 +196,7 @@ class GalleryDownloader(
setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie)
} else
- setRequestProperty("Referer", getReferer(galleryBlock.id))
+ setRequestProperty("Referer", getReferer(galleryID))
if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs()
@@ -193,31 +209,43 @@ class GalleryDownloader(
onErrorHandler?.invoke(e)
notificationBuilder
- .setContentTitle(galleryBlock.title)
+ .setContentTitle(reader.title)
.setContentText(getString(R.string.reader_notification_error))
.setProgress(0, 0, false)
- notificationManager.notify(galleryBlock.id, notificationBuilder.build())
+ notificationManager.notify(galleryID, notificationBuilder.build())
}
cache.absolutePath
}
}.forEach {
list.add(it.await())
+
+ val index = list.size
+
+ onProgressHandler?.invoke(index)
+
+ notificationBuilder
+ .setProgress(reader.readerItems.size, index, false)
+ .setContentText("$index/${reader.readerItems.size}")
+
+ if (download)
+ notificationManager.notify(galleryID, notificationBuilder.build())
+
onDownloadedHandler?.invoke(list)
}
}
Timer(false).schedule(1000) {
notificationBuilder
- .setContentTitle(galleryBlock.title)
+ .setContentTitle(reader.title)
.setContentText(getString(R.string.reader_notification_complete))
.setProgress(0, 0, false)
if (download) {
- File(cacheDir, "imageCache/${galleryBlock.id}").let {
+ File(cacheDir, "imageCache/${galleryID}").let {
if (it.exists()) {
- val target = File(getDownloadDirectory(this@GalleryDownloader), galleryBlock.id.toString())
+ val target = File(getDownloadDirectory(this@GalleryDownloader), galleryID.toString())
if (!target.exists())
target.mkdirs()
@@ -227,7 +255,7 @@ class GalleryDownloader(
}
}
- notificationManager.notify(galleryBlock.id, notificationBuilder.build())
+ notificationManager.notify(galleryID, notificationBuilder.build())
download = false
}
@@ -235,20 +263,20 @@ class GalleryDownloader(
onCompleteHandler?.invoke()
}
- remove(galleryBlock.id)
+ remove(galleryID)
}
}
fun cancel() {
downloadJob?.cancel()
- remove(galleryBlock.id)
+ remove(galleryID)
}
suspend fun cancelAndJoin() {
downloadJob?.cancelAndJoin()
- remove(galleryBlock.id)
+ remove(galleryID)
}
fun invokeOnReaderLoaded() {
@@ -258,7 +286,7 @@ class GalleryDownloader(
}
fun clearNotification() {
- notificationManager.cancel(galleryBlock.id)
+ notificationManager.cancel(galleryID)
}
fun invokeOnNotifyChanged() {
@@ -267,22 +295,28 @@ class GalleryDownloader(
private fun initNotification() {
val intent = Intent(this, ReaderActivity::class.java).apply {
- putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), galleryBlock))
+ putExtra("galleryID", galleryID)
}
val pendingIntent = TaskStackBuilder.create(this).run {
addNextIntentWithParentStack(intent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
+ notificationManager = NotificationManagerCompat.from(this)
+
notificationBuilder = NotificationCompat.Builder(this, "download").apply {
- setContentTitle(galleryBlock.title)
+ setContentTitle(getString(R.string.reader_loading))
setContentText(getString(R.string.reader_notification_text))
setSmallIcon(R.drawable.ic_download)
setContentIntent(pendingIntent)
setProgress(0, 0, true)
priority = NotificationCompat.PRIORITY_LOW
}
- notificationManager = NotificationManagerCompat.from(this)
+
+ CoroutineScope(Dispatchers.Default).launch {
+ while (reader == null) ;
+ notificationBuilder.setContentTitle(reader.await().title)
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/util/file.kt b/app/src/main/java/xyz/quaver/pupil/util/file.kt
index a1823442..07032896 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/file.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/file.kt
@@ -1,3 +1,21 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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
@@ -16,6 +34,7 @@ fun getCachedGallery(context: Context, galleryID: Int): File {
}
}
+@Suppress("DEPRECATION")
fun getDownloadDirectory(context: Context): File? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
context.getExternalFilesDir("Pupil")
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 1815b58a..665ccdfe 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/history.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/history.kt
@@ -1,3 +1,21 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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 kotlinx.serialization.ImplicitReflectionSerializer
@@ -11,7 +29,7 @@ class Histories(private val file: File) : ArrayList() {
init {
if (!file.exists())
- file.parentFile.mkdirs()
+ file.parentFile?.mkdirs()
try {
load()
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 13f87003..15970b02 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/lock.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/lock.kt
@@ -1,3 +1,21 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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
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 1461f75c..fec736d6 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/update.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/update.kt
@@ -1,7 +1,25 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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.util.Log
import kotlinx.serialization.json.*
+import xyz.quaver.pupil.BuildConfig
import java.net.URL
fun getReleases(url: String) : JsonArray {
@@ -14,26 +32,27 @@ fun getReleases(url: String) : JsonArray {
}
}
-fun checkUpdate(url: String, currentVersion: String) : JsonObject? {
+fun checkUpdate(url: String) : JsonObject? {
val releases = getReleases(url)
if (releases.isEmpty())
return null
- val latestVersion = releases[0].jsonObject["tag_name"]?.content
+ return releases.firstOrNull {
+ if (BuildConfig.PRERELEASE) {
+ BuildConfig.VERSION_NAME != it.jsonObject["tag_name"]?.content
+ } else {
+ it.jsonObject["prerelease"]?.boolean == false &&
+ BuildConfig.VERSION_NAME != (it.jsonObject["tag_name"]?.content ?: "")
+ }
+ }?.jsonObject
+}
- return when {
- currentVersion.split('-').size == 1 -> {
- when {
- currentVersion != latestVersion -> releases[0].jsonObject
- else -> null
- }
- }
- else -> {
- when {
- (currentVersion.split('-')[0] == latestVersion) -> releases[0].jsonObject
- else -> null
- }
- }
+fun getApkUrl(releases: JsonObject) : Pair? {
+ releases["assets"]?.jsonArray?.forEach {
+ if (Regex("Pupil-v(\\d+\\.)+\\d+\\.apk").matches(it.jsonObject["name"]?.content ?: ""))
+ return Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content)
}
+
+ return null
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_download.xml b/app/src/main/res/drawable/ic_download.xml
index c95797c1..aa88433b 100644
--- a/app/src/main/res/drawable/ic_download.xml
+++ b/app/src/main/res/drawable/ic_download.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/drawable/ic_numeric.xml b/app/src/main/res/drawable/ic_numeric.xml
deleted file mode 100644
index fc54c34a..00000000
--- a/app/src/main/res/drawable/ic_numeric.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/image_broken_variant.xml b/app/src/main/res/drawable/image_broken_variant.xml
new file mode 100644
index 00000000..c4aa1d54
--- /dev/null
+++ b/app/src/main/res/drawable/image_broken_variant.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/numeric.xml b/app/src/main/res/drawable/numeric.xml
index fc54c34a..db07bdc4 100644
--- a/app/src/main/res/drawable/numeric.xml
+++ b/app/src/main/res/drawable/numeric.xml
@@ -4,5 +4,5 @@
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
-
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/sort_variant.xml b/app/src/main/res/drawable/sort_variant.xml
new file mode 100644
index 00000000..fdea2e49
--- /dev/null
+++ b/app/src/main/res/drawable/sort_variant.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main_content.xml b/app/src/main/res/layout/activity_main_content.xml
index bde8ddf4..38049787 100644
--- a/app/src/main/res/layout/activity_main_content.xml
+++ b/app/src/main/res/layout/activity_main_content.xml
@@ -56,6 +56,30 @@
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
+
+
+
+
+
+
+
+
@@ -55,7 +56,6 @@
android:id="@+id/reader_fab_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- app:srcCompat="@drawable/ic_downloading"
app:fab_label="@string/reader_fab_download"
app:fab_size="mini"/>
@@ -63,7 +63,6 @@
android:id="@+id/reader_fab_fullscreen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- app:srcCompat="@drawable/ic_fullscreen"
app:fab_label="@string/reader_fab_fullscreen"
app:fab_size="mini"/>
diff --git a/app/src/main/res/layout/item_reader.xml b/app/src/main/res/layout/item_reader.xml
index 9f5abe22..eb9909bd 100644
--- a/app/src/main/res/layout/item_reader.xml
+++ b/app/src/main/res/layout/item_reader.xml
@@ -3,6 +3,7 @@
android:contentDescription="@string/reader_imageview_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:minHeight="100dp"
android:paddingBottom="8dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>
\ No newline at end of file
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
index 82a54109..b94c8378 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/main.xml
@@ -2,15 +2,19 @@
なしロックを無効にしますか?
+ ロード中
+ ソート
+ 投稿日時順
+ 人気順
\ 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 a1fe7f96..f8839501 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -54,7 +54,7 @@
hitomi.la에 연결할 수 없습니다%1$d 페이지로 이동접속 불가 현상 안내
- 최근 https 차단으로 접속이 안 되는 경우가 발생하고 있습니다\n이 경우 플레이스토어에서 SNIper앱을 이용하시면 정상이용이 가능합니다.
+ 최근 https 차단으로 접속이 안 되는 경우가 발생하고 있습니다 이 경우 플레이스토어에서 Intra앱을 이용하시면 정상이용이 가능합니다.갤러리 내보내기내보내기 완료폴더 열기
@@ -79,4 +79,8 @@
잠금이 일치하지 않습니다. 다시 시도하세요.없음잠금을 해제할까요?
+ 로딩중
+ 정렬
+ 인기순
+ 시간순
\ 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 3a2738d3..76341623 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,7 +1,7 @@
Pupil
- https://api.github.com/repos/tom5079/Pupil-issue/releases
+ https://api.github.com/repos/tom5079/Pupil/releasesPupil-v(\\d+\\.)+\\d+\\.apkhttp://bit.ly/2EZDClw
@@ -45,6 +45,10 @@
Email me!Kakaotalk
+ Sort
+ Newest
+ Popular
+
Jump to pageCurrent page: %1$d\nMaximum page: %2$dOpen Gallery by ID
@@ -71,6 +75,7 @@
Type: %1$sLanguage: %1$s
+ LoadingGo to pageFullscreenBackground download
diff --git a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt
index c4a08536..fae6b731 100644
--- a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt
+++ b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt
@@ -1,6 +1,25 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2019 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 .
+ */
+
+@file:Suppress("UNUSED_VARIABLE")
+
package xyz.quaver.pupil
-import kotlinx.serialization.ImplicitReflectionSerializer
import org.junit.Test
/**
@@ -13,7 +32,10 @@ class ExampleUnitTest {
@Test
fun test() {
+ val current = "0.1"
+ val latest = "0.2"
+ print(current < latest)
}
}
diff --git a/build.gradle b/build.gradle
index 8695ee94..548c220f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.3.31'
+ ext.kotlin_version = '1.3.41'
repositories {
google()
jcenter()
@@ -14,7 +14,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
- classpath 'com.google.gms:google-services:4.2.0'
+ classpath 'com.google.gms:google-services:4.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'io.fabric.tools:gradle:1.29.0'
diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/common.kt b/libpupil/src/main/java/xyz/quaver/hitomi/common.kt
index bc3c181b..9e35af1d 100644
--- a/libpupil/src/main/java/xyz/quaver/hitomi/common.kt
+++ b/libpupil/src/main/java/xyz/quaver/hitomi/common.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2019 tom5079
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package xyz.quaver.hitomi
const val protocol = "https:"
diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/galleries.kt b/libpupil/src/main/java/xyz/quaver/hitomi/galleries.kt
index ffbe2a98..3f292dca 100644
--- a/libpupil/src/main/java/xyz/quaver/hitomi/galleries.kt
+++ b/libpupil/src/main/java/xyz/quaver/hitomi/galleries.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2019 tom5079
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package xyz.quaver.hitomi
import org.jsoup.Jsoup
diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/galleryblock.kt b/libpupil/src/main/java/xyz/quaver/hitomi/galleryblock.kt
index 3139cfa7..4196d98e 100644
--- a/libpupil/src/main/java/xyz/quaver/hitomi/galleryblock.kt
+++ b/libpupil/src/main/java/xyz/quaver/hitomi/galleryblock.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2019 tom5079
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package xyz.quaver.hitomi
import kotlinx.serialization.Serializable
diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/reader.kt b/libpupil/src/main/java/xyz/quaver/hitomi/reader.kt
new file mode 100644
index 00000000..1b1d6b08
--- /dev/null
+++ b/libpupil/src/main/java/xyz/quaver/hitomi/reader.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2019 tom5079
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package xyz.quaver.hitomi
+
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonConfiguration
+import kotlinx.serialization.list
+import org.jsoup.Jsoup
+import xyz.quaver.hiyobi.HiyobiReader
+import java.net.URL
+
+fun getReferer(galleryID: Int) = "https://hitomi.la/reader/$galleryID.html"
+fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
+
+fun webpReaderFromReader(reader: Reader) : Reader {
+ if (reader is HiyobiReader)
+ return reader
+
+ return Reader(reader.title, reader.readerItems.map {
+ ReaderItem(
+ if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url,
+ it.galleryInfo
+ )
+ })
+}
+
+@Serializable
+data class GalleryInfo(
+ val width: Int,
+ val haswebp: Int,
+ val name: String,
+ val height: Int
+)
+@Serializable
+data class ReaderItem(
+ val url: String,
+ val galleryInfo: GalleryInfo?
+)
+
+@Serializable
+open class Reader(val title: String, val readerItems: List)
+
+//Set header `Referer` to reader url to avoid 403 error
+fun getReader(galleryID: Int) : Reader {
+ val readerUrl = "https://hitomi.la/reader/$galleryID.html"
+ val galleryInfoUrl = "https://ltn.hitomi.la/galleries/$galleryID.js"
+
+ val doc = Jsoup.connect(readerUrl).get()
+
+ val title = doc.title()
+
+ val images = doc.select(".img-url").map {
+ protocol + urlFromURL(it.text())
+ }
+
+ val galleryInfo = ArrayList()
+
+ galleryInfo.addAll(
+ Json(JsonConfiguration.Stable).parse(
+ GalleryInfo.serializer().list,
+ Regex("""\[.+]""").find(
+ URL(galleryInfoUrl).readText()
+ )?.value ?: "[]"
+ )
+ )
+
+ if (images.size > galleryInfo.size)
+ galleryInfo.addAll(arrayOfNulls(images.size - galleryInfo.size))
+
+ return Reader(title, (images zip galleryInfo).map {
+ ReaderItem(it.first, it.second)
+ })
+}
\ No newline at end of file
diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/readers.kt b/libpupil/src/main/java/xyz/quaver/hitomi/readers.kt
deleted file mode 100644
index 0be173e9..00000000
--- a/libpupil/src/main/java/xyz/quaver/hitomi/readers.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package xyz.quaver.hitomi
-
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.JsonConfiguration
-import kotlinx.serialization.list
-import org.jsoup.Jsoup
-import java.net.URL
-
-fun getReferer(galleryID: Int) = "https://hitomi.la/reader/$galleryID.html"
-
-@Serializable
-data class GalleryInfo(
- val width: Int,
- val haswebp: Int,
- val name: String,
- val height: Int
-)
-@Serializable
-data class ReaderItem(
- val url: String,
- val galleryInfo: GalleryInfo?
-)
-typealias Reader = List
-//Set header `Referer` to reader url to avoid 403 error
-fun getReader(galleryID: Int) : Reader {
- val readerUrl = "https://hitomi.la/reader/$galleryID.html"
- val galleryInfoUrl = "https://ltn.hitomi.la/galleries/$galleryID.js"
-
- try {
- val doc = Jsoup.connect(readerUrl).get()
-
- val images = doc.select(".img-url").map {
- protocol + urlFromURL(it.text())
- }
-
- val galleryInfo = ArrayList()
-
- galleryInfo.addAll(
- Json(JsonConfiguration.Stable).parse(
- GalleryInfo.serializer().list,
- Regex("""\[.+]""").find(
- URL(galleryInfoUrl).readText()
- )?.value ?: "[]"
- )
- )
-
- if (images.size > galleryInfo.size)
- galleryInfo.addAll(arrayOfNulls(images.size - galleryInfo.size))
-
- return (images zip galleryInfo).map {
- ReaderItem(it.first, it.second)
- }
- } catch (e: Exception) {
- return emptyList()
- }
-}
\ No newline at end of file
diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/results.kt b/libpupil/src/main/java/xyz/quaver/hitomi/results.kt
index 0142dd2d..ee51d067 100644
--- a/libpupil/src/main/java/xyz/quaver/hitomi/results.kt
+++ b/libpupil/src/main/java/xyz/quaver/hitomi/results.kt
@@ -1,13 +1,28 @@
+/*
+ * Copyright 2019 tom5079
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package xyz.quaver.hitomi
-import kotlinx.coroutines.asCoroutineDispatcher
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import java.util.*
-import java.util.concurrent.Executors
-val searchDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
-fun doSearch(query: String) : List {
+fun doSearch(query: String, sortByPopularity: Boolean = false) : List {
val terms = query
.trim()
.replace(Regex("""^\?"""), "")
@@ -27,7 +42,20 @@ fun doSearch(query: String) : List {
positiveTerms.push(term)
}
+ val positiveResults = positiveTerms.map {
+ CoroutineScope(Dispatchers.IO).async {
+ getGalleryIDsForQuery(it)
+ }
+ }
+
+ val negativeResults = negativeTerms.map {
+ CoroutineScope(Dispatchers.IO).async {
+ getGalleryIDsForQuery(it)
+ }
+ }
+
var results = when {
+ sortByPopularity -> getGalleryIDsFromNozomi(null, "popular", "all")
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(null, "index", "all")
else -> getGalleryIDsForQuery(positiveTerms.poll())
}
@@ -42,25 +70,19 @@ fun doSearch(query: String) : List {
}
//positive results
- positiveTerms.map {
- launch(searchDispatcher) {
- val newResults = getGalleryIDsForQuery(it)
- filterPositive(newResults.sorted())
- }
- }.forEach {
- it.join()
+ positiveResults.forEach {
+ val result = it.await()
+
+ filterPositive(result.sorted())
}
//negative results
- negativeTerms.map {
- launch(searchDispatcher) {
- filterNegative(getGalleryIDsForQuery(it).sorted())
- }
- }.forEach {
- it.join()
+ negativeResults.forEach {
+ val result = it.await()
+
+ filterNegative(result.sorted())
}
}
return results
-
}
\ 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 833c826c..5b08b1df 100644
--- a/libpupil/src/main/java/xyz/quaver/hitomi/search.kt
+++ b/libpupil/src/main/java/xyz/quaver/hitomi/search.kt
@@ -1,5 +1,22 @@
+/*
+ * Copyright 2019 tom5079
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package xyz.quaver.hitomi
+import java.io.ByteArrayOutputStream
import java.net.URL
import java.nio.ByteBuffer
import java.nio.ByteOrder
@@ -163,8 +180,10 @@ fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : List
val nozomi = ArrayList()
+ val bytes = inputStream.readBytes()
+
val arrayBuffer = ByteBuffer
- .wrap(inputStream.readBytes())
+ .wrap(bytes)
.order(ByteOrder.BIG_ENDIAN)
while (arrayBuffer.hasRemaining())
diff --git a/libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt b/libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt
index a20de850..fb9a5459 100644
--- a/libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt
+++ b/libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt
@@ -1,9 +1,25 @@
+/*
+ * Copyright 2019 tom5079
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package xyz.quaver.hiyobi
-import kotlinx.io.IOException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.json.content
+import org.jsoup.Jsoup
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.ReaderItem
import java.net.URL
@@ -12,13 +28,15 @@ import javax.net.ssl.HttpsURLConnection
const val hiyobi = "xn--9w3b15m8vo.asia"
const val user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"
-var cookie: String = ""
-get() {
- if (field.isEmpty())
- field = renewCookie()
+class HiyobiReader(title: String, readerItems: List) : Reader(title, readerItems)
- return field
-}
+var cookie: String = ""
+ get() {
+ if (field.isEmpty())
+ field = renewCookie()
+
+ return field
+ }
fun renewCookie() : String {
val url = "https://$hiyobi/"
@@ -35,26 +53,25 @@ fun renewCookie() : String {
}
}
-fun getReader(galleryId: Int) : Reader {
- val url = "https://$hiyobi/data/json/${galleryId}_list.json"
+fun getReader(galleryID: Int) : Reader {
+ val reader = "https://$hiyobi/reader/$galleryID"
+ val url = "https://$hiyobi/data/json/${galleryID}_list.json"
- try {
- val json = Json(JsonConfiguration.Stable).parseJson(
- with(URL(url).openConnection() as HttpsURLConnection) {
- setRequestProperty("User-Agent", user_agent)
- setRequestProperty("Cookie", cookie)
- connectTimeout = 2000
- connect()
+ val title = Jsoup.connect(reader).get().title()
- inputStream.bufferedReader().use { it.readText() }
- }
- )
+ val json = Json(JsonConfiguration.Stable).parseJson(
+ with(URL(url).openConnection() as HttpsURLConnection) {
+ setRequestProperty("User-Agent", user_agent)
+ setRequestProperty("Cookie", cookie)
+ connectTimeout = 2000
+ connect()
- return json.jsonArray.map {
- val name = it.jsonObject["name"]!!.content
- ReaderItem("https://$hiyobi/data/$galleryId/$name", null)
+ inputStream.bufferedReader().use { it.readText() }
}
- } catch (e: Exception) {
- return emptyList()
- }
+ )
+
+ return Reader(title, json.jsonArray.map {
+ val name = it.jsonObject["name"]!!.content
+ ReaderItem("https://$hiyobi/data/$galleryID/$name", null)
+ })
}
\ 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 1b864d91..851ce929 100644
--- a/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt
+++ b/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt
@@ -1,9 +1,24 @@
+/*
+ * Copyright 2019 tom5079
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UNUSED_VARIABLE")
+
package xyz.quaver.hitomi
import org.junit.Test
-import java.net.InetAddress
-import java.net.UnknownHostException
-
class UnitTest {
@Test
@@ -11,21 +26,11 @@ class UnitTest {
}
- private fun getByIp(host: String): InetAddress {
- try {
- return InetAddress.getByName(host)
- } catch (e: UnknownHostException) {
- // unlikely
- throw RuntimeException(e)
- }
-
- }
-
@Test
fun test_nozomi() {
- val nozomi = fetchNozomi(start = 0, count = 5)
+ val nozomi = getGalleryIDsFromNozomi(null, "popular", "all")
- nozomi.first
+ print(nozomi.size)
}
@Test
@@ -44,7 +49,7 @@ class UnitTest {
@Test
fun test_doSearch() {
- val r = doSearch("female:loli female:bondage language:korean -male:yaoi -male:guro -female:guro")
+ val r = doSearch("female:loli female:bondage language:korean -male:yaoi -male:guro -female:guro", true)
print(r.size)
}
@@ -72,8 +77,6 @@ class UnitTest {
@Test
fun test_hiyobi() {
- xyz.quaver.hiyobi.getReader(1415416).forEach {
- println(it.url)
- }
+
}
}
\ No newline at end of file