Compare commits

...

55 Commits

Author SHA1 Message Date
tom5079
aa10ada3ee Dependency update 2021-11-03 09:42:12 +09:00
tom5079
10c97987fb Aligned with new hitomi.la image servers 2021-10-30 08:52:10 +09:00
tom5079
b532615bbd Aligned with new hitomi.la image servers 2021-10-29 16:55:12 +09:00
tom5079
3066f41af3 Update README.md 2021-10-28 08:33:35 +09:00
tom5079
0c401c6741 Merge remote-tracking branch 'origin/master' 2021-10-28 08:32:45 +09:00
tom5079
1a21d1c937 Aligned with new hitomi.la image servers 2021-10-28 08:30:59 +09:00
tom5079
525b49a5c9 Update README.md 2021-10-25 22:07:12 +09:00
tom5079
34c074bf7b Built APK 2021-10-25 09:33:25 +09:00
tom5079
b4dc961cdc Aligned with new hitomi.la image servers 2021-10-25 09:32:05 +09:00
tom5079
93374d2cfe Updated gradlew permission 2021-09-14 00:39:35 +09:00
tom5079
4009b10549 Align with hitomi image server 2021-08-11 22:22:35 +09:00
tom5079
db1864205f Merge remote-tracking branch 'origin/master' 2021-07-23 22:11:36 +09:00
tom5079
bf39ccabbd Fixed images not showing up 2021-07-23 22:11:28 +09:00
tom5079
0e8e7767ee Update README.md 2021-07-23 22:10:02 +09:00
tom5079
5b6c86e34f Fixed images not showing up 2021-07-23 22:07:18 +09:00
tom5079
6bbaca3686 Update README.md 2021-07-23 21:52:35 +09:00
tom5079
35eae90df1 Updated README.md 2021-07-23 21:51:38 +09:00
tom5079
488d43e076 Merge remote-tracking branch 'origin/master' 2021-07-23 21:50:25 +09:00
tom5079
7c5e93c171 Merge branch 'dev' 2021-07-23 21:49:18 +09:00
tom5079
a20ef783e1 Fixed thumbnail not visible 2021-07-23 21:36:41 +09:00
tom5079
8ae0dce0ed Update README.md 2021-07-10 12:36:42 +09:00
tom5079
44aea606b7 resigned apk 2021-07-09 18:22:53 +09:00
tom5079
a05dc8c661 Alignment with changed hitomi.la image server 2021-07-09 18:03:57 +09:00
tom5079
1f80e36017 Check update onResume() instead of onCreate() 2021-07-03 16:25:08 +09:00
tom5079
1efca40744 Dependency update & report savedset io exception 2021-07-03 16:22:52 +09:00
tom5079
86e3131afa Update README.md 2021-06-18 07:43:58 +09:00
tom5079
4910b4a4b0 Update README.md 2021-06-14 08:28:58 +09:00
tom5079
9c7320c0a0 Fix app crashing 2021-06-12 16:02:38 +09:00
tom5079
02c17c3b75 Potential fix for UpdateBroadcastReceiver 2021-06-12 15:47:23 +09:00
tom5079
49a47f4b4f 5.1.9-hotfix1 2021-06-08 20:05:16 +09:00
tom5079
68280f4a62 Update README.md 2021-06-08 20:02:03 +09:00
tom5079
0e3669b247 Update README.md 2021-06-08 14:03:02 +09:00
tom5079
4c9aa29d46 Fixed Downloaded folder showing up as not downloaded 2021-06-08 12:01:16 +09:00
tom5079
66fbf10f2d Update README.md 2021-06-08 09:19:49 +09:00
tom5079
15ad806eb8 Update README.md 2021-06-08 09:19:35 +09:00
tom5079
b7f80b9c82 5.1.9 2021-06-08 09:18:20 +09:00
tom5079
9b511d2f8f Fixed radio button acting up 2021-06-08 09:08:24 +09:00
tom5079
6ebce2deb3 Dependency update 2021-06-08 08:48:05 +09:00
tom5079
95dade13f4 Dependency update 2021-05-18 10:57:36 +09:00
tom5079
ba4449d003 Fixed Proxy dialog 2021-04-04 08:22:55 +09:00
tom5079
7632fe5e86 Dependency update 2021-02-18 10:03:51 +09:00
tom5079
2c56bcacee Dependency update & Apk build 2021-02-17 18:09:13 +09:00
tom5079
c8202db3c6 Merge remote-tracking branch 'origin/dev' into dev 2021-02-17 17:44:54 +09:00
tom5079
223d689b0c Merge pull request #115 from tom5079/issue-112
Pupil-112 [Feature request] Add the ability to manage the maximum parallel downloads
2021-02-17 17:44:32 +09:00
tom5079
4f0e7d9696 Dependency update 2021-02-17 17:44:14 +09:00
tom5079
f4ce911de9 Pupil-112 [Feature request] Add the ability to manage the maximum parallel downloads
Dependency update
2021-02-16 16:57:23 +09:00
tom5079
d0ad7effa0 Updated README.md 2021-02-13 18:14:18 +09:00
tom5079
a032beecbf Merge remote-tracking branch 'origin/master' 2021-02-13 18:13:52 +09:00
tom5079
46ec9e48d9 (Android 11) Show warning when the download folder is set to app internal space 2021-02-13 18:12:45 +09:00
tom5079
26bcef1cc0 Fixed Related gallery not showing up on GalleryDialog 2021-02-13 17:51:18 +09:00
tom5079
bfb2f44f8f Fixed favorite tag duplication 2021-02-13 17:43:44 +09:00
tom5079
28b19b6774 migration bug fixed 2021-01-13 09:49:49 +09:00
tom5079
8d72f4a3aa Update README.md 2021-01-12 12:55:50 +09:00
tom5079
9c62e0399d Update README.md 2021-01-12 12:43:09 +09:00
tom5079
65ea09854e Fixed Bug occuring on Android 11 2021-01-12 12:40:21 +09:00
36 changed files with 389 additions and 621 deletions

17
.idea/deploymentTargetDropDown.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_3a_API_30_x86.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-10-25T00:27:52.904947Z" />
</component>
</project>

3
.idea/gradle.xml generated
View File

@@ -4,7 +4,7 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="PLATFORM" />
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
@@ -14,7 +14,6 @@
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>

View File

@@ -71,5 +71,15 @@
<option name="name" value="maven3" />
<option name="url" value="http://dl.bintray.com/piasy/maven" />
</remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://guardian.github.io/maven/repo-releases/" />
</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>
</project>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@@ -2,7 +2,7 @@
*Pupil, Hitomi.la viewer for Android*
![](https://img.shields.io/github/downloads/tom5079/Pupil/total)
[![](https://img.shields.io/github/downloads/tom5079/Pupil/5.1.6-hotfix7/Pupil-v5.1.6-hotfix7.apk?color=%234fc3f7&label=DOWNLOAD%20APP&style=for-the-badge)](https://github.com/tom5079/Pupil/releases/download/5.1.6-hotfix7/Pupil-v5.1.6-hotfix7.apk)
[![](https://img.shields.io/github/downloads/tom5079/Pupil/5.1.16/Pupil-v5.1.16.apk?color=%234fc3f7&label=DOWNLOAD%20APP&style=for-the-badge)](https://github.com/tom5079/Pupil/releases/download/5.1.16/Pupil-v5.1.16.apk)
[![](https://discordapp.com/api/guilds/610452916612104194/embed.png?style=banner2)](https://discord.gg/Stj4b5v)
# Features
@@ -20,7 +20,7 @@ or Build app yourself
# Contribution
Any kind of contribution is appriciated. Feel free to leave PR!
Any kind of contribution is appreciated. Feel free to leave PR!
## Tag Translation
Head over to [tags branch](https://github.com/tom5079/Pupil/tree/tags)

View File

@@ -37,22 +37,22 @@ android {
applicationId "xyz.quaver.pupil"
minSdkVersion 16
targetSdkVersion 30
versionCode 64
versionName "5.1.7-beta1"
versionCode 67
versionName "5.1.18"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
debug {
minifyEnabled true
shrinkResources true
defaultConfig.minSdkVersion 21
minifyEnabled false
shrinkResources false
debuggable true
applicationIdSuffix ".debug"
versionNameSuffix "-DEBUG"
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
ext.enableCrashlytics = false
ext.alwaysUpdateBuildId = false
}
@@ -74,44 +74,43 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildToolsVersion = "29.0.3"
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.0"
implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.activity:activity-ktx:1.2.0-beta01"
implementation "androidx.fragment:fragment-ktx:1.3.0-beta01"
implementation "androidx.appcompat:appcompat:1.3.0"
implementation "androidx.activity:activity-ktx:1.3.0-beta01"
implementation "androidx.fragment:fragment-ktx:1.3.4"
implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.gridlayout:gridlayout:1.0.0"
implementation "androidx.biometric:biometric:1.0.1"
implementation "androidx.work:work-runtime-ktx:2.4.0"
implementation "androidx.biometric:biometric:1.1.0"
implementation "androidx.work:work-runtime-ktx:2.6.0-beta01"
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
implementation "com.google.android.material:material:1.3.0-alpha04"
implementation "com.google.android.material:material:1.3.0"
implementation "com.google.firebase:firebase-core:18.0.0"
implementation "com.google.firebase:firebase-analytics:18.0.0"
implementation "com.google.firebase:firebase-crashlytics:17.3.0"
implementation "com.google.firebase:firebase-perf:19.0.10"
implementation platform('com.google.firebase:firebase-bom:26.5.0')
implementation "com.google.firebase:firebase-analytics-ktx"
implementation "com.google.firebase:firebase-crashlytics-ktx"
implementation "com.google.firebase:firebase-perf-ktx"
implementation "com.google.android.gms:play-services-oss-licenses:17.0.0"
implementation "com.google.android.gms:play-services-mlkit-face-detection:16.1.1"
implementation "com.google.android.gms:play-services-mlkit-face-detection:16.1.7"
implementation "com.github.clans:fab:1.6.4"
//implementation "com.quiph.ui:recyclerviewfastscroller:0.2.1"
implementation 'com.github.piasy:BigImageViewer:1.6.7'
implementation 'com.github.piasy:FrescoImageLoader:1.6.7'
implementation 'com.github.piasy:FrescoImageViewFactory:1.6.7'
implementation 'com.github.piasy:BigImageViewer:1.8.1'
implementation 'com.github.piasy:FrescoImageLoader:1.8.1'
implementation 'com.github.piasy:FrescoImageViewFactory:1.8.1'
//noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
@@ -126,9 +125,9 @@ dependencies {
implementation "ru.noties.markwon:core:3.1.0"
implementation "xyz.quaver:libpupil:1.8.16"
implementation "xyz.quaver:documentfilex:0.4-alpha02"
implementation "xyz.quaver:floatingsearchview:1.0.7"
implementation "xyz.quaver:libpupil:2.1.11"
implementation "xyz.quaver:documentfilex:0.6.1"
implementation "xyz.quaver:floatingsearchview:1.1.7"
testImplementation "junit:junit:4.13.1"
androidTestImplementation "androidx.test.ext:junit:1.1.2"

View File

@@ -5,13 +5,13 @@
"kind": "Directory"
},
"applicationId": "xyz.quaver.pupil",
"variantName": "processReleaseResources",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"versionCode": 64,
"versionName": "5.1.7-beta1",
"versionCode": 67,
"versionName": "5.1.18",
"outputFile": "app-release.apk"
}
]

View File

@@ -26,6 +26,7 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
@@ -34,8 +35,8 @@ import com.github.piasy.biv.loader.fresco.FrescoImageLoader
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
import com.google.android.gms.common.GooglePlayServicesRepairableException
import com.google.android.gms.security.ProviderInstaller
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import okhttp3.Dispatcher
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
@@ -46,6 +47,7 @@ import xyz.quaver.pupil.util.downloader.DownloadManager
import xyz.quaver.setClient
import java.io.File
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.reflect.KClass
@@ -96,6 +98,11 @@ class Pupil : Application() {
val tag = request.tag() ?: return@addInterceptor chain.proceed(request)
interceptors[tag::class]?.invoke(chain) ?: chain.proceed(request)
}.apply {
(Preferences.get<String>("max_concurrent_download").toIntOrNull() ?: 0).let {
if (it != 0)
dispatcher(Dispatcher(Executors.newFixedThreadPool(it)))
}
}
try {
@@ -108,8 +115,6 @@ class Pupil : Application() {
if (!FileX(this, it).canWrite())
throw Exception()
DownloadManager.getInstance(this).migrate()
}
} catch (e: Exception) {
Preferences.remove("download_folder")
@@ -125,8 +130,13 @@ class Pupil : Application() {
favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "")
favoriteTags.filter { it.tag.contains('_') }.forEach {
favoriteTags.remove(it)
}
/*
if (BuildConfig.DEBUG)
FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(false)
FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(false)*/
try {
ProviderInstaller.installIfNeeded(this)

View File

@@ -36,6 +36,7 @@ import com.daimajia.swipe.interfaces.SwipeAdapterInterface
import com.github.piasy.biv.loader.ImageLoader
import kotlinx.coroutines.*
import xyz.quaver.hitomi.getGallery
import xyz.quaver.hitomi.getGalleryInfo
import xyz.quaver.hitomi.getReader
import xyz.quaver.io.util.getChild
import xyz.quaver.pupil.R
@@ -231,7 +232,7 @@ class GalleryBlockAdapter(private val galleries: List<Int>) : RecyclerSwipeAdapt
binding.galleryblockPagecount.text = "-"
CoroutineScope(Dispatchers.IO).launch {
val pageCount = kotlin.runCatching {
getReader(galleryBlock.id).galleryInfo.files.size
getGalleryInfo(galleryBlock.id).files.size
}.getOrNull() ?: return@launch
withContext(Dispatchers.Main) {
binding.galleryblockPagecount.text = itemView.context.getString(R.string.galleryblock_pagecount, pageCount)

View File

@@ -1,86 +0,0 @@
/*
* 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.adapters
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import xyz.quaver.pupil.R
import xyz.quaver.pupil.databinding.MirrorsItemBinding
import xyz.quaver.pupil.util.Preferences
import java.util.*
class MirrorAdapter(context: Context) : RecyclerView.Adapter<MirrorAdapter.ViewHolder>() {
@SuppressLint("ClickableViewAccessibility")
inner class ViewHolder(val binding: MirrorsItemBinding) : RecyclerView.ViewHolder(binding.root) {
init {
binding.mirrorButton.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN)
onStartDrag?.invoke(this)
true
}
}
fun bind(mirror: String) {
binding.mirrorName.text = mirror
}
}
val mirrors = context.resources.getStringArray(R.array.mirrors).map {
it.split('|').let { split ->
Pair(split.first(), split.last())
}
}.toMap()
val list = mirrors.keys.toMutableList().apply {
Preferences.get<String>("mirrors")
.split(">")
.reversed()
.forEach {
if (this.contains(it)) {
this.remove(it)
this.add(0, it)
}
}
}
val onItemMove : ((Int, Int) -> Unit) = { from, to ->
Collections.swap(list, from, to)
notifyItemMoved(from, to)
onItemMoved?.invoke(list)
}
var onStartDrag : ((ViewHolder) -> Unit)? = null
var onItemMoved : ((List<String>) -> (Unit))? = null
@SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(mirrors[list.elementAt(position)] ?: error(""))
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(MirrorsItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun getItemCount() = mirrors.size
}

View File

@@ -40,7 +40,7 @@ import com.github.piasy.biv.view.BigImageView
import com.github.piasy.biv.view.ImageShownCallback
import com.github.piasy.biv.view.ImageViewFactory
import kotlinx.coroutines.*
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.GalleryInfo
import xyz.quaver.pupil.R
import xyz.quaver.pupil.databinding.ReaderItemBinding
import xyz.quaver.pupil.ui.ReaderActivity
@@ -52,7 +52,7 @@ class ReaderAdapter(
private val activity: ReaderActivity,
private val galleryID: Int
) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
var reader: Reader? = null
var galleryInfo: GalleryInfo? = null
var isFullScreen = false
@@ -101,7 +101,7 @@ class ReaderAdapter(
binding.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
height = 0
dimensionRatio =
"${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
"${galleryInfo!!.files[position].width}:${galleryInfo!!.files[position].height}"
}
} else {
binding.root.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
@@ -158,7 +158,7 @@ class ReaderAdapter(
holder.bind(position)
}
override fun getItemCount() = reader?.galleryInfo?.files?.size ?: 0
override fun getItemCount() = galleryInfo?.files?.size ?: 0
override fun onViewRecycled(holder: ViewHolder) {
holder.clear()

View File

@@ -54,7 +54,7 @@ class UpdateBroadcastReceiver : BroadcastReceiver() {
val uri = downloadManager.query(query).use { cursor ->
if (cursor.moveToFirst()) {
cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)).let {
cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))?.let {
val uri = Uri.parse(it)
when (uri.scheme) {

View File

@@ -305,10 +305,10 @@ class DownloadService : Service() {
initNotification(galleryID)
val reader = cache.getReader()
val galleryInfo = cache.getGalleryInfo()
// Gallery doesn't exist
if (reader == null) {
if (galleryInfo == null) {
delete(galleryID)
progress[galleryID] = mutableListOf()
return@launch
@@ -316,7 +316,7 @@ class DownloadService : Service() {
histories.add(galleryID)
progress[galleryID] = MutableList(reader.galleryInfo.files.size) { 0F }
progress[galleryID] = MutableList(galleryInfo.files.size) { 0F }
cache.metadata.imageList?.let {
it.forEachIndexed { index, image ->
@@ -334,7 +334,7 @@ class DownloadService : Service() {
return@launch
}
notification[galleryID]?.setContentTitle(reader.galleryInfo.title?.ellipsize(30))
notification[galleryID]?.setContentTitle(galleryInfo.title?.ellipsize(30))
notify(galleryID)
val queued = mutableSetOf<Int>()
@@ -348,7 +348,7 @@ class DownloadService : Service() {
}
}
reader.requestBuilders.forEachIndexed { index, it ->
galleryInfo.requestBuilders.forEachIndexed { index, it ->
if (progress[galleryID]?.get(index)?.isInfinite() == false) {
val request = it.tag(Tag(galleryID, index, startId)).build()
client.newCall(request).enqueue(callback)

View File

@@ -21,6 +21,7 @@ package xyz.quaver.pupil.ui
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.text.util.Linkify
@@ -31,8 +32,7 @@ import android.view.animation.DecelerateInterpolator
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDelegate
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView
@@ -124,11 +124,31 @@ class MainActivity :
if (Preferences["download_folder", ""].isEmpty())
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
checkUpdate(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
!Preferences["download_folder_ignore_warning", false] &&
ContextCompat.getExternalFilesDirs(this, null).filterNotNull().map { Uri.fromFile(it).toString() }
.contains(Preferences["download_folder", ""])
) {
AlertDialog.Builder(this)
.setTitle(R.string.warning)
.setMessage(R.string.unaccessible_download_folder)
.setPositiveButton(android.R.string.ok) { _, _ ->
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
}.setNegativeButton(R.string.ignore) { _, _ ->
Preferences["download_folder_ignore_warning"] = true
}.show()
}
initView()
}
override fun onResume() {
super.onResume()
checkUpdate(this)
}
@OptIn(ExperimentalStdlibApi::class)
override fun onBackPressed() {
when {
@@ -196,7 +216,7 @@ class MainActivity :
min(
max(
binding.contents.searchview.translationY - dy,
-binding.contents.searchview.findViewById<CardView>(R.id.search_query_section).height.toFloat()
-binding.contents.searchview.binding.querySection.root.height.toFloat()
), 0F)
if (dy > 0)
@@ -458,7 +478,7 @@ class MainActivity :
with(binding.contents.searchview) {
onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener {
override fun onMenuOpened() {
(binding.contents.recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
(this@MainActivity.binding.contents.recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
}
override fun onMenuClosed() {
@@ -542,7 +562,7 @@ class MainActivity :
}
}
attachNavigationDrawerToMenuButton(binding.drawer)
attachNavigationDrawerToMenuButton(this@MainActivity.binding.drawer)
}
}
@@ -781,7 +801,7 @@ class MainActivity :
}
} catch (e: Exception) {
if (e.message != "No result")
if (e !is CancellationException)
FirebaseCrashlytics.getInstance().recordException(e)
withContext(Dispatchers.Main) {

View File

@@ -49,7 +49,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import xyz.quaver.Code
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.ReaderAdapter
import xyz.quaver.pupil.databinding.NumberpickerDialogBinding
@@ -184,7 +183,7 @@ class ReaderActivity : BaseActivity() {
with(binding.numberPicker) {
minValue = 1
maxValue = cache.metadata.reader?.galleryInfo?.files?.size ?: 0
maxValue = cache.metadata.galleryInfo?.files?.size ?: 0
value = currentPage
}
val dialog = AlertDialog.Builder(this).apply {
@@ -299,26 +298,19 @@ class ReaderActivity : BaseActivity() {
downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0
if (title == getString(R.string.reader_loading)) {
val reader = cache.metadata.reader
val galleryInfo = cache.metadata.galleryInfo
if (reader != null) {
if (galleryInfo != null) {
with(binding.recyclerview.adapter as ReaderAdapter) {
this.reader = reader
this.galleryInfo = galleryInfo
notifyDataSetChanged()
}
title = reader.galleryInfo.title
title = galleryInfo.title
menu?.findItem(R.id.reader_menu_page_indicator)?.title =
"$currentPage/${reader.galleryInfo.files.size}"
"$currentPage/${galleryInfo.files.size}"
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(
this@ReaderActivity,
when (reader.code) {
Code.HITOMI -> R.drawable.hitomi
Code.HIYOBI -> R.drawable.ic_hiyobi
else -> android.R.color.transparent
}
)
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity, R.drawable.hitomi)
}
}

View File

@@ -19,25 +19,29 @@
package xyz.quaver.pupil.ui.dialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.ArrayAdapter
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import xyz.quaver.pupil.R
import xyz.quaver.pupil.databinding.DefaultQueryDialogBinding
import xyz.quaver.pupil.types.Tags
import xyz.quaver.pupil.util.Preferences
class DefaultQueryDialog(context : Context) : AlertDialog(context) {
class DefaultQueryDialog : DialogFragment() {
private val languages = context.resources.getStringArray(R.array.languages).map {
it.split("|").let { split ->
Pair(split[0], split[1])
}
}.toMap()
private val reverseLanguages = languages.entries.associate { (k, v) -> v to k }
private val languages: Map<String, String> by lazy {
requireContext().resources.getStringArray(R.array.languages).map {
it.split("|").let { split ->
Pair(split[0], split[1])
}
}.toMap()
}
private val reverseLanguages: Map<String, String> by lazy {
languages.entries.associate { (k, v) -> v to k }
}
private val excludeBL = "-male:yaoi"
private val excludeGuro = listOf("-female:guro", "-male:guro")
@@ -45,41 +49,8 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
var onPositiveButtonClickListener : ((Tags) -> (Unit))? = null
private lateinit var binding: DefaultQueryDialogBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTitle(R.string.default_query_dialog_title)
binding = DefaultQueryDialogBinding.inflate(layoutInflater)
setView(binding.root)
initView()
setButton(Dialog.BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ ->
val newTags = Tags.parse(binding.edittext.text.toString())
with(binding.languageSelector) {
if (selectedItemPosition != 0)
newTags.add("language:${reverseLanguages[selectedItem]}")
}
if (binding.BLCheckbox.isChecked)
newTags.add(excludeBL)
if (binding.guroCheckbox.isChecked)
excludeGuro.forEach { tag ->
newTags.add(tag)
}
if (binding.loliCheckbox.isChecked)
excludeLoli.forEach { tag ->
newTags.add(tag)
}
onPositiveButtonClickListener?.invoke(newTags)
}
}
private var _binding: DefaultQueryDialogBinding? = null
private val binding get() = _binding!!
private fun initView() {
val tags = Tags.parse(
@@ -158,4 +129,43 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
_binding = DefaultQueryDialogBinding.inflate(layoutInflater)
initView()
return AlertDialog.Builder(requireContext()).apply {
setTitle(R.string.default_query_dialog_title)
setView(binding.root)
setPositiveButton(android.R.string.ok) { _, _ ->
val newTags = Tags.parse(binding.edittext.text.toString())
with(binding.languageSelector) {
if (selectedItemPosition != 0)
newTags.add("language:${reverseLanguages[selectedItem]}")
}
if (binding.BLCheckbox.isChecked)
newTags.add(excludeBL)
if (binding.guroCheckbox.isChecked)
excludeGuro.forEach { tag ->
newTags.add(tag)
}
if (binding.loliCheckbox.isChecked)
excludeLoli.forEach { tag ->
newTags.add(tag)
}
onPositiveButtonClickListener?.invoke(newTags)
}
}.create()
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}

View File

@@ -39,7 +39,6 @@ import xyz.quaver.pupil.databinding.DownloadLocationItemBinding
import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.byteToString
import xyz.quaver.pupil.util.downloader.DownloadManager
import xyz.quaver.pupil.util.migrate
import java.io.File
class DownloadLocationDialogFragment : DialogFragment() {
@@ -51,14 +50,11 @@ class DownloadLocationDialogFragment : DialogFragment() {
private val requestDownloadFolderLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val activity = activity ?: return@registerForActivityResult
val context = context ?: return@registerForActivityResult
val dialog = dialog ?: return@registerForActivityResult
it.data?.data?.also { uri ->
val takeFlags: Int =
activity.intent.flags and
(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
context.contentResolver.takePersistableUriPermission(uri, takeFlags)
@@ -79,6 +75,15 @@ class DownloadLocationDialogFragment : DialogFragment() {
if (key == null) entries[key]!!.locationAvailable.text = downloadFolder
}
}
} else {
val downloadFolder = DownloadManager.getInstance(context ?: return@registerForActivityResult).downloadFolder.canonicalPath
val key = entries.keys.firstOrNull { it?.canonicalPath == downloadFolder }
if (key == null)
entries[key]!!.locationAvailable.text = downloadFolder
else {
entries[null]!!.button.isChecked = false
entries[key]!!.button.isChecked = true
}
}
}
@@ -124,8 +129,8 @@ class DownloadLocationDialogFragment : DialogFragment() {
byteToString(dir.freeSpace)
)
root.setOnClickListener {
entries.values.forEach { _ ->
button.isChecked = false
entries.values.forEach { entry ->
entry.button.isChecked = false
}
button.performClick()
Preferences["download_folder"] = dir.toUri().toString()
@@ -137,8 +142,8 @@ class DownloadLocationDialogFragment : DialogFragment() {
DownloadLocationItemBinding.inflate(layoutInflater, binding.root, true).apply {
locationType.text = requireContext().getString(R.string.settings_download_folder_custom)
root.setOnClickListener {
entries.values.forEach {
it.button.isChecked = false
entries.values.forEach { entry ->
entry.button.isChecked = false
}
button.performClick()
@@ -181,8 +186,6 @@ class DownloadLocationDialogFragment : DialogFragment() {
setPositiveButton(requireContext().getText(android.R.string.ok)) { _, _ ->
if (Preferences["download_folder", ""].isEmpty())
Preferences["download_folder"] = context.getExternalFilesDir(null)?.toUri()?.toString() ?: ""
DownloadManager.getInstance(requireContext()).migrate()
}
isCancelable = false

View File

@@ -203,7 +203,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(
}
private fun addRelated(gallery: Gallery) {
val galleries = ArrayList<Int>()
val galleries = mutableListOf<Int>()
val adapter = GalleryBlockAdapter(galleries).apply {
onChipClickedHandler.add { tag ->
@@ -216,7 +216,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(
GalleryDialogDetailsBinding.inflate(layoutInflater, binding.contents, true).apply {
type.setText(R.string.gallery_related)
RecyclerView(context).apply {
contents.addView(RecyclerView(context).apply {
layoutManager = LinearLayoutManager(context)
this.adapter = adapter
@@ -236,7 +236,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(
true
}
}
}
})
CoroutineScope(Dispatchers.IO).launch {
gallery.related.forEach { galleryID ->

View File

@@ -1,91 +0,0 @@
/*
* 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.ui.dialog
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.MirrorAdapter
import xyz.quaver.pupil.util.Preferences
class MirrorDialog(context: Context) : AlertDialog(context) {
class ItemTouchHelperCallback : ItemTouchHelper.Callback() {
var onMoveItem : ((Int, Int) -> (Unit))? = null
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) = makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0)
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
onMoveItem?.invoke(viewHolder.adapterPosition, target.adapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
}
@SuppressLint("InflateParams")
override fun onCreate(savedInstanceState: Bundle?) {
setTitle(R.string.settings_mirror_title)
setView(build())
setButton(Dialog.BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> }
super.onCreate(savedInstanceState)
}
private fun build() : View {
return RecyclerView(context).apply recyclerview@{
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
layoutManager = LinearLayoutManager(context)
adapter = MirrorAdapter(context).apply adapter@{
val itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback().apply {
onMoveItem = this@adapter.onItemMove
}).apply {
attachToRecyclerView(this@recyclerview)
}
onStartDrag = {
itemTouchHelper.startDrag(it)
}
onItemMoved = {
Preferences["mirrors"] = it.joinToString(">")
}
}
}
}
}

View File

@@ -18,12 +18,14 @@
package xyz.quaver.pupil.ui.dialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import xyz.quaver.pupil.R
@@ -37,17 +39,19 @@ import xyz.quaver.pupil.util.getProxyInfo
import xyz.quaver.pupil.util.proxyInfo
import java.net.Proxy
class ProxyDialog(context: Context) : AlertDialog(context) {
class ProxyDialogFragment : DialogFragment() {
private lateinit var binding: ProxyDialogBinding
private var _binding: ProxyDialogBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ProxyDialogBinding.inflate(layoutInflater)
setView(binding.root)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
_binding = ProxyDialogBinding.inflate(layoutInflater)
initView()
return AlertDialog.Builder(requireContext()).apply {
setView(binding.root)
}.create()
}
private fun initView() {
@@ -105,9 +109,9 @@ class ProxyDialog(context: Context) : AlertDialog(context) {
if (type != Proxy.Type.DIRECT) {
if (addr == null || addr.isEmpty())
binding.addr.error = context.getText(R.string.proxy_dialog_error)
binding.addr.error = requireContext().getText(R.string.proxy_dialog_error)
if (port == null)
binding.port.error = context.getText(R.string.proxy_dialog_error)
binding.port.error = requireContext().getText(R.string.proxy_dialog_error)
if (addr == null || addr.isEmpty() || port == null)
return@setOnClickListener

View File

@@ -29,15 +29,20 @@ import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.Dispatcher
import xyz.quaver.io.FileX
import xyz.quaver.io.util.getChild
import xyz.quaver.pupil.R
import xyz.quaver.pupil.client
import xyz.quaver.pupil.clientBuilder
import xyz.quaver.pupil.clientHolder
import xyz.quaver.pupil.ui.LockActivity
import xyz.quaver.pupil.ui.SettingsActivity
import xyz.quaver.pupil.ui.dialog.*
import xyz.quaver.pupil.util.*
import xyz.quaver.pupil.util.downloader.DownloadManager
import java.util.*
import java.util.concurrent.Executors
class SettingsFragment :
PreferenceFragmentCompat(),
@@ -85,12 +90,12 @@ class SettingsFragment :
DownloadLocationDialogFragment().show(parentFragmentManager, "Download Location Dialog")
}
"default_query" -> {
DefaultQueryDialog(requireContext()).apply {
DefaultQueryDialog().apply {
onPositiveButtonClickListener = { newTags ->
Preferences["default_query"] = newTags.toString()
summary = newTags.toString()
}
}.show()
}.show(parentFragmentManager, "Default Query Dialog")
}
"app_lock" -> {
val intent = Intent(requireContext(), LockActivity::class.java).apply {
@@ -98,13 +103,8 @@ class SettingsFragment :
}
lockLauncher.launch(intent)
}
"mirrors" -> {
MirrorDialog(requireContext())
.show()
}
"proxy" -> {
ProxyDialog(requireContext())
.show()
ProxyDialogFragment().show(parentFragmentManager, "Proxy Dialog")
}
"user_id" -> {
(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(
@@ -168,6 +168,18 @@ class SettingsFragment :
"download_folder_name" -> {
summary = Preferences["download_folder_name", "[-id-] -title-"]
}
"max_concurrent_download" -> {
val newValue = Preferences.get<String>(key).toIntOrNull() ?: 0
if (newValue == 0)
clientBuilder.dispatcher(Dispatcher())
else
clientBuilder.dispatcher((Dispatcher(Executors.newFixedThreadPool(newValue))))
clientHolder = null
client
}
else -> return
}
}
}
@@ -247,6 +259,11 @@ class SettingsFragment :
onPreferenceClickListener = this@SettingsFragment
}
"proxy" -> {
summary = getProxyInfo().type.name
onPreferenceClickListener = this@SettingsFragment
}
"tag_translation" -> {
this as ListPreference
@@ -268,14 +285,6 @@ class SettingsFragment :
onPreferenceChangeListener = this@SettingsFragment
}
"mirrors" -> {
onPreferenceClickListener = this@SettingsFragment
}
"proxy" -> {
summary = getProxyInfo().type.name
onPreferenceClickListener = this@SettingsFragment
}
"dark_mode" -> {
onPreferenceChangeListener = this@SettingsFragment
}

View File

@@ -25,6 +25,7 @@ import android.graphics.drawable.Animatable
import android.text.Editable
import android.text.TextWatcher
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.inputmethod.EditorInfo
@@ -58,8 +59,8 @@ class FloatingSearchView @JvmOverloads constructor(context: Context, attrs: Attr
searchInputView.addTextChangedListener(this)
onSearchListener = this
onBindSuggestionCallback = { a, b, c, d, e ->
onBindSuggestion(a, b, c, d, e)
onBindSuggestionCallback = { binding, item, itemPosition ->
onBindSuggestion(binding.root, binding.leftIcon, binding.body, item, itemPosition)
}
}
@@ -110,7 +111,7 @@ class FloatingSearchView @JvmOverloads constructor(context: Context, attrs: Attr
) {
when(item) {
is TagSuggestion -> {
val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}"
val tag = "${item.n}:${item.s}"
leftIcon?.setImageDrawable(
ResourcesCompat.getDrawable(

View File

@@ -18,11 +18,13 @@
package xyz.quaver.pupil.util
import kotlinx.serialization.*
import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer
import java.io.File
import java.util.*
class SavedSet <T: Any> (private val file: File, private val any: T, private val set: MutableSet<T> = mutableSetOf()) : MutableSet<T> by set {
@@ -46,6 +48,8 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
Json.decodeFromString(serializer, file.readText())
}.onSuccess {
set.addAll(it)
}.onFailure {
FirebaseCrashlytics.getInstance().recordException(it)
}
}
@@ -57,8 +61,6 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
@Synchronized
override fun add(element: T): Boolean {
load()
set.remove(element)
return set.add(element).also {
@@ -68,8 +70,6 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
@Synchronized
override fun addAll(elements: Collection<T>): Boolean {
load()
set.removeAll(elements)
return set.addAll(elements).also {
@@ -79,8 +79,6 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
@Synchronized
override fun remove(element: T): Boolean {
load()
return set.remove(element).also {
save()
}

View File

@@ -32,24 +32,62 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.Request
import xyz.quaver.Code
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.GalleryInfo
import xyz.quaver.io.FileX
import xyz.quaver.io.util.*
import xyz.quaver.pupil.client
import xyz.quaver.pupil.util.Preferences
import java.io.File
import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
@Serializable
data class Metadata(
var galleryBlock: GalleryBlock? = null,
var reader: Reader? = null,
data class OldGalleryBlock(
val code: String,
val id: Int,
val galleryUrl: String,
val thumbnails: List<String>,
val title: String,
val artists: List<String>,
val series: List<String>,
val type: String,
val language: String,
val relatedTags: List<String>
)
@Serializable
data class OldReader(val code: String, val galleryInfo: GalleryInfo)
@Serializable
data class OldMetadata(
var galleryBlock: OldGalleryBlock? = null,
var reader: OldReader? = null,
var imageList: MutableList<String?>? = null
) {
fun copy(): Metadata = Metadata(galleryBlock, reader, imageList?.let { MutableList(it.size) { i -> it[i] } })
fun copy(): OldMetadata = OldMetadata(galleryBlock, reader, imageList?.let { MutableList(it.size) { i -> it[i] } })
}
@Serializable
data class Metadata(
var galleryBlock: GalleryBlock? = null,
var galleryInfo: GalleryInfo? = null,
var imageList: MutableList<String?>? = null
) {
constructor(old: OldMetadata) : this(old.galleryBlock?.let { galleryBlock -> GalleryBlock(
galleryBlock.id,
galleryBlock.galleryUrl,
galleryBlock.thumbnails,
galleryBlock.title,
galleryBlock.artists,
galleryBlock.series,
galleryBlock.type,
galleryBlock.language,
galleryBlock.relatedTags) },
old.reader?.galleryInfo,
old.imageList
)
fun copy(): Metadata = Metadata(galleryBlock, galleryInfo, imageList?.let { MutableList(it.size) { i -> it[i] } })
}
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
@@ -74,8 +112,12 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
}
var metadata = kotlin.runCatching {
findFile(".metadata")?.readText()?.let {
Json.decodeFromString<Metadata>(it)
findFile(".metadata")?.readText()?.let { metadata ->
kotlin.runCatching {
Json.decodeFromString<Metadata>(metadata)
}.getOrElse {
Metadata(Json.decodeFromString<OldMetadata>(metadata))
}
}
}.getOrNull() ?: Metadata()
@@ -110,27 +152,13 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
}
suspend fun getGalleryBlock(): GalleryBlock? {
val sources = listOf(
{ xyz.quaver.hitomi.getGalleryBlock(galleryID) },
{ xyz.quaver.hiyobi.getGalleryBlock(galleryID) }
)
return metadata.galleryBlock
?: withContext(Dispatchers.IO) {
var galleryBlock: GalleryBlock? = null
for (source in sources) {
galleryBlock = try {
source.invoke()
} catch (e: Exception) { null }
if (galleryBlock != null)
break
}
galleryBlock?.also {
setMetadata { metadata -> metadata.galleryBlock = it }
}
try {
xyz.quaver.hitomi.getGalleryBlock(galleryID).also {
setMetadata { metadata -> metadata.galleryBlock = it }
}
} catch (e: Exception) { return@withContext null }
}
}
@@ -154,41 +182,21 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
}.getOrNull()?.uri }
} } ?: Uri.EMPTY
suspend fun getReader(): Reader? {
val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') }
suspend fun getGalleryInfo(): GalleryInfo? {
val sources = mapOf(
Code.HITOMI to { xyz.quaver.hitomi.getReader(galleryID) },
Code.HIYOBI to { xyz.quaver.hiyobi.getReader(galleryID) }
).let {
if (mirrors.isNotEmpty())
it.toSortedMap{ o1, o2 -> mirrors.indexOf(o1.name) - mirrors.indexOf(o2.name) }
else
it
}
return metadata.reader
return metadata.galleryInfo
?: withContext(Dispatchers.IO) {
var reader: Reader? = null
try {
xyz.quaver.hitomi.getGalleryInfo(galleryID).also {
setMetadata { metadata ->
metadata.galleryInfo = it
for (source in sources) {
reader = try {
source.value.invoke()
} catch (e: Exception) {
null
}
if (reader != null)
break
}
reader?.also {
setMetadata { metadata ->
metadata.reader = it
if (metadata.imageList == null)
metadata.imageList = MutableList(reader.galleryInfo.files.size) { null }
if (metadata.imageList == null)
metadata.imageList = MutableList(it.files.size) { null }
}
}
} catch (e: Exception) {
null
}
}
}

View File

@@ -19,19 +19,13 @@
package xyz.quaver.pupil.util
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.content.ContextCompat
import kotlinx.serialization.json.*
import okhttp3.OkHttpClient
import okhttp3.Request
import xyz.quaver.Code
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.GalleryInfo
import xyz.quaver.hitomi.getReferer
import xyz.quaver.hitomi.imageUrlFromImage
import xyz.quaver.hiyobi.createImgList
import java.util.*
import kotlin.collections.ArrayList
@@ -41,7 +35,7 @@ fun String.wordCapitalize() : String {
@SuppressLint("DefaultLocale")
for (word in this.split(" "))
result.add(word.capitalize(Locale.US))
result.add(word.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() })
return result.joinToString(" ")
}
@@ -103,25 +97,15 @@ fun GalleryBlock.formatDownloadFolderTest(format: String): String =
}
}.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
val Reader.requestBuilders: List<Request.Builder>
val GalleryInfo.requestBuilders: List<Request.Builder>
get() {
val galleryID = this.galleryInfo.id ?: 0
val galleryID = this.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)
}
}
return this.files.map {
Request.Builder()
.url(imageUrlFromImage(galleryID, it, !lowQuality))
.header("Referer", getReferer(galleryID))
}
}
@@ -137,5 +121,9 @@ operator fun JsonElement.get(index: Int) =
operator fun JsonElement.get(tag: String) =
this.jsonObject[tag]
fun JsonElement.getOrNull(tag: String) = kotlin.runCatching {
this.jsonObject.getOrDefault(tag, null)
}.getOrNull()
val JsonElement.content
get() = this.jsonPrimitive.contentOrNull

View File

@@ -18,49 +18,26 @@
package xyz.quaver.pupil.util
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.util.Base64
import android.webkit.URLUtil
import androidx.appcompat.app.AlertDialog
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.preference.PreferenceManager
import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.*
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Request
import okhttp3.Response
import ru.noties.markwon.Markwon
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.getGalleryBlock
import xyz.quaver.hitomi.getReader
import xyz.quaver.io.FileX
import xyz.quaver.io.util.getChild
import xyz.quaver.io.util.readText
import xyz.quaver.io.util.writeBytes
import xyz.quaver.io.util.writeText
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R
import xyz.quaver.pupil.client
import xyz.quaver.pupil.favorites
import xyz.quaver.pupil.services.DownloadService
import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.Metadata
import java.io.File
import java.io.IOException
import java.net.URL
@@ -182,7 +159,7 @@ fun checkUpdate(context: Context, force: Boolean = false) {
Preferences["update_download_id"] = it
}
}
setNegativeButton(if (force) android.R.string.cancel else R.string.ignore_update) { _, _ ->
setNegativeButton(if (force) android.R.string.cancel else R.string.ignore) { _, _ ->
if (!force)
preferences.edit()
.putLong("ignore_update_until", System.currentTimeMillis() + 604800000)
@@ -222,126 +199,3 @@ fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((
}
})
}
private var job: Job? = null
private val receiver = object: BroadcastReceiver() {
val ACTION_CANCEL = "ACTION_IMPORT_CANCEL"
override fun onReceive(context: Context?, intent: Intent?) {
context ?: return
when (intent?.action) {
ACTION_CANCEL -> {
job?.cancel()
NotificationManagerCompat.from(context).cancel(R.id.notification_id_import)
context.unregisterReceiver(this)
}
}
}
}
@SuppressLint("RestrictedApi")
fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
registerReceiver(receiver, IntentFilter().apply { addAction(receiver.ACTION_CANCEL) })
val notificationManager = NotificationManagerCompat.from(this)
val action = NotificationCompat.Action.Builder(0, getText(android.R.string.cancel),
PendingIntent.getBroadcast(this, R.id.notification_import_cancel_action.normalizeID(), Intent(receiver.ACTION_CANCEL), PendingIntent.FLAG_UPDATE_CURRENT)
).build()
val notification = NotificationCompat.Builder(this, "import")
.setContentTitle(getText(R.string.import_old_galleries_notification))
.setProgress(0, 0, true)
.addAction(action)
.setSmallIcon(R.drawable.ic_notification)
.setOngoing(true)
DownloadService.cancel(this)
job?.cancel()
job = CoroutineScope(Dispatchers.IO).launch {
val images = listOf(
"jpg",
"png",
"gif",
"webp"
)
val downloadFolders = downloadFolder.listFiles { folder ->
folder.isDirectory && !downloadFolderMap.values.contains(folder.name)
}?.map {
if (it !is FileX)
FileX(this@migrate, it)
else
it
}
if (downloadFolders.isNullOrEmpty()) return@launch
downloadFolders.forEachIndexed { index, folder ->
notification
.setContentText(getString(R.string.import_old_galleries_notification_text, index, downloadFolders.size))
.setProgress(index, downloadFolders.size, false)
notificationManager.notify(R.id.notification_id_import, notification.build())
val metadata = kotlin.runCatching {
folder.getChild(".metadata").readText()?.let { Json.parseToJsonElement(it) }
}.getOrNull()
val galleryID = metadata?.get("reader")?.get("galleryInfo")?.get("id")?.content?.toIntOrNull()
?: folder.name.toIntOrNull() ?: return@forEachIndexed
val galleryBlock: GalleryBlock? = kotlin.runCatching {
metadata?.get("galleryBlock")?.let { Json.decodeFromJsonElement<GalleryBlock>(it) }
}.getOrNull() ?: kotlin.runCatching {
getGalleryBlock(galleryID)
}.getOrNull() ?: kotlin.runCatching {
xyz.quaver.hiyobi.getGalleryBlock(galleryID)
}.getOrNull()
val reader: Reader? = kotlin.runCatching {
metadata?.get("reader")?.let { Json.decodeFromJsonElement<Reader>(it) }
}.getOrNull() ?: kotlin.runCatching {
getReader(galleryID)
}.getOrNull() ?: kotlin.runCatching {
xyz.quaver.hiyobi.getReader(galleryID)
}.getOrNull()
metadata?.get("thumbnail")?.jsonPrimitive?.contentOrNull?.also { thumbnail ->
val file = folder.getChild(".thumbnail").also {
if (it.exists())
it.delete()
it.createNewFile()
}
file.writeBytes(Base64.decode(thumbnail, Base64.DEFAULT))
}
val list: MutableList<String?> =
MutableList(reader!!.galleryInfo.files.size) { null }
folder.list { _, name ->
name?.substringAfterLast('.') in images
}?.sorted()?.take(list.size)?.forEachIndexed { i, name ->
list[i] = name
}
folder.getChild(".metadata").also { if (it.exists()) it.delete(); it.createNewFile() }.writeText(
Json.encodeToString(Metadata(galleryBlock, reader, list))
)
Cache.delete(this@migrate, galleryID)
downloadFolderMap[galleryID] = folder.name
downloadFolder.getChild(".download").let { if (!it.exists()) it.createNewFile(); it.writeText(Json.encodeToString(downloadFolderMap)) }
}
notification
.setContentText(getText(R.string.import_old_galleries_notification_done))
.setProgress(0, 0, false)
.setOngoing(false)
.mActions.clear()
notificationManager.notify(R.id.notification_id_import, notification.build())
kotlin.runCatching {
unregisterReceiver(receiver)
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 B

View File

@@ -73,7 +73,7 @@
<string name="main_menu_sort">ソート</string>
<string name="main_menu_sort_newest">投稿日時順</string>
<string name="main_menu_sort_popular">人気順</string>
<string name="ignore_update">無視</string>
<string name="ignore">無視</string>
<string name="lock_corrupted">ロックファイルが破損されています。Pupilを再再インストールしてください。</string>
<string name="settings_dark_mode_title">ダークモード</string>
<string name="settings_dark_mode_summary">夜にシコりたい方々へ</string>
@@ -151,8 +151,10 @@
<string name="no_camera">この機器には前面カメラが装着されていません</string>
<string name="error">エラー</string>
<string name="settings_cache_limit">キャッシュサイズ制限</string>
<string name="settings_cache_unlimited">制限なし</string>
<string name="unlimited">制限なし</string>
<string name="settings_tag_translation">タグ言語</string>
<string name="settings_tag_translation_message">Githubにて翻訳に参加できます</string>
<string name="settings_concurrent_download">並列ダウンロード</string>
<string name="settings_max_concurrent_download">並列ダウンロード</string>
<string name="unaccessible_download_folder">アンドロイド11以上では外部からのアプリ内部空間接近が不可能です。ダウンロードフォルダを変更しますか</string>
<string name="settings_networking">ネットワーク</string>
</resources>

View File

@@ -71,7 +71,7 @@
<string name="main_menu_sort">정렬</string>
<string name="main_menu_sort_popular">인기순</string>
<string name="main_menu_sort_newest">시간순</string>
<string name="ignore_update">무시</string>
<string name="ignore">무시</string>
<string name="lock_corrupted">잠금 파일이 손상되었습니다! 앱을 재설치 해 주시기 바랍니다.</string>
<string name="settings_dark_mode_title">다크 모드</string>
<string name="settings_dark_mode_summary">딥 다크한 모오드</string>
@@ -151,8 +151,10 @@
<string name="no_camera">이 장치에는 전면 카메라가 없습니다</string>
<string name="error">오류</string>
<string name="settings_cache_limit">캐시 크기 제한</string>
<string name="settings_cache_unlimited">무제한</string>
<string name="unlimited">무제한</string>
<string name="settings_tag_translation">태그 언어</string>
<string name="settings_tag_translation_message">Github에서 번역에 참여하세요</string>
<string name="settings_concurrent_download">병렬 다운로드</string>
<string name="settings_max_concurrent_download">병렬 다운로드</string>
<string name="unaccessible_download_folder">안드로이드 11 이상에서는 외부에서 현재 다운로드 폴더에 접근할 수 없습니다. 변경하시겠습니까?</string>
<string name="settings_networking">네트워크</string>
</resources>

View File

@@ -48,11 +48,6 @@
<item>japanese|日本語</item>
</string-array>
<string-array name="mirrors">
<item>HITOMI|hitomi.la</item>
<item>HIYOBI|hiyobi.me</item>
</string-array>
<string-array name="proxy_type">
<item>Direct</item>
<item>HTTP</item>
@@ -70,7 +65,7 @@
</string-array>
<string-array name="cache_size_text">
<item>@string/settings_cache_unlimited</item>
<item>@string/unlimited</item>
<item>1G</item>
<item>2G</item>
<item>4G</item>
@@ -79,4 +74,24 @@
<item>32G</item>
</string-array>
<string-array name="concurrent_download">
<item>0</item>
<item>1</item>
<item>2</item>
<item>4</item>
<item>8</item>
<item>16</item>
<item>32</item>
</string-array>
<string-array name="concurrent_download_text">
<item>@string/unlimited</item>
<item>1</item>
<item>2</item>
<item>4</item>
<item>8</item>
<item>16</item>
<item>32</item>
</string-array>
</resources>

View File

@@ -28,7 +28,9 @@
<string name="warning">Warning</string>
<string name="error">Error</string>
<string name="ignore_update">Ignore</string>
<string name="ignore">Ignore</string>
<string name="unlimited">Unlimited</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
@@ -47,6 +49,8 @@
<string name="main_no_result">No result</string>
<string name="unaccessible_download_folder">From Android 11 and above, current Download folder cannot be accessed by outside apps. Would you like to change the download folder?</string>
<string name="main_drawer_home">Home</string>
<string name="main_drawer_history">History</string>
<string name="main_drawer_downloads">Downloads</string>
@@ -164,7 +168,6 @@
<string name="settings_download_folder_custom">Custom Location</string>
<string name="settings_download_folder_not_writable">This folder is not writable. Please select another folder.</string>
<string name="settings_cache_limit">Cache Limit</string>
<string name="settings_cache_unlimited">Unlimited</string>
<string name="settings_nomedia_title">Hide image from gallery</string>
<string name="settings_low_quality">Low quality images</string>
<string name="settings_low_quality_summary">Load low quality images to improve load speed and data usage</string>
@@ -174,14 +177,17 @@
<string name="settings_app_lock">App lock</string>
<string name="settings_app_lock_type">App lock type</string>
<!-- SETTINGS/NETWORKING -->
<string name="settings_networking">Networking</string>
<string name="settings_mirror_summary">Load images from mirrors</string>
<string name="settings_proxy_title">Proxy</string>
<string name="settings_max_concurrent_download">Concurrent Download</string>
<!-- SETTINGS/MISCELLANEOUS -->
<string name="settings_miscellaneous_title">Miscellaneous</string>
<string name="settings_tag_translation">Tag Language</string>
<string name="settings_concurrent_download">Concurrent Download</string>
<string name="settings_tag_translation_message">Participate in translation on Github</string>
<string name="settings_mirror_summary">Load images from mirrors</string>
<string name="settings_proxy_title">Proxy</string>
<string name="settings_rtl">Turn pages Right-to-Left</string>
<string name="settings_security_mode_title">Enable security mode</string>
<string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<Preference
app:key="app_version"
@@ -72,6 +73,23 @@
</PreferenceCategory>
<PreferenceCategory
app:title="@string/settings_networking">
<Preference
app:key="proxy"
app:title="@string/settings_proxy_title"/>
<ListPreference
app:key="max_concurrent_download"
android:title="@string/settings_max_concurrent_download"
app:entries="@array/concurrent_download_text"
app:entryValues="@array/concurrent_download"
android:defaultValue="0"
app:useSimpleSummaryProvider="true"/>
</PreferenceCategory>
<PreferenceCategory
app:title="@string/settings_miscellaneous_title">
@@ -80,15 +98,6 @@
app:title="@string/settings_tag_translation"
app:useSimpleSummaryProvider="true"/>
<Preference
app:key="mirrors"
app:title="@string/settings_mirror_title"
app:summary="@string/settings_mirror_summary"/>
<Preference
app:key="proxy"
app:title="@string/settings_proxy_title"/>
<SwitchPreferenceCompat
app:key="rtl"
app:title="@string/settings_rtl"

View File

@@ -3,29 +3,29 @@
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
classpath 'com.android.tools.build:gradle:4.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.google.gms:google-services:4.3.4"
classpath "com.google.gms:google-services:4.3.8"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath "com.google.firebase:firebase-crashlytics-gradle:2.4.1"
classpath "com.google.firebase:perf-plugin:1.3.4"
classpath "com.google.android.gms:oss-licenses-plugin:0.10.2"
classpath "com.google.firebase:firebase-crashlytics-gradle:2.7.0"
classpath "com.google.firebase:perf-plugin:1.4.0"
classpath "com.google.android.gms:oss-licenses-plugin:0.10.4"
}
}
allprojects {
repositories {
maven { url "http://dl.bintray.com/piasy/maven" }
google()
mavenCentral()
jcenter()
maven { url "https://jitpack.io" }
maven { url "https://guardian.github.com/maven/repo-releases" }
maven { url "https://guardian.github.io/maven/repo-releases/" }
}
}

View File

@@ -20,4 +20,4 @@ kotlin.code.style=official
android.enableJetifier=true
android.useAndroidX=true
kotlin_version=1.4.20
kotlin_version=1.5.10

View File

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip

0
gradlew vendored Normal file → Executable file
View File