(Almost) Full OkHTTP implementation

This commit is contained in:
tom5079
2020-08-28 22:43:47 +09:00
parent ece127e982
commit fdd9b02388
24 changed files with 307 additions and 97 deletions

View File

@@ -51,5 +51,10 @@
<option name="name" value="maven4" /> <option name="name" value="maven4" />
<option name="url" value="https://maven.fabric.io/public" /> <option name="url" value="https://maven.fabric.io/public" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="MavenLocal" />
<option name="name" value="MavenLocal" />
<option name="url" value="file:/$USER_HOME$/.m2/repository/" />
</remote-repository>
</component> </component>
</project> </project>

View File

@@ -48,6 +48,7 @@ android {
} }
} }
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental' freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
} }
compileOptions { compileOptions {
@@ -68,7 +69,6 @@ dependencies {
implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation "androidx.biometric:biometric:1.0.1" implementation "androidx.biometric:biometric:1.0.1"
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.fragment:fragment-ktx:1.2.5' implementation 'androidx.fragment:fragment-ktx:1.2.5'
implementation "com.daimajia.swipelayout:library:1.2.0@aar" implementation "com.daimajia.swipelayout:library:1.2.0@aar"
implementation 'com.google.android.material:material:1.3.0-alpha02' implementation 'com.google.android.material:material:1.3.0-alpha02'
@@ -78,8 +78,12 @@ dependencies {
implementation 'com.google.firebase:firebase-perf:19.0.8' implementation 'com.google.firebase:firebase-perf:19.0.8'
implementation 'com.github.arimorty:floatingsearchview:2.1.1' implementation 'com.github.arimorty:floatingsearchview:2.1.1'
implementation 'com.github.clans:fab:1.6.4' implementation 'com.github.clans:fab:1.6.4'
//noinspection GradleDependency
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
implementation 'com.github.bumptech.glide:glide:4.11.0' implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation "com.github.bumptech.glide:okhttp3-integration:4.11.0" implementation ("com.github.bumptech.glide:okhttp3-integration:4.11.0") {
transitive = false
}
implementation 'com.github.bumptech.glide:annotations:4.11.0' implementation 'com.github.bumptech.glide:annotations:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
kapt 'com.github.bumptech.glide:compiler:4.11.0' kapt 'com.github.bumptech.glide:compiler:4.11.0'
@@ -92,7 +96,7 @@ 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.0") { implementation ("xyz.quaver:libpupil:1.1") {
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm' exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm'
} }
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13'

View File

@@ -37,8 +37,11 @@
</provider> </provider>
<service android:name=".services.DownloadService"
android:exported="false"/>
<receiver <receiver
android:name=".BroadcastReciever" android:name=".reciever.UpdateBroadcastReciever"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" /> <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />

View File

@@ -18,6 +18,7 @@
package xyz.quaver.pupil package xyz.quaver.pupil
import android.app.Application
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
@@ -25,7 +26,6 @@ import android.content.Context
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
import androidx.multidex.MultiDexApplication
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.gms.common.GooglePlayServicesNotAvailableException import com.google.android.gms.common.GooglePlayServicesNotAvailableException
import com.google.android.gms.common.GooglePlayServicesRepairableException import com.google.android.gms.common.GooglePlayServicesRepairableException
@@ -35,16 +35,37 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
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.proxy import okhttp3.Dispatcher
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import xyz.quaver.pupil.util.GalleryList import xyz.quaver.pupil.util.GalleryList
import xyz.quaver.pupil.util.getProxy import xyz.quaver.pupil.util.getProxyInfo
import xyz.quaver.setClient
import java.io.File import java.io.File
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.reflect.KClass
class Pupil : MultiDexApplication() { typealias PupilInterceptor = (Interceptor.Chain) -> Response
lateinit var histories: GalleryList lateinit var histories: GalleryList
private set
lateinit var favorites: GalleryList lateinit var favorites: GalleryList
private set
val interceptors = mutableMapOf<KClass<out Any>, PupilInterceptor>()
lateinit var clientBuilder: OkHttpClient.Builder
var clientHolder: OkHttpClient? = null
val client: OkHttpClient
get() = clientHolder ?: clientBuilder.build().also {
clientHolder = it
setClient(it)
}
class Pupil : Application() {
init { init {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
@@ -63,7 +84,19 @@ class Pupil : MultiDexApplication() {
FirebaseCrashlytics.getInstance().setUserId(userID) FirebaseCrashlytics.getInstance().setUserId(userID)
proxy = getProxy(this) val proxyInfo = getProxyInfo(this)
clientBuilder = OkHttpClient.Builder()
.connectTimeout(0, TimeUnit.SECONDS)
.readTimeout(0, TimeUnit.SECONDS)
.proxy(proxyInfo.proxy())
.proxyAuthenticator(proxyInfo.authenticator())
.addInterceptor { chain ->
val request = chain.request()
val tag = request.tag() ?: return@addInterceptor chain.proceed(request)
interceptors[tag::class]?.invoke(chain) ?: chain.proceed(request)
}
try { try {
preference.getString("dl_location", null).also { preference.getString("dl_location", null).also {
@@ -112,6 +145,13 @@ class Pupil : MultiDexApplication() {
lockscreenVisibility = Notification.VISIBILITY_SECRET lockscreenVisibility = Notification.VISIBILITY_SECRET
}) })
manager.createNotificationChannel(NotificationChannel("downloader", getString(R.string.channel_downloader), NotificationManager.IMPORTANCE_LOW).apply {
description = getString(R.string.channel_downloader_description)
enableLights(false)
enableVibration(false)
lockscreenVisibility = Notification.VISIBILITY_SECRET
})
manager.createNotificationChannel(NotificationChannel("update", getString(R.string.channel_update), NotificationManager.IMPORTANCE_HIGH).apply { manager.createNotificationChannel(NotificationChannel("update", getString(R.string.channel_update), NotificationManager.IMPORTANCE_HIGH).apply {
description = getString(R.string.channel_update_description) description = getString(R.string.channel_update_description)
enableLights(true) enableLights(true)
@@ -127,7 +167,6 @@ class Pupil : MultiDexApplication() {
}) })
} }
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
AppCompatDelegate.setDefaultNightMode(when (preference.getBoolean("dark_mode", false)) { AppCompatDelegate.setDefaultNightMode(when (preference.getBoolean("dark_mode", false)) {
true -> AppCompatDelegate.MODE_NIGHT_YES true -> AppCompatDelegate.MODE_NIGHT_YES
false -> AppCompatDelegate.MODE_NIGHT_NO false -> AppCompatDelegate.MODE_NIGHT_NO

View File

@@ -35,7 +35,7 @@ class PupilGlideModule : AppGlideModule() {
registry.append( registry.append(
GlideUrl::class.java, GlideUrl::class.java,
InputStream::class.java, InputStream::class.java,
OkHttpUrlLoader.Factory(DownloadWorker.getInstance(context).client) OkHttpUrlLoader.Factory(client)
) )
} }

View File

@@ -52,6 +52,7 @@ import xyz.quaver.hitomi.getReader
import xyz.quaver.pupil.BuildConfig import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
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.GalleryList
import xyz.quaver.pupil.util.download.Cache import xyz.quaver.pupil.util.download.Cache
@@ -68,8 +69,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
PREV PREV
} }
private lateinit var favorites: GalleryList
val timer = Timer() val timer = Timer()
var isThin = false var isThin = false
@@ -257,9 +256,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
} }
} }
if (!::favorites.isInitialized)
favorites = (context.applicationContext as Pupil).favorites
with(galleryblock_favorite) { with(galleryblock_favorite) {
setImageResource(if (favorites.contains(galleryBlock.id)) R.drawable.ic_star_filled else R.drawable.ic_star_empty) setImageResource(if (favorites.contains(galleryBlock.id)) R.drawable.ic_star_filled else R.drawable.ic_star_empty)
setOnClickListener { setOnClickListener {

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package xyz.quaver.pupil package xyz.quaver.pupil.reciever
import android.app.DownloadManager import android.app.DownloadManager
import android.app.PendingIntent import android.app.PendingIntent
@@ -29,15 +29,10 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import xyz.quaver.pupil.R
import java.io.File import java.io.File
class BroadcastReciever : BroadcastReceiver() { class UpdateBroadcastReciever : BroadcastReceiver() {
companion object {
const val ACTION_CANCEL_IMPORT = "ACTION_CANCEL_IMPORT"
const val EXTRA_IMPORT_NOTIFICATION_ID = "EXTRA_IMPORT_NOTIFICATION_ID"
}
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
context ?: return context ?: return

View File

@@ -0,0 +1,155 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2020 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.services
import android.app.Service
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.SparseArray
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.Interceptor
import okhttp3.ResponseBody
import okio.*
import xyz.quaver.pupil.R
private typealias ProgressListener = (Any?, Long, Long, Boolean) -> Unit
class Cache(context: Context) : ContextWrapper(context) {
}
class DownloadService : Service() {
//region Notification
private val notificationManager by lazy {
NotificationManagerCompat.from(this)
}
private val serviceNotification by lazy {
NotificationCompat.Builder(this, "downloader")
.setContentTitle(getString(R.string.downloader_running))
.setProgress(0, 0, false)
.setSmallIcon(R.drawable.ic_notification)
.setOngoing(true)
}
//endregion
//region ProgressListener
@Suppress("UNCHECKED_CAST")
private val progressListener: ProgressListener = listener@{ tag, bytesRead, contentLength, done ->
val (galleryID, index) = (tag as? Pair<Int, Int>) ?: return@listener
if (!done && progress[galleryID]?.get(index)?.isFinite() == true)
progress[galleryID]?.set(index, bytesRead * 100F / contentLength)
}
private class ProgressResponseBody(
val tag: Any?,
val responseBody: ResponseBody,
val progressListener : ProgressListener
) : ResponseBody() {
private var bufferedSource : BufferedSource? = null
override fun contentLength() = responseBody.contentLength()
override fun contentType() = responseBody.contentType()
override fun source(): BufferedSource {
if (bufferedSource == null)
bufferedSource = Okio.buffer(source(responseBody.source()))
return bufferedSource!!
}
private fun source(source: Source) = object: ForwardingSource(source) {
var totalBytesRead = 0L
override fun read(sink: Buffer, byteCount: Long): Long {
val bytesRead = super.read(sink, byteCount)
totalBytesRead += if (bytesRead == -1L) 0L else bytesRead
progressListener.invoke(tag, totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
return bytesRead
}
}
}
val interceptor = Interceptor { chain ->
val request = chain.request()
var response = chain.proceed(request)
var retry = 5
while (!response.isSuccessful && retry > 0) {
response = chain.proceed(request)
retry--
}
response.newBuilder()
.body(response.body()?.let {
ProgressResponseBody(request.tag(), it, progressListener)
}).build()
}
//endregion
/**
* KEY
* primary galleryID
* secondary index
* PRIMARY VALUE
* MutableList -> Download in progress
* null -> Loading / Gallery doesn't exist
* SECONDARY VALUE
* 0 <= value < 100 -> Download in progress
* Float.POSITIVE_INFINITY -> Download completed
*/
val progress = SparseArray<MutableList<Float>?>()
override fun onCreate() {
startForeground(R.id.downloader_notification_id, serviceNotification.build())
}
override fun onDestroy() {
}
inner class Binder : android.os.Binder() {
val service = this@DownloadService
}
private val binder = Binder()
override fun onBind(p0: Intent?) = binder
fun load(galleryID: Int) {
}
fun download(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
}
}

View File

@@ -22,7 +22,9 @@ import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.net.Proxy
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.* import android.text.*
import android.text.style.AlignmentSpan import android.text.style.AlignmentSpan
@@ -59,9 +61,11 @@ 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
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.favorites
import xyz.quaver.pupil.histories
import xyz.quaver.pupil.services.DownloadService
import xyz.quaver.pupil.types.TagSuggestion 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
@@ -110,9 +114,6 @@ class MainActivity : AppCompatActivity() {
private var loadingJob: Job? = null private var loadingJob: Job? = null
private var currentPage = 0 private var currentPage = 0
private lateinit var histories: GalleryList
private lateinit var favorites: GalleryList
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -147,11 +148,6 @@ class MainActivity : AppCompatActivity() {
} }
} }
with(application as Pupil) {
this@MainActivity.histories = histories
this@MainActivity.favorites = favorites
}
if (intent.action == Intent.ACTION_VIEW) { if (intent.action == Intent.ACTION_VIEW) {
intent.dataString?.let { url -> intent.dataString?.let { url ->
restore(favorites, url, restore(favorites, url,
@@ -166,6 +162,15 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
Intent(this, DownloadService::class.java).let {
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ->
startForegroundService(it)
else ->
startService(it)
}
}
checkUpdate(this) checkUpdate(this)
initView() initView()

View File

@@ -46,6 +46,8 @@ import xyz.quaver.Code
import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.ReaderAdapter import xyz.quaver.pupil.adapters.ReaderAdapter
import xyz.quaver.pupil.favorites
import xyz.quaver.pupil.histories
import xyz.quaver.pupil.util.GalleryList import xyz.quaver.pupil.util.GalleryList
import xyz.quaver.pupil.util.download.Cache import xyz.quaver.pupil.util.download.Cache
import xyz.quaver.pupil.util.download.DownloadWorker import xyz.quaver.pupil.util.download.DownloadWorker
@@ -78,16 +80,12 @@ class ReaderActivity : AppCompatActivity() {
private var menu: Menu? = null private var menu: Menu? = null
private lateinit var favorites: GalleryList
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
title = getString(R.string.reader_loading) title = getString(R.string.reader_loading)
supportActionBar?.setDisplayHomeAsUpEnabled(false) supportActionBar?.setDisplayHomeAsUpEnabled(false)
favorites = (application as Pupil).favorites
window.setFlags( window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE) WindowManager.LayoutParams.FLAG_SECURE)
@@ -96,7 +94,7 @@ class ReaderActivity : AppCompatActivity() {
handleIntent(intent) handleIntent(intent)
(application as Pupil).histories.add(galleryID) histories.add(galleryID)
FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID) FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID)
if (galleryID == 0) { if (galleryID == 0) {

View File

@@ -35,6 +35,7 @@ import kotlinx.serialization.json.Json
import net.rdrei.android.dirchooser.DirectoryChooserActivity import net.rdrei.android.dirchooser.DirectoryChooserActivity
import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.favorites
import xyz.quaver.pupil.ui.fragment.LockSettingsFragment import xyz.quaver.pupil.ui.fragment.LockSettingsFragment
import xyz.quaver.pupil.ui.fragment.SettingsFragment import xyz.quaver.pupil.ui.fragment.SettingsFragment
import xyz.quaver.pupil.util.* import xyz.quaver.pupil.util.*
@@ -100,7 +101,7 @@ class SettingsActivity : AppCompatActivity() {
inputStream.readBytes().toString(Charset.defaultCharset()) inputStream.readBytes().toString(Charset.defaultCharset())
} }
(application as Pupil).favorites.addAll(Json.decodeFromString<List<Int>>(str).also { favorites.addAll(Json.decodeFromString<List<Int>>(str).also {
Snackbar.make( Snackbar.make(
window.decorView, window.decorView,
getString(R.string.settings_restore_success, it.size), getString(R.string.settings_restore_success, it.size),

View File

@@ -48,6 +48,7 @@ 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
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
@@ -81,7 +82,7 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
context.startActivity(Intent(context, ReaderActivity::class.java).apply { context.startActivity(Intent(context, ReaderActivity::class.java).apply {
putExtra("galleryID", galleryID) putExtra("galleryID", galleryID)
}) })
(context.applicationContext as Pupil).histories.add(galleryID) histories.add(galleryID)
} }
} }
@@ -264,7 +265,7 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
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].id)
}) })
(context.applicationContext as Pupil).histories.add(galleries[position].id) histories.add(galleries[position].id)
} }
onItemLongClickListener = { _, position, _ -> onItemLongClickListener = { _, position, _ ->
GalleryDialog( GalleryDialog(

View File

@@ -31,8 +31,10 @@ import androidx.preference.PreferenceManager
import kotlinx.android.synthetic.main.dialog_proxy.view.* import kotlinx.android.synthetic.main.dialog_proxy.view.*
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import xyz.quaver.proxy
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.client
import xyz.quaver.pupil.clientBuilder
import xyz.quaver.pupil.clientHolder
import xyz.quaver.pupil.util.ProxyInfo import xyz.quaver.pupil.util.ProxyInfo
import xyz.quaver.pupil.util.getProxyInfo import xyz.quaver.pupil.util.getProxyInfo
import java.net.Proxy import java.net.Proxy
@@ -117,12 +119,15 @@ class ProxyDialog(context: Context) : Dialog(context) {
} }
ProxyInfo(type, addr, port, username, password).let { ProxyInfo(type, addr, port, username, password).let {
PreferenceManager.getDefaultSharedPreferences(context).edit().putString("proxy", PreferenceManager.getDefaultSharedPreferences(context).edit().putString("proxy",
Json.encodeToString(it) Json.encodeToString(it)
).apply() ).apply()
proxy = it.proxy() clientBuilder
.proxy(it.proxy())
.proxyAuthenticator(it.authenticator())
clientHolder = null
client
} }
dismiss() dismiss()

View File

@@ -30,6 +30,8 @@ import com.google.android.material.snackbar.Snackbar
import okhttp3.* import okhttp3.*
import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.client
import xyz.quaver.pupil.favorites
import xyz.quaver.pupil.util.restore import xyz.quaver.pupil.util.restore
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@@ -52,7 +54,7 @@ class ManageFavoritesFragment : PreferenceFragmentCompat() {
.build() .build()
).build() ).build()
OkHttpClient().newCall(request).enqueue(object: Callback { client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) { override fun onFailure(call: Call, e: IOException) {
val view = view ?: return val view = view ?: return
Snackbar.make(view, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show() Snackbar.make(view, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
@@ -79,7 +81,6 @@ class ManageFavoritesFragment : PreferenceFragmentCompat() {
.setTitle(R.string.settings_restore_title) .setTitle(R.string.settings_restore_title)
.setView(editText) .setView(editText)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
val favorites = (activity?.application as? Pupil)?.favorites ?: return@setPositiveButton
restore(favorites, editText.text.toString(), restore(favorites, editText.text.toString(),
onFailure = onFailure@{ onFailure = onFailure@{
val view = view ?: return@onFailure val view = view ?: return@onFailure

View File

@@ -40,6 +40,7 @@ import net.rdrei.android.dirchooser.DirectoryChooserActivity
import net.rdrei.android.dirchooser.DirectoryChooserConfig import net.rdrei.android.dirchooser.DirectoryChooserConfig
import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.histories
import xyz.quaver.pupil.ui.LockActivity import xyz.quaver.pupil.ui.LockActivity
import xyz.quaver.pupil.ui.SettingsActivity import xyz.quaver.pupil.ui.SettingsActivity
import xyz.quaver.pupil.ui.dialog.DefaultQueryDialog import xyz.quaver.pupil.ui.dialog.DefaultQueryDialog
@@ -143,8 +144,6 @@ class SettingsFragment :
}.show() }.show()
} }
"clear_history" -> { "clear_history" -> {
val histories = (requireContext().applicationContext as Pupil).histories
AlertDialog.Builder(requireContext()).apply { AlertDialog.Builder(requireContext()).apply {
setTitle(R.string.warning) setTitle(R.string.warning)
setMessage(R.string.settings_clear_history_alert_message) setMessage(R.string.settings_clear_history_alert_message)
@@ -220,10 +219,10 @@ class SettingsFragment :
when (key) { when (key) {
"proxy" -> { "proxy" -> {
summary = getProxyInfo(requireContext()).type.name summary = context?.let { getProxyInfo(it).type.name }
} }
"dl_location" -> { "dl_location" -> {
summary = getDownloadDirectory(requireContext()).canonicalPath summary = context?.let { getDownloadDirectory(it).canonicalPath }
} }
} }
} }
@@ -286,7 +285,6 @@ class SettingsFragment :
onPreferenceClickListener = this@SettingsFragment onPreferenceClickListener = this@SettingsFragment
} }
"clear_history" -> { "clear_history" -> {
val histories = (requireActivity().application as Pupil).histories
summary = getString(R.string.settings_clear_history_summary, histories.size) summary = getString(R.string.settings_clear_history_summary, histories.size)
onPreferenceClickListener = this@SettingsFragment onPreferenceClickListener = this@SettingsFragment

View File

@@ -28,14 +28,13 @@ 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 okhttp3.Dispatcher
import xyz.quaver.Code import xyz.quaver.Code
import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader import xyz.quaver.hitomi.Reader
import xyz.quaver.proxy
import xyz.quaver.pupil.util.getCachedGallery import xyz.quaver.pupil.util.getCachedGallery
import xyz.quaver.pupil.util.getDownloadDirectory import xyz.quaver.pupil.util.getDownloadDirectory
import xyz.quaver.pupil.util.isParentOf import xyz.quaver.pupil.util.isParentOf
import xyz.quaver.readBytes
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
@@ -91,11 +90,12 @@ class Cache(context: Context) : ContextWrapper(context) {
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
val thumbnail = if (metadata?.thumbnail == null) val thumbnail = if (metadata?.thumbnail == null)
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val thumbnails = getGalleryBlock(galleryID)?.thumbnails val thumbnail = getGalleryBlock(galleryID)?.thumbnails?.firstOrNull() ?: return@withContext null
try { try {
Base64.encodeToString(URL(thumbnails?.firstOrNull()).openConnection(proxy).getInputStream().use { val data = URL(thumbnail).readBytes().apply {
it.readBytes() if (isEmpty()) return@withContext null
}, Base64.DEFAULT) }
Base64.encodeToString(data, Base64.DEFAULT)
} catch (e: Exception) { } catch (e: Exception) {
null null
} }

View File

@@ -40,13 +40,13 @@ 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.proxy
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.client
import xyz.quaver.pupil.interceptors
import xyz.quaver.pupil.ui.ReaderActivity import xyz.quaver.pupil.ui.ReaderActivity
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class DownloadWorker private constructor(context: Context) : ContextWrapper(context) { class DownloadWorker private constructor(context: Context) : ContextWrapper(context) {
@@ -86,7 +86,6 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
} }
private fun source(source: Source) = object: ForwardingSource(source) { private fun source(source: Source) = object: ForwardingSource(source) {
var totalBytesRead = 0L var totalBytesRead = 0L
override fun read(sink: Buffer, byteCount: Long): Long { override fun read(sink: Buffer, byteCount: Long): Long {
@@ -100,6 +99,24 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
} }
} }
init {
interceptors[Pair::class] = { chain ->
val request = chain.request()
var response = chain.proceed(request)
var retry = 5
while (!response.isSuccessful && retry > 0) {
response = chain.proceed(request)
retry--
}
response.newBuilder()
.body(response.body()?.let {
ProgressResponseBody(request.tag(), it, progressListener)
}).build()
}
}
//endregion //endregion
//region Singleton //region Singleton
@@ -135,34 +152,6 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
private val loop = loop() private val loop = loop()
private val worker = SparseArray<Job?>() private val worker = SparseArray<Job?>()
val interceptor = Interceptor { chain ->
val request = chain.request()
var response = chain.proceed(request)
var retry = 5
while (!response.isSuccessful && retry > 0) {
response = chain.proceed(request)
retry--
}
response.newBuilder()
.body(response.body()?.let {
ProgressResponseBody(request.tag(), it, progressListener)
}).build()
}
val client : OkHttpClient =
OkHttpClient.Builder()
.connectTimeout(0, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.readTimeout(0, TimeUnit.SECONDS)
.dispatcher(Dispatcher().apply {
maxRequests = 4
maxRequestsPerHost = 4
})
.proxy(proxy)
.build()
fun stop() { fun stop() {
queue.clear() queue.clear()
@@ -300,8 +289,8 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
val ext = call.request().url().encodedPath().split('.').last() val ext = call.request().url().encodedPath().split('.').last()
try { try {
response.body().use { response.body()!!.use {
Cache(this@DownloadWorker).putImage(galleryID, i, ext, it!!.byteStream()) Cache(this@DownloadWorker).putImage(galleryID, i, ext, it.byteStream())
} }
progress[galleryID]?.set(i, Float.POSITIVE_INFINITY) progress[galleryID]?.set(i, Float.POSITIVE_INFINITY)

View File

@@ -53,7 +53,7 @@ fun byteToString(byte: Long, precision : Int = 1) : String {
} }
/* /**
* Convert android generated ID to requestCode * Convert android generated ID to requestCode
* to prevent java.lang.IllegalArgumentException: Can only use lower 16 bits for requestCode * to prevent java.lang.IllegalArgumentException: Can only use lower 16 bits for requestCode
* *

View File

@@ -23,8 +23,7 @@ import androidx.preference.PreferenceManager
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Authenticator import okhttp3.*
import okhttp3.Credentials
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.Proxy import java.net.Proxy

View File

@@ -35,6 +35,7 @@ import okhttp3.*
import ru.noties.markwon.Markwon import ru.noties.markwon.Markwon
import xyz.quaver.pupil.BuildConfig import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.client
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
@@ -187,7 +188,7 @@ fun restore(favorites: GalleryList, url: String, onFailure: ((Exception) -> Unit
.get() .get()
.build() .build()
OkHttpClient().newCall(request).enqueue(object: Callback { client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) { override fun onFailure(call: Call, e: IOException) {
onFailure?.invoke(e) onFailure?.invoke(e)
} }

View File

@@ -140,4 +140,7 @@
<string name="settings_manage_favorites">ブックマーク管理</string> <string name="settings_manage_favorites">ブックマーク管理</string>
<string name="settings_backup_failed">エラーが発生しました</string> <string name="settings_backup_failed">エラーが発生しました</string>
<string name="settings_backup_share">バックアップ共有</string> <string name="settings_backup_share">バックアップ共有</string>
<string name="channel_downloader">ダウンローダ</string>
<string name="channel_downloader_description">ダウンローダの状態を表示</string>
<string name="downloader_running">ダウンローダー起動中</string>
</resources> </resources>

View File

@@ -140,4 +140,7 @@
<string name="settings_manage_favorites">즐겨찾기 관리</string> <string name="settings_manage_favorites">즐겨찾기 관리</string>
<string name="settings_backup_failed">업로드 실패</string> <string name="settings_backup_failed">업로드 실패</string>
<string name="settings_backup_share">백업 공유</string> <string name="settings_backup_share">백업 공유</string>
<string name="channel_downloader">다운로더</string>
<string name="channel_downloader_description">다운로더 작동 여부 표시</string>
<string name="downloader_running">다운로더 작동중…</string>
</resources> </resources>

View File

@@ -10,4 +10,7 @@
<item name="request_write_permission_and_saf" type="id" /> <item name="request_write_permission_and_saf" type="id" />
<item name="notification_id_update" type="id" /> <item name="notification_id_update" type="id" />
<item name="downloader_notification_id" type="id" />
<item name="downloader_notification_request" type="id" />
</resources> </resources>

View File

@@ -29,6 +29,9 @@
<string name="channel_download">Download</string> <string name="channel_download">Download</string>
<string name="channel_download_description">Shows download status</string> <string name="channel_download_description">Shows download status</string>
<string name="channel_downloader">Downloader</string>
<string name="channel_downloader_description">Shows downloader status</string>
<string name="channel_update">Update</string> <string name="channel_update">Update</string>
<string name="channel_update_description">Shows update progress</string> <string name="channel_update_description">Shows update progress</string>
@@ -103,6 +106,9 @@
<string name="reader_notification_text">Downloading&#8230;</string> <string name="reader_notification_text">Downloading&#8230;</string>
<string name="reader_notification_complete">Download complete</string> <string name="reader_notification_complete">Download complete</string>
<!-- DOWNLOADER -->
<string name="downloader_running">Downloader running…</string>
<!-- SETTINGS --> <!-- SETTINGS -->
<string name="settings_title">Settings</string> <string name="settings_title">Settings</string>