what i got so far
This commit is contained in:
@@ -60,7 +60,7 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
|
||||||
//implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC"
|
//implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC"
|
||||||
@@ -96,15 +96,15 @@ dependencies {
|
|||||||
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
||||||
//implementation 'com.andrognito.pinlockview:pinlockview:2.1.0'
|
//implementation 'com.andrognito.pinlockview:pinlockview:2.1.0'
|
||||||
implementation "ru.noties.markwon:core:3.1.0"
|
implementation "ru.noties.markwon:core:3.1.0"
|
||||||
implementation ("xyz.quaver:libpupil:1.1") {
|
implementation ("xyz.quaver:libpupil:1.3") {
|
||||||
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm'
|
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm'
|
||||||
}
|
}
|
||||||
implementation "xyz.quaver:documentfilex:0.2.2"
|
implementation "xyz.quaver:documentfilex:0.2.11-alpha6"
|
||||||
testImplementation 'junit:junit:4.13'
|
testImplementation 'junit:junit:4.13'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
androidTestImplementation 'androidx.test:rules:1.3.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
androidExtensions {
|
androidExtensions {
|
||||||
|
|||||||
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
@@ -21,6 +21,7 @@
|
|||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
-dontoptimize
|
||||||
|
|
||||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
-keep class * extends com.bumptech.glide.module.AppGlideModule {
|
-keep class * extends com.bumptech.glide.module.AppGlideModule {
|
||||||
@@ -46,4 +47,5 @@
|
|||||||
-keepclasseswithmembers class xyz.quaver.pupil.** { # <-- change package name to your app's
|
-keepclasseswithmembers class xyz.quaver.pupil.** { # <-- change package name to your app's
|
||||||
kotlinx.serialization.KSerializer serializer(...);
|
kotlinx.serialization.KSerializer serializer(...);
|
||||||
}
|
}
|
||||||
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
|
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
|
||||||
|
-keep class xyz.quaver.pupil.util.Preferences
|
||||||
@@ -28,6 +28,7 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
||||||
|
import xyz.quaver.hitomi.getSuggestionsForQuery
|
||||||
import xyz.quaver.hiyobi.cookie
|
import xyz.quaver.hiyobi.cookie
|
||||||
import xyz.quaver.hiyobi.createImgList
|
import xyz.quaver.hiyobi.createImgList
|
||||||
import xyz.quaver.hiyobi.getReader
|
import xyz.quaver.hiyobi.getReader
|
||||||
@@ -121,4 +122,9 @@ class ExampleInstrumentedTest {
|
|||||||
|
|
||||||
Log.i("PUPILD", Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.title ?: "null")
|
Log.i("PUPILD", Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.title ?: "null")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_suggestion() {
|
||||||
|
getSuggestionsForQuery("female:l")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
tools:replace="android:theme"
|
tools:replace="android:theme"
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.app.Notification
|
|||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@@ -38,6 +39,7 @@ import kotlinx.coroutines.launch
|
|||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
import xyz.quaver.setClient
|
import xyz.quaver.setClient
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -91,11 +93,11 @@ class Pupil : Application() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Preferences.get<String>("download_folder").also {
|
Preferences.get<String>("download_folder").also {
|
||||||
if (!File(it).canWrite())
|
if (!FileX(this, it).canWrite())
|
||||||
throw Exception()
|
throw Exception()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Preferences.remove("dl_location")
|
Preferences.remove("download_folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
histories = GalleryList(File(ContextCompat.getDataDir(this), "histories.json"))
|
histories = GalleryList(File(ContextCompat.getDataDir(this), "histories.json"))
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import com.bumptech.glide.annotation.GlideModule
|
|||||||
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
|
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
import com.bumptech.glide.module.AppGlideModule
|
import com.bumptech.glide.module.AppGlideModule
|
||||||
import xyz.quaver.pupil.util.download.DownloadWorker
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
@GlideModule
|
@GlideModule
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.graphics.Color
|
|||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
import android.graphics.PorterDuffColorFilter
|
import android.graphics.PorterDuffColorFilter
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.Base64
|
|
||||||
import android.util.SparseBooleanArray
|
import android.util.SparseBooleanArray
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -46,22 +45,20 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
|
||||||
import xyz.quaver.hitomi.getReader
|
import xyz.quaver.hitomi.getReader
|
||||||
import xyz.quaver.pupil.BuildConfig
|
import xyz.quaver.pupil.BuildConfig
|
||||||
import xyz.quaver.pupil.Pupil
|
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.favorites
|
import xyz.quaver.pupil.favorites
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.util.GalleryList
|
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
import xyz.quaver.pupil.util.download.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
|
import xyz.quaver.pupil.util.downloader.DownloadFolderManager
|
||||||
import xyz.quaver.pupil.util.wordCapitalize
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List<GalleryBlock>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List<Int>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
||||||
|
|
||||||
enum class ViewType {
|
enum class ViewType {
|
||||||
NEXT,
|
NEXT,
|
||||||
@@ -77,22 +74,23 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
var timerTask: TimerTask? = null
|
var timerTask: TimerTask? = null
|
||||||
|
|
||||||
private fun updateProgress(context: Context, galleryID: Int) {
|
private fun updateProgress(context: Context, galleryID: Int) {
|
||||||
val reader = Cache(context).getReaderOrNull(galleryID)
|
val cache = Cache.getInstance(context, galleryID)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
if (reader == null || Preferences["cache_disable"]) {
|
if (cache.metadata.reader == null || Preferences["cache_disable"]) {
|
||||||
view.galleryblock_progressbar.visibility = View.GONE
|
view.galleryblock_progressbar.visibility = View.GONE
|
||||||
view.galleryblock_progress_complete.visibility = View.GONE
|
view.galleryblock_progress_complete.visibility = View.GONE
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
with(view.galleryblock_progressbar) {
|
with(view.galleryblock_progressbar) {
|
||||||
|
val imageList = cache.metadata.imageList!!
|
||||||
|
|
||||||
progress = Cache(context).getImages(galleryID)?.size ?: 0
|
progress = imageList.filterNotNull().size
|
||||||
|
|
||||||
if (visibility == View.GONE) {
|
if (visibility == View.GONE) {
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
max = reader.galleryInfo.files.size
|
max = imageList.size
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress == max) {
|
if (progress == max) {
|
||||||
@@ -116,7 +114,11 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind(galleryBlock: GalleryBlock) {
|
fun bind(galleryID: Int) {
|
||||||
|
val cache = Cache.getInstance(view.context, galleryID)
|
||||||
|
|
||||||
|
val galleryBlock = cache.metadata.galleryBlock!!
|
||||||
|
|
||||||
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 {
|
||||||
@@ -136,13 +138,8 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
it.start()
|
it.start()
|
||||||
})
|
})
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val thumbnail = Cache(context).getThumbnail(galleryBlock.id).let {
|
val thumbnail = cache.getThumbnail()
|
||||||
if (it != null)
|
|
||||||
Base64.decode(it, Base64.DEFAULT)
|
|
||||||
else
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
galleryblock_thumbnail.post {
|
galleryblock_thumbnail.post {
|
||||||
glide
|
glide
|
||||||
@@ -158,27 +155,9 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check cache
|
|
||||||
val cache = Cache(context).getCachedGallery(galleryBlock.id)
|
|
||||||
val reader = Cache(context).getReaderOrNull(galleryBlock.id)
|
|
||||||
|
|
||||||
if (reader != null) {
|
|
||||||
val count = cache.listFiles()?.count {
|
|
||||||
Regex("^[0-9]+.+\$").matches(it.name)
|
|
||||||
} ?: 0
|
|
||||||
|
|
||||||
with(galleryblock_progressbar) {
|
|
||||||
max = reader.galleryInfo.files.size
|
|
||||||
progress = count
|
|
||||||
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
galleryblock_progressbar.visibility = View.GONE
|
|
||||||
|
|
||||||
if (timerTask == null)
|
if (timerTask == null)
|
||||||
timerTask = timer.schedule(0, 1000) {
|
timerTask = timer.schedule(0, 1000) {
|
||||||
updateProgress(context, galleryBlock.id)
|
updateProgress(context, galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryblock_title.text = galleryBlock.title
|
galleryblock_title.text = galleryBlock.title
|
||||||
@@ -339,9 +318,9 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
if (holder is GalleryViewHolder) {
|
if (holder is GalleryViewHolder) {
|
||||||
val gallery = galleries[position-(if (showPrev) 1 else 0)]
|
val galleryID = galleries[position-(if (showPrev) 1 else 0)]
|
||||||
|
|
||||||
holder.bind(gallery)
|
holder.bind(galleryID)
|
||||||
|
|
||||||
with(holder.view.galleryblock_primary) {
|
with(holder.view.galleryblock_primary) {
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
@@ -367,7 +346,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
mItemManger.closeAllExcept(layout)
|
mItemManger.closeAllExcept(layout)
|
||||||
|
|
||||||
holder.view.galleryblock_download.text =
|
holder.view.galleryblock_download.text =
|
||||||
if (Cache(holder.view.context).isDownloading(gallery.id))
|
if (DownloadFolderManager.getInstance(holder.view.context).isDownloading(galleryID))
|
||||||
holder.view.context.getString(android.R.string.cancel)
|
holder.view.context.getString(android.R.string.cancel)
|
||||||
else
|
else
|
||||||
holder.view.context.getString(R.string.main_download)
|
holder.view.context.getString(R.string.main_download)
|
||||||
@@ -392,8 +371,8 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() =
|
override fun getItemCount() =
|
||||||
(if (galleries.isEmpty()) 0 else galleries.size)+
|
galleries.size +
|
||||||
(if (showNext) 1 else 0)+
|
(if (showNext) 1 else 0) +
|
||||||
(if (showPrev) 1 else 0)
|
(if (showPrev) 1 else 0)
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.RequestManager
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
import com.bumptech.glide.load.model.LazyHeaders
|
import com.bumptech.glide.load.model.LazyHeaders
|
||||||
@@ -38,28 +38,29 @@ import xyz.quaver.hitomi.imageUrlFromImage
|
|||||||
import xyz.quaver.hiyobi.cookie
|
import xyz.quaver.hiyobi.cookie
|
||||||
import xyz.quaver.hiyobi.createImgList
|
import xyz.quaver.hiyobi.createImgList
|
||||||
import xyz.quaver.hiyobi.user_agent
|
import xyz.quaver.hiyobi.user_agent
|
||||||
|
import xyz.quaver.io.util.readBytes
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
import xyz.quaver.pupil.util.download.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.download.DownloadWorker
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class ReaderAdapter(private val glide: RequestManager,
|
class ReaderAdapter(private val activity: ReaderActivity,
|
||||||
private val galleryID: Int) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
private val galleryID: Int) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||||
|
|
||||||
var reader: Reader? = null
|
var reader: Reader? = null
|
||||||
val timer = Timer()
|
val timer = Timer()
|
||||||
|
|
||||||
|
private val glide = Glide.with(activity)
|
||||||
|
|
||||||
var isFullScreen = false
|
var isFullScreen = false
|
||||||
|
|
||||||
var onItemClickListener : ((Int) -> (Unit))? = null
|
var onItemClickListener : ((Int) -> (Unit))? = null
|
||||||
|
|
||||||
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
var downloadWorker: DownloadWorker? = null
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return LayoutInflater.from(parent.context).inflate(
|
return LayoutInflater.from(parent.context).inflate(
|
||||||
R.layout.item_reader, parent, false
|
R.layout.item_reader, parent, false
|
||||||
@@ -68,11 +69,12 @@ class ReaderAdapter(private val glide: RequestManager,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var cache: Cache? = null
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
holder.view as ConstraintLayout
|
holder.view as ConstraintLayout
|
||||||
|
|
||||||
if (downloadWorker == null)
|
if (cache == null)
|
||||||
downloadWorker = DownloadWorker.getInstance(holder.view.context)
|
cache = Cache.getInstance(holder.view.context, galleryID)
|
||||||
|
|
||||||
if (isFullScreen) {
|
if (isFullScreen) {
|
||||||
holder.view.layoutParams.height = RecyclerView.LayoutParams.MATCH_PARENT
|
holder.view.layoutParams.height = RecyclerView.LayoutParams.MATCH_PARENT
|
||||||
@@ -124,15 +126,15 @@ class ReaderAdapter(private val glide: RequestManager,
|
|||||||
.into(holder.view.image)
|
.into(holder.view.image)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val image = Cache(holder.view.context).getImage(galleryID, position)
|
val image = cache!!.getImage(position)
|
||||||
val progress = downloadWorker!!.progress[galleryID]?.get(position)
|
val progress = activity.downloader?.progress?.get(galleryID)?.get(position)
|
||||||
|
|
||||||
if (progress?.isInfinite() == true && image != null) {
|
if (progress?.isInfinite() == true && image != null) {
|
||||||
holder.view.reader_item_progressbar.visibility = View.INVISIBLE
|
holder.view.reader_item_progressbar.visibility = View.INVISIBLE
|
||||||
|
|
||||||
holder.view.image.post {
|
holder.view.image.post {
|
||||||
glide
|
glide
|
||||||
.load(image)
|
.load(image.readBytes())
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.skipMemoryCache(true)
|
.skipMemoryCache(true)
|
||||||
.fitCenter()
|
.fitCenter()
|
||||||
|
|||||||
@@ -18,24 +18,37 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.services
|
package xyz.quaver.pupil.services
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.app.TaskStackBuilder
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
|
import okhttp3.Response
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import okio.*
|
import okio.*
|
||||||
import xyz.quaver.pupil.PupilInterceptor
|
import xyz.quaver.pupil.PupilInterceptor
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.client
|
||||||
import xyz.quaver.pupil.interceptors
|
import xyz.quaver.pupil.interceptors
|
||||||
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
|
import xyz.quaver.pupil.util.downloader.DownloadFolderManager
|
||||||
|
import xyz.quaver.pupil.util.requestBuilders
|
||||||
|
import xyz.quaver.pupil.util.startForegroundServiceCompat
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit
|
private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit
|
||||||
class DownloadService : Service() {
|
class DownloadService : Service() {
|
||||||
|
|
||||||
data class Tag(val galleryID: Int, val index: Int)
|
data class Tag(val galleryID: Int, val index: Int)
|
||||||
|
|
||||||
//region Notification
|
//region Notification
|
||||||
@@ -50,13 +63,60 @@ class DownloadService : Service() {
|
|||||||
.setSmallIcon(R.drawable.ic_notification)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val notification = SparseArray<NotificationCompat.Builder?>()
|
||||||
|
|
||||||
|
private fun initNotification(galleryID: Int) {
|
||||||
|
val intent = Intent(this, ReaderActivity::class.java).apply {
|
||||||
|
putExtra("galleryID", galleryID)
|
||||||
|
}
|
||||||
|
val pendingIntent = TaskStackBuilder.create(this).run {
|
||||||
|
addNextIntentWithParentStack(intent)
|
||||||
|
getPendingIntent(galleryID, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.put(galleryID, NotificationCompat.Builder(this, "download").apply {
|
||||||
|
setContentTitle(getString(R.string.reader_loading))
|
||||||
|
setContentText(getString(R.string.reader_notification_text))
|
||||||
|
setSmallIcon(R.drawable.ic_notification) // had to use this because old android doesn't support VectorDrawable on Notification :P
|
||||||
|
setContentIntent(pendingIntent)
|
||||||
|
setProgress(0, 0, true)
|
||||||
|
setOngoing(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
notify(galleryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notify(galleryID: Int) {
|
||||||
|
val max = progress[galleryID]?.size ?: 0
|
||||||
|
val progress = progress[galleryID]?.count { it.isInfinite() } ?: 0
|
||||||
|
|
||||||
|
val notification = notification[galleryID] ?: return
|
||||||
|
|
||||||
|
if (isCompleted(galleryID)) {
|
||||||
|
notification
|
||||||
|
.setContentText(getString(R.string.reader_notification_complete))
|
||||||
|
.setProgress(0, 0, false)
|
||||||
|
.setOngoing(false)
|
||||||
|
|
||||||
|
notificationManager.cancel(galleryID)
|
||||||
|
} else
|
||||||
|
notification
|
||||||
|
.setProgress(max, progress, false)
|
||||||
|
.setContentText("$progress/$max")
|
||||||
|
|
||||||
|
if (DownloadFolderManager.getInstance(this).getDownloadFolder(galleryID) != null)
|
||||||
|
notification.let { notificationManager.notify(galleryID, it.build()) }
|
||||||
|
else
|
||||||
|
notificationManager.cancel(galleryID)
|
||||||
|
}
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
//region ProgressListener
|
//region ProgressListener
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
private val progressListener: ProgressListener = { (galleryID, index), bytesRead, contentLength, done ->
|
private val progressListener: ProgressListener = { (galleryID, index), bytesRead, contentLength, done ->
|
||||||
if (!done && progress[galleryID]?.get(index)?.isFinite() == true)
|
if (!done && progress[galleryID]?.get(index)?.isFinite() == true)
|
||||||
progress[galleryID]?.set(index, bytesRead * 100F / contentLength)
|
progress[galleryID]?.set(index, bytesRead * 100F / contentLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ProgressResponseBody(
|
private class ProgressResponseBody(
|
||||||
@@ -107,6 +167,7 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
//region Downloader
|
||||||
/**
|
/**
|
||||||
* KEY
|
* KEY
|
||||||
* primary galleryID
|
* primary galleryID
|
||||||
@@ -120,11 +181,163 @@ class DownloadService : Service() {
|
|||||||
*/
|
*/
|
||||||
val progress = SparseArray<MutableList<Float>?>()
|
val progress = SparseArray<MutableList<Float>?>()
|
||||||
|
|
||||||
override fun onCreate() {
|
fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it.isInfinite() } == true
|
||||||
startForeground(R.id.downloader_notification_id, serviceNotification.build())
|
|
||||||
interceptors[Tag::class] = interceptor
|
private val callback = object: Callback {
|
||||||
|
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
if (e.message?.contains("cancel", true) == false) {
|
||||||
|
val galleryID = (call.request().tag() as Tag).galleryID
|
||||||
|
|
||||||
|
Log.i("PUPILD", "$galleryID ERR-RETRYING $e ${e.message}")
|
||||||
|
|
||||||
|
// Retry
|
||||||
|
cancel(galleryID)
|
||||||
|
download(galleryID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
val (galleryID, index) = call.request().tag() as Tag
|
||||||
|
val ext = call.request().url().encodedPath().split('.').last()
|
||||||
|
|
||||||
|
kotlin.runCatching {
|
||||||
|
val image = response.body()?.use { it.bytes() } ?: throw Exception()
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
kotlin.runCatching {
|
||||||
|
Cache.getInstance(this@DownloadService, galleryID).putImage(index, "$index.$ext", image)
|
||||||
|
}.onSuccess {
|
||||||
|
notify(galleryID)
|
||||||
|
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
|
||||||
|
}.onFailure {
|
||||||
|
Log.i("PUPILD", "$galleryID-$index DLERR-RETRYING $it ${it.message}")
|
||||||
|
|
||||||
|
cancel(galleryID)
|
||||||
|
download(galleryID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cancel() {
|
||||||
|
client.dispatcher().queuedCalls().filter {
|
||||||
|
it.request().tag() is Tag
|
||||||
|
}.forEach {
|
||||||
|
it.cancel()
|
||||||
|
}
|
||||||
|
client.dispatcher().runningCalls().filter {
|
||||||
|
it.request().tag() is Tag
|
||||||
|
}.forEach {
|
||||||
|
it.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.clear()
|
||||||
|
notification.clear()
|
||||||
|
notificationManager.cancelAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel(galleryID: Int) {
|
||||||
|
client.dispatcher().queuedCalls().filter {
|
||||||
|
(it.request().tag() as Tag).galleryID == galleryID
|
||||||
|
}.forEach {
|
||||||
|
it.cancel()
|
||||||
|
}
|
||||||
|
client.dispatcher().runningCalls().filter {
|
||||||
|
(it.request().tag() as Tag).galleryID == galleryID
|
||||||
|
}.forEach {
|
||||||
|
it.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.remove(galleryID)
|
||||||
|
notification.remove(galleryID)
|
||||||
|
notificationManager.cancel(galleryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
cancel(galleryID)
|
||||||
|
Cache.delete(galleryID)
|
||||||
|
DownloadFolderManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun download(galleryID: Int): Job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
if (progress.indexOfKey(galleryID) >= 0)
|
||||||
|
cancel(galleryID)
|
||||||
|
|
||||||
|
val cache = Cache.getInstance(this@DownloadService, galleryID)
|
||||||
|
|
||||||
|
initNotification(galleryID)
|
||||||
|
|
||||||
|
val reader = cache.getReader()
|
||||||
|
|
||||||
|
// Gallery doesn't exist
|
||||||
|
if (reader == null) {
|
||||||
|
delete(galleryID)
|
||||||
|
progress.put(galleryID, null)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress.indexOfKey(galleryID) < 0)
|
||||||
|
progress.put(galleryID, mutableListOf())
|
||||||
|
|
||||||
|
cache.metadata.imageList?.forEach {
|
||||||
|
progress[galleryID]?.add(if (it != null) Float.POSITIVE_INFINITY else 0F)
|
||||||
|
}
|
||||||
|
|
||||||
|
notification[galleryID]?.setContentTitle(reader.galleryInfo.title)
|
||||||
|
notify(galleryID)
|
||||||
|
|
||||||
|
reader.requestBuilders.filterIndexed { index, _ -> !progress[galleryID]!![index].isInfinite() }.forEachIndexed { index, it ->
|
||||||
|
val request = it.tag(Tag(galleryID, index)).build()
|
||||||
|
client.newCall(request).enqueue(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KEY_COMMAND = "COMMAND" // String
|
||||||
|
const val KEY_ID = "ID" // Int
|
||||||
|
|
||||||
|
const val COMMAND_DOWNLOAD = "DOWNLOAD"
|
||||||
|
const val COMMAND_CANCEL = "CANCEL"
|
||||||
|
const val COMMAND_DELETE = "DELETE"
|
||||||
|
|
||||||
|
private fun command(context: Context, extras: Intent.() -> Unit) {
|
||||||
|
context.startForegroundServiceCompat(Intent(context, DownloadService::class.java).apply(extras))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun download(context: Context, galleryID: Int) {
|
||||||
|
command(context) {
|
||||||
|
putExtra(KEY_COMMAND, COMMAND_DOWNLOAD)
|
||||||
|
putExtra(KEY_ID, galleryID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel(context: Context, galleryID: Int? = null) {
|
||||||
|
command(context) {
|
||||||
|
putExtra(KEY_COMMAND, COMMAND_CANCEL)
|
||||||
|
galleryID?.let { putExtra(KEY_ID, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(context: Context, galleryID: Int) {
|
||||||
|
command(context) {
|
||||||
|
putExtra(KEY_COMMAND, COMMAND_DELETE)
|
||||||
|
putExtra(KEY_ID, galleryID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
when (intent?.getStringExtra(KEY_COMMAND)) {
|
||||||
|
COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) download(it) }
|
||||||
|
COMMAND_CANCEL -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) cancel(it) else cancel() }
|
||||||
|
COMMAND_DELETE -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) delete(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
inner class Binder : android.os.Binder() {
|
inner class Binder : android.os.Binder() {
|
||||||
val service = this@DownloadService
|
val service = this@DownloadService
|
||||||
@@ -133,20 +346,13 @@ class DownloadService : Service() {
|
|||||||
private val binder = Binder()
|
private val binder = Binder()
|
||||||
override fun onBind(p0: Intent?) = binder
|
override fun onBind(p0: Intent?) = binder
|
||||||
|
|
||||||
val cache = SparseArray<Cache>()
|
override fun onCreate() {
|
||||||
fun load(galleryID: Int) {
|
startForeground(R.id.downloader_notification_id, serviceNotification.build())
|
||||||
if (progress.indexOfKey(galleryID) < 0)
|
interceptors[Tag::class] = interceptor
|
||||||
progress.put(galleryID, mutableListOf())
|
|
||||||
|
|
||||||
if (cache.indexOfKey(galleryID) < 0)
|
|
||||||
cache.put(galleryID, Cache.getInstance(this, galleryID))
|
|
||||||
|
|
||||||
cache[galleryID].metadata.imageList?.forEach {
|
|
||||||
progress[galleryID]?.add(if (it == null) Float.POSITIVE_INFINITY else 0F)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun download(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
|
override fun onDestroy() {
|
||||||
|
interceptors.remove(Tag::class)
|
||||||
|
cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
package xyz.quaver.pupil.types
|
package xyz.quaver.pupil.types
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Tag(val area: String?, val tag: String, val isNegative: Boolean = false) {
|
data class Tag(val area: String?, val tag: String, val isNegative: Boolean = false) {
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ import kotlinx.coroutines.*
|
|||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
|
||||||
import xyz.quaver.hitomi.doSearch
|
import xyz.quaver.hitomi.doSearch
|
||||||
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
||||||
import xyz.quaver.hitomi.getSuggestionsForQuery
|
import xyz.quaver.hitomi.getSuggestionsForQuery
|
||||||
@@ -68,8 +67,8 @@ import xyz.quaver.pupil.types.TagSuggestion
|
|||||||
import xyz.quaver.pupil.types.Tags
|
import xyz.quaver.pupil.types.Tags
|
||||||
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
import xyz.quaver.pupil.util.download.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.download.DownloadWorker
|
import xyz.quaver.pupil.util.downloader.DownloadFolderManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
@@ -92,7 +91,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
POPULAR
|
POPULAR
|
||||||
}
|
}
|
||||||
|
|
||||||
private val galleries = ArrayList<GalleryBlock>()
|
private val galleries = ArrayList<Int>()
|
||||||
|
|
||||||
private var query = ""
|
private var query = ""
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -112,6 +111,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private var loadingJob: Job? = null
|
private var loadingJob: Job? = null
|
||||||
private var currentPage = 0
|
private var currentPage = 0
|
||||||
|
|
||||||
|
private lateinit var downloadFolderManager: DownloadFolderManager
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@@ -146,15 +147,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
Intent(this, DownloadService::class.java).let {
|
downloadFolderManager = DownloadFolderManager.getInstance(this)
|
||||||
when {
|
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ->
|
|
||||||
startForegroundService(it)
|
|
||||||
else ->
|
|
||||||
startService(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkUpdate(this)
|
checkUpdate(this)
|
||||||
|
|
||||||
initView()
|
initView()
|
||||||
@@ -336,7 +329,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
with(main_fab_cancel) {
|
with(main_fab_cancel) {
|
||||||
setImageResource(R.drawable.cancel)
|
setImageResource(R.drawable.cancel)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
DownloadWorker.getInstance(context).stop()
|
DownloadService.cancel(this@MainActivity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,19 +440,15 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onDownloadClickedHandler = { position ->
|
onDownloadClickedHandler = { position ->
|
||||||
val galleryID = galleries[position].id
|
val galleryID = galleries[position]
|
||||||
val worker = DownloadWorker.getInstance(context)
|
|
||||||
if (Preferences["cache_disable"])
|
if (Preferences["cache_disable"])
|
||||||
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
||||||
else {
|
else {
|
||||||
if (worker.progress.indexOfKey(galleryID) >= 0 && Cache(context).isDownloading(galleryID)) { //download in progress
|
if (downloadFolderManager.isDownloading(galleryID)) { //download in progress
|
||||||
Cache(context).setDownloading(galleryID, false)
|
DownloadService.cancel(this@MainActivity, galleryID)
|
||||||
worker.cancel(galleryID)
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Cache(context).setDownloading(galleryID, true)
|
DownloadService.download(this@MainActivity, galleryID)
|
||||||
|
|
||||||
worker.queue.add(galleryID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,25 +456,20 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDeleteClickedHandler = { position ->
|
onDeleteClickedHandler = { position ->
|
||||||
val galleryID = galleries[position].id
|
val galleryID = galleries[position]
|
||||||
|
DownloadService.delete(this@MainActivity, galleryID)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
histories.remove(galleryID)
|
||||||
DownloadWorker.getInstance(context).cancel(galleryID)
|
|
||||||
|
|
||||||
Cache(context).getCachedGallery(galleryID).deleteRecursively()
|
if (this@MainActivity.mode != Mode.SEARCH)
|
||||||
|
runOnUiThread {
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
|
||||||
histories.remove(galleryID)
|
completeFlag.put(galleryID, false)
|
||||||
|
|
||||||
if (this@MainActivity.mode != Mode.SEARCH)
|
|
||||||
runOnUiThread {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
|
|
||||||
completeFlag.put(galleryID, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
closeAllItems()
|
closeAllItems()
|
||||||
}
|
}
|
||||||
@@ -496,8 +480,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
return@listener
|
return@listener
|
||||||
|
|
||||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
||||||
val gallery = galleries[position]
|
intent.putExtra("galleryID", galleries[position])
|
||||||
intent.putExtra("galleryID", gallery.id)
|
|
||||||
|
|
||||||
//TODO: Maybe sprinkling some transitions will be nice :D
|
//TODO: Maybe sprinkling some transitions will be nice :D
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
@@ -507,7 +490,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
if (v !is CardView)
|
if (v !is CardView)
|
||||||
return@listener false
|
return@listener false
|
||||||
|
|
||||||
val galleryID = galleries[position].id
|
val galleryID = galleries[position]
|
||||||
|
|
||||||
GalleryDialog(
|
GalleryDialog(
|
||||||
this@MainActivity,
|
this@MainActivity,
|
||||||
@@ -762,7 +745,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
if (!favoritesFile.exists()) {
|
if (!favoritesFile.exists()) {
|
||||||
favoritesFile.createNewFile()
|
favoritesFile.createNewFile()
|
||||||
favoritesFile.writeText(Json.encodeToString(Tags()))
|
favoritesFile.writeText("[]")
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener {
|
setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener {
|
||||||
@@ -788,7 +771,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
R.id.main_menu_sort_newest -> {
|
R.id.main_menu_sort_newest -> {
|
||||||
sortMode = SortMode.NEWEST
|
sortMode = MainActivity.SortMode.NEWEST
|
||||||
it.isChecked = true
|
it.isChecked = true
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
@@ -1023,7 +1006,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).also {
|
else -> doSearch("$defaultQuery $query", sortMode == MainActivity.SortMode.POPULAR).also {
|
||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1107,16 +1090,16 @@ class MainActivity : AppCompatActivity() {
|
|||||||
for (chunk in chunks)
|
for (chunk in chunks)
|
||||||
chunk.map { galleryID ->
|
chunk.map { galleryID ->
|
||||||
async {
|
async {
|
||||||
Cache(this@MainActivity).getGalleryBlock(galleryID)
|
Cache.getInstance(this@MainActivity, galleryID).getGalleryBlock()?.let {
|
||||||
|
galleryID
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.forEach {
|
}.forEach {
|
||||||
val galleryBlock = it.await()
|
it.await()?.also {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
main_progressbar.hide()
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
galleries.add(it)
|
||||||
main_progressbar.hide()
|
|
||||||
|
|
||||||
if (galleryBlock != null) {
|
|
||||||
galleries.add(galleryBlock)
|
|
||||||
main_recyclerview.adapter!!.notifyItemInserted(galleries.size - 1)
|
main_recyclerview.adapter!!.notifyItemInserted(galleries.size - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,14 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
@@ -47,9 +51,10 @@ import xyz.quaver.pupil.R
|
|||||||
import xyz.quaver.pupil.adapters.ReaderAdapter
|
import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||||
import xyz.quaver.pupil.favorites
|
import xyz.quaver.pupil.favorites
|
||||||
import xyz.quaver.pupil.histories
|
import xyz.quaver.pupil.histories
|
||||||
|
import xyz.quaver.pupil.services.DownloadService
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
import xyz.quaver.pupil.util.download.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.download.DownloadWorker
|
import xyz.quaver.pupil.util.downloader.DownloadFolderManager
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
import kotlin.concurrent.timer
|
import kotlin.concurrent.timer
|
||||||
@@ -72,6 +77,22 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var cache: Cache
|
||||||
|
var downloader: DownloadService? = null
|
||||||
|
private val conn = object: ServiceConnection {
|
||||||
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
|
downloader = (service as DownloadService.Binder).service
|
||||||
|
Log.i("PUPILD", "CON")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
|
downloader = null
|
||||||
|
Log.i("PUPILD", "DIS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var deleteOnExit = true
|
||||||
|
|
||||||
private val timer = Timer()
|
private val timer = Timer()
|
||||||
private var autoTimer: Timer? = null
|
private var autoTimer: Timer? = null
|
||||||
|
|
||||||
@@ -81,6 +102,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_reader)
|
||||||
|
|
||||||
title = getString(R.string.reader_loading)
|
title = getString(R.string.reader_loading)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||||
@@ -89,23 +111,18 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
WindowManager.LayoutParams.FLAG_SECURE)
|
WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
|
|
||||||
setContentView(R.layout.activity_reader)
|
|
||||||
|
|
||||||
handleIntent(intent)
|
handleIntent(intent)
|
||||||
|
cache = Cache.getInstance(this, galleryID)
|
||||||
histories.add(galleryID)
|
|
||||||
FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID)
|
FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID)
|
||||||
|
|
||||||
if (galleryID == 0) {
|
if (galleryID == 0) {
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
initView()
|
|
||||||
if (Preferences["cache_disable"]) {
|
if (Preferences["cache_disable"]) {
|
||||||
reader_download_progressbar.visibility = View.GONE
|
reader_download_progressbar.visibility = View.GONE
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val reader = Cache(this@ReaderActivity).getReader(galleryID)
|
val reader = cache.getReader()
|
||||||
|
|
||||||
launch(Dispatchers.Main) initDownloader@{
|
launch(Dispatchers.Main) initDownloader@{
|
||||||
if (reader == null) {
|
if (reader == null) {
|
||||||
@@ -115,6 +132,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
return@initDownloader
|
return@initDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
histories.add(galleryID)
|
||||||
(reader_recyclerview.adapter as ReaderAdapter).apply {
|
(reader_recyclerview.adapter as ReaderAdapter).apply {
|
||||||
this.reader = reader
|
this.reader = reader
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
@@ -132,6 +150,8 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
initDownloader()
|
initDownloader()
|
||||||
|
|
||||||
|
initView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
@@ -187,9 +207,9 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
R.id.reader_menu_page_indicator -> {
|
R.id.reader_menu_page_indicator -> {
|
||||||
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, reader_layout, false)
|
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, reader_layout, false)
|
||||||
with(view.dialog_number_picker) {
|
with(view.dialog_number_picker) {
|
||||||
minValue=1
|
minValue = 1
|
||||||
maxValue=Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.files?.size ?: 0
|
maxValue = cache.metadata.reader?.galleryInfo?.files?.size ?: 0
|
||||||
value=currentPage
|
value = currentPage
|
||||||
}
|
}
|
||||||
val dialog = AlertDialog.Builder(this).apply {
|
val dialog = AlertDialog.Builder(this).apply {
|
||||||
setView(view)
|
setView(view)
|
||||||
@@ -224,8 +244,12 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
timer.cancel()
|
timer.cancel()
|
||||||
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
|
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
|
||||||
|
|
||||||
if (!Cache(this).isDownloading(galleryID))
|
if (deleteOnExit) {
|
||||||
DownloadWorker.getInstance(this@ReaderActivity).cancel(galleryID)
|
downloader?.cancel(galleryID)
|
||||||
|
DownloadFolderManager.getInstance(this).deleteDownloadFolder(galleryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
unbindService(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
@@ -261,16 +285,16 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initDownloader() {
|
private fun initDownloader() {
|
||||||
val worker = DownloadWorker.getInstance(this).apply {
|
DownloadService.download(this, galleryID)
|
||||||
cancel(galleryID)
|
bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE)
|
||||||
queue.add(galleryID)
|
|
||||||
}
|
|
||||||
|
|
||||||
timer.schedule(1000, 1000) {
|
timer.schedule(1000, 1000) {
|
||||||
if (worker.progress.indexOfKey(galleryID) < 0) //loading
|
val downloader = downloader ?: return@schedule
|
||||||
|
|
||||||
|
if (downloader.progress.indexOfKey(galleryID) < 0) //loading
|
||||||
return@schedule
|
return@schedule
|
||||||
|
|
||||||
if (worker.progress[galleryID] == null) { //Gallery not found
|
if (downloader.progress[galleryID] == null) { //Gallery not found
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
Snackbar
|
Snackbar
|
||||||
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
||||||
@@ -279,14 +303,13 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
||||||
reader_download_progressbar.progress = worker.progress[galleryID]?.count { it.isInfinite() } ?: 0
|
reader_download_progressbar.progress = downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0
|
||||||
reader_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
reader_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
||||||
|
|
||||||
if (title == getString(R.string.reader_loading)) {
|
if (title == getString(R.string.reader_loading)) {
|
||||||
val reader = Cache(this@ReaderActivity).getReaderOrNull(galleryID)
|
val reader = cache.metadata.reader
|
||||||
|
|
||||||
if (reader != null) {
|
if (reader != null) {
|
||||||
|
|
||||||
with (reader_recyclerview.adapter as ReaderAdapter) {
|
with (reader_recyclerview.adapter as ReaderAdapter) {
|
||||||
this.reader = reader
|
this.reader = reader
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
@@ -304,7 +327,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (worker.progress[galleryID]?.all { it.isInfinite() } == true) { //Download finished
|
if (downloader.isCompleted(galleryID)) { //Download finished
|
||||||
reader_download_progressbar.visibility = View.GONE
|
reader_download_progressbar.visibility = View.GONE
|
||||||
|
|
||||||
animateDownloadFAB(false)
|
animateDownloadFAB(false)
|
||||||
@@ -315,7 +338,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
with(reader_recyclerview) {
|
with(reader_recyclerview) {
|
||||||
adapter = ReaderAdapter(Glide.with(this@ReaderActivity), galleryID).apply {
|
adapter = ReaderAdapter(this@ReaderActivity, galleryID).apply {
|
||||||
onItemClickListener = {
|
onItemClickListener = {
|
||||||
if (isScroll) {
|
if (isScroll) {
|
||||||
isScroll = false
|
isScroll = false
|
||||||
@@ -350,19 +373,18 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
with(reader_fab_download) {
|
with(reader_fab_download) {
|
||||||
animateDownloadFAB(Cache(context).isDownloading(galleryID)) //If download in progress, animate button
|
animateDownloadFAB(DownloadFolderManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
|
||||||
|
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false))
|
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false))
|
||||||
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
||||||
else {
|
else {
|
||||||
if (Cache(context).isDownloading(galleryID)) {
|
if (deleteOnExit) {
|
||||||
Cache(context).setDownloading(galleryID, false)
|
deleteOnExit = false
|
||||||
|
cache.moveToDownload()
|
||||||
animateDownloadFAB(false)
|
|
||||||
} else {
|
|
||||||
Cache(context).setDownloading(galleryID, true)
|
|
||||||
animateDownloadFAB(true)
|
animateDownloadFAB(true)
|
||||||
|
} else {
|
||||||
|
animateDownloadFAB(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,10 +393,8 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
with(reader_fab_retry) {
|
with(reader_fab_retry) {
|
||||||
setImageResource(R.drawable.refresh)
|
setImageResource(R.drawable.refresh)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
DownloadWorker.getInstance(context).let {
|
downloader?.cancel(galleryID)
|
||||||
it.cancel(galleryID)
|
downloader?.download(galleryID)
|
||||||
it.queue.add(galleryID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,8 +470,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
icon?.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
|
icon?.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
|
||||||
override fun onAnimationEnd(drawable: Drawable?) {
|
override fun onAnimationEnd(drawable: Drawable?) {
|
||||||
val worker = DownloadWorker.getInstance(context)
|
if (downloader?.isCompleted(galleryID) == true) // If download is finished, stop animating
|
||||||
if (worker.progress[galleryID]?.all { it.isInfinite() } == true) // If download is finished, stop animating
|
|
||||||
post {
|
post {
|
||||||
setImageResource(R.drawable.ic_download)
|
setImageResource(R.drawable.ic_download)
|
||||||
labelText = getString(R.string.reader_fab_download_cancel)
|
labelText = getString(R.string.reader_fab_download_cancel)
|
||||||
|
|||||||
@@ -27,15 +27,12 @@ import android.os.Bundle
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.android.synthetic.main.settings_activity.*
|
import kotlinx.android.synthetic.main.settings_activity.*
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
||||||
import xyz.quaver.io.util.toFile
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.pupil.Pupil
|
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.favorites
|
import xyz.quaver.pupil.favorites
|
||||||
import xyz.quaver.pupil.ui.fragment.LockSettingsFragment
|
import xyz.quaver.pupil.ui.fragment.LockSettingsFragment
|
||||||
@@ -126,16 +123,14 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
||||||
contentResolver.takePersistableUriPermission(uri, takeFlags)
|
contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||||
|
|
||||||
val file = uri.toFile(this)
|
if (FileX(this, uri).canWrite())
|
||||||
|
Preferences["download_folder"] = uri.toString()
|
||||||
if (file?.canWrite() != true)
|
else
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
settings,
|
settings,
|
||||||
R.string.settings_dl_location_not_writable,
|
R.string.settings_download_folder_not_writable,
|
||||||
Snackbar.LENGTH_LONG
|
Snackbar.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
else
|
|
||||||
Preferences["dl_location"] = file.canonicalPath
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,11 +141,11 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
if (!File(directory).canWrite())
|
if (!File(directory).canWrite())
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
settings,
|
settings,
|
||||||
R.string.settings_dl_location_not_writable,
|
R.string.settings_download_folder_not_writable,
|
||||||
Snackbar.LENGTH_LONG
|
Snackbar.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
else
|
else
|
||||||
Preferences["dl_location"] = File(directory).canonicalPath
|
Preferences["download_folder"] = File(directory).canonicalPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|||||||
@@ -18,21 +18,19 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui.dialog
|
package xyz.quaver.pupil.ui.dialog
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.RadioButton
|
import android.widget.RadioButton
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import kotlinx.android.synthetic.main.item_dl_location.view.*
|
import androidx.core.net.toUri
|
||||||
|
import kotlinx.android.synthetic.main.item_download_folder.view.*
|
||||||
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
||||||
import net.rdrei.android.dirchooser.DirectoryChooserConfig
|
import net.rdrei.android.dirchooser.DirectoryChooserConfig
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
@@ -44,7 +42,7 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) {
|
|||||||
private val buttons = mutableListOf<Pair<RadioButton, File?>>()
|
private val buttons = mutableListOf<Pair<RadioButton, File?>>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
setTitle(R.string.settings_dl_location)
|
setTitle(R.string.settings_download_folder)
|
||||||
|
|
||||||
setView(build())
|
setView(build())
|
||||||
|
|
||||||
@@ -54,7 +52,7 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun build() : View {
|
private fun build() : View {
|
||||||
val view = layoutInflater.inflate(R.layout.dialog_dl_location, null) as LinearLayout
|
val view = layoutInflater.inflate(R.layout.dialog_download_folder, null) as LinearLayout
|
||||||
|
|
||||||
val externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null)
|
val externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null)
|
||||||
|
|
||||||
@@ -62,13 +60,13 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) {
|
|||||||
|
|
||||||
dir ?: return@forEachIndexed
|
dir ?: return@forEachIndexed
|
||||||
|
|
||||||
view.addView(layoutInflater.inflate(R.layout.item_dl_location, view, false).apply {
|
view.addView(layoutInflater.inflate(R.layout.item_download_folder, view, false).apply {
|
||||||
location_type.text = context.getString(when (index) {
|
location_type.text = context.getString(when (index) {
|
||||||
0 -> R.string.settings_dl_location_internal
|
0 -> R.string.settings_download_folder_internal
|
||||||
else -> R.string.settings_dl_location_removable
|
else -> R.string.settings_download_folder_removable
|
||||||
})
|
})
|
||||||
location_available.text = context.getString(
|
location_available.text = context.getString(
|
||||||
R.string.settings_dl_location_available,
|
R.string.settings_download_folder_available,
|
||||||
byteToString(dir.freeSpace)
|
byteToString(dir.freeSpace)
|
||||||
)
|
)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
@@ -76,14 +74,14 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) {
|
|||||||
pair.first.isChecked = false
|
pair.first.isChecked = false
|
||||||
}
|
}
|
||||||
button.performClick()
|
button.performClick()
|
||||||
Preferences["dl_location"] = dir.canonicalPath
|
Preferences["download_folder"] = dir.toUri().toString()
|
||||||
}
|
}
|
||||||
buttons.add(button to dir)
|
buttons.add(button to dir)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
view.addView(layoutInflater.inflate(R.layout.item_dl_location, view, false).apply {
|
view.addView(layoutInflater.inflate(R.layout.item_download_folder, view, false).apply {
|
||||||
location_type.text = context.getString(R.string.settings_dl_location_custom)
|
location_type.text = context.getString(R.string.settings_download_folder_custom)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
buttons.forEach { pair ->
|
buttons.forEach { pair ->
|
||||||
pair.first.isChecked = false
|
pair.first.isChecked = false
|
||||||
@@ -91,17 +89,12 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) {
|
|||||||
button.performClick()
|
button.performClick()
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
|
||||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
|
putExtra("android.content.extra.SHOW_ADVANCED", true)
|
||||||
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), R.id.request_write_permission_and_saf.normalizeID())
|
|
||||||
else {
|
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
|
|
||||||
putExtra("android.content.extra.SHOW_ADVANCED", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
activity.startActivityForResult(intent, R.id.request_download_folder.normalizeID())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activity.startActivityForResult(intent, R.id.request_download_folder.normalizeID())
|
||||||
|
|
||||||
dismiss()
|
dismiss()
|
||||||
} else { // Can't use SAF on old Androids!
|
} else { // Can't use SAF on old Androids!
|
||||||
val config = DirectoryChooserConfig.builder()
|
val config = DirectoryChooserConfig.builder()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import android.app.Dialog
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout.LayoutParams
|
import android.widget.LinearLayout.LayoutParams
|
||||||
@@ -36,15 +37,10 @@ import kotlinx.android.synthetic.main.dialog_gallery.*
|
|||||||
import kotlinx.android.synthetic.main.dialog_gallery_details.view.*
|
import kotlinx.android.synthetic.main.dialog_gallery_details.view.*
|
||||||
import kotlinx.android.synthetic.main.dialog_gallery_dotindicator.view.*
|
import kotlinx.android.synthetic.main.dialog_gallery_dotindicator.view.*
|
||||||
import kotlinx.android.synthetic.main.item_gallery_details.view.*
|
import kotlinx.android.synthetic.main.item_gallery_details.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import xyz.quaver.hitomi.Gallery
|
import xyz.quaver.hitomi.Gallery
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
|
||||||
import xyz.quaver.hitomi.getGallery
|
import xyz.quaver.hitomi.getGallery
|
||||||
import xyz.quaver.pupil.BuildConfig
|
import xyz.quaver.pupil.BuildConfig
|
||||||
import xyz.quaver.pupil.Pupil
|
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||||
import xyz.quaver.pupil.adapters.ThumbnailPageAdapter
|
import xyz.quaver.pupil.adapters.ThumbnailPageAdapter
|
||||||
@@ -52,7 +48,7 @@ import xyz.quaver.pupil.histories
|
|||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
import xyz.quaver.pupil.util.ItemClickSupport
|
import xyz.quaver.pupil.util.ItemClickSupport
|
||||||
import xyz.quaver.pupil.util.download.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.wordCapitalize
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
|
|
||||||
class GalleryDialog(context: Context, private val glide: RequestManager, private val galleryID: Int) : Dialog(context) {
|
class GalleryDialog(context: Context, private val glide: RequestManager, private val galleryID: Int) : Dialog(context) {
|
||||||
@@ -131,7 +127,7 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
|
|
||||||
private fun addDetails(gallery: Gallery) {
|
private fun addDetails(gallery: Gallery) {
|
||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
|
|
||||||
inflater.inflate(R.layout.dialog_gallery_details, gallery_contents, false).apply {
|
inflater.inflate(R.layout.dialog_gallery_details, gallery_contents, false).apply {
|
||||||
gallery_details.setText(R.string.gallery_details)
|
gallery_details.setText(R.string.gallery_details)
|
||||||
|
|
||||||
@@ -230,7 +226,7 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
|
|
||||||
private fun addRelated(gallery: Gallery) {
|
private fun addRelated(gallery: Gallery) {
|
||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
val galleries = ArrayList<GalleryBlock>()
|
val galleries = ArrayList<Int>()
|
||||||
|
|
||||||
val adapter = GalleryBlockAdapter(glide, galleries).apply {
|
val adapter = GalleryBlockAdapter(glide, galleries).apply {
|
||||||
onChipClickedHandler.add { tag ->
|
onChipClickedHandler.add { tag ->
|
||||||
@@ -240,19 +236,6 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
gallery.related.forEachIndexed { i, galleryID ->
|
|
||||||
async(Dispatchers.IO) {
|
|
||||||
Cache(context).getGalleryBlock(galleryID)
|
|
||||||
}.let {
|
|
||||||
val galleryBlock = it.await() ?: return@let
|
|
||||||
|
|
||||||
galleries.add(galleryBlock)
|
|
||||||
adapter.notifyItemInserted(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inflater.inflate(R.layout.dialog_gallery_details, gallery_contents, false).apply {
|
inflater.inflate(R.layout.dialog_gallery_details, gallery_contents, false).apply {
|
||||||
gallery_details.setText(R.string.gallery_related)
|
gallery_details.setText(R.string.gallery_related)
|
||||||
|
|
||||||
@@ -263,15 +246,15 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
ItemClickSupport.addTo(this).apply {
|
ItemClickSupport.addTo(this).apply {
|
||||||
onItemClickListener = { _, position, _ ->
|
onItemClickListener = { _, position, _ ->
|
||||||
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||||
putExtra("galleryID", galleries[position].id)
|
putExtra("galleryID", galleries[position])
|
||||||
})
|
})
|
||||||
histories.add(galleries[position].id)
|
histories.add(galleries[position])
|
||||||
}
|
}
|
||||||
onItemLongClickListener = { _, position, _ ->
|
onItemLongClickListener = { _, position, _ ->
|
||||||
GalleryDialog(
|
GalleryDialog(
|
||||||
context,
|
context,
|
||||||
glide,
|
glide,
|
||||||
galleries[position].id
|
galleries[position]
|
||||||
).apply {
|
).apply {
|
||||||
onChipClickedHandler.add { tag ->
|
onChipClickedHandler.add { tag ->
|
||||||
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
|
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
|
||||||
@@ -287,6 +270,18 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
}.let {
|
}.let {
|
||||||
gallery_contents.addView(it)
|
gallery_contents.addView(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
gallery.related.forEach { galleryID ->
|
||||||
|
Cache.getInstance(context, galleryID).getGalleryBlock()?.let {
|
||||||
|
galleries.add(galleryID)
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
adapter.notifyItemInserted(galleries.size-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,7 @@ import androidx.preference.PreferenceFragmentCompat
|
|||||||
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 xyz.quaver.io.FileX
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.histories
|
import xyz.quaver.pupil.histories
|
||||||
import xyz.quaver.pupil.ui.LockActivity
|
import xyz.quaver.pupil.ui.LockActivity
|
||||||
@@ -141,7 +142,7 @@ class SettingsFragment :
|
|||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
"dl_location" -> {
|
"download_folder" -> {
|
||||||
DownloadLocationDialog(requireActivity()).show()
|
DownloadLocationDialog(requireActivity()).show()
|
||||||
}
|
}
|
||||||
"default_query" -> {
|
"default_query" -> {
|
||||||
@@ -208,9 +209,6 @@ class SettingsFragment :
|
|||||||
"proxy" -> {
|
"proxy" -> {
|
||||||
summary = context?.let { getProxyInfo().type.name }
|
summary = context?.let { getProxyInfo().type.name }
|
||||||
}
|
}
|
||||||
"dl_location" -> {
|
|
||||||
summary = context?.let { getDownloadDirectory(it).canonicalPath }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,8 +273,14 @@ class SettingsFragment :
|
|||||||
|
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
"dl_location" -> {
|
"download_folder" -> {
|
||||||
summary = getDownloadDirectory(requireContext()).canonicalPath
|
setSummaryProvider {
|
||||||
|
val uri: String = Preferences[it.key]
|
||||||
|
|
||||||
|
kotlin.runCatching {
|
||||||
|
FileX(context, uri).canonicalPath
|
||||||
|
}.getOrElse { "" }
|
||||||
|
}
|
||||||
|
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ import android.util.SparseArray
|
|||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|||||||
@@ -20,50 +20,58 @@ package xyz.quaver.pupil.util.downloader
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.util.Base64
|
import android.util.Log
|
||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import xyz.quaver.Code
|
import xyz.quaver.Code
|
||||||
import xyz.quaver.hitomi.Gallery
|
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.hitomi.getGallery
|
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.io.util.getChild
|
import xyz.quaver.io.util.*
|
||||||
import xyz.quaver.io.util.readBytes
|
|
||||||
import xyz.quaver.io.util.readText
|
|
||||||
import xyz.quaver.io.util.writeBytes
|
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
import xyz.quaver.pupil.util.formatDownloadFolder
|
import xyz.quaver.pupil.util.formatDownloadFolder
|
||||||
|
import kotlin.io.deleteRecursively
|
||||||
|
import kotlin.io.writeText
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Metadata(
|
data class Metadata(
|
||||||
var galleryBlock: GalleryBlock? = null,
|
var galleryBlock: GalleryBlock? = null,
|
||||||
var gallery: Gallery? = null,
|
|
||||||
var thumbnail: String? = null,
|
|
||||||
var reader: Reader? = null,
|
var reader: Reader? = null,
|
||||||
var imageList: MutableList<String?>? = null
|
var imageList: MutableList<String?>? = null
|
||||||
) {
|
) {
|
||||||
fun copy(): Metadata = Metadata(galleryBlock, gallery, thumbnail, reader, imageList?.let { MutableList(it.size) { i -> it[i] } })
|
fun copy(): Metadata = Metadata(galleryBlock, reader, imageList?.let { MutableList(it.size) { i -> it[i] } })
|
||||||
}
|
}
|
||||||
|
|
||||||
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
|
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val mutex = Mutex()
|
||||||
private val instances = SparseArray<Cache>()
|
private val instances = SparseArray<Cache>()
|
||||||
|
|
||||||
fun getInstance(context: Context, galleryID: Int) =
|
fun getInstance(context: Context, galleryID: Int) =
|
||||||
instances[galleryID] ?: synchronized(this) {
|
instances[galleryID] ?: runBlocking { mutex.withLock {
|
||||||
instances[galleryID] ?: Cache(context, galleryID).also { instances.put(galleryID, it) }
|
instances[galleryID] ?: Cache(context, galleryID).also { instances.put(galleryID, it) }
|
||||||
}
|
} }
|
||||||
|
|
||||||
|
fun delete(galleryID: Int) { runBlocking { mutex.withLock {
|
||||||
|
instances[galleryID]?.galleryFolder?.deleteRecursively()
|
||||||
|
instances.delete(galleryID)
|
||||||
|
} } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
galleryFolder.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mutex = Mutex()
|
||||||
var metadata = kotlin.runCatching {
|
var metadata = kotlin.runCatching {
|
||||||
findFile(".metadata")?.readText()?.let {
|
findFile(".metadata")?.readText()?.let {
|
||||||
Json.decodeFromString<Metadata>(it)
|
Json.decodeFromString<Metadata>(it)
|
||||||
@@ -76,7 +84,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
val cacheFolder: FileX
|
val cacheFolder: FileX
|
||||||
get() = FileX(this, cacheDir, "imageCache/$galleryID")
|
get() = FileX(this, cacheDir, "imageCache/$galleryID")
|
||||||
|
|
||||||
val cachedGallery: FileX
|
val galleryFolder: FileX
|
||||||
get() = DownloadFolderManager.getInstance(this).getDownloadFolder(galleryID)
|
get() = DownloadFolderManager.getInstance(this).getDownloadFolder(galleryID)
|
||||||
?: FileX(this, cacheDir, "imageCache/$galleryID")
|
?: FileX(this, cacheDir, "imageCache/$galleryID")
|
||||||
|
|
||||||
@@ -87,16 +95,19 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
if (it.exists()) it else null
|
if (it.exists()) it else null
|
||||||
} }
|
} }
|
||||||
|
|
||||||
@Synchronized
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
fun setMetadata(change: (Metadata) -> Unit) {
|
suspend fun setMetadata(change: (Metadata) -> Unit) { mutex.withLock {
|
||||||
change.invoke(metadata)
|
change.invoke(metadata)
|
||||||
|
|
||||||
val file = cachedGallery.getChild(".metadata")
|
val file = galleryFolder.getChild(".metadata")
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
file.writeText(Json.encodeToString(Metadata))
|
kotlin.runCatching {
|
||||||
|
file.createNewFile()
|
||||||
|
file.writeText(Json.encodeToString(metadata))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} }
|
||||||
|
|
||||||
suspend fun getGalleryBlock(): GalleryBlock? {
|
suspend fun getGalleryBlock(): GalleryBlock? {
|
||||||
val sources = listOf(
|
val sources = listOf(
|
||||||
@@ -123,59 +134,47 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getGallery(): Gallery? =
|
|
||||||
metadata.gallery
|
|
||||||
?: withContext(Dispatchers.IO) {
|
|
||||||
kotlin.runCatching {
|
|
||||||
getGallery(galleryID)
|
|
||||||
}.getOrNull()?.also {
|
|
||||||
launch { setMetadata { metadata ->
|
|
||||||
metadata.gallery = it
|
|
||||||
|
|
||||||
if (metadata.imageList == null)
|
|
||||||
metadata.imageList = MutableList(it.thumbnails.size) { null }
|
|
||||||
} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
suspend fun getThumbnail(): String? =
|
suspend fun getThumbnail(): ByteArray? =
|
||||||
metadata.thumbnail
|
findFile(".thumbnail")?.readBytes()
|
||||||
?: withContext(Dispatchers.IO) {
|
?: getGalleryBlock()?.thumbnails?.firstOrNull()?.let { withContext(Dispatchers.IO) {
|
||||||
getGalleryBlock()?.thumbnails?.firstOrNull()?.let { thumbnail ->
|
val request = Request.Builder()
|
||||||
kotlin.runCatching {
|
.url(it)
|
||||||
val request = Request.Builder()
|
.build()
|
||||||
.url(thumbnail)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val image = client.newCall(request).execute().body()?.use { it.bytes() }
|
kotlin.runCatching {
|
||||||
|
client.newCall(request).execute().body()?.use { it.bytes() }
|
||||||
|
}.getOrNull()?.also { kotlin.run {
|
||||||
|
galleryFolder.getChild(".thumbnail").writeBytes(it)
|
||||||
|
} }
|
||||||
|
} }
|
||||||
|
|
||||||
Base64.encodeToString(image, Base64.DEFAULT)
|
suspend fun getReader(): Reader? {
|
||||||
}.getOrNull()
|
val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') }
|
||||||
}?.also {
|
|
||||||
launch { setMetadata { metadata -> metadata.thumbnail = it } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getReader(galleryID: Int): Reader? {
|
|
||||||
val mirrors = Preferences.get<String>("mirrors").split('>')
|
|
||||||
|
|
||||||
val sources = mapOf(
|
val sources = mapOf(
|
||||||
Code.HITOMI to { xyz.quaver.hitomi.getReader(galleryID) },
|
Code.HITOMI to { xyz.quaver.hitomi.getReader(galleryID) },
|
||||||
Code.HIYOBI to { xyz.quaver.hiyobi.getReader(galleryID) }
|
Code.HIYOBI to { xyz.quaver.hiyobi.getReader(galleryID) }
|
||||||
).toSortedMap { o1, o2 -> mirrors.indexOf(o1.name) - mirrors.indexOf(o2.name) }
|
).let {
|
||||||
|
if (mirrors.isNotEmpty())
|
||||||
|
it.toSortedMap{ o1, o2 -> mirrors.indexOf(o1.name) - mirrors.indexOf(o2.name) }
|
||||||
|
else
|
||||||
|
it
|
||||||
|
}
|
||||||
|
|
||||||
return metadata.reader
|
return metadata.reader
|
||||||
?: withContext(Dispatchers.IO) {
|
?: withContext(Dispatchers.IO) {
|
||||||
var reader: Reader? = null
|
var reader: Reader? = null
|
||||||
|
|
||||||
for (source in sources) {
|
for (source in sources) {
|
||||||
reader = try { withTimeoutOrNull(1000) {
|
reader = try {
|
||||||
source.value.invoke()
|
source.value.invoke()
|
||||||
} } catch (e: Exception) { null }
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
if (reader != null)
|
if (reader != null)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
reader?.also {
|
reader?.also {
|
||||||
@@ -193,13 +192,11 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
metadata.imageList?.get(index)?.let { findFile(it) }
|
metadata.imageList?.get(index)?.let { findFile(it) }
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
fun putImage(index: Int, fileName: String, data: ByteArray) = CoroutineScope(Dispatchers.IO).launch {
|
suspend fun putImage(index: Int, fileName: String, data: ByteArray) {
|
||||||
val file = FileX(this@Cache, cachedGallery, fileName).also {
|
val file = galleryFolder.getChild(fileName)
|
||||||
it.createNewFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
file.createNewFile()
|
||||||
file.writeBytes(data)
|
file.writeBytes(data)
|
||||||
|
|
||||||
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,11 +205,12 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
if (downloadFolder == null)
|
if (downloadFolder == null)
|
||||||
DownloadFolderManager.getInstance(this@Cache).addDownloadFolder(galleryID, this@Cache.formatDownloadFolder())
|
DownloadFolderManager.getInstance(this@Cache).addDownloadFolder(galleryID, this@Cache.formatDownloadFolder())
|
||||||
|
|
||||||
metadata.imageList?.forEach {
|
metadata.imageList?.forEach { imageName ->
|
||||||
it ?: return@forEach
|
imageName ?: return@forEach
|
||||||
|
|
||||||
val target = downloadFolder!!.getChild(it)
|
Log.i("PUPIL", downloadFolder?.uri.toString())
|
||||||
val source = cacheFolder.getChild(it)
|
val target = downloadFolder!!.getChild(imageName)
|
||||||
|
val source = cacheFolder.getChild(imageName)
|
||||||
|
|
||||||
if (!source.exists())
|
if (!source.exists())
|
||||||
return@forEach
|
return@forEach
|
||||||
@@ -223,6 +221,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.i("PUPIL", downloadFolder?.uri.toString())
|
||||||
val cacheMetadata = cacheFolder.getChild(".metadata")
|
val cacheMetadata = cacheFolder.getChild(".metadata")
|
||||||
val downloadMetadata = downloadFolder!!.getChild(".metadata")
|
val downloadMetadata = downloadFolder!!.getChild(".metadata")
|
||||||
|
|
||||||
|
|||||||
@@ -24,13 +24,18 @@ import android.webkit.URLUtil
|
|||||||
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.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Call
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
|
import xyz.quaver.io.util.getChild
|
||||||
import xyz.quaver.io.util.readText
|
import xyz.quaver.io.util.readText
|
||||||
|
import xyz.quaver.pupil.client
|
||||||
|
import xyz.quaver.pupil.services.DownloadService
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
|
||||||
class DownloadFolderManager private constructor(context: Context) : ContextWrapper(context) {
|
class DownloadFolderManager private constructor(context: Context) : ContextWrapper(context) {
|
||||||
@@ -46,59 +51,70 @@ class DownloadFolderManager private constructor(context: Context) : ContextWrapp
|
|||||||
|
|
||||||
val defaultDownloadFolder = FileX(this, getExternalFilesDir(null)!!)
|
val defaultDownloadFolder = FileX(this, getExternalFilesDir(null)!!)
|
||||||
|
|
||||||
val downloadFolder = {
|
val downloadFolder: FileX
|
||||||
val uri: String = Preferences["download_directory"]
|
get() = {
|
||||||
|
kotlin.runCatching {
|
||||||
if (!URLUtil.isValidUrl(uri))
|
FileX(this, Preferences.get<String>("download_folder"))
|
||||||
Preferences["download_directory"] = defaultDownloadFolder
|
}.getOrElse {
|
||||||
|
Preferences["download_folder"] = defaultDownloadFolder.uri.toString()
|
||||||
|
defaultDownloadFolder
|
||||||
|
}
|
||||||
|
}.invoke()
|
||||||
|
|
||||||
|
private val downloadFolderMapMutex = Mutex()
|
||||||
|
private val downloadFolderMap: MutableMap<Int, String> = runBlocking { downloadFolderMapMutex.withLock {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
FileX(this, uri)
|
downloadFolder.getChild(".download").readText()?.let {
|
||||||
}.getOrElse {
|
|
||||||
Preferences["download_directory"] = defaultDownloadFolder
|
|
||||||
FileX(this, defaultDownloadFolder)
|
|
||||||
}
|
|
||||||
}.invoke()
|
|
||||||
|
|
||||||
private val downloadFolderMap: MutableMap<Int, String> =
|
|
||||||
kotlin.runCatching {
|
|
||||||
FileX(this@DownloadFolderManager, downloadFolder, ".download").readText()?.let {
|
|
||||||
Json.decodeFromString<MutableMap<Int, String>>(it)
|
Json.decodeFromString<MutableMap<Int, String>>(it)
|
||||||
}
|
}
|
||||||
}.getOrNull() ?: mutableMapOf()
|
}.getOrNull() ?: mutableMapOf()
|
||||||
private val downloadFolderMapMutex = Mutex()
|
} }
|
||||||
|
|
||||||
@Synchronized
|
fun isDownloading(galleryID: Int): Boolean {
|
||||||
fun getDownloadFolder(galleryID: Int): FileX? =
|
val isThisGallery: (Call) -> Boolean = { (it.request().tag() as? DownloadService.Tag)?.galleryID == galleryID }
|
||||||
downloadFolderMap[galleryID]?.let { FileX(this, downloadFolder, it) }
|
|
||||||
|
|
||||||
@Synchronized
|
return downloadFolderMap.containsKey(galleryID)
|
||||||
fun addDownloadFolder(galleryID: Int, name: String) {
|
&& client.dispatcher().let { it.queuedCalls().any(isThisGallery) || it.runningCalls().any(isThisGallery) }
|
||||||
if (downloadFolderMap.containsKey(galleryID))
|
|
||||||
return
|
|
||||||
|
|
||||||
if (FileX(this@DownloadFolderManager, downloadFolder, name).mkdir()) {
|
|
||||||
downloadFolderMap[galleryID] = name
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock {
|
|
||||||
FileX(this@DownloadFolderManager, downloadFolder, ".download").writeText(Json.encodeToString(downloadFolderMap))
|
|
||||||
} }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
fun getDownloadFolder(galleryID: Int): FileX? = runBlocking { downloadFolderMapMutex.withLock {
|
||||||
fun removeDownloadFolder(galleryID: Int) {
|
downloadFolderMap[galleryID]?.let { downloadFolder.getChild(it) }
|
||||||
|
} }
|
||||||
|
|
||||||
|
fun addDownloadFolder(galleryID: Int, name: String) { runBlocking { downloadFolderMapMutex.withLock {
|
||||||
|
if (downloadFolderMap.containsKey(galleryID))
|
||||||
|
return@withLock
|
||||||
|
|
||||||
|
val folder = downloadFolder.getChild(name)
|
||||||
|
|
||||||
|
if (!folder.exists())
|
||||||
|
folder.mkdirs()
|
||||||
|
|
||||||
|
downloadFolderMap[galleryID] = name
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock {
|
||||||
|
downloadFolder.getChild(".download").let {
|
||||||
|
it.createNewFile()
|
||||||
|
it.writeText(Json.encodeToString(downloadFolderMap))
|
||||||
|
}
|
||||||
|
} }
|
||||||
|
} } }
|
||||||
|
|
||||||
|
fun deleteDownloadFolder(galleryID: Int) { runBlocking { downloadFolderMapMutex.withLock {
|
||||||
if (!downloadFolderMap.containsKey(galleryID))
|
if (!downloadFolderMap.containsKey(galleryID))
|
||||||
return
|
return@withLock
|
||||||
|
|
||||||
downloadFolderMap[galleryID]?.let {
|
downloadFolderMap[galleryID]?.let {
|
||||||
if (FileX(this@DownloadFolderManager, downloadFolder, it).delete()) {
|
if (downloadFolder.getChild(it).delete()) {
|
||||||
downloadFolderMap.remove(galleryID)
|
downloadFolderMap.remove(galleryID)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock {
|
CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock {
|
||||||
FileX(this@DownloadFolderManager, downloadFolder, ".download").writeText(Json.encodeToString(downloadFolderMap))
|
downloadFolder.getChild(".download").let {
|
||||||
|
it.createNewFile()
|
||||||
|
it.writeText(Json.encodeToString(downloadFolderMap))
|
||||||
|
}
|
||||||
} }
|
} }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} } }
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ import java.io.FileOutputStream
|
|||||||
import java.lang.reflect.Array
|
import java.lang.reflect.Array
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
|
@Deprecated("Use downloader.Cache instead")
|
||||||
fun getCachedGallery(context: Context, galleryID: Int) =
|
fun getCachedGallery(context: Context, galleryID: Int) =
|
||||||
File(getDownloadDirectory(context), galleryID.toString()).let {
|
File(getDownloadDirectory(context), galleryID.toString()).let {
|
||||||
if (it.exists())
|
if (it.exists())
|
||||||
@@ -35,6 +36,7 @@ fun getCachedGallery(context: Context, galleryID: Int) =
|
|||||||
File(context.cacheDir, "imageCache/$galleryID")
|
File(context.cacheDir, "imageCache/$galleryID")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use downloader.Cache instead")
|
||||||
fun getDownloadDirectory(context: Context) =
|
fun getDownloadDirectory(context: Context) =
|
||||||
Preferences.get<String>("dl_location").let {
|
Preferences.get<String>("dl_location").let {
|
||||||
if (it.isNotEmpty() && !it.startsWith("content"))
|
if (it.isNotEmpty() && !it.startsWith("content"))
|
||||||
@@ -43,81 +45,6 @@ fun getDownloadDirectory(context: Context) =
|
|||||||
context.getExternalFilesDir(null)!!
|
context.getExternalFilesDir(null)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun URL.download(to: File, onDownloadProgress: ((Long, Long) -> Unit)? = null) {
|
@Deprecated("Use FileX instead")
|
||||||
|
|
||||||
if (to.parentFile?.exists() == false)
|
|
||||||
to.parentFile!!.mkdirs()
|
|
||||||
|
|
||||||
if (!to.exists())
|
|
||||||
to.createNewFile()
|
|
||||||
|
|
||||||
FileOutputStream(to).use { out ->
|
|
||||||
|
|
||||||
with(openConnection()) {
|
|
||||||
val fileSize = contentLength.toLong()
|
|
||||||
|
|
||||||
getInputStream().use {
|
|
||||||
|
|
||||||
var bytesCopied: Long = 0
|
|
||||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
|
||||||
|
|
||||||
var bytes = it.read(buffer)
|
|
||||||
while (bytes >= 0) {
|
|
||||||
out.write(buffer, 0, bytes)
|
|
||||||
bytesCopied += bytes
|
|
||||||
onDownloadProgress?.invoke(bytesCopied, fileSize)
|
|
||||||
bytes = it.read(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getExtSdCardPaths(context: Context) =
|
|
||||||
ContextCompat.getExternalFilesDirs(context, null).drop(1).map {
|
|
||||||
it.absolutePath.substringBeforeLast("/Android/data").let { path ->
|
|
||||||
runCatching {
|
|
||||||
File(path).canonicalPath
|
|
||||||
}.getOrElse {
|
|
||||||
path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const val PRIMARY_VOLUME_NAME = "primary"
|
|
||||||
fun getVolumePath(context: Context, volumeID: String?): String? {
|
|
||||||
return runCatching {
|
|
||||||
val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
|
|
||||||
val storageVolumeClass = Class.forName("android.os.storage.StorageVolume")
|
|
||||||
|
|
||||||
val getVolumeList = storageVolumeClass.javaClass.getMethod("getVolumeList")
|
|
||||||
val getUUID = storageVolumeClass.getMethod("getUuid")
|
|
||||||
val getPath = storageVolumeClass.getMethod("getPath")
|
|
||||||
val isPrimary = storageVolumeClass.getMethod("isPrimary")
|
|
||||||
|
|
||||||
val result = getVolumeList.invoke(storageManager)!!
|
|
||||||
|
|
||||||
val length = Array.getLength(result)
|
|
||||||
|
|
||||||
for (i in 0 until length) {
|
|
||||||
val storageVolumeElement = Array.get(result, i)
|
|
||||||
val uuid = getUUID.invoke(storageVolumeElement) as? String
|
|
||||||
val primary = isPrimary.invoke(storageVolumeElement) as? Boolean
|
|
||||||
|
|
||||||
// primary volume?
|
|
||||||
if (primary == true && volumeID == PRIMARY_VOLUME_NAME)
|
|
||||||
return@runCatching getPath.invoke(storageVolumeElement) as? String
|
|
||||||
|
|
||||||
// other volumes?
|
|
||||||
if (volumeID == uuid) {
|
|
||||||
return@runCatching getPath.invoke(storageVolumeElement) as? String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return@runCatching null
|
|
||||||
}.getOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun File.isParentOf(another: File) =
|
fun File.isParentOf(another: File) =
|
||||||
another.absolutePath.startsWith(this.absolutePath)
|
another.absolutePath.startsWith(this.absolutePath)
|
||||||
@@ -19,12 +19,23 @@
|
|||||||
package xyz.quaver.pupil.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import xyz.quaver.Code
|
||||||
|
import xyz.quaver.hitomi.Reader
|
||||||
|
import xyz.quaver.hitomi.getReferer
|
||||||
|
import xyz.quaver.hitomi.imageUrlFromImage
|
||||||
|
import xyz.quaver.hiyobi.cookie
|
||||||
|
import xyz.quaver.hiyobi.createImgList
|
||||||
|
import xyz.quaver.hiyobi.user_agent
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.Metadata
|
import xyz.quaver.pupil.util.downloader.Metadata
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -77,17 +88,47 @@ fun OkHttpClient.Builder.proxyInfo(proxyInfo: ProxyInfo) = this.apply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val formatMap = mapOf<String, (Cache) -> (String)>(
|
val formatMap = mapOf<String, (Cache) -> (String)>(
|
||||||
"\$ID" to { runBlocking { it.getGalleryBlock()?.id.toString() } },
|
"-id-" to { runBlocking { it.getGalleryBlock()?.id.toString() } },
|
||||||
"\$TITLE" to { runBlocking { it.getGalleryBlock()?.title.toString() } },
|
"-title-" to { runBlocking { it.getGalleryBlock()?.title.toString() } },
|
||||||
// TODO
|
// TODO
|
||||||
)
|
)
|
||||||
/**
|
/**
|
||||||
* Formats download folder name with given Metadata
|
* Formats download folder name with given Metadata
|
||||||
*/
|
*/
|
||||||
fun Cache.formatDownloadFolder(): String {
|
fun Cache.formatDownloadFolder(): String =
|
||||||
return Preferences["download_folder_format", "\$ID"].apply {
|
Preferences["download_folder_format", "-id-"].let {
|
||||||
formatMap.entries.forEach { (key, lambda) ->
|
formatMap.entries.fold(it) { str, (k, v) ->
|
||||||
this.replace(key, lambda.invoke(this@formatDownloadFolder))
|
str.replace(k, v.invoke(this), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fun Context.startForegroundServiceCompat(service: Intent) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 26)
|
||||||
|
startForegroundService(service)
|
||||||
|
else
|
||||||
|
startService(service)
|
||||||
|
}
|
||||||
|
|
||||||
|
val Reader.requestBuilders: List<Request.Builder>
|
||||||
|
get() {
|
||||||
|
val galleryID = this.galleryInfo.id ?: 0
|
||||||
|
val lowQuality = Preferences["low_quality", true]
|
||||||
|
|
||||||
|
return when(code) {
|
||||||
|
Code.HITOMI -> {
|
||||||
|
this.galleryInfo.files.map {
|
||||||
|
Request.Builder()
|
||||||
|
.url(imageUrlFromImage(galleryID, it, !lowQuality))
|
||||||
|
.header("Referer", getReferer(galleryID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Code.HIYOBI -> {
|
||||||
|
createImgList(galleryID, this, lowQuality).map {
|
||||||
|
Request.Builder()
|
||||||
|
.url(it.path)
|
||||||
|
.header("User-Agent", user_agent)
|
||||||
|
.header("Cookie", cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -96,18 +96,18 @@
|
|||||||
<string name="settings_backup_file_created">バックアップファイルを作成しました</string>
|
<string name="settings_backup_file_created">バックアップファイルを作成しました</string>
|
||||||
<string name="settings_restore_failed">復元に失敗しました</string>
|
<string name="settings_restore_failed">復元に失敗しました</string>
|
||||||
<string name="settings_restore_success">%1$d項目を復元しました</string>
|
<string name="settings_restore_success">%1$d項目を復元しました</string>
|
||||||
<string name="settings_dl_location">ダウンロード場所</string>
|
<string name="settings_download_folder">ダウンロード場所</string>
|
||||||
<string name="settings_dl_location_internal">内部ストレージ</string>
|
<string name="settings_download_folder_internal">内部ストレージ</string>
|
||||||
<string name="settings_dl_location_removable">外部SDカード</string>
|
<string name="settings_download_folder_removable">外部SDカード</string>
|
||||||
<string name="settings_dl_location_available">%s 使用可能</string>
|
<string name="settings_download_folder_available">%s 使用可能</string>
|
||||||
<string name="update_download_completed">ダウンロードが完了しました</string>
|
<string name="update_download_completed">ダウンロードが完了しました</string>
|
||||||
<string name="update_download_completed_description">ここをクリックしてアップデートを行えます</string>
|
<string name="update_download_completed_description">ここをクリックしてアップデートを行えます</string>
|
||||||
<string name="settings_beta">ベータチャンネルでアップデートを受信</string>
|
<string name="settings_beta">ベータチャンネルでアップデートを受信</string>
|
||||||
<string name="settings_app_version_description">v%s</string>
|
<string name="settings_app_version_description">v%s</string>
|
||||||
<string name="settings_low_quality">低解像度イメージ</string>
|
<string name="settings_low_quality">低解像度イメージ</string>
|
||||||
<string name="settings_low_quality_summary">ロード速度とデータ使用料を改善するため低解像度イメージをロード</string>
|
<string name="settings_low_quality_summary">ロード速度とデータ使用料を改善するため低解像度イメージをロード</string>
|
||||||
<string name="settings_dl_location_custom">手動で設定</string>
|
<string name="settings_download_folder_custom">手動で設定</string>
|
||||||
<string name="settings_dl_location_not_writable">このフォルダにアクセスできません。他のフォルダを選択してください。</string>
|
<string name="settings_download_folder_not_writable">このフォルダにアクセスできません。他のフォルダを選択してください。</string>
|
||||||
<string name="settings_proxy_title">プロクシ</string>
|
<string name="settings_proxy_title">プロクシ</string>
|
||||||
<string name="proxy_dialog_username_hint">ID</string>
|
<string name="proxy_dialog_username_hint">ID</string>
|
||||||
<string name="proxy_dialog_type">プロクシタイプ</string>
|
<string name="proxy_dialog_type">プロクシタイプ</string>
|
||||||
|
|||||||
@@ -94,10 +94,10 @@
|
|||||||
<string name="settings_backup_file_created">백업 파일을 생성하였습니다</string>
|
<string name="settings_backup_file_created">백업 파일을 생성하였습니다</string>
|
||||||
<string name="settings_restore_failed">복원에 실패했습니다</string>
|
<string name="settings_restore_failed">복원에 실패했습니다</string>
|
||||||
<string name="settings_restore_success">%1$d개 항목을 복원했습니다</string>
|
<string name="settings_restore_success">%1$d개 항목을 복원했습니다</string>
|
||||||
<string name="settings_dl_location">다운로드 위치</string>
|
<string name="settings_download_folder">다운로드 위치</string>
|
||||||
<string name="settings_dl_location_internal">내부 저장공간</string>
|
<string name="settings_download_folder_internal">내부 저장공간</string>
|
||||||
<string name="settings_dl_location_removable">외부 SD카드</string>
|
<string name="settings_download_folder_removable">외부 SD카드</string>
|
||||||
<string name="settings_dl_location_available">%s 사용 가능</string>
|
<string name="settings_download_folder_available">%s 사용 가능</string>
|
||||||
<string name="update_download_completed">다운로드가 완료되었습니다</string>
|
<string name="update_download_completed">다운로드가 완료되었습니다</string>
|
||||||
<string name="update_download_completed_description">여기를 클릭해서 업데이트를 진행할 수 있습니다</string>
|
<string name="update_download_completed_description">여기를 클릭해서 업데이트를 진행할 수 있습니다</string>
|
||||||
<string name="settings_beta">베타 채널에서 업데이트</string>
|
<string name="settings_beta">베타 채널에서 업데이트</string>
|
||||||
@@ -106,8 +106,8 @@
|
|||||||
<string name="settings_low_quality_summary">로드 속도와 데이터 사용량을 줄이기 위해 저해상도 이미지를 로드</string>
|
<string name="settings_low_quality_summary">로드 속도와 데이터 사용량을 줄이기 위해 저해상도 이미지를 로드</string>
|
||||||
<string name="settings_mirror_summary">미러 서버에서 이미지 로드</string>
|
<string name="settings_mirror_summary">미러 서버에서 이미지 로드</string>
|
||||||
<string name="settings_mirror_title">미러 설정</string>
|
<string name="settings_mirror_title">미러 설정</string>
|
||||||
<string name="settings_dl_location_custom">직접 설정</string>
|
<string name="settings_download_folder_custom">직접 설정</string>
|
||||||
<string name="settings_dl_location_not_writable">이 폴더에 접근할 수 없습니다. 다른 폴더를 선택해주세요.</string>
|
<string name="settings_download_folder_not_writable">이 폴더에 접근할 수 없습니다. 다른 폴더를 선택해주세요.</string>
|
||||||
<string name="settings_proxy_title">프록시</string>
|
<string name="settings_proxy_title">프록시</string>
|
||||||
<string name="proxy_dialog_username_hint">ID</string>
|
<string name="proxy_dialog_username_hint">ID</string>
|
||||||
<string name="proxy_dialog_type">프록시 타입</string>
|
<string name="proxy_dialog_type">프록시 타입</string>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<string name="channel_update_description">Shows update progress</string>
|
<string name="channel_update_description">Shows update progress</string>
|
||||||
|
|
||||||
<string name="unable_to_connect">Unable to connect to hitomi.la</string>
|
<string name="unable_to_connect">Unable to connect to hitomi.la</string>
|
||||||
|
|
||||||
<string name="lock_corrupted">Lock file corrupted! Please re-install Pupil</string>
|
<string name="lock_corrupted">Lock file corrupted! Please re-install Pupil</string>
|
||||||
|
|
||||||
<string name="main_no_result">No result</string>
|
<string name="main_no_result">No result</string>
|
||||||
@@ -135,12 +135,12 @@
|
|||||||
<string name="settings_clear_history">Clear history</string>
|
<string name="settings_clear_history">Clear history</string>
|
||||||
<string name="settings_clear_history_alert_message">Do you want to clear histories?</string>
|
<string name="settings_clear_history_alert_message">Do you want to clear histories?</string>
|
||||||
<string name="settings_clear_history_summary">%1$d histories saved</string>
|
<string name="settings_clear_history_summary">%1$d histories saved</string>
|
||||||
<string name="settings_dl_location">Download directory</string>
|
<string name="settings_download_folder">Download directory</string>
|
||||||
<string name="settings_dl_location_removable">Removable Storage</string>
|
<string name="settings_download_folder_removable">Removable Storage</string>
|
||||||
<string name="settings_dl_location_internal">Internal Storage</string>
|
<string name="settings_download_folder_internal">Internal Storage</string>
|
||||||
<string name="settings_dl_location_available">%s available</string>
|
<string name="settings_download_folder_available">%s available</string>
|
||||||
<string name="settings_dl_location_custom">Custom Location</string>
|
<string name="settings_download_folder_custom">Custom Location</string>
|
||||||
<string name="settings_dl_location_not_writable">This folder is not writable. Please select another folder.</string>
|
<string name="settings_download_folder_not_writable">This folder is not writable. Please select another folder.</string>
|
||||||
<string name="settings_cache_disable">Disable Cache</string>
|
<string name="settings_cache_disable">Disable Cache</string>
|
||||||
<string name="settings_download_when_cache_disable_warning">Download is disabled when the cache is disabled</string>
|
<string name="settings_download_when_cache_disable_warning">Download is disabled when the cache is disabled</string>
|
||||||
<string name="settings_low_quality">Low quality images</string>
|
<string name="settings_low_quality">Low quality images</string>
|
||||||
|
|||||||
@@ -44,8 +44,8 @@
|
|||||||
app:title="@string/settings_clear_history"/>
|
app:title="@string/settings_clear_history"/>
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
app:key="dl_location"
|
app:key="download_folder"
|
||||||
app:title="@string/settings_dl_location"/>
|
app:title="@string/settings_download_folder"/>
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
app:key="cache_disable"
|
app:key="cache_disable"
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
mavenLocal()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
maven { url 'https://guardian.github.com/maven/repo-releases' }
|
maven { url 'https://guardian.github.com/maven/repo-releases' }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user