Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ed59bb8a9 | ||
|
|
8163f2fd28 | ||
|
|
521a65c9d2 | ||
|
|
eb98424668 | ||
|
|
961c731743 | ||
|
|
5188769fb6 | ||
|
|
8f27d9e30f | ||
|
|
b58566999e |
13
.idea/deploymentTargetDropDown.xml
generated
13
.idea/deploymentTargetDropDown.xml
generated
@@ -1,17 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="deploymentTargetDropDown">
|
<component name="deploymentTargetDropDown">
|
||||||
<runningDeviceTargetSelectedWithDropDown>
|
|
||||||
<Target>
|
|
||||||
<type value="RUNNING_DEVICE_TARGET" />
|
|
||||||
<deviceKey>
|
|
||||||
<Key>
|
|
||||||
<type value="VIRTUAL_DEVICE_PATH" />
|
|
||||||
<value value="$USER_HOME$/.android/avd/Pixel_2_API_31.avd" />
|
|
||||||
</Key>
|
|
||||||
</deviceKey>
|
|
||||||
</Target>
|
|
||||||
</runningDeviceTargetSelectedWithDropDown>
|
|
||||||
<targetSelectedWithDropDown>
|
<targetSelectedWithDropDown>
|
||||||
<Target>
|
<Target>
|
||||||
<type value="QUICK_BOOT_TARGET" />
|
<type value="QUICK_BOOT_TARGET" />
|
||||||
@@ -23,6 +12,6 @@
|
|||||||
</deviceKey>
|
</deviceKey>
|
||||||
</Target>
|
</Target>
|
||||||
</targetSelectedWithDropDown>
|
</targetSelectedWithDropDown>
|
||||||
<timeTargetWasSelectedWithDropDown value="2022-02-01T02:15:22.286886Z" />
|
<timeTargetWasSelectedWithDropDown value="2022-02-01T08:00:57.223690Z" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
*Pupil, Hitomi.la viewer for Android*
|
*Pupil, Hitomi.la viewer for Android*
|
||||||
|
|
||||||

|

|
||||||
[](https://github.com/tom5079/Pupil/releases/download/5.2.23/Pupil-v5.2.23.apk)
|
[](https://github.com/tom5079/Pupil/releases/download/5.3.2/Pupil-v5.3.2.apk)
|
||||||
[](https://discord.gg/Stj4b5v)
|
[](https://discord.gg/Stj4b5v)
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ android {
|
|||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 31
|
targetSdkVersion 31
|
||||||
versionCode 69
|
versionCode 69
|
||||||
versionName "5.3.0"
|
versionName "5.3.3"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
|
|||||||
1
app/proguard-rules.pro
vendored
1
app/proguard-rules.pro
vendored
@@ -34,3 +34,4 @@
|
|||||||
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
|
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
|
||||||
-keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment
|
-keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment
|
||||||
-keep class xyz.quaver.pupil.** { *; }
|
-keep class xyz.quaver.pupil.** { *; }
|
||||||
|
-keep class app.cash.zipline.** { *; }
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"filters": [],
|
"filters": [],
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"versionCode": 69,
|
"versionCode": 69,
|
||||||
"versionName": "5.3.0",
|
"versionName": "5.3.3",
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ lateinit var runtime: QuickJs
|
|||||||
private set
|
private set
|
||||||
|
|
||||||
class Pupil : Application() {
|
class Pupil : Application() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
lateinit var instance: Pupil
|
lateinit var instance: Pupil
|
||||||
private set
|
private set
|
||||||
@@ -104,7 +103,6 @@ class Pupil : Application() {
|
|||||||
version = newVersion
|
version = newVersion
|
||||||
evaluationContext.cancelChildren()
|
evaluationContext.cancelChildren()
|
||||||
withContext(evaluationContext) {
|
withContext(evaluationContext) {
|
||||||
Log.d("PUPILD", "UPDATE!")
|
|
||||||
runtime.evaluate(URL("https://tom5079.github.io/PupilSources/assets/js/gg.js").readText())
|
runtime.evaluate(URL("https://tom5079.github.io/PupilSources/assets/js/gg.js").readText())
|
||||||
runtimeReady = true
|
runtimeReady = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,14 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.hitomi
|
package xyz.quaver.pupil.hitomi
|
||||||
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
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.Request
|
import okhttp3.Request
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
import xyz.quaver.pupil.runtime
|
import xyz.quaver.pupil.runtime
|
||||||
|
import xyz.quaver.pupil.runtimeReady
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
@@ -84,11 +82,11 @@ data class GalleryInfo(
|
|||||||
val groups: List<Group>? = null,
|
val groups: List<Group>? = null,
|
||||||
val parodys: List<Parody>? = null,
|
val parodys: List<Parody>? = null,
|
||||||
val tags: List<Tag>? = null,
|
val tags: List<Tag>? = null,
|
||||||
val related: List<Int>,
|
val related: List<Int> = emptyList(),
|
||||||
val languages: List<Language>,
|
val languages: List<Language> = emptyList(),
|
||||||
val characters: List<Character>? = null,
|
val characters: List<Character>? = null,
|
||||||
val scene_indexes: List<Int>,
|
val scene_indexes: List<Int>? = emptyList(),
|
||||||
val files: List<GalleryFiles>
|
val files: List<GalleryFiles> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
val json = Json {
|
val json = Json {
|
||||||
@@ -135,13 +133,16 @@ val evaluationContext = Executors.newSingleThreadExecutor().asCoroutineDispatche
|
|||||||
object gg {
|
object gg {
|
||||||
|
|
||||||
suspend fun m(g: Int): Int = withContext(evaluationContext) {
|
suspend fun m(g: Int): Int = withContext(evaluationContext) {
|
||||||
|
while (!runtimeReady) delay(1000)
|
||||||
runtime.evaluate("gg.m($g)").toString().toInt()
|
runtime.evaluate("gg.m($g)").toString().toInt()
|
||||||
}
|
}
|
||||||
suspend fun b(): String = withContext(evaluationContext) {
|
suspend fun b(): String = withContext(evaluationContext) {
|
||||||
|
while (!runtimeReady) delay(1000)
|
||||||
runtime.evaluate("gg.b").toString()
|
runtime.evaluate("gg.b").toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun s(h: String): String = withContext(evaluationContext) {
|
suspend fun s(h: String): String = withContext(evaluationContext) {
|
||||||
|
while (!runtimeReady) delay(1000)
|
||||||
runtime.evaluate("gg.s('$h')").toString()
|
runtime.evaluate("gg.s('$h')").toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ data class GalleryBlock(
|
|||||||
val type: String,
|
val type: String,
|
||||||
val language: String,
|
val language: String,
|
||||||
val relatedTags: List<String>,
|
val relatedTags: List<String>,
|
||||||
val groups: List<String>
|
val groups: List<String> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun getGalleryBlock(galleryID: Int) : GalleryBlock {
|
suspend fun getGalleryBlock(galleryID: Int) : GalleryBlock {
|
||||||
|
|||||||
@@ -220,15 +220,11 @@ class DownloadService : Service() {
|
|||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
runCatching {
|
runCatching {
|
||||||
response.also {
|
val image = response.also { if (it.code() != 200) throw IOException( "$galleryID $index ${response.request().url()} CODE ${it.code()}" ) }.body()?.use { it.bytes() } ?: throw Exception("Response null")
|
||||||
if (it.code() != 200) throw IOException(
|
|
||||||
"$galleryID $index ${response.request().url()} CODE ${it.code()}"
|
|
||||||
)
|
|
||||||
}.body()?.use {
|
|
||||||
val padding = ceil(progress[galleryID]?.size?.let { log10(it.toFloat()) } ?: 0F).toInt()
|
val padding = ceil(progress[galleryID]?.size?.let { log10(it.toFloat()) } ?: 0F).toInt()
|
||||||
|
|
||||||
Cache.getInstance(this@DownloadService, galleryID)
|
Cache.getInstance(this@DownloadService, galleryID)
|
||||||
.putImage(index, "${index.toString().padStart(padding, '0')}.$ext", it.byteStream())
|
.putImage(index, "${index.toString().padStart(padding, '0')}.$ext", image)
|
||||||
|
|
||||||
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
|
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
|
||||||
notify(galleryID)
|
notify(galleryID)
|
||||||
@@ -241,7 +237,6 @@ class DownloadService : Service() {
|
|||||||
|
|
||||||
startId?.let { stopSelf(it) }
|
startId?.let { stopSelf(it) }
|
||||||
}
|
}
|
||||||
} ?: throw Exception("Response null")
|
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
FirebaseCrashlytics.getInstance().recordException(it)
|
FirebaseCrashlytics.getInstance().recordException(it)
|
||||||
}
|
}
|
||||||
@@ -329,6 +324,7 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isCompleted(galleryID)) {
|
if (isCompleted(galleryID)) {
|
||||||
|
if (DownloadManager.getInstance(this@DownloadService).getDownloadFolder(galleryID) != null)
|
||||||
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
|
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
|
||||||
|
|
||||||
notificationManager.cancel(galleryID)
|
notificationManager.cancel(galleryID)
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import xyz.quaver.io.util.readText
|
|||||||
import xyz.quaver.io.util.writeText
|
import xyz.quaver.io.util.writeText
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.histories
|
import xyz.quaver.pupil.histories
|
||||||
|
import xyz.quaver.pupil.hitomi.json
|
||||||
import xyz.quaver.pupil.util.byteToString
|
import xyz.quaver.pupil.util.byteToString
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
@@ -118,7 +119,7 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
if (!metadataFile.exists()) return@forEach
|
if (!metadataFile.exists()) return@forEach
|
||||||
|
|
||||||
val metadata = metadataFile.readText()?.let {
|
val metadata = metadataFile.readText()?.let {
|
||||||
Json.decodeFromString<Metadata>(it)
|
json.decodeFromString<Metadata>(it)
|
||||||
} ?: return@forEach
|
} ?: return@forEach
|
||||||
|
|
||||||
val galleryID = metadata.galleryBlock?.id ?: metadata.galleryInfo?.id?.toIntOrNull() ?: return@forEach
|
val galleryID = metadata.galleryBlock?.id ?: metadata.galleryInfo?.id?.toIntOrNull() ?: return@forEach
|
||||||
|
|||||||
@@ -21,12 +21,9 @@ package xyz.quaver.pupil.util.downloader
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
@@ -35,35 +32,50 @@ import okhttp3.Request
|
|||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.io.util.*
|
import xyz.quaver.io.util.*
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
import xyz.quaver.pupil.hitomi.GalleryBlock
|
import xyz.quaver.pupil.hitomi.*
|
||||||
import xyz.quaver.pupil.hitomi.GalleryInfo
|
|
||||||
import xyz.quaver.pupil.hitomi.getGalleryBlock
|
|
||||||
import xyz.quaver.pupil.hitomi.getGalleryInfo
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class OldGalleryBlock(
|
data class OldGalleryInfo(
|
||||||
val code: String,
|
val language_localname: String? = null,
|
||||||
val id: Int,
|
val language: String? = null,
|
||||||
val galleryUrl: String,
|
val date: String? = null,
|
||||||
val thumbnails: List<String>,
|
val files: List<OldGalleryFiles>,
|
||||||
val title: String,
|
val id: Int? = null,
|
||||||
val artists: List<String>,
|
val type: String? = null,
|
||||||
val series: List<String>,
|
val title: String? = null
|
||||||
val type: String,
|
|
||||||
val language: String,
|
|
||||||
val relatedTags: List<String>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class OldGalleryFiles(
|
||||||
|
val width: Int,
|
||||||
|
val hash: String,
|
||||||
|
val haswebp: Int = 0,
|
||||||
|
val name: String,
|
||||||
|
val height: Int,
|
||||||
|
val hasavif: Int = 0,
|
||||||
|
val hasavifsmalltn: Int? = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class OldMetadata(
|
||||||
|
var galleryBlock: GalleryBlock? = null,
|
||||||
|
var reader: OldGalleryInfo? = null,
|
||||||
|
var imageList: MutableList<String?>? = null
|
||||||
|
) {
|
||||||
|
fun copy(): OldMetadata = OldMetadata(galleryBlock, reader, imageList?.let { MutableList(it.size) { i -> it[i] } })
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Metadata(
|
data class Metadata(
|
||||||
var galleryBlock: GalleryBlock? = null,
|
var galleryBlock: GalleryBlock? = null,
|
||||||
var galleryInfo: GalleryInfo? = null,
|
var galleryInfo: GalleryInfo? = null,
|
||||||
var imageList: MutableList<String?>? = null
|
var imageList: MutableList<String?>? = null
|
||||||
) {
|
) {
|
||||||
|
constructor(old: OldMetadata) : this(old.galleryBlock, getGalleryInfo(old.galleryBlock?.id ?: throw Exception()), old.imageList)
|
||||||
fun copy(): Metadata = Metadata(galleryBlock, galleryInfo, imageList?.let { MutableList(it.size) { i -> it[i] } })
|
fun copy(): Metadata = Metadata(galleryBlock, galleryInfo, imageList?.let { MutableList(it.size) { i -> it[i] } })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,9 +102,13 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
|
|
||||||
var metadata = kotlin.runCatching {
|
var metadata = kotlin.runCatching {
|
||||||
findFile(".metadata")?.readText()?.let { metadata ->
|
findFile(".metadata")?.readText()?.let { metadata ->
|
||||||
Json.decodeFromString<Metadata>(metadata)
|
kotlin.runCatching {
|
||||||
|
json.decodeFromString<Metadata>(metadata)
|
||||||
|
}.getOrElse {
|
||||||
|
Metadata(json.decodeFromString<OldMetadata>(metadata))
|
||||||
}
|
}
|
||||||
}.getOrNull() ?: Metadata()
|
}
|
||||||
|
}.onFailure { it.printStackTrace() }.getOrNull() ?: Metadata()
|
||||||
|
|
||||||
val downloadFolder: FileX?
|
val downloadFolder: FileX?
|
||||||
get() = DownloadManager.getInstance(this).getDownloadFolder(galleryID)
|
get() = DownloadManager.getInstance(this).getDownloadFolder(galleryID)
|
||||||
@@ -179,14 +195,13 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
metadata.imageList?.getOrNull(index)?.let { findFile(it) }
|
metadata.imageList?.getOrNull(index)?.let { findFile(it) }
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
fun putImage(index: Int, fileName: String, data: InputStream) {
|
suspend fun putImage(index: Int, fileName: String, data: ByteArray) = coroutineScope {
|
||||||
val file = cacheFolder.getChild(fileName)
|
val file = cacheFolder.getChild(fileName)
|
||||||
|
|
||||||
if (!file.exists())
|
if (!file.exists())
|
||||||
file.createNewFile()
|
file.createNewFile()
|
||||||
file.outputStream()?.use {
|
|
||||||
data.copyTo(it)
|
file.writeBytes(data)
|
||||||
}
|
|
||||||
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ fun OkHttpClient.Builder.proxyInfo(proxyInfo: ProxyInfo) = this.apply {
|
|||||||
val formatMap = mapOf<String, GalleryBlock.() -> (String)>(
|
val formatMap = mapOf<String, GalleryBlock.() -> (String)>(
|
||||||
"-id-" to { id.toString() },
|
"-id-" to { id.toString() },
|
||||||
"-title-" to { title },
|
"-title-" to { title },
|
||||||
"-artist-" to { artists.joinToString() },
|
"-artist-" to { if (artists.isNotEmpty()) artists.joinToString() else "N/A" },
|
||||||
"-group-" to { groups.joinToString() }
|
"-group-" to { if (groups.isNotEmpty()) groups.joinToString() else "N/A" }
|
||||||
// TODO
|
// TODO
|
||||||
)
|
)
|
||||||
/**
|
/**
|
||||||
@@ -100,13 +100,11 @@ fun GalleryBlock.formatDownloadFolderTest(format: String): String =
|
|||||||
|
|
||||||
suspend fun GalleryInfo.getRequestBuilders(): List<Request.Builder> {
|
suspend fun GalleryInfo.getRequestBuilders(): List<Request.Builder> {
|
||||||
val galleryID = this.id.toIntOrNull() ?: 0
|
val galleryID = this.id.toIntOrNull() ?: 0
|
||||||
val lowQuality = Preferences["low_quality", true]
|
|
||||||
|
|
||||||
return this.files.map {
|
return this.files.map {
|
||||||
Request.Builder()
|
Request.Builder()
|
||||||
.url(
|
.url(
|
||||||
runCatching {
|
runCatching {
|
||||||
imageUrlFromImage(galleryID, it, !lowQuality)
|
imageUrlFromImage(galleryID, it, false)
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
FirebaseCrashlytics.getInstance().recordException(it)
|
FirebaseCrashlytics.getInstance().recordException(it)
|
||||||
|
|||||||
@@ -56,12 +56,6 @@
|
|||||||
app:key="nomedia"
|
app:key="nomedia"
|
||||||
app:title="@string/settings_nomedia_title"/>
|
app:title="@string/settings_nomedia_title"/>
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
app:key="low_quality"
|
|
||||||
app:title="@string/settings_low_quality"
|
|
||||||
app:summary="@string/settings_low_quality_summary"
|
|
||||||
app:defaultValue="true"/>
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
|
|||||||
Reference in New Issue
Block a user