Utilizing Glide
Fixed Reader FAB icon Changed to use gallery id instead of galleryblock to open Reader
This commit is contained in:
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/classes" />
|
<output url="file://$PROJECT_DIR$/classes" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'kotlinx-serialization'
|
apply plugin: 'kotlinx-serialization'
|
||||||
apply plugin: 'com.google.gms.google-services'
|
apply plugin: 'com.google.gms.google-services'
|
||||||
@@ -52,8 +53,13 @@ dependencies {
|
|||||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
||||||
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
||||||
implementation 'com.github.clans:fab:1.6.4'
|
implementation 'com.github.clans:fab:1.6.4'
|
||||||
|
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 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
||||||
implementation "ru.noties.markwon:core:${markwonVersion}"
|
implementation "ru.noties.markwon:core:${markwonVersion}"
|
||||||
|
kapt 'com.github.bumptech.glide:compiler:4.9.0'
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package xyz.quaver.pupil.adapters
|
|||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.Log
|
|
||||||
import android.util.SparseBooleanArray
|
import android.util.SparseBooleanArray
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -15,6 +14,8 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
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 com.google.android.material.chip.Chip
|
||||||
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -23,9 +24,8 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
import kotlinx.serialization.list
|
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.ReaderItem
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
@@ -47,8 +47,8 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
|
|
||||||
private lateinit var favorites: Histories
|
private lateinit var favorites: Histories
|
||||||
|
|
||||||
inner class GalleryViewHolder(private val view: CardView) : RecyclerView.ViewHolder(view) {
|
inner class GalleryViewHolder(val view: CardView) : RecyclerView.ViewHolder(view) {
|
||||||
fun bind(item: Pair<GalleryBlock, Deferred<String>>) {
|
fun bind(holder: GalleryViewHolder, item: Pair<GalleryBlock, Deferred<String>>) {
|
||||||
with(view) {
|
with(view) {
|
||||||
val resources = context.resources
|
val resources = context.resources
|
||||||
val languages = resources.getStringArray(R.array.languages).map {
|
val languages = resources.getStringArray(R.array.languages).map {
|
||||||
@@ -62,17 +62,15 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
val artists = galleryBlock.artists
|
val artists = galleryBlock.artists
|
||||||
val series = galleryBlock.series
|
val series = galleryBlock.series
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val cache = thumbnail.await()
|
val cache = thumbnail.await()
|
||||||
|
|
||||||
if (!File(cache).exists())
|
Glide.with(holder.view)
|
||||||
return@launch
|
.load(cache)
|
||||||
|
.skipMemoryCache(true)
|
||||||
val bitmap = BitmapFactory.decodeFile(thumbnail.await())
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.error(R.drawable.image_broken_variant)
|
||||||
launch(Dispatchers.Main) {
|
.into(galleryblock_thumbnail)
|
||||||
galleryblock_thumbnail.setImageBitmap(bitmap)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check cache
|
//Check cache
|
||||||
@@ -81,10 +79,10 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
|
|
||||||
if (readerCache.invoke().exists()) {
|
if (readerCache.invoke().exists()) {
|
||||||
val reader = Json(JsonConfiguration.Stable)
|
val reader = Json(JsonConfiguration.Stable)
|
||||||
.parse(ReaderItem.serializer().list, readerCache.invoke().readText())
|
.parse(Reader.serializer(), readerCache.invoke().readText())
|
||||||
|
|
||||||
with(galleryblock_progressbar) {
|
with(galleryblock_progressbar) {
|
||||||
max = reader.size
|
max = reader.readerItems.size
|
||||||
progress = imageCache.invoke().list()?.size ?: 0
|
progress = imageCache.invoke().list()?.size ?: 0
|
||||||
|
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
@@ -108,8 +106,8 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
} else {
|
} else {
|
||||||
if (visibility == View.GONE) {
|
if (visibility == View.GONE) {
|
||||||
val reader = Json(JsonConfiguration.Stable)
|
val reader = Json(JsonConfiguration.Stable)
|
||||||
.parse(ReaderItem.serializer().list, readerCache.invoke().readText())
|
.parse(Reader.serializer(), readerCache.invoke().readText())
|
||||||
max = reader.size
|
max = reader.readerItems.size
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +332,7 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
if (holder is GalleryViewHolder)
|
if (holder is GalleryViewHolder)
|
||||||
holder.bind(galleries[position-(if (showPrev) 1 else 0)])
|
holder.bind(holder, galleries[position-(if (showPrev) 1 else 0)])
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
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
|
import xyz.quaver.pupil.R
|
||||||
|
|
||||||
class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||||
@@ -25,47 +25,19 @@ class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<Rea
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val progressDrawable = CircularProgressDrawable(holder.view.context).apply {
|
||||||
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
|
strokeWidth = 10f
|
||||||
// Raw height and width of image
|
centerRadius = 100f
|
||||||
val (height: Int, width: Int) = options.run { outHeight to outWidth }
|
start()
|
||||||
var inSampleSize = 1
|
|
||||||
|
|
||||||
if (height > 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
|
Glide.with(holder.view)
|
||||||
}
|
.load(images[position])
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
with(holder.view as ImageView) {
|
.skipMemoryCache(true)
|
||||||
val options = BitmapFactory.Options()
|
.placeholder(progressDrawable)
|
||||||
|
.error(R.drawable.image_broken_variant)
|
||||||
options.inJustDecodeBounds = true
|
.into(holder.view as ImageView)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = images.size
|
override fun getItemCount() = images.size
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package xyz.quaver.pupil.types
|
||||||
|
|
||||||
|
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import xyz.quaver.hitomi.Suggestion
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class SelectorSuggestion : SearchSuggestion {
|
||||||
|
|
||||||
|
override fun getBody(): String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import kotlinx.android.parcel.Parcelize
|
|||||||
import xyz.quaver.hitomi.Suggestion
|
import xyz.quaver.hitomi.Suggestion
|
||||||
|
|
||||||
@Parcelize
|
@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)
|
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
|
||||||
|
|
||||||
override fun getBody(): String {
|
override fun getBody(): String {
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ import java.net.URL
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.ceil
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@@ -72,8 +74,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private var query = ""
|
private var query = ""
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
findViewById<SearchInputView>(R.id.search_bar_text)
|
with(findViewById<SearchInputView>(R.id.search_bar_text)) {
|
||||||
.setText(query, TextView.BufferType.EDITABLE)
|
if (text.toString() != value)
|
||||||
|
setText(query, TextView.BufferType.EDITABLE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var mode = Mode.SEARCH
|
private var mode = Mode.SEARCH
|
||||||
@@ -155,7 +159,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val perPage = preference.getString("per_page", "25")!!.toInt()
|
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) {
|
return when(keyCode) {
|
||||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
@@ -380,9 +384,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
||||||
val gallery = galleries[position].first
|
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)
|
startActivity(intent)
|
||||||
|
|
||||||
histories.add(gallery.id)
|
histories.add(gallery.id)
|
||||||
@@ -391,7 +395,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
if (v !is CardView)
|
if (v !is CardView)
|
||||||
return@setOnItemLongClickListener true
|
return@setOnItemLongClickListener true
|
||||||
|
|
||||||
val galleryBlock = galleries[position].first
|
val gallery = galleries[position].first
|
||||||
val view = LayoutInflater.from(this@MainActivity)
|
val view = LayoutInflater.from(this@MainActivity)
|
||||||
.inflate(R.layout.dialog_galleryblock, recyclerView, false)
|
.inflate(R.layout.dialog_galleryblock, recyclerView, false)
|
||||||
|
|
||||||
@@ -400,15 +404,15 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}.create()
|
}.create()
|
||||||
|
|
||||||
with(view.main_dialog_download) {
|
with(view.main_dialog_download) {
|
||||||
text = when(GalleryDownloader.get(galleryBlock.id)) {
|
text = when(GalleryDownloader.get(gallery.id)) {
|
||||||
null -> getString(R.string.reader_fab_download)
|
null -> getString(R.string.reader_fab_download)
|
||||||
else -> getString(R.string.reader_fab_download_cancel)
|
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 {
|
setOnClickListener {
|
||||||
val downloader = GalleryDownloader.get(galleryBlock.id)
|
val downloader = GalleryDownloader.get(gallery.id)
|
||||||
if (downloader == null)
|
if (downloader == null)
|
||||||
GalleryDownloader(context, galleryBlock, true).start()
|
GalleryDownloader(context, gallery.id, true).start()
|
||||||
else {
|
else {
|
||||||
downloader.cancel()
|
downloader.cancel()
|
||||||
downloader.clearNotification()
|
downloader.clearNotification()
|
||||||
@@ -420,16 +424,16 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
view.main_dialog_delete.setOnClickListener {
|
view.main_dialog_delete.setOnClickListener {
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
with(GalleryDownloader[galleryBlock.id]) {
|
with(GalleryDownloader[gallery.id]) {
|
||||||
this?.cancelAndJoin()
|
this?.cancelAndJoin()
|
||||||
this?.clearNotification()
|
this?.clearNotification()
|
||||||
}
|
}
|
||||||
val cache = File(cacheDir, "imageCache/${galleryBlock.id}")
|
val cache = File(cacheDir, "imageCache/${gallery.id}")
|
||||||
val data = getCachedGallery(context, galleryBlock.id)
|
val data = getCachedGallery(context, gallery.id)
|
||||||
cache.deleteRecursively()
|
cache.deleteRecursively()
|
||||||
data.deleteRecursively()
|
data.deleteRecursively()
|
||||||
|
|
||||||
downloads.remove(galleryBlock.id)
|
downloads.remove(gallery.id)
|
||||||
|
|
||||||
if (mode == Mode.DOWNLOAD) {
|
if (mode == Mode.DOWNLOAD) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
@@ -440,7 +444,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(adapter as GalleryBlockAdapter).completeFlag.put(galleryBlock.id, false)
|
(adapter as GalleryBlockAdapter).completeFlag.put(gallery.id, false)
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
@@ -583,7 +587,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
//BOTTOM
|
//BOTTOM
|
||||||
|
|
||||||
//Scrolling DOWN
|
//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) {
|
with(main_recyclerview.adapter as GalleryBlockAdapter) {
|
||||||
if(!showNext) {
|
if(!showNext) {
|
||||||
showNext = true
|
showNext = true
|
||||||
@@ -595,7 +599,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
getChildAt(childCount-1)
|
getChildAt(childCount-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val absDist = Math.abs(dist)
|
val absDist = abs(dist)
|
||||||
|
|
||||||
if (next is LinearLayout) {
|
if (next is LinearLayout) {
|
||||||
val icon = next.findViewById<ImageView>(R.id.icon_next)
|
val icon = next.findViewById<ImageView>(R.id.icon_next)
|
||||||
@@ -701,7 +705,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
setMessage(getString(
|
setMessage(getString(
|
||||||
R.string.main_jump_message,
|
R.string.main_jump_message,
|
||||||
currentPage+1,
|
currentPage+1,
|
||||||
Math.ceil(totalItems / perPage.toDouble()).roundToInt()
|
ceil(totalItems / perPage.toDouble()).roundToInt()
|
||||||
))
|
))
|
||||||
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
@@ -729,10 +733,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
||||||
val gallery =
|
val gallery =
|
||||||
getGalleryBlock(editText.text.toString().toInt()) ?: throw Exception()
|
getGalleryBlock(editText.text.toString().toInt()) ?: throw Exception()
|
||||||
intent.putExtra(
|
intent.putExtra("galleryID", gallery.id)
|
||||||
"galleryblock",
|
|
||||||
Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery)
|
|
||||||
)
|
|
||||||
|
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -747,10 +748,17 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setOnQueryChangeListener { _, query ->
|
setOnQueryChangeListener { _, query ->
|
||||||
|
this@MainActivity.query = query
|
||||||
|
|
||||||
clearSuggestions()
|
clearSuggestions()
|
||||||
|
|
||||||
if (query.isEmpty() or query.endsWith(' '))
|
if (query.isEmpty() or query.endsWith(' ')) {
|
||||||
|
swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
|
||||||
|
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
||||||
|
})
|
||||||
|
|
||||||
return@setOnQueryChangeListener
|
return@setOnQueryChangeListener
|
||||||
|
}
|
||||||
|
|
||||||
val currentQuery = query.split(" ").last().replace('_', ' ')
|
val currentQuery = query.split(" ").last().replace('_', ' ')
|
||||||
|
|
||||||
@@ -852,8 +860,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
|
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
|
||||||
append("${suggestion.n}:${suggestion.s.replace(Regex("\\s"), "_")} ")
|
append("${suggestion.n}:${suggestion.s.replace(Regex("\\s"), "_")} ")
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSuggestions()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSearchAction(currentQuery: String?) {
|
override fun onSearchAction(currentQuery: String?) {
|
||||||
@@ -863,7 +869,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
|
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
|
||||||
override fun onFocus() {
|
override fun onFocus() {
|
||||||
if (searchInputView.text.isEmpty())
|
if (query.isEmpty() or query.endsWith(' '))
|
||||||
swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
|
swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
|
||||||
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.os.Bundle
|
|||||||
import android.view.*
|
import android.view.*
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.PagerSnapHelper
|
import androidx.recyclerview.widget.PagerSnapHelper
|
||||||
@@ -21,13 +22,8 @@ import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.io.IOException
|
import kotlinx.io.IOException
|
||||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
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.Pupil
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.adapters.ReaderAdapter
|
import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||||
@@ -37,8 +33,8 @@ import xyz.quaver.pupil.util.ItemClickSupport
|
|||||||
|
|
||||||
class ReaderActivity : AppCompatActivity() {
|
class ReaderActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private var galleryID = 0
|
||||||
private val images = ArrayList<String>()
|
private val images = ArrayList<String>()
|
||||||
private lateinit var galleryBlock: GalleryBlock
|
|
||||||
private var gallerySize = 0
|
private var gallerySize = 0
|
||||||
private var currentPage = 0
|
private var currentPage = 0
|
||||||
|
|
||||||
@@ -66,6 +62,9 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
title = getString(R.string.reader_loading)
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||||
|
|
||||||
favorites = (application as Pupil).favorites
|
favorites = (application as Pupil).favorites
|
||||||
|
|
||||||
window.setFlags(
|
window.setFlags(
|
||||||
@@ -76,16 +75,13 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
handleIntent(intent)
|
handleIntent(intent)
|
||||||
|
|
||||||
Crashlytics.setInt("GalleryID", galleryBlock.id)
|
Crashlytics.setInt("GalleryID", galleryID)
|
||||||
|
|
||||||
if (!::galleryBlock.isInitialized) {
|
if (galleryID == 0) {
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
supportActionBar?.title = galleryBlock.title
|
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
|
||||||
|
|
||||||
initDownloader()
|
initDownloader()
|
||||||
|
|
||||||
initView()
|
initView()
|
||||||
@@ -106,25 +102,16 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
if (uri != null && lastPathSegment != null) {
|
if (uri != null && lastPathSegment != null) {
|
||||||
val nonNumber = Regex("[^-?0-9]+")
|
val nonNumber = Regex("[^-?0-9]+")
|
||||||
|
|
||||||
val galleryID = when (uri.host) {
|
galleryID = when (uri.host) {
|
||||||
"hitomi.la" -> lastPathSegment.replace(nonNumber, "").toInt()
|
"hitomi.la" -> lastPathSegment.replace(nonNumber, "").toInt()
|
||||||
"히요비.asia" -> lastPathSegment.toInt()
|
"히요비.asia" -> lastPathSegment.toInt()
|
||||||
"xn--9w3b15m8vo.asia" -> lastPathSegment.toInt()
|
"xn--9w3b15m8vo.asia" -> lastPathSegment.toInt()
|
||||||
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
||||||
else -> return
|
else -> return
|
||||||
}
|
}
|
||||||
|
|
||||||
runBlocking {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
galleryBlock = getGalleryBlock(galleryID) ?: return@launch
|
|
||||||
}.join()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
galleryBlock = Json(JsonConfiguration.Stable).parse(
|
galleryID = intent.getIntExtra("galleryID", 0)
|
||||||
GalleryBlock.serializer(),
|
|
||||||
intent.getStringExtra("galleryblock")!!
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +135,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
with(menu?.findItem(R.id.reader_menu_favorite)) {
|
with(menu?.findItem(R.id.reader_menu_favorite)) {
|
||||||
this ?: return@with
|
this ?: return@with
|
||||||
|
|
||||||
if (favorites.contains(galleryBlock.id))
|
if (favorites.contains(galleryID))
|
||||||
(icon as Animatable).start()
|
(icon as Animatable).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +163,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
R.id.reader_menu_favorite -> {
|
R.id.reader_menu_favorite -> {
|
||||||
val id = galleryBlock.id
|
val id = galleryID
|
||||||
val favorite = menu?.findItem(R.id.reader_menu_favorite) ?: return true
|
val favorite = menu?.findItem(R.id.reader_menu_favorite) ?: return true
|
||||||
|
|
||||||
if (favorites.contains(id)) {
|
if (favorites.contains(id)) {
|
||||||
@@ -215,11 +202,11 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initDownloader() {
|
private fun initDownloader() {
|
||||||
var d: GalleryDownloader? = GalleryDownloader.get(galleryBlock.id)
|
var d: GalleryDownloader? = GalleryDownloader.get(galleryID)
|
||||||
|
|
||||||
if (d == null) {
|
if (d == null) {
|
||||||
try {
|
try {
|
||||||
d = GalleryDownloader(this, galleryBlock)
|
d = GalleryDownloader(this, galleryID)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
|
Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
|
||||||
finish()
|
finish()
|
||||||
@@ -230,17 +217,18 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
downloader = d.apply {
|
downloader = d.apply {
|
||||||
onReaderLoadedHandler = {
|
onReaderLoadedHandler = {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
title = it.title
|
||||||
with(reader_download_progressbar) {
|
with(reader_download_progressbar) {
|
||||||
max = it.size
|
max = it.readerItems.size
|
||||||
progress = 0
|
progress = 0
|
||||||
}
|
}
|
||||||
with(reader_progressbar) {
|
with(reader_progressbar) {
|
||||||
max = it.size
|
max = it.readerItems.size
|
||||||
progress = 0
|
progress = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
gallerySize = it.size
|
gallerySize = it.readerItems.size
|
||||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.size}"
|
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.readerItems.size}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onProgressHandler = {
|
onProgressHandler = {
|
||||||
@@ -341,14 +329,9 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reader_fab_fullscreen.setOnClickListener {
|
with(reader_fab_download) {
|
||||||
isFullscreen = true
|
setImageResource(R.drawable.ic_download)
|
||||||
fullscreen(isFullscreen)
|
setOnClickListener {
|
||||||
|
|
||||||
reader_fab.close(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
reader_fab_download.setOnClickListener {
|
|
||||||
downloader.download = !downloader.download
|
downloader.download = !downloader.download
|
||||||
|
|
||||||
if (!downloader.download)
|
if (!downloader.download)
|
||||||
@@ -356,6 +339,17 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(reader_fab_fullscreen) {
|
||||||
|
setImageResource(R.drawable.ic_fullscreen)
|
||||||
|
setOnClickListener {
|
||||||
|
isFullscreen = true
|
||||||
|
fullscreen(isFullscreen)
|
||||||
|
|
||||||
|
this@ReaderActivity.reader_fab.close(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun fullscreen(isFullscreen: Boolean) {
|
private fun fullscreen(isFullscreen: Boolean) {
|
||||||
with(window.attributes) {
|
with(window.attributes) {
|
||||||
if (isFullscreen) {
|
if (isFullscreen) {
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ import kotlinx.coroutines.*
|
|||||||
import kotlinx.io.IOException
|
import kotlinx.io.IOException
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
import kotlinx.serialization.list
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.hitomi.*
|
import xyz.quaver.hitomi.getReader
|
||||||
|
import xyz.quaver.hitomi.getReferer
|
||||||
import xyz.quaver.hiyobi.cookie
|
import xyz.quaver.hiyobi.cookie
|
||||||
import xyz.quaver.hiyobi.user_agent
|
import xyz.quaver.hiyobi.user_agent
|
||||||
import xyz.quaver.pupil.R
|
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
@@ -30,7 +31,7 @@ import kotlin.concurrent.schedule
|
|||||||
|
|
||||||
class GalleryDownloader(
|
class GalleryDownloader(
|
||||||
base: Context,
|
base: Context,
|
||||||
private val galleryBlock: GalleryBlock,
|
private val galleryID: Int,
|
||||||
_notify: Boolean = false
|
_notify: Boolean = false
|
||||||
) : ContextWrapper(base) {
|
) : ContextWrapper(base) {
|
||||||
|
|
||||||
@@ -41,10 +42,10 @@ class GalleryDownloader(
|
|||||||
set(value) {
|
set(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
field = true
|
field = true
|
||||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
notificationManager.notify(galleryID, notificationBuilder.build())
|
||||||
|
|
||||||
val data = getCachedGallery(this, galleryBlock.id)
|
val data = getCachedGallery(this, galleryID)
|
||||||
val cache = File(cacheDir, "imageCache/${galleryBlock.id}")
|
val cache = File(cacheDir, "imageCache/$galleryID")
|
||||||
|
|
||||||
if (File(cache, "images").exists() && !data.exists()) {
|
if (File(cache, "images").exists() && !data.exists()) {
|
||||||
cache.copyRecursively(data, true)
|
cache.copyRecursively(data, true)
|
||||||
@@ -54,7 +55,7 @@ class GalleryDownloader(
|
|||||||
if (reader?.isActive == false && downloadJob?.isActive != true)
|
if (reader?.isActive == false && downloadJob?.isActive != true)
|
||||||
field = false
|
field = false
|
||||||
|
|
||||||
downloads.add(galleryBlock.id)
|
downloads.add(galleryID)
|
||||||
} else {
|
} else {
|
||||||
field = false
|
field = false
|
||||||
}
|
}
|
||||||
@@ -78,24 +79,24 @@ class GalleryDownloader(
|
|||||||
companion object : SparseArray<GalleryDownloader>()
|
companion object : SparseArray<GalleryDownloader>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
put(galleryBlock.id, this)
|
put(galleryID, this)
|
||||||
|
|
||||||
initNotification()
|
initNotification()
|
||||||
|
|
||||||
reader = CoroutineScope(Dispatchers.IO).async {
|
reader = CoroutineScope(Dispatchers.IO).async {
|
||||||
download = _notify
|
download = _notify
|
||||||
val json = Json(JsonConfiguration.Stable)
|
val json = Json(JsonConfiguration.Stable)
|
||||||
val serializer = ReaderItem.serializer().list
|
val serializer = Reader.serializer()
|
||||||
|
|
||||||
//Check cache
|
//Check cache
|
||||||
val cache = File(getCachedGallery(this@GalleryDownloader, galleryBlock.id), "reader.json")
|
val cache = File(getCachedGallery(this@GalleryDownloader, galleryID), "reader.json")
|
||||||
|
|
||||||
if (cache.exists()) {
|
if (cache.exists()) {
|
||||||
val cached = json.parse(serializer, cache.readText())
|
val cached = json.parse(serializer, cache.readText())
|
||||||
|
|
||||||
if (cached.isNotEmpty()) {
|
if (cached.readerItems.isNotEmpty()) {
|
||||||
useHiyobi = when {
|
useHiyobi = when {
|
||||||
cached.first().url.contains("hitomi.la") -> false
|
cached.readerItems[0].url.contains("hitomi.la") -> false
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,22 +109,22 @@ class GalleryDownloader(
|
|||||||
//Cache doesn't exist. Load from internet
|
//Cache doesn't exist. Load from internet
|
||||||
val reader = when {
|
val reader = when {
|
||||||
useHiyobi -> {
|
useHiyobi -> {
|
||||||
xyz.quaver.hiyobi.getReader(galleryBlock.id).let {
|
xyz.quaver.hiyobi.getReader(galleryID).let {
|
||||||
when {
|
when {
|
||||||
it.isEmpty() -> {
|
it.readerItems.isEmpty() -> {
|
||||||
useHiyobi = false
|
useHiyobi = false
|
||||||
getReader(galleryBlock.id)
|
getReader(galleryID)
|
||||||
}
|
}
|
||||||
else -> it
|
else -> it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
getReader(galleryBlock.id)
|
getReader(galleryID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader.isNotEmpty()) {
|
if (reader.readerItems.isNotEmpty()) {
|
||||||
//Save cache
|
//Save cache
|
||||||
if (cache.parentFile?.exists() == false)
|
if (cache.parentFile?.exists() == false)
|
||||||
cache.parentFile!!.mkdirs()
|
cache.parentFile!!.mkdirs()
|
||||||
@@ -141,7 +142,7 @@ class GalleryDownloader(
|
|||||||
downloadJob = CoroutineScope(Dispatchers.Default).launch {
|
downloadJob = CoroutineScope(Dispatchers.Default).launch {
|
||||||
val reader = reader!!.await()
|
val reader = reader!!.await()
|
||||||
|
|
||||||
if (reader.isEmpty())
|
if (reader.readerItems.isEmpty())
|
||||||
onErrorHandler?.invoke(IOException("Couldn't retrieve Reader"))
|
onErrorHandler?.invoke(IOException("Couldn't retrieve Reader"))
|
||||||
|
|
||||||
val list = ArrayList<String>()
|
val list = ArrayList<String>()
|
||||||
@@ -149,29 +150,20 @@ class GalleryDownloader(
|
|||||||
onReaderLoadedHandler?.invoke(reader)
|
onReaderLoadedHandler?.invoke(reader)
|
||||||
|
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setProgress(reader.size, 0, false)
|
.setProgress(reader.readerItems.size, 0, false)
|
||||||
.setContentText("0/${reader.size}")
|
.setContentText("0/${reader.readerItems.size}")
|
||||||
|
|
||||||
reader.chunked(4).forEachIndexed { chunkIndex, chunked ->
|
reader.readerItems.chunked(4).forEachIndexed { chunkIndex, chunked ->
|
||||||
chunked.mapIndexed { i, it ->
|
chunked.mapIndexed { i, it ->
|
||||||
val index = chunkIndex*4+i
|
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) {
|
async(Dispatchers.IO) {
|
||||||
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
|
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
|
||||||
|
|
||||||
val name = "$index".padStart(4, '0')
|
val name = "$index".padStart(4, '0')
|
||||||
val ext = url.split('.').last()
|
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())
|
if (!cache.exists())
|
||||||
try {
|
try {
|
||||||
@@ -180,7 +172,7 @@ class GalleryDownloader(
|
|||||||
setRequestProperty("User-Agent", user_agent)
|
setRequestProperty("User-Agent", user_agent)
|
||||||
setRequestProperty("Cookie", cookie)
|
setRequestProperty("Cookie", cookie)
|
||||||
} else
|
} else
|
||||||
setRequestProperty("Referer", getReferer(galleryBlock.id))
|
setRequestProperty("Referer", getReferer(galleryID))
|
||||||
|
|
||||||
if (cache.parentFile?.exists() == false)
|
if (cache.parentFile?.exists() == false)
|
||||||
cache.parentFile!!.mkdirs()
|
cache.parentFile!!.mkdirs()
|
||||||
@@ -193,31 +185,43 @@ class GalleryDownloader(
|
|||||||
onErrorHandler?.invoke(e)
|
onErrorHandler?.invoke(e)
|
||||||
|
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setContentTitle(galleryBlock.title)
|
.setContentTitle(reader.title)
|
||||||
.setContentText(getString(R.string.reader_notification_error))
|
.setContentText(getString(R.string.reader_notification_error))
|
||||||
.setProgress(0, 0, false)
|
.setProgress(0, 0, false)
|
||||||
|
|
||||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
notificationManager.notify(galleryID, notificationBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.absolutePath
|
cache.absolutePath
|
||||||
}
|
}
|
||||||
}.forEach {
|
}.forEach {
|
||||||
list.add(it.await())
|
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)
|
onDownloadedHandler?.invoke(list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer(false).schedule(1000) {
|
Timer(false).schedule(1000) {
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setContentTitle(galleryBlock.title)
|
.setContentTitle(reader.title)
|
||||||
.setContentText(getString(R.string.reader_notification_complete))
|
.setContentText(getString(R.string.reader_notification_complete))
|
||||||
.setProgress(0, 0, false)
|
.setProgress(0, 0, false)
|
||||||
|
|
||||||
if (download) {
|
if (download) {
|
||||||
File(cacheDir, "imageCache/${galleryBlock.id}").let {
|
File(cacheDir, "imageCache/${galleryID}").let {
|
||||||
if (it.exists()) {
|
if (it.exists()) {
|
||||||
val target = File(getDownloadDirectory(this@GalleryDownloader), galleryBlock.id.toString())
|
val target = File(getDownloadDirectory(this@GalleryDownloader), galleryID.toString())
|
||||||
|
|
||||||
if (!target.exists())
|
if (!target.exists())
|
||||||
target.mkdirs()
|
target.mkdirs()
|
||||||
@@ -227,7 +231,7 @@ class GalleryDownloader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
notificationManager.notify(galleryID, notificationBuilder.build())
|
||||||
|
|
||||||
download = false
|
download = false
|
||||||
}
|
}
|
||||||
@@ -235,20 +239,20 @@ class GalleryDownloader(
|
|||||||
onCompleteHandler?.invoke()
|
onCompleteHandler?.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(galleryBlock.id)
|
remove(galleryID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancel() {
|
fun cancel() {
|
||||||
downloadJob?.cancel()
|
downloadJob?.cancel()
|
||||||
|
|
||||||
remove(galleryBlock.id)
|
remove(galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun cancelAndJoin() {
|
suspend fun cancelAndJoin() {
|
||||||
downloadJob?.cancelAndJoin()
|
downloadJob?.cancelAndJoin()
|
||||||
|
|
||||||
remove(galleryBlock.id)
|
remove(galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invokeOnReaderLoaded() {
|
fun invokeOnReaderLoaded() {
|
||||||
@@ -258,7 +262,7 @@ class GalleryDownloader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearNotification() {
|
fun clearNotification() {
|
||||||
notificationManager.cancel(galleryBlock.id)
|
notificationManager.cancel(galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invokeOnNotifyChanged() {
|
fun invokeOnNotifyChanged() {
|
||||||
@@ -267,22 +271,28 @@ class GalleryDownloader(
|
|||||||
|
|
||||||
private fun initNotification() {
|
private fun initNotification() {
|
||||||
val intent = Intent(this, ReaderActivity::class.java).apply {
|
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 {
|
val pendingIntent = TaskStackBuilder.create(this).run {
|
||||||
addNextIntentWithParentStack(intent)
|
addNextIntentWithParentStack(intent)
|
||||||
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
|
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notificationManager = NotificationManagerCompat.from(this)
|
||||||
|
|
||||||
notificationBuilder = NotificationCompat.Builder(this, "download").apply {
|
notificationBuilder = NotificationCompat.Builder(this, "download").apply {
|
||||||
setContentTitle(galleryBlock.title)
|
setContentTitle(getString(R.string.reader_loading))
|
||||||
setContentText(getString(R.string.reader_notification_text))
|
setContentText(getString(R.string.reader_notification_text))
|
||||||
setSmallIcon(R.drawable.ic_download)
|
setSmallIcon(R.drawable.ic_download)
|
||||||
setContentIntent(pendingIntent)
|
setContentIntent(pendingIntent)
|
||||||
setProgress(0, 0, true)
|
setProgress(0, 0, true)
|
||||||
priority = NotificationCompat.PRIORITY_LOW
|
priority = NotificationCompat.PRIORITY_LOW
|
||||||
}
|
}
|
||||||
notificationManager = NotificationManagerCompat.from(this)
|
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
while (reader == null) ;
|
||||||
|
notificationBuilder.setContentTitle(reader.await().title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
android:contentDescription="@string/reader_imageview_description"
|
android:contentDescription="@string/reader_imageview_description"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="100dp"
|
||||||
android:paddingBottom="8dp"
|
android:paddingBottom="8dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:adjustViewBounds="true"/>
|
android:adjustViewBounds="true"/>
|
||||||
@@ -79,4 +79,5 @@
|
|||||||
<string name="settings_lock_wrong_confirm">ロックが一致しません。やり直してください。</string>
|
<string name="settings_lock_wrong_confirm">ロックが一致しません。やり直してください。</string>
|
||||||
<string name="settings_lock_none">なし</string>
|
<string name="settings_lock_none">なし</string>
|
||||||
<string name="settings_lock_remove_message">ロックを無効にしますか?</string>
|
<string name="settings_lock_remove_message">ロックを無効にしますか?</string>
|
||||||
|
<string name="reader_loading">ロード中</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -79,4 +79,5 @@
|
|||||||
<string name="settings_lock_wrong_confirm">잠금이 일치하지 않습니다. 다시 시도하세요.</string>
|
<string name="settings_lock_wrong_confirm">잠금이 일치하지 않습니다. 다시 시도하세요.</string>
|
||||||
<string name="settings_lock_none">없음</string>
|
<string name="settings_lock_none">없음</string>
|
||||||
<string name="settings_lock_remove_message">잠금을 해제할까요?</string>
|
<string name="settings_lock_remove_message">잠금을 해제할까요?</string>
|
||||||
|
<string name="reader_loading">로딩중</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -71,6 +71,7 @@
|
|||||||
<string name="galleryblock_type">Type: %1$s</string>
|
<string name="galleryblock_type">Type: %1$s</string>
|
||||||
<string name="galleryblock_language">Language: %1$s</string>
|
<string name="galleryblock_language">Language: %1$s</string>
|
||||||
|
|
||||||
|
<string name="reader_loading">Loading</string>
|
||||||
<string name="reader_go_to_page">Go to page</string>
|
<string name="reader_go_to_page">Go to page</string>
|
||||||
<string name="reader_fab_fullscreen">Fullscreen</string>
|
<string name="reader_fab_fullscreen">Fullscreen</string>
|
||||||
<string name="reader_fab_download">Background download</string>
|
<string name="reader_fab_download">Background download</string>
|
||||||
|
|||||||
72
libpupil/src/main/java/xyz/quaver/hitomi/reader.kt
Normal file
72
libpupil/src/main/java/xyz/quaver/hitomi/reader.kt
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
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<ReaderItem>)
|
||||||
|
|
||||||
|
//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?>()
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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<ReaderItem>
|
|
||||||
//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?>()
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package xyz.quaver.hiyobi
|
package xyz.quaver.hiyobi
|
||||||
|
|
||||||
import kotlinx.io.IOException
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
import kotlinx.serialization.json.content
|
import kotlinx.serialization.json.content
|
||||||
|
import org.jsoup.Jsoup
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.hitomi.ReaderItem
|
import xyz.quaver.hitomi.ReaderItem
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
@@ -12,6 +12,8 @@ import javax.net.ssl.HttpsURLConnection
|
|||||||
const val hiyobi = "xn--9w3b15m8vo.asia"
|
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"
|
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"
|
||||||
|
|
||||||
|
class HiyobiReader(title: String, readerItems: List<ReaderItem>) : Reader(title, readerItems)
|
||||||
|
|
||||||
var cookie: String = ""
|
var cookie: String = ""
|
||||||
get() {
|
get() {
|
||||||
if (field.isEmpty())
|
if (field.isEmpty())
|
||||||
@@ -35,10 +37,12 @@ fun renewCookie() : String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getReader(galleryId: Int) : Reader {
|
fun getReader(galleryID: Int) : Reader {
|
||||||
val url = "https://$hiyobi/data/json/${galleryId}_list.json"
|
val reader = "https://$hiyobi/reader/$galleryID"
|
||||||
|
val url = "https://$hiyobi/data/json/${galleryID}_list.json"
|
||||||
|
|
||||||
|
val title = Jsoup.connect(reader).get().title()
|
||||||
|
|
||||||
try {
|
|
||||||
val json = Json(JsonConfiguration.Stable).parseJson(
|
val json = Json(JsonConfiguration.Stable).parseJson(
|
||||||
with(URL(url).openConnection() as HttpsURLConnection) {
|
with(URL(url).openConnection() as HttpsURLConnection) {
|
||||||
setRequestProperty("User-Agent", user_agent)
|
setRequestProperty("User-Agent", user_agent)
|
||||||
@@ -50,11 +54,8 @@ fun getReader(galleryId: Int) : Reader {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return json.jsonArray.map {
|
return Reader(title, json.jsonArray.map {
|
||||||
val name = it.jsonObject["name"]!!.content
|
val name = it.jsonObject["name"]!!.content
|
||||||
ReaderItem("https://$hiyobi/data/$galleryId/$name", null)
|
ReaderItem("https://$hiyobi/data/$galleryID/$name", null)
|
||||||
}
|
})
|
||||||
} catch (e: Exception) {
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user