Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
474d3ad80a | ||
|
|
69f9b099b7 | ||
|
|
7c2bf8fb9d | ||
|
|
fb42b48880 | ||
|
|
bb0988a188 | ||
|
|
9ac7fb490e | ||
|
|
1eb75acb40 | ||
|
|
8410a2fdb3 | ||
|
|
dca6ba457b | ||
|
|
b103188faf | ||
|
|
7e87bb6838 | ||
|
|
bd4b61d7ac |
6
.idea/copyright/Apache.xml
generated
Normal file
6
.idea/copyright/Apache.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="CopyrightManager">
|
||||||
|
<copyright>
|
||||||
|
<option name="notice" value=" Copyright &#36;today.year tom5079 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License." />
|
||||||
|
<option name="myName" value="Apache" />
|
||||||
|
</copyright>
|
||||||
|
</component>
|
||||||
6
.idea/copyright/GPL.xml
generated
Normal file
6
.idea/copyright/GPL.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="CopyrightManager">
|
||||||
|
<copyright>
|
||||||
|
<option name="notice" value=" Pupil, Hitomi.la viewer for Android Copyright (C) &#36;today.year 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/>." />
|
||||||
|
<option name="myName" value="GPL" />
|
||||||
|
</copyright>
|
||||||
|
</component>
|
||||||
8
.idea/copyright/profiles_settings.xml
generated
Normal file
8
.idea/copyright/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="CopyrightManager">
|
||||||
|
<settings>
|
||||||
|
<module2copyright>
|
||||||
|
<element module="Pupil" copyright="GPL" />
|
||||||
|
<element module="libpupil" copyright="Apache" />
|
||||||
|
</module2copyright>
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -13,7 +13,6 @@
|
|||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveModulePerSourceSet" value="false" />
|
<option name="resolveModulePerSourceSet" value="false" />
|
||||||
<option name="useQualifiedModuleNames" value="true" />
|
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
10
.idea/inspectionProfiles/Project_Default.xml
generated
10
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,10 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
|
||||||
<option name="processCode" value="true" />
|
|
||||||
<option name="processLiterals" value="true" />
|
|
||||||
<option name="processComments" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
7
.idea/misc.xml
generated
7
.idea/misc.xml
generated
@@ -1,6 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
3
.idea/scopes/Pupil.xml
generated
Normal file
3
.idea/scopes/Pupil.xml
generated
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<component name="DependencyValidationManager">
|
||||||
|
<scope name="Pupil" pattern="file[app]:*/" />
|
||||||
|
</component>
|
||||||
3
.idea/scopes/libpupil.xml
generated
Normal file
3
.idea/scopes/libpupil.xml
generated
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<component name="DependencyValidationManager">
|
||||||
|
<scope name="libpupil" pattern="file[libpupil]:*/" />
|
||||||
|
</component>
|
||||||
2
.idea/vcs.xml
generated
2
.idea/vcs.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
License is specified in following module separately
|
License is specified in following module separately
|
||||||
|
|
||||||
app/
|
app/
|
||||||
libpupil/
|
libpupil/
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'kotlinx-serialization'
|
apply plugin: 'kotlinx-serialization'
|
||||||
apply plugin: 'com.google.gms.google-services'
|
apply plugin: 'com.google.gms.google-services'
|
||||||
@@ -12,8 +13,8 @@ android {
|
|||||||
applicationId "xyz.quaver.pupil"
|
applicationId "xyz.quaver.pupil"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 20
|
versionCode 21
|
||||||
versionName "2.11.1"
|
versionName "3.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
@@ -23,6 +24,9 @@ android {
|
|||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
|
buildTypes.each {
|
||||||
|
it.buildConfigField('boolean', 'PRERELEASE', 'false')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
|
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
|
||||||
@@ -52,8 +56,13 @@ dependencies {
|
|||||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
||||||
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
||||||
implementation 'com.github.clans:fab:1.6.4'
|
implementation 'com.github.clans:fab:1.6.4'
|
||||||
|
implementation 'com.github.bumptech.glide:glide:4.9.0'
|
||||||
|
implementation ("com.github.bumptech.glide:recyclerview-integration:4.9.0") {
|
||||||
|
transitive = false
|
||||||
|
}
|
||||||
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
||||||
implementation "ru.noties.markwon:core:${markwonVersion}"
|
implementation "ru.noties.markwon:core:${markwonVersion}"
|
||||||
|
kapt 'com.github.bumptech.glide:compiler:4.9.0'
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":20,"versionName":"2.11.1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
|
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":21,"versionName":"2.12","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
|
||||||
@@ -1,3 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("UNUSED_VARIABLE")
|
||||||
|
|
||||||
package xyz.quaver.pupil
|
package xyz.quaver.pupil
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -36,7 +56,7 @@ class ExampleInstrumentedTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun checkCacheDir() {
|
fun checkCacheDir() {
|
||||||
val activityTestRule = ActivityTestRule<LockActivity>(LockActivity::class.java)
|
val activityTestRule = ActivityTestRule(LockActivity::class.java)
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
|
||||||
activityTestRule.launchActivity(Intent())
|
activityTestRule.launchActivity(Intent())
|
||||||
@@ -50,7 +70,7 @@ class ExampleInstrumentedTest {
|
|||||||
|
|
||||||
val data: ByteArray
|
val data: ByteArray
|
||||||
|
|
||||||
with(URL(reader[0].url).openConnection() as HttpsURLConnection) {
|
with(URL(reader.readerItems[0].url).openConnection() as HttpsURLConnection) {
|
||||||
setRequestProperty("User-Agent", user_agent)
|
setRequestProperty("User-Agent", user_agent)
|
||||||
setRequestProperty("Cookie", cookie)
|
setRequestProperty("Cookie", cookie)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
package="xyz.quaver.pupil">
|
package="xyz.quaver.pupil">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="28"/>
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Pupil"
|
android:name=".Pupil"
|
||||||
@@ -14,6 +16,7 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
<activity android:name=".ui.LockActivity"/>
|
<activity android:name=".ui.LockActivity"/>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.ReaderActivity"
|
android:name=".ui.ReaderActivity"
|
||||||
|
|||||||
@@ -1,9 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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
|
package xyz.quaver.pupil
|
||||||
|
|
||||||
|
import android.app.DownloadManager
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.Log
|
|
||||||
import android.util.SparseBooleanArray
|
import android.util.SparseBooleanArray
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -15,6 +32,8 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -23,9 +42,8 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
import kotlinx.serialization.list
|
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.ReaderItem
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
@@ -47,8 +65,8 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
|
|
||||||
private lateinit var favorites: Histories
|
private lateinit var favorites: Histories
|
||||||
|
|
||||||
inner class GalleryViewHolder(private val view: CardView) : RecyclerView.ViewHolder(view) {
|
inner class GalleryViewHolder(val view: CardView) : RecyclerView.ViewHolder(view) {
|
||||||
fun bind(item: Pair<GalleryBlock, Deferred<String>>) {
|
fun bind(holder: GalleryViewHolder, item: Pair<GalleryBlock, Deferred<String>>) {
|
||||||
with(view) {
|
with(view) {
|
||||||
val resources = context.resources
|
val resources = context.resources
|
||||||
val languages = resources.getStringArray(R.array.languages).map {
|
val languages = resources.getStringArray(R.array.languages).map {
|
||||||
@@ -62,17 +80,15 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
val artists = galleryBlock.artists
|
val artists = galleryBlock.artists
|
||||||
val series = galleryBlock.series
|
val series = galleryBlock.series
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val cache = thumbnail.await()
|
val cache = thumbnail.await()
|
||||||
|
|
||||||
if (!File(cache).exists())
|
Glide.with(holder.view)
|
||||||
return@launch
|
.load(cache)
|
||||||
|
.skipMemoryCache(true)
|
||||||
val bitmap = BitmapFactory.decodeFile(thumbnail.await())
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.error(R.drawable.image_broken_variant)
|
||||||
launch(Dispatchers.Main) {
|
.into(galleryblock_thumbnail)
|
||||||
galleryblock_thumbnail.setImageBitmap(bitmap)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check cache
|
//Check cache
|
||||||
@@ -81,10 +97,10 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
|
|
||||||
if (readerCache.invoke().exists()) {
|
if (readerCache.invoke().exists()) {
|
||||||
val reader = Json(JsonConfiguration.Stable)
|
val reader = Json(JsonConfiguration.Stable)
|
||||||
.parse(ReaderItem.serializer().list, readerCache.invoke().readText())
|
.parse(Reader.serializer(), readerCache.invoke().readText())
|
||||||
|
|
||||||
with(galleryblock_progressbar) {
|
with(galleryblock_progressbar) {
|
||||||
max = reader.size
|
max = reader.readerItems.size
|
||||||
progress = imageCache.invoke().list()?.size ?: 0
|
progress = imageCache.invoke().list()?.size ?: 0
|
||||||
|
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
@@ -108,8 +124,8 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
} else {
|
} else {
|
||||||
if (visibility == View.GONE) {
|
if (visibility == View.GONE) {
|
||||||
val reader = Json(JsonConfiguration.Stable)
|
val reader = Json(JsonConfiguration.Stable)
|
||||||
.parse(ReaderItem.serializer().list, readerCache.invoke().readText())
|
.parse(Reader.serializer(), readerCache.invoke().readText())
|
||||||
max = reader.size
|
max = reader.readerItems.size
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +350,7 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
if (holder is GalleryViewHolder)
|
if (holder is GalleryViewHolder)
|
||||||
holder.bind(galleries[position-(if (showPrev) 1 else 0)])
|
holder.bind(holder, galleries[position-(if (showPrev) 1 else 0)])
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||||
|
|||||||
@@ -1,13 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
|
||||||
class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||||
@@ -25,47 +43,19 @@ class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<Rea
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val progressDrawable = CircularProgressDrawable(holder.view.context).apply {
|
||||||
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
|
strokeWidth = 10f
|
||||||
// Raw height and width of image
|
centerRadius = 100f
|
||||||
val (height: Int, width: Int) = options.run { outHeight to outWidth }
|
start()
|
||||||
var inSampleSize = 1
|
|
||||||
|
|
||||||
if (height > reqHeight || width > reqWidth) {
|
|
||||||
|
|
||||||
val halfHeight: Int = height / 2
|
|
||||||
val halfWidth: Int = width / 2
|
|
||||||
|
|
||||||
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
|
||||||
// height and width larger than the requested height and width.
|
|
||||||
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
|
|
||||||
inSampleSize *= 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return inSampleSize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
with(holder.view as ImageView) {
|
Glide.with(holder.view)
|
||||||
val options = BitmapFactory.Options()
|
.load(images[position])
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
options.inJustDecodeBounds = true
|
.skipMemoryCache(true)
|
||||||
BitmapFactory.decodeFile(images[position], options)
|
.placeholder(progressDrawable)
|
||||||
|
.error(R.drawable.image_broken_variant)
|
||||||
val (reqWidth, reqHeight) = context.resources.displayMetrics.let {
|
.into(holder.view as ImageView)
|
||||||
Pair(it.widthPixels, it.heightPixels)
|
|
||||||
}
|
|
||||||
|
|
||||||
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
|
|
||||||
|
|
||||||
options.inPreferredConfig = Bitmap.Config.RGB_565
|
|
||||||
|
|
||||||
options.inJustDecodeBounds = false
|
|
||||||
|
|
||||||
val image = BitmapFactory.decodeFile(images[position], options)
|
|
||||||
|
|
||||||
setImageBitmap(image)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = images.size
|
override fun getItemCount() = images.size
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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.types
|
package xyz.quaver.pupil.types
|
||||||
|
|
||||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
@@ -5,7 +23,7 @@ import kotlinx.android.parcel.Parcelize
|
|||||||
import xyz.quaver.hitomi.Suggestion
|
import xyz.quaver.hitomi.Suggestion
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class TagSuggestion constructor(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion {
|
data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion {
|
||||||
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
|
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
|
||||||
|
|
||||||
override fun getBody(): String {
|
override fun getBody(): String {
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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.types
|
package xyz.quaver.pupil.types
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -90,8 +108,8 @@ class Tags(tag: List<Tag?>?) : ArrayList<Tag>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeByArea(area: String) {
|
fun removeByArea(area: String, isNegative: Boolean? = null) {
|
||||||
filter { it.area == area }.forEach {
|
filter { it.area == area && (if(isNegative == null) true else (it.isNegative == isNegative)) }.forEach {
|
||||||
remove(it)
|
remove(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
|||||||
@@ -1,12 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.DownloadManager
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
import android.text.*
|
import android.text.*
|
||||||
import android.text.style.AlignmentSpan
|
import android.text.style.AlignmentSpan
|
||||||
import android.view.*
|
import android.view.*
|
||||||
@@ -19,6 +42,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
@@ -41,7 +65,6 @@ import kotlinx.serialization.list
|
|||||||
import kotlinx.serialization.stringify
|
import kotlinx.serialization.stringify
|
||||||
import ru.noties.markwon.Markwon
|
import ru.noties.markwon.Markwon
|
||||||
import xyz.quaver.hitomi.*
|
import xyz.quaver.hitomi.*
|
||||||
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.adapters.GalleryBlockAdapter
|
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||||
@@ -55,6 +78,8 @@ import java.net.URL
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.ceil
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@@ -66,17 +91,25 @@ class MainActivity : AppCompatActivity() {
|
|||||||
DOWNLOAD,
|
DOWNLOAD,
|
||||||
FAVORITE
|
FAVORITE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class SortMode {
|
||||||
|
NEWEST,
|
||||||
|
POPULAR
|
||||||
|
}
|
||||||
|
|
||||||
private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
|
private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
|
||||||
|
|
||||||
private var query = ""
|
private var query = ""
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
findViewById<SearchInputView>(R.id.search_bar_text)
|
with(findViewById<SearchInputView>(R.id.search_bar_text)) {
|
||||||
.setText(query, TextView.BufferType.EDITABLE)
|
if (text.toString() != value)
|
||||||
|
setText(query, TextView.BufferType.EDITABLE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var mode = Mode.SEARCH
|
private var mode = Mode.SEARCH
|
||||||
|
private var sortMode = SortMode.NEWEST
|
||||||
|
|
||||||
private val REQUEST_SETTINGS = 45162
|
private val REQUEST_SETTINGS = 45162
|
||||||
private val REQUEST_LOCK = 561
|
private val REQUEST_LOCK = 561
|
||||||
@@ -132,7 +165,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
else -> super.onBackPressed()
|
else -> super.onBackPressed()
|
||||||
@@ -155,7 +188,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val perPage = preference.getString("per_page", "25")!!.toInt()
|
val perPage = preference.getString("per_page", "25")!!.toInt()
|
||||||
val maxPage = Math.ceil(totalItems / perPage.toDouble()).roundToInt()
|
val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
|
||||||
|
|
||||||
return when(keyCode) {
|
return when(keyCode) {
|
||||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
@@ -165,7 +198,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,7 +212,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +230,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,14 +287,36 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
val update =
|
val update =
|
||||||
checkUpdate(getString(R.string.release_url), BuildConfig.VERSION_NAME) ?: return@launch
|
checkUpdate(getString(R.string.release_url)) ?: return@launch
|
||||||
|
|
||||||
|
val (url, fileName) = getApkUrl(update) ?: return@launch
|
||||||
|
fileName ?: return@launch
|
||||||
|
|
||||||
val dialog = AlertDialog.Builder(this@MainActivity).apply {
|
val dialog = AlertDialog.Builder(this@MainActivity).apply {
|
||||||
setTitle(R.string.update_title)
|
setTitle(R.string.update_title)
|
||||||
val msg = extractReleaseNote(update, Locale.getDefault().language)
|
val msg = extractReleaseNote(update, Locale.getDefault().language)
|
||||||
setMessage(Markwon.create(context).toMarkdown(msg))
|
setMessage(Markwon.create(context).toMarkdown(msg))
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.update))))
|
val request = DownloadManager.Request(Uri.parse(url)).apply {
|
||||||
|
setDescription(getString(R.string.update_notification_description))
|
||||||
|
setTitle(getString(R.string.app_name))
|
||||||
|
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
val id = manager.enqueue(request)
|
||||||
|
|
||||||
|
registerReceiver(object: BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
val install = Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
setDataAndType(manager.getUriForDownloadedFile(id), manager.getMimeTypeForDownloadedFile(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(install)
|
||||||
|
unregisterReceiver(this)
|
||||||
|
}
|
||||||
|
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
|
||||||
}
|
}
|
||||||
setNegativeButton(android.R.string.no) { _, _ ->}
|
setNegativeButton(android.R.string.no) { _, _ ->}
|
||||||
}
|
}
|
||||||
@@ -284,6 +339,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
main_searchview.translationY = p1.toFloat()
|
main_searchview.translationY = p1.toFloat()
|
||||||
main_recyclerview.scrollBy(0, prevP1 - p1)
|
main_recyclerview.scrollBy(0, prevP1 - p1)
|
||||||
|
|
||||||
|
with(main_fab) {
|
||||||
|
if (prevP1 > p1)
|
||||||
|
hideMenuButton(true)
|
||||||
|
else if (prevP1 < p1)
|
||||||
|
showMenuButton(true)
|
||||||
|
}
|
||||||
|
|
||||||
prevP1 = p1
|
prevP1 = p1
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -300,7 +362,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
currentPage = 0
|
currentPage = 0
|
||||||
query = ""
|
query = ""
|
||||||
mode = Mode.SEARCH
|
mode = Mode.SEARCH
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
R.id.main_drawer_history -> {
|
R.id.main_drawer_history -> {
|
||||||
@@ -309,7 +371,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
currentPage = 0
|
currentPage = 0
|
||||||
query = ""
|
query = ""
|
||||||
mode = Mode.HISTORY
|
mode = Mode.HISTORY
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
R.id.main_drawer_downloads -> {
|
R.id.main_drawer_downloads -> {
|
||||||
@@ -318,7 +380,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
currentPage = 0
|
currentPage = 0
|
||||||
query = ""
|
query = ""
|
||||||
mode = Mode.DOWNLOAD
|
mode = Mode.DOWNLOAD
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
R.id.main_drawer_favorite -> {
|
R.id.main_drawer_favorite -> {
|
||||||
@@ -327,7 +389,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
currentPage = 0
|
currentPage = 0
|
||||||
query = ""
|
query = ""
|
||||||
mode = Mode.FAVORITE
|
mode = Mode.FAVORITE
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
R.id.main_drawer_help -> {
|
R.id.main_drawer_help -> {
|
||||||
@@ -351,9 +413,67 @@ class MainActivity : AppCompatActivity() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(main_fab_jump) {
|
||||||
|
setImageResource(R.drawable.ic_jump)
|
||||||
|
setOnClickListener {
|
||||||
|
val preference = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
val perPage = preference.getString("per_page", "25")!!.toInt()
|
||||||
|
val editText = EditText(context)
|
||||||
|
|
||||||
|
AlertDialog.Builder(context).apply {
|
||||||
|
setView(editText)
|
||||||
|
setTitle(R.string.main_jump_title)
|
||||||
|
setMessage(getString(
|
||||||
|
R.string.main_jump_message,
|
||||||
|
currentPage+1,
|
||||||
|
ceil(totalItems / perPage.toDouble()).roundToInt()
|
||||||
|
))
|
||||||
|
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with(main_fab_id) {
|
||||||
|
setImageResource(R.drawable.numeric)
|
||||||
|
setOnClickListener {
|
||||||
|
val editText = EditText(context)
|
||||||
|
|
||||||
|
AlertDialog.Builder(context).apply {
|
||||||
|
setView(editText)
|
||||||
|
setTitle(R.string.main_open_gallery_by_id)
|
||||||
|
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
try {
|
||||||
|
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
||||||
|
val gallery =
|
||||||
|
getGalleryBlock(editText.text.toString().toInt()) ?: throw Exception()
|
||||||
|
intent.putExtra("galleryID", gallery.id)
|
||||||
|
|
||||||
|
startActivity(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Snackbar.make(main_layout,
|
||||||
|
R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setupSearchBar()
|
setupSearchBar()
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +487,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,9 +500,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
||||||
val gallery = galleries[position].first
|
val gallery = galleries[position].first
|
||||||
intent.putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery))
|
intent.putExtra("galleryID", gallery.id)
|
||||||
|
|
||||||
//TODO: Maybe sprinke some transitions will be nice :D
|
//TODO: Maybe sprinkling some transitions will be nice :D
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
|
|
||||||
histories.add(gallery.id)
|
histories.add(gallery.id)
|
||||||
@@ -391,7 +511,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
if (v !is CardView)
|
if (v !is CardView)
|
||||||
return@setOnItemLongClickListener true
|
return@setOnItemLongClickListener true
|
||||||
|
|
||||||
val galleryBlock = galleries[position].first
|
val gallery = galleries[position].first
|
||||||
val view = LayoutInflater.from(this@MainActivity)
|
val view = LayoutInflater.from(this@MainActivity)
|
||||||
.inflate(R.layout.dialog_galleryblock, recyclerView, false)
|
.inflate(R.layout.dialog_galleryblock, recyclerView, false)
|
||||||
|
|
||||||
@@ -400,15 +520,15 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}.create()
|
}.create()
|
||||||
|
|
||||||
with(view.main_dialog_download) {
|
with(view.main_dialog_download) {
|
||||||
text = when(GalleryDownloader.get(galleryBlock.id)) {
|
text = when(GalleryDownloader.get(gallery.id)) {
|
||||||
null -> getString(R.string.reader_fab_download)
|
null -> getString(R.string.reader_fab_download)
|
||||||
else -> getString(R.string.reader_fab_download_cancel)
|
else -> getString(R.string.reader_fab_download_cancel)
|
||||||
}
|
}
|
||||||
isEnabled = !(adapter as GalleryBlockAdapter).completeFlag.get(galleryBlock.id, false)
|
isEnabled = !(adapter as GalleryBlockAdapter).completeFlag.get(gallery.id, false)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
val downloader = GalleryDownloader.get(galleryBlock.id)
|
val downloader = GalleryDownloader.get(gallery.id)
|
||||||
if (downloader == null)
|
if (downloader == null)
|
||||||
GalleryDownloader(context, galleryBlock, true).start()
|
GalleryDownloader(context, gallery.id, true).start()
|
||||||
else {
|
else {
|
||||||
downloader.cancel()
|
downloader.cancel()
|
||||||
downloader.clearNotification()
|
downloader.clearNotification()
|
||||||
@@ -420,27 +540,27 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
view.main_dialog_delete.setOnClickListener {
|
view.main_dialog_delete.setOnClickListener {
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
with(GalleryDownloader[galleryBlock.id]) {
|
with(GalleryDownloader[gallery.id]) {
|
||||||
this?.cancelAndJoin()
|
this?.cancelAndJoin()
|
||||||
this?.clearNotification()
|
this?.clearNotification()
|
||||||
}
|
}
|
||||||
val cache = File(cacheDir, "imageCache/${galleryBlock.id}")
|
val cache = File(cacheDir, "imageCache/${gallery.id}")
|
||||||
val data = getCachedGallery(context, galleryBlock.id)
|
val data = getCachedGallery(context, gallery.id)
|
||||||
cache.deleteRecursively()
|
cache.deleteRecursively()
|
||||||
data.deleteRecursively()
|
data.deleteRecursively()
|
||||||
|
|
||||||
downloads.remove(galleryBlock.id)
|
downloads.remove(gallery.id)
|
||||||
|
|
||||||
if (mode == Mode.DOWNLOAD) {
|
if (mode == Mode.DOWNLOAD) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(adapter as GalleryBlockAdapter).completeFlag.put(galleryBlock.id, false)
|
(adapter as GalleryBlockAdapter).completeFlag.put(gallery.id, false)
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
@@ -503,7 +623,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
fetchGalleries(query)
|
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,7 +702,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
//BOTTOM
|
//BOTTOM
|
||||||
|
|
||||||
//Scrolling DOWN
|
//Scrolling DOWN
|
||||||
if (dist < 0 && currentPage != Math.ceil(totalItems.toDouble()/perPage).roundToInt()-1) {
|
if (dist < 0 && currentPage != ceil(totalItems.toDouble()/perPage).roundToInt()-1) {
|
||||||
with(main_recyclerview.adapter as GalleryBlockAdapter) {
|
with(main_recyclerview.adapter as GalleryBlockAdapter) {
|
||||||
if(!showNext) {
|
if(!showNext) {
|
||||||
showNext = true
|
showNext = true
|
||||||
@@ -595,7 +714,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
getChildAt(childCount-1)
|
getChildAt(childCount-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val absDist = Math.abs(dist)
|
val absDist = abs(dist)
|
||||||
|
|
||||||
if (next is LinearLayout) {
|
if (next is LinearLayout) {
|
||||||
val icon = next.findViewById<ImageView>(R.id.icon_next)
|
val icon = next.findViewById<ImageView>(R.id.icon_next)
|
||||||
@@ -690,72 +809,52 @@ class MainActivity : AppCompatActivity() {
|
|||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
when(it.itemId) {
|
when(it.itemId) {
|
||||||
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS)
|
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS)
|
||||||
R.id.main_menu_jump -> {
|
R.id.main_menu_sort_newest -> {
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(context)
|
sortMode = SortMode.NEWEST
|
||||||
val perPage = preference.getString("per_page", "25")!!.toInt()
|
it.isChecked = true
|
||||||
val editText = EditText(context)
|
|
||||||
|
|
||||||
AlertDialog.Builder(context).apply {
|
runOnUiThread {
|
||||||
setView(editText)
|
currentPage = 0
|
||||||
setTitle(R.string.main_jump_title)
|
|
||||||
setMessage(getString(
|
|
||||||
R.string.main_jump_message,
|
|
||||||
currentPage+1,
|
|
||||||
Math.ceil(totalItems / perPage.toDouble()).roundToInt()
|
|
||||||
))
|
|
||||||
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
cancelFetch()
|
||||||
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1
|
clearGalleries()
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
runOnUiThread {
|
loadBlocks()
|
||||||
cancelFetch()
|
}
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
}
|
}
|
||||||
R.id.main_menu_id -> {
|
R.id.main_menu_sort_popular -> {
|
||||||
val editText = EditText(context)
|
sortMode = SortMode.POPULAR
|
||||||
|
it.isChecked = true
|
||||||
|
|
||||||
AlertDialog.Builder(context).apply {
|
runOnUiThread {
|
||||||
setView(editText)
|
currentPage = 0
|
||||||
setTitle(R.string.main_open_gallery_by_id)
|
|
||||||
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
cancelFetch()
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
clearGalleries()
|
||||||
try {
|
fetchGalleries(query, sortMode)
|
||||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
loadBlocks()
|
||||||
val gallery =
|
}
|
||||||
getGalleryBlock(editText.text.toString().toInt()) ?: throw Exception()
|
|
||||||
intent.putExtra(
|
|
||||||
"galleryblock",
|
|
||||||
Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery)
|
|
||||||
)
|
|
||||||
|
|
||||||
startActivity(intent)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Snackbar.make(main_layout,
|
|
||||||
R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnQueryChangeListener { _, query ->
|
setOnQueryChangeListener { _, query ->
|
||||||
clearSuggestions()
|
this@MainActivity.query = query
|
||||||
|
|
||||||
if (query.isEmpty() or query.endsWith(' '))
|
|
||||||
return@setOnQueryChangeListener
|
|
||||||
|
|
||||||
val currentQuery = query.split(" ").last().replace('_', ' ')
|
|
||||||
|
|
||||||
suggestionJob?.cancel()
|
suggestionJob?.cancel()
|
||||||
|
|
||||||
|
clearSuggestions()
|
||||||
|
|
||||||
|
if (query.isEmpty() or query.endsWith(' ')) {
|
||||||
|
swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
|
||||||
|
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
||||||
|
})
|
||||||
|
|
||||||
|
return@setOnQueryChangeListener
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentQuery = query.split(" ").last().replace('_', ' ')
|
||||||
|
|
||||||
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val suggestions = ArrayList(getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) })
|
val suggestions = ArrayList(getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) })
|
||||||
|
|
||||||
@@ -774,13 +873,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setOnBindSuggestionCallback { suggestionView, leftIcon, textView, item, _ ->
|
setOnBindSuggestionCallback { suggestionView, leftIcon, textView, item, _ ->
|
||||||
val suggestion = item as TagSuggestion
|
item as TagSuggestion
|
||||||
val tag = "${suggestion.n}:${suggestion.s.replace(Regex("\\s"), "_")}"
|
|
||||||
|
val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}"
|
||||||
|
|
||||||
leftIcon.setImageDrawable(
|
leftIcon.setImageDrawable(
|
||||||
ResourcesCompat.getDrawable(
|
ResourcesCompat.getDrawable(
|
||||||
resources,
|
resources,
|
||||||
when(suggestion.n) {
|
when(item.n) {
|
||||||
"female" -> R.drawable.ic_gender_female
|
"female" -> R.drawable.ic_gender_female
|
||||||
"male" -> R.drawable.ic_gender_male
|
"male" -> R.drawable.ic_gender_male
|
||||||
"language" -> R.drawable.ic_translate
|
"language" -> R.drawable.ic_translate
|
||||||
@@ -800,7 +900,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
else
|
else
|
||||||
setImageResource(R.drawable.ic_star_empty)
|
setImageResource(R.drawable.ic_star_empty)
|
||||||
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
rotation = 0f
|
rotation = 0f
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
|
|
||||||
@@ -827,13 +926,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (suggestion.t == -1) {
|
if (item.t == -1) {
|
||||||
textView.text = suggestion.s
|
textView.text = item.s
|
||||||
} else {
|
} else {
|
||||||
val text = "${suggestion.s}\n ${suggestion.t}"
|
val text = "${item.s}\n ${item.t}"
|
||||||
|
|
||||||
val len = text.length
|
val len = text.length
|
||||||
val left = suggestion.s.length
|
val left = item.s.length
|
||||||
|
|
||||||
textView.text = SpannableString(text).apply {
|
textView.text = SpannableString(text).apply {
|
||||||
val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE)
|
val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE)
|
||||||
@@ -846,14 +945,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
setOnSearchListener(object : FloatingSearchView.OnSearchListener {
|
setOnSearchListener(object : FloatingSearchView.OnSearchListener {
|
||||||
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
|
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
|
||||||
val suggestion = searchSuggestion as TagSuggestion
|
if (searchSuggestion !is TagSuggestion)
|
||||||
|
return
|
||||||
|
|
||||||
with(searchInputView.text) {
|
with(searchInputView.text) {
|
||||||
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
|
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
|
||||||
append("${suggestion.n}:${suggestion.s.replace(Regex("\\s"), "_")} ")
|
append("${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")} ")
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSuggestions()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSearchAction(currentQuery: String?) {
|
override fun onSearchAction(currentQuery: String?) {
|
||||||
@@ -863,7 +961,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
|
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
|
||||||
override fun onFocus() {
|
override fun onFocus() {
|
||||||
if (searchInputView.text.isEmpty())
|
if (query.isEmpty() or query.endsWith(' '))
|
||||||
swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
|
swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
|
||||||
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
||||||
})
|
})
|
||||||
@@ -872,18 +970,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onFocusCleared() {
|
override fun onFocusCleared() {
|
||||||
suggestionJob?.cancel()
|
suggestionJob?.cancel()
|
||||||
|
|
||||||
val query = searchInputView.text.toString()
|
runOnUiThread {
|
||||||
|
cancelFetch()
|
||||||
if (query != this@MainActivity.query) {
|
clearGalleries()
|
||||||
this@MainActivity.query = query
|
currentPage = 0
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
runOnUiThread {
|
loadBlocks()
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
fetchGalleries(query)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -912,9 +1004,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
main_progressbar.show()
|
main_progressbar.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchGalleries(query: String) {
|
private fun fetchGalleries(query: String, sortMode: SortMode) {
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
|
|
||||||
val defaultQuery = preference.getString("default_query", "")!!
|
val defaultQuery = preference.getString("default_query", "")!!
|
||||||
|
|
||||||
galleryIDs = null
|
galleryIDs = null
|
||||||
@@ -927,12 +1018,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
Mode.SEARCH -> {
|
Mode.SEARCH -> {
|
||||||
when {
|
when {
|
||||||
query.isEmpty() and defaultQuery.isEmpty() -> {
|
query.isEmpty() and defaultQuery.isEmpty() -> {
|
||||||
fetchNozomi(start = currentPage*perPage, count = perPage).let {
|
when(sortMode) {
|
||||||
totalItems = it.second
|
SortMode.POPULAR -> getGalleryIDsFromNozomi(null, "popular", "all")
|
||||||
it.first
|
else -> getGalleryIDsFromNozomi(null, "index", "all")
|
||||||
|
}.apply {
|
||||||
|
totalItems = size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> doSearch("$defaultQuery $query").apply {
|
else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).apply {
|
||||||
totalItems = size
|
totalItems = size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -985,7 +1078,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private fun loadBlocks() {
|
private fun loadBlocks() {
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
|
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
|
||||||
val defaultQuery = preference.getString("default_query", "")!!
|
|
||||||
|
|
||||||
loadingJob = CoroutineScope(Dispatchers.IO).launch {
|
loadingJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val galleryIDs = galleryIDs?.await()
|
val galleryIDs = galleryIDs?.await()
|
||||||
@@ -999,12 +1091,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
when {
|
galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size)).chunked(5).let { chunks ->
|
||||||
query.isEmpty() and defaultQuery.isEmpty() and (mode == Mode.SEARCH) ->
|
|
||||||
galleryIDs
|
|
||||||
else ->
|
|
||||||
galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size))
|
|
||||||
}.chunked(5).let { chunks ->
|
|
||||||
for (chunk in chunks)
|
for (chunk in chunks)
|
||||||
chunk.map { galleryID ->
|
chunk.map { galleryID ->
|
||||||
async {
|
async {
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -21,13 +39,7 @@ import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.io.IOException
|
|
||||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
|
||||||
import xyz.quaver.hitomi.getGalleryBlock
|
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.adapters.ReaderAdapter
|
import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||||
@@ -37,8 +49,8 @@ import xyz.quaver.pupil.util.ItemClickSupport
|
|||||||
|
|
||||||
class ReaderActivity : AppCompatActivity() {
|
class ReaderActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private var galleryID = 0
|
||||||
private val images = ArrayList<String>()
|
private val images = ArrayList<String>()
|
||||||
private lateinit var galleryBlock: GalleryBlock
|
|
||||||
private var gallerySize = 0
|
private var gallerySize = 0
|
||||||
private var currentPage = 0
|
private var currentPage = 0
|
||||||
|
|
||||||
@@ -66,6 +78,9 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
title = getString(R.string.reader_loading)
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||||
|
|
||||||
favorites = (application as Pupil).favorites
|
favorites = (application as Pupil).favorites
|
||||||
|
|
||||||
window.setFlags(
|
window.setFlags(
|
||||||
@@ -76,16 +91,13 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
handleIntent(intent)
|
handleIntent(intent)
|
||||||
|
|
||||||
Crashlytics.setInt("GalleryID", galleryBlock.id)
|
Crashlytics.setInt("GalleryID", galleryID)
|
||||||
|
|
||||||
if (!::galleryBlock.isInitialized) {
|
if (galleryID == 0) {
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
supportActionBar?.title = galleryBlock.title
|
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
|
||||||
|
|
||||||
initDownloader()
|
initDownloader()
|
||||||
|
|
||||||
initView()
|
initView()
|
||||||
@@ -106,25 +118,16 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
if (uri != null && lastPathSegment != null) {
|
if (uri != null && lastPathSegment != null) {
|
||||||
val nonNumber = Regex("[^-?0-9]+")
|
val nonNumber = Regex("[^-?0-9]+")
|
||||||
|
|
||||||
val galleryID = when (uri.host) {
|
galleryID = when (uri.host) {
|
||||||
"hitomi.la" -> lastPathSegment.replace(nonNumber, "").toInt()
|
"hitomi.la" -> lastPathSegment.replace(nonNumber, "").toInt()
|
||||||
"히요비.asia" -> lastPathSegment.toInt()
|
"히요비.asia" -> lastPathSegment.toInt()
|
||||||
"xn--9w3b15m8vo.asia" -> lastPathSegment.toInt()
|
"xn--9w3b15m8vo.asia" -> lastPathSegment.toInt()
|
||||||
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
||||||
else -> return
|
else -> return
|
||||||
}
|
}
|
||||||
|
|
||||||
runBlocking {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
galleryBlock = getGalleryBlock(galleryID) ?: return@launch
|
|
||||||
}.join()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
galleryBlock = Json(JsonConfiguration.Stable).parse(
|
galleryID = intent.getIntExtra("galleryID", 0)
|
||||||
GalleryBlock.serializer(),
|
|
||||||
intent.getStringExtra("galleryblock")!!
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +151,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
with(menu?.findItem(R.id.reader_menu_favorite)) {
|
with(menu?.findItem(R.id.reader_menu_favorite)) {
|
||||||
this ?: return@with
|
this ?: return@with
|
||||||
|
|
||||||
if (favorites.contains(galleryBlock.id))
|
if (favorites.contains(galleryID))
|
||||||
(icon as Animatable).start()
|
(icon as Animatable).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +179,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
R.id.reader_menu_favorite -> {
|
R.id.reader_menu_favorite -> {
|
||||||
val id = galleryBlock.id
|
val id = galleryID
|
||||||
val favorite = menu?.findItem(R.id.reader_menu_favorite) ?: return true
|
val favorite = menu?.findItem(R.id.reader_menu_favorite) ?: return true
|
||||||
|
|
||||||
if (favorites.contains(id)) {
|
if (favorites.contains(id)) {
|
||||||
@@ -215,32 +218,26 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initDownloader() {
|
private fun initDownloader() {
|
||||||
var d: GalleryDownloader? = GalleryDownloader.get(galleryBlock.id)
|
var d: GalleryDownloader? = GalleryDownloader.get(galleryID)
|
||||||
|
|
||||||
if (d == null) {
|
if (d == null)
|
||||||
try {
|
d = GalleryDownloader(this, galleryID)
|
||||||
d = GalleryDownloader(this, galleryBlock)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
|
|
||||||
finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
downloader = d.apply {
|
downloader = d.apply {
|
||||||
onReaderLoadedHandler = {
|
onReaderLoadedHandler = {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
title = it.title
|
||||||
with(reader_download_progressbar) {
|
with(reader_download_progressbar) {
|
||||||
max = it.size
|
max = it.readerItems.size
|
||||||
progress = 0
|
progress = 0
|
||||||
}
|
}
|
||||||
with(reader_progressbar) {
|
with(reader_progressbar) {
|
||||||
max = it.size
|
max = it.readerItems.size
|
||||||
progress = 0
|
progress = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
gallerySize = it.size
|
gallerySize = it.readerItems.size
|
||||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.size}"
|
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.readerItems.size}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onProgressHandler = {
|
onProgressHandler = {
|
||||||
@@ -262,8 +259,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onErrorHandler = {
|
onErrorHandler = {
|
||||||
if (it is IOException)
|
Snackbar.make(reader_layout, it.message ?: it.javaClass.name, Snackbar.LENGTH_INDEFINITE).show()
|
||||||
Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
|
|
||||||
downloader.download = false
|
downloader.download = false
|
||||||
}
|
}
|
||||||
onCompleteHandler = {
|
onCompleteHandler = {
|
||||||
@@ -317,6 +313,11 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
|
if (dy < 0)
|
||||||
|
this@ReaderActivity.reader_fab.showMenuButton(true)
|
||||||
|
else if (dy > 0)
|
||||||
|
this@ReaderActivity.reader_fab.hideMenuButton(true)
|
||||||
|
|
||||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
||||||
|
|
||||||
if (layoutManager.findFirstVisibleItemPosition() == -1)
|
if (layoutManager.findFirstVisibleItemPosition() == -1)
|
||||||
@@ -341,18 +342,24 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reader_fab_fullscreen.setOnClickListener {
|
with(reader_fab_download) {
|
||||||
isFullscreen = true
|
setImageResource(R.drawable.ic_download)
|
||||||
fullscreen(isFullscreen)
|
setOnClickListener {
|
||||||
|
downloader.download = !downloader.download
|
||||||
|
|
||||||
reader_fab.close(true)
|
if (!downloader.download)
|
||||||
|
downloader.clearNotification()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reader_fab_download.setOnClickListener {
|
with(reader_fab_fullscreen) {
|
||||||
downloader.download = !downloader.download
|
setImageResource(R.drawable.ic_fullscreen)
|
||||||
|
setOnClickListener {
|
||||||
|
isFullscreen = true
|
||||||
|
fullscreen(isFullscreen)
|
||||||
|
|
||||||
if (!downloader.download)
|
this@ReaderActivity.reader_fab.close(true)
|
||||||
downloader.clearNotification()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
@@ -222,14 +240,14 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
addAll(languages.values)
|
addAll(languages.values)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (tags.any { it.area == "language" }) {
|
if (tags.any { it.area == "language" && !it.isNegative }) {
|
||||||
val tag = languages[tags.first { it.area == "language" }.tag]
|
val tag = languages[tags.first { it.area == "language" }.tag]
|
||||||
if (tag != null) {
|
if (tag != null) {
|
||||||
setSelection(
|
setSelection(
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
(adapter as ArrayAdapter<String>).getPosition(tag)
|
(adapter as ArrayAdapter<String>).getPosition(tag)
|
||||||
)
|
)
|
||||||
tags.removeByArea("language")
|
tags.removeByArea("language", false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
@@ -13,12 +31,13 @@ import kotlinx.coroutines.*
|
|||||||
import kotlinx.io.IOException
|
import kotlinx.io.IOException
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
import kotlinx.serialization.list
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.hitomi.*
|
import xyz.quaver.hitomi.getReader
|
||||||
|
import xyz.quaver.hitomi.getReferer
|
||||||
import xyz.quaver.hiyobi.cookie
|
import xyz.quaver.hiyobi.cookie
|
||||||
import xyz.quaver.hiyobi.user_agent
|
import xyz.quaver.hiyobi.user_agent
|
||||||
import xyz.quaver.pupil.R
|
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
@@ -30,7 +49,7 @@ import kotlin.concurrent.schedule
|
|||||||
|
|
||||||
class GalleryDownloader(
|
class GalleryDownloader(
|
||||||
base: Context,
|
base: Context,
|
||||||
private val galleryBlock: GalleryBlock,
|
private val galleryID: Int,
|
||||||
_notify: Boolean = false
|
_notify: Boolean = false
|
||||||
) : ContextWrapper(base) {
|
) : ContextWrapper(base) {
|
||||||
|
|
||||||
@@ -41,10 +60,10 @@ class GalleryDownloader(
|
|||||||
set(value) {
|
set(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
field = true
|
field = true
|
||||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
notificationManager.notify(galleryID, notificationBuilder.build())
|
||||||
|
|
||||||
val data = getCachedGallery(this, galleryBlock.id)
|
val data = getCachedGallery(this, galleryID)
|
||||||
val cache = File(cacheDir, "imageCache/${galleryBlock.id}")
|
val cache = File(cacheDir, "imageCache/$galleryID")
|
||||||
|
|
||||||
if (File(cache, "images").exists() && !data.exists()) {
|
if (File(cache, "images").exists() && !data.exists()) {
|
||||||
cache.copyRecursively(data, true)
|
cache.copyRecursively(data, true)
|
||||||
@@ -54,7 +73,7 @@ class GalleryDownloader(
|
|||||||
if (reader?.isActive == false && downloadJob?.isActive != true)
|
if (reader?.isActive == false && downloadJob?.isActive != true)
|
||||||
field = false
|
field = false
|
||||||
|
|
||||||
downloads.add(galleryBlock.id)
|
downloads.add(galleryID)
|
||||||
} else {
|
} else {
|
||||||
field = false
|
field = false
|
||||||
}
|
}
|
||||||
@@ -78,60 +97,64 @@ class GalleryDownloader(
|
|||||||
companion object : SparseArray<GalleryDownloader>()
|
companion object : SparseArray<GalleryDownloader>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
put(galleryBlock.id, this)
|
put(galleryID, this)
|
||||||
|
|
||||||
initNotification()
|
initNotification()
|
||||||
|
|
||||||
reader = CoroutineScope(Dispatchers.IO).async {
|
reader = CoroutineScope(Dispatchers.IO).async {
|
||||||
download = _notify
|
try {
|
||||||
val json = Json(JsonConfiguration.Stable)
|
download = _notify
|
||||||
val serializer = ReaderItem.serializer().list
|
val json = Json(JsonConfiguration.Stable)
|
||||||
|
val serializer = Reader.serializer()
|
||||||
|
|
||||||
//Check cache
|
//Check cache
|
||||||
val cache = File(getCachedGallery(this@GalleryDownloader, galleryBlock.id), "reader.json")
|
val cache = File(getCachedGallery(this@GalleryDownloader, galleryID), "reader.json")
|
||||||
|
|
||||||
if (cache.exists()) {
|
if (cache.exists()) {
|
||||||
val cached = json.parse(serializer, cache.readText())
|
val cached = json.parse(serializer, cache.readText())
|
||||||
|
|
||||||
if (cached.isNotEmpty()) {
|
if (cached.readerItems.isNotEmpty()) {
|
||||||
useHiyobi = when {
|
useHiyobi = when {
|
||||||
cached.first().url.contains("hitomi.la") -> false
|
cached.readerItems[0].url.contains("hitomi.la") -> false
|
||||||
else -> true
|
else -> true
|
||||||
|
}
|
||||||
|
|
||||||
|
onReaderLoadedHandler?.invoke(cached)
|
||||||
|
|
||||||
|
return@async cached
|
||||||
}
|
}
|
||||||
|
|
||||||
onReaderLoadedHandler?.invoke(cached)
|
|
||||||
|
|
||||||
return@async cached
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//Cache doesn't exist. Load from internet
|
//Cache doesn't exist. Load from internet
|
||||||
val reader = when {
|
val reader = when {
|
||||||
useHiyobi -> {
|
useHiyobi -> {
|
||||||
xyz.quaver.hiyobi.getReader(galleryBlock.id).let {
|
xyz.quaver.hiyobi.getReader(galleryID).let {
|
||||||
when {
|
when {
|
||||||
it.isEmpty() -> {
|
it.readerItems.isEmpty() -> {
|
||||||
useHiyobi = false
|
useHiyobi = false
|
||||||
getReader(galleryBlock.id)
|
getReader(galleryID)
|
||||||
|
}
|
||||||
|
else -> it
|
||||||
}
|
}
|
||||||
else -> it
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else -> {
|
||||||
|
getReader(galleryID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
|
||||||
getReader(galleryBlock.id)
|
if (reader.readerItems.isNotEmpty()) {
|
||||||
|
//Save cache
|
||||||
|
if (cache.parentFile?.exists() == false)
|
||||||
|
cache.parentFile!!.mkdirs()
|
||||||
|
|
||||||
|
cache.writeText(json.stringify(serializer, reader))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reader
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Reader("", listOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader.isNotEmpty()) {
|
|
||||||
//Save cache
|
|
||||||
if (cache.parentFile?.exists() == false)
|
|
||||||
cache.parentFile!!.mkdirs()
|
|
||||||
|
|
||||||
cache.writeText(json.stringify(serializer, reader))
|
|
||||||
}
|
|
||||||
|
|
||||||
reader
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,37 +164,30 @@ class GalleryDownloader(
|
|||||||
downloadJob = CoroutineScope(Dispatchers.Default).launch {
|
downloadJob = CoroutineScope(Dispatchers.Default).launch {
|
||||||
val reader = reader!!.await()
|
val reader = reader!!.await()
|
||||||
|
|
||||||
if (reader.isEmpty())
|
if (reader.readerItems.isEmpty()) {
|
||||||
onErrorHandler?.invoke(IOException("Couldn't retrieve Reader"))
|
onErrorHandler?.invoke(IOException(getString(R.string.unable_to_connect)))
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
val list = ArrayList<String>()
|
val list = ArrayList<String>()
|
||||||
|
|
||||||
onReaderLoadedHandler?.invoke(reader)
|
onReaderLoadedHandler?.invoke(reader)
|
||||||
|
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setProgress(reader.size, 0, false)
|
.setProgress(reader.readerItems.size, 0, false)
|
||||||
.setContentText("0/${reader.size}")
|
.setContentText("0/${reader.readerItems.size}")
|
||||||
|
|
||||||
reader.chunked(4).forEachIndexed { chunkIndex, chunked ->
|
reader.readerItems.chunked(4).forEachIndexed { chunkIndex, chunked ->
|
||||||
chunked.mapIndexed { i, it ->
|
chunked.mapIndexed { i, it ->
|
||||||
val index = chunkIndex*4+i
|
val index = chunkIndex*4+i
|
||||||
|
|
||||||
onProgressHandler?.invoke(index)
|
|
||||||
|
|
||||||
notificationBuilder
|
|
||||||
.setProgress(reader.size, index, false)
|
|
||||||
.setContentText("$index/${reader.size}")
|
|
||||||
|
|
||||||
if (download)
|
|
||||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
|
||||||
|
|
||||||
async(Dispatchers.IO) {
|
async(Dispatchers.IO) {
|
||||||
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
|
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
|
||||||
|
|
||||||
val name = "$index".padStart(4, '0')
|
val name = "$index".padStart(4, '0')
|
||||||
val ext = url.split('.').last()
|
val ext = url.split('.').last()
|
||||||
|
|
||||||
val cache = File(getCachedGallery(this@GalleryDownloader, galleryBlock.id), "images/$name.$ext")
|
val cache = File(getCachedGallery(this@GalleryDownloader, galleryID), "images/$name.$ext")
|
||||||
|
|
||||||
if (!cache.exists())
|
if (!cache.exists())
|
||||||
try {
|
try {
|
||||||
@@ -180,7 +196,7 @@ class GalleryDownloader(
|
|||||||
setRequestProperty("User-Agent", user_agent)
|
setRequestProperty("User-Agent", user_agent)
|
||||||
setRequestProperty("Cookie", cookie)
|
setRequestProperty("Cookie", cookie)
|
||||||
} else
|
} else
|
||||||
setRequestProperty("Referer", getReferer(galleryBlock.id))
|
setRequestProperty("Referer", getReferer(galleryID))
|
||||||
|
|
||||||
if (cache.parentFile?.exists() == false)
|
if (cache.parentFile?.exists() == false)
|
||||||
cache.parentFile!!.mkdirs()
|
cache.parentFile!!.mkdirs()
|
||||||
@@ -193,31 +209,43 @@ class GalleryDownloader(
|
|||||||
onErrorHandler?.invoke(e)
|
onErrorHandler?.invoke(e)
|
||||||
|
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setContentTitle(galleryBlock.title)
|
.setContentTitle(reader.title)
|
||||||
.setContentText(getString(R.string.reader_notification_error))
|
.setContentText(getString(R.string.reader_notification_error))
|
||||||
.setProgress(0, 0, false)
|
.setProgress(0, 0, false)
|
||||||
|
|
||||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
notificationManager.notify(galleryID, notificationBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.absolutePath
|
cache.absolutePath
|
||||||
}
|
}
|
||||||
}.forEach {
|
}.forEach {
|
||||||
list.add(it.await())
|
list.add(it.await())
|
||||||
|
|
||||||
|
val index = list.size
|
||||||
|
|
||||||
|
onProgressHandler?.invoke(index)
|
||||||
|
|
||||||
|
notificationBuilder
|
||||||
|
.setProgress(reader.readerItems.size, index, false)
|
||||||
|
.setContentText("$index/${reader.readerItems.size}")
|
||||||
|
|
||||||
|
if (download)
|
||||||
|
notificationManager.notify(galleryID, notificationBuilder.build())
|
||||||
|
|
||||||
onDownloadedHandler?.invoke(list)
|
onDownloadedHandler?.invoke(list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer(false).schedule(1000) {
|
Timer(false).schedule(1000) {
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setContentTitle(galleryBlock.title)
|
.setContentTitle(reader.title)
|
||||||
.setContentText(getString(R.string.reader_notification_complete))
|
.setContentText(getString(R.string.reader_notification_complete))
|
||||||
.setProgress(0, 0, false)
|
.setProgress(0, 0, false)
|
||||||
|
|
||||||
if (download) {
|
if (download) {
|
||||||
File(cacheDir, "imageCache/${galleryBlock.id}").let {
|
File(cacheDir, "imageCache/${galleryID}").let {
|
||||||
if (it.exists()) {
|
if (it.exists()) {
|
||||||
val target = File(getDownloadDirectory(this@GalleryDownloader), galleryBlock.id.toString())
|
val target = File(getDownloadDirectory(this@GalleryDownloader), galleryID.toString())
|
||||||
|
|
||||||
if (!target.exists())
|
if (!target.exists())
|
||||||
target.mkdirs()
|
target.mkdirs()
|
||||||
@@ -227,7 +255,7 @@ class GalleryDownloader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
notificationManager.notify(galleryID, notificationBuilder.build())
|
||||||
|
|
||||||
download = false
|
download = false
|
||||||
}
|
}
|
||||||
@@ -235,20 +263,20 @@ class GalleryDownloader(
|
|||||||
onCompleteHandler?.invoke()
|
onCompleteHandler?.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(galleryBlock.id)
|
remove(galleryID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancel() {
|
fun cancel() {
|
||||||
downloadJob?.cancel()
|
downloadJob?.cancel()
|
||||||
|
|
||||||
remove(galleryBlock.id)
|
remove(galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun cancelAndJoin() {
|
suspend fun cancelAndJoin() {
|
||||||
downloadJob?.cancelAndJoin()
|
downloadJob?.cancelAndJoin()
|
||||||
|
|
||||||
remove(galleryBlock.id)
|
remove(galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invokeOnReaderLoaded() {
|
fun invokeOnReaderLoaded() {
|
||||||
@@ -258,7 +286,7 @@ class GalleryDownloader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearNotification() {
|
fun clearNotification() {
|
||||||
notificationManager.cancel(galleryBlock.id)
|
notificationManager.cancel(galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invokeOnNotifyChanged() {
|
fun invokeOnNotifyChanged() {
|
||||||
@@ -267,22 +295,28 @@ class GalleryDownloader(
|
|||||||
|
|
||||||
private fun initNotification() {
|
private fun initNotification() {
|
||||||
val intent = Intent(this, ReaderActivity::class.java).apply {
|
val intent = Intent(this, ReaderActivity::class.java).apply {
|
||||||
putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), galleryBlock))
|
putExtra("galleryID", galleryID)
|
||||||
}
|
}
|
||||||
val pendingIntent = TaskStackBuilder.create(this).run {
|
val pendingIntent = TaskStackBuilder.create(this).run {
|
||||||
addNextIntentWithParentStack(intent)
|
addNextIntentWithParentStack(intent)
|
||||||
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
|
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notificationManager = NotificationManagerCompat.from(this)
|
||||||
|
|
||||||
notificationBuilder = NotificationCompat.Builder(this, "download").apply {
|
notificationBuilder = NotificationCompat.Builder(this, "download").apply {
|
||||||
setContentTitle(galleryBlock.title)
|
setContentTitle(getString(R.string.reader_loading))
|
||||||
setContentText(getString(R.string.reader_notification_text))
|
setContentText(getString(R.string.reader_notification_text))
|
||||||
setSmallIcon(R.drawable.ic_download)
|
setSmallIcon(R.drawable.ic_download)
|
||||||
setContentIntent(pendingIntent)
|
setContentIntent(pendingIntent)
|
||||||
setProgress(0, 0, true)
|
setProgress(0, 0, true)
|
||||||
priority = NotificationCompat.PRIORITY_LOW
|
priority = NotificationCompat.PRIORITY_LOW
|
||||||
}
|
}
|
||||||
notificationManager = NotificationManagerCompat.from(this)
|
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
while (reader == null) ;
|
||||||
|
notificationBuilder.setContentTitle(reader.await().title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -16,6 +34,7 @@ fun getCachedGallery(context: Context, galleryID: Int): File {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
fun getDownloadDirectory(context: Context): File? {
|
fun getDownloadDirectory(context: Context): File? {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||||
context.getExternalFilesDir("Pupil")
|
context.getExternalFilesDir("Pupil")
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||||
@@ -11,7 +29,7 @@ class Histories(private val file: File) : ArrayList<Int>() {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
if (!file.exists())
|
if (!file.exists())
|
||||||
file.parentFile.mkdirs()
|
file.parentFile?.mkdirs()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
load()
|
load()
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|||||||
@@ -1,7 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
|
import xyz.quaver.pupil.BuildConfig
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
fun getReleases(url: String) : JsonArray {
|
fun getReleases(url: String) : JsonArray {
|
||||||
@@ -14,26 +32,27 @@ fun getReleases(url: String) : JsonArray {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkUpdate(url: String, currentVersion: String) : JsonObject? {
|
fun checkUpdate(url: String) : JsonObject? {
|
||||||
val releases = getReleases(url)
|
val releases = getReleases(url)
|
||||||
|
|
||||||
if (releases.isEmpty())
|
if (releases.isEmpty())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
val latestVersion = releases[0].jsonObject["tag_name"]?.content
|
return releases.firstOrNull {
|
||||||
|
if (BuildConfig.PRERELEASE) {
|
||||||
|
BuildConfig.VERSION_NAME != it.jsonObject["tag_name"]?.content
|
||||||
|
} else {
|
||||||
|
it.jsonObject["prerelease"]?.boolean == false &&
|
||||||
|
BuildConfig.VERSION_NAME != (it.jsonObject["tag_name"]?.content ?: "")
|
||||||
|
}
|
||||||
|
}?.jsonObject
|
||||||
|
}
|
||||||
|
|
||||||
return when {
|
fun getApkUrl(releases: JsonObject) : Pair<String?, String?>? {
|
||||||
currentVersion.split('-').size == 1 -> {
|
releases["assets"]?.jsonArray?.forEach {
|
||||||
when {
|
if (Regex("Pupil-v(\\d+\\.)+\\d+\\.apk").matches(it.jsonObject["name"]?.content ?: ""))
|
||||||
currentVersion != latestVersion -> releases[0].jsonObject
|
return Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content)
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
when {
|
|
||||||
(currentVersion.split('-')[0] == latestVersion) -> releases[0].jsonObject
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<vector android:height="24dp" android:tint="#fff"
|
<vector android:height="24dp" android:tint="#fff"
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="#ff000000" android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
|
<path android:fillColor="#fff" android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
<!-- drawable/numeric.xml -->
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:height="24dp"
|
|
||||||
android:width="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#000" android:pathData="M4,17V9H2V7H6V17H4M22,15C22,16.11 21.1,17 20,17H16V15H20V13H18V11H20V9H16V7H20A2,2 0 0,1 22,9V10.5A1.5,1.5 0 0,1 20.5,12A1.5,1.5 0 0,1 22,13.5V15M14,15V17H8V13C8,11.89 8.9,11 10,11H12V9H8V7H12A2,2 0 0,1 14,9V11C14,12.11 13.1,13 12,13H10V15H14Z" />
|
|
||||||
</vector>
|
|
||||||
8
app/src/main/res/drawable/image_broken_variant.xml
Normal file
8
app/src/main/res/drawable/image_broken_variant.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/image_broken_variant.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#000" android:pathData="M21,5V11.59L18,8.58L14,12.59L10,8.59L6,12.59L3,9.58V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5M18,11.42L21,14.43V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V12.42L6,15.41L10,11.41L14,15.41" />
|
||||||
|
</vector>
|
||||||
@@ -4,5 +4,5 @@
|
|||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path android:fillColor="#000" android:pathData="M4,17V9H2V7H6V17H4M22,15C22,16.11 21.1,17 20,17H16V15H20V13H18V11H20V9H16V7H20A2,2 0 0,1 22,9V10.5A1.5,1.5 0 0,1 20.5,12A1.5,1.5 0 0,1 22,13.5V15M14,15V17H8V13C8,11.89 8.9,11 10,11H12V9H8V7H12A2,2 0 0,1 14,9V11C14,12.11 13.1,13 12,13H10V15H14Z" />
|
<path android:fillColor="#fff" android:pathData="M4,17V9H2V7H6V17H4M22,15C22,16.11 21.1,17 20,17H16V15H20V13H18V11H20V9H16V7H20A2,2 0 0,1 22,9V10.5A1.5,1.5 0 0,1 20.5,12A1.5,1.5 0 0,1 22,13.5V15M14,15V17H8V13C8,11.89 8.9,11 10,11H12V9H8V7H12A2,2 0 0,1 14,9V11C14,12.11 13.1,13 12,13H10V15H14Z" />
|
||||||
</vector>
|
</vector>
|
||||||
8
app/src/main/res/drawable/sort_variant.xml
Normal file
8
app/src/main/res/drawable/sort_variant.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/sort_variant.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#fff" android:pathData="M3,13H15V11H3M3,6V8H21V6M3,18H9V16H3V18Z" />
|
||||||
|
</vector>
|
||||||
@@ -56,6 +56,30 @@
|
|||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||||
|
|
||||||
|
<com.github.clans.fab.FloatingActionMenu
|
||||||
|
android:id="@+id/main_fab"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
app:menu_colorNormal="@color/colorAccent">
|
||||||
|
|
||||||
|
<com.github.clans.fab.FloatingActionButton
|
||||||
|
android:id="@+id/main_fab_jump"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:fab_label="@string/main_jump_title"
|
||||||
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
|
<com.github.clans.fab.FloatingActionButton
|
||||||
|
android:id="@+id/main_fab_id"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:fab_label="@string/main_open_gallery_by_id"
|
||||||
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
|
</com.github.clans.fab.FloatingActionMenu>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
<com.arlib.floatingsearchview.FloatingSearchView
|
<com.arlib.floatingsearchview.FloatingSearchView
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="4dp"
|
android:layout_height="4dp"
|
||||||
android:progressTint="@color/material_green_a700"
|
android:progressTint="@color/material_green_a700"
|
||||||
|
tools:ignore="UnusedAttribute"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -55,7 +56,6 @@
|
|||||||
android:id="@+id/reader_fab_download"
|
android:id="@+id/reader_fab_download"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:srcCompat="@drawable/ic_downloading"
|
|
||||||
app:fab_label="@string/reader_fab_download"
|
app:fab_label="@string/reader_fab_download"
|
||||||
app:fab_size="mini"/>
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
@@ -63,7 +63,6 @@
|
|||||||
android:id="@+id/reader_fab_fullscreen"
|
android:id="@+id/reader_fab_fullscreen"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:srcCompat="@drawable/ic_fullscreen"
|
|
||||||
app:fab_label="@string/reader_fab_fullscreen"
|
app:fab_label="@string/reader_fab_fullscreen"
|
||||||
app:fab_size="mini"/>
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
android:contentDescription="@string/reader_imageview_description"
|
android:contentDescription="@string/reader_imageview_description"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="100dp"
|
||||||
android:paddingBottom="8dp"
|
android:paddingBottom="8dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:adjustViewBounds="true"/>
|
android:adjustViewBounds="true"/>
|
||||||
@@ -2,15 +2,19 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item android:id="@+id/main_menu_jump"
|
<item
|
||||||
android:icon="@drawable/ic_jump"
|
android:id="@+id/main_menu_sort"
|
||||||
android:title="@string/main_jump_title"
|
android:title="@string/main_menu_sort">
|
||||||
app:showAsAction="ifRoom"/>
|
<menu>
|
||||||
|
<group android:checkableBehavior="single">
|
||||||
<item android:id="@+id/main_menu_id"
|
<item android:id="@+id/main_menu_sort_newest"
|
||||||
android:icon="@drawable/ic_numeric"
|
android:title="@string/main_menu_sort_newest"
|
||||||
android:title="@string/main_open_gallery_by_id"
|
android:checked="true"/>
|
||||||
app:showAsAction="ifRoom"/>
|
<item android:id="@+id/main_menu_sort_popular"
|
||||||
|
android:title="@string/main_menu_sort_popular"/>
|
||||||
|
</group>
|
||||||
|
</menu>
|
||||||
|
</item>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/main_menu_settings"
|
android:id="@+id/main_menu_settings"
|
||||||
|
|||||||
@@ -79,4 +79,8 @@
|
|||||||
<string name="settings_lock_wrong_confirm">ロックが一致しません。やり直してください。</string>
|
<string name="settings_lock_wrong_confirm">ロックが一致しません。やり直してください。</string>
|
||||||
<string name="settings_lock_none">なし</string>
|
<string name="settings_lock_none">なし</string>
|
||||||
<string name="settings_lock_remove_message">ロックを無効にしますか?</string>
|
<string name="settings_lock_remove_message">ロックを無効にしますか?</string>
|
||||||
|
<string name="reader_loading">ロード中</string>
|
||||||
|
<string name="main_menu_sort">ソート</string>
|
||||||
|
<string name="main_menu_sort_newest">投稿日時順</string>
|
||||||
|
<string name="main_menu_sort_popular">人気順</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
<string name="unable_to_connect">hitomi.la에 연결할 수 없습니다</string>
|
<string name="unable_to_connect">hitomi.la에 연결할 수 없습니다</string>
|
||||||
<string name="main_move">%1$d 페이지로 이동</string>
|
<string name="main_move">%1$d 페이지로 이동</string>
|
||||||
<string name="https_block_alert_title">접속 불가 현상 안내</string>
|
<string name="https_block_alert_title">접속 불가 현상 안내</string>
|
||||||
<string name="https_block_alert">최근 https 차단으로 접속이 안 되는 경우가 발생하고 있습니다\n이 경우 플레이스토어에서 SNIper앱을 이용하시면 정상이용이 가능합니다.</string>
|
<string name="https_block_alert">최근 https 차단으로 접속이 안 되는 경우가 발생하고 있습니다 이 경우 플레이스토어에서 Intra앱을 이용하시면 정상이용이 가능합니다.</string>
|
||||||
<string name="main_dialog_export">갤러리 내보내기</string>
|
<string name="main_dialog_export">갤러리 내보내기</string>
|
||||||
<string name="main_export_complete">내보내기 완료</string>
|
<string name="main_export_complete">내보내기 완료</string>
|
||||||
<string name="main_export_open_folder">폴더 열기</string>
|
<string name="main_export_open_folder">폴더 열기</string>
|
||||||
@@ -79,4 +79,8 @@
|
|||||||
<string name="settings_lock_wrong_confirm">잠금이 일치하지 않습니다. 다시 시도하세요.</string>
|
<string name="settings_lock_wrong_confirm">잠금이 일치하지 않습니다. 다시 시도하세요.</string>
|
||||||
<string name="settings_lock_none">없음</string>
|
<string name="settings_lock_none">없음</string>
|
||||||
<string name="settings_lock_remove_message">잠금을 해제할까요?</string>
|
<string name="settings_lock_remove_message">잠금을 해제할까요?</string>
|
||||||
|
<string name="reader_loading">로딩중</string>
|
||||||
|
<string name="main_menu_sort">정렬</string>
|
||||||
|
<string name="main_menu_sort_popular">인기순</string>
|
||||||
|
<string name="main_menu_sort_newest">시간순</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<string name="app_name" translatable="false" tools:override="true">Pupil</string>
|
<string name="app_name" translatable="false" tools:override="true">Pupil</string>
|
||||||
|
|
||||||
<string name="release_url" translatable="false">https://api.github.com/repos/tom5079/Pupil-issue/releases</string>
|
<string name="release_url" translatable="false">https://api.github.com/repos/tom5079/Pupil/releases</string>
|
||||||
<string name="release_name" translatable="false">Pupil-v(\\d+\\.)+\\d+\\.apk</string>
|
<string name="release_name" translatable="false">Pupil-v(\\d+\\.)+\\d+\\.apk</string>
|
||||||
|
|
||||||
<string name="home_page" translatable="false">http://bit.ly/2EZDClw</string>
|
<string name="home_page" translatable="false">http://bit.ly/2EZDClw</string>
|
||||||
@@ -45,6 +45,10 @@
|
|||||||
<string name="main_drawer_group_contact_email">Email me!</string>
|
<string name="main_drawer_group_contact_email">Email me!</string>
|
||||||
<string name="main_drawer_grouop_contact_kakaotalk">Kakaotalk</string>
|
<string name="main_drawer_grouop_contact_kakaotalk">Kakaotalk</string>
|
||||||
|
|
||||||
|
<string name="main_menu_sort">Sort</string>
|
||||||
|
<string name="main_menu_sort_newest">Newest</string>
|
||||||
|
<string name="main_menu_sort_popular">Popular</string>
|
||||||
|
|
||||||
<string name="main_jump_title">Jump to page</string>
|
<string name="main_jump_title">Jump to page</string>
|
||||||
<string name="main_jump_message">Current page: %1$d\nMaximum page: %2$d</string>
|
<string name="main_jump_message">Current page: %1$d\nMaximum page: %2$d</string>
|
||||||
<string name="main_open_gallery_by_id">Open Gallery by ID</string>
|
<string name="main_open_gallery_by_id">Open Gallery by ID</string>
|
||||||
@@ -71,6 +75,7 @@
|
|||||||
<string name="galleryblock_type">Type: %1$s</string>
|
<string name="galleryblock_type">Type: %1$s</string>
|
||||||
<string name="galleryblock_language">Language: %1$s</string>
|
<string name="galleryblock_language">Language: %1$s</string>
|
||||||
|
|
||||||
|
<string name="reader_loading">Loading</string>
|
||||||
<string name="reader_go_to_page">Go to page</string>
|
<string name="reader_go_to_page">Go to page</string>
|
||||||
<string name="reader_fab_fullscreen">Fullscreen</string>
|
<string name="reader_fab_fullscreen">Fullscreen</string>
|
||||||
<string name="reader_fab_download">Background download</string>
|
<string name="reader_fab_download">Background download</string>
|
||||||
|
|||||||
@@ -1,6 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("UNUSED_VARIABLE")
|
||||||
|
|
||||||
package xyz.quaver.pupil
|
package xyz.quaver.pupil
|
||||||
|
|
||||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,7 +32,10 @@ class ExampleUnitTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test() {
|
fun test() {
|
||||||
|
val current = "0.1"
|
||||||
|
val latest = "0.2"
|
||||||
|
|
||||||
|
print(current < latest)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.31'
|
ext.kotlin_version = '1.3.41'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
@@ -14,7 +14,7 @@ buildscript {
|
|||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
classpath 'com.google.gms:google-services:4.2.0'
|
classpath 'com.google.gms:google-services:4.3.0'
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
classpath 'io.fabric.tools:gradle:1.29.0'
|
classpath 'io.fabric.tools:gradle:1.29.0'
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 tom5079
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.hitomi
|
package xyz.quaver.hitomi
|
||||||
|
|
||||||
const val protocol = "https:"
|
const val protocol = "https:"
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 tom5079
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.hitomi
|
package xyz.quaver.hitomi
|
||||||
|
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 tom5079
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.hitomi
|
package xyz.quaver.hitomi
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|||||||
88
libpupil/src/main/java/xyz/quaver/hitomi/reader.kt
Normal file
88
libpupil/src/main/java/xyz/quaver/hitomi/reader.kt
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 tom5079
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.hitomi
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
|
import kotlinx.serialization.list
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import xyz.quaver.hiyobi.HiyobiReader
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
fun getReferer(galleryID: Int) = "https://hitomi.la/reader/$galleryID.html"
|
||||||
|
fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
|
||||||
|
|
||||||
|
fun webpReaderFromReader(reader: Reader) : Reader {
|
||||||
|
if (reader is HiyobiReader)
|
||||||
|
return reader
|
||||||
|
|
||||||
|
return Reader(reader.title, reader.readerItems.map {
|
||||||
|
ReaderItem(
|
||||||
|
if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url,
|
||||||
|
it.galleryInfo
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GalleryInfo(
|
||||||
|
val width: Int,
|
||||||
|
val haswebp: Int,
|
||||||
|
val name: String,
|
||||||
|
val height: Int
|
||||||
|
)
|
||||||
|
@Serializable
|
||||||
|
data class ReaderItem(
|
||||||
|
val url: String,
|
||||||
|
val galleryInfo: GalleryInfo?
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
open class Reader(val title: String, val readerItems: List<ReaderItem>)
|
||||||
|
|
||||||
|
//Set header `Referer` to reader url to avoid 403 error
|
||||||
|
fun getReader(galleryID: Int) : Reader {
|
||||||
|
val readerUrl = "https://hitomi.la/reader/$galleryID.html"
|
||||||
|
val galleryInfoUrl = "https://ltn.hitomi.la/galleries/$galleryID.js"
|
||||||
|
|
||||||
|
val doc = Jsoup.connect(readerUrl).get()
|
||||||
|
|
||||||
|
val title = doc.title()
|
||||||
|
|
||||||
|
val images = doc.select(".img-url").map {
|
||||||
|
protocol + urlFromURL(it.text())
|
||||||
|
}
|
||||||
|
|
||||||
|
val galleryInfo = ArrayList<GalleryInfo?>()
|
||||||
|
|
||||||
|
galleryInfo.addAll(
|
||||||
|
Json(JsonConfiguration.Stable).parse(
|
||||||
|
GalleryInfo.serializer().list,
|
||||||
|
Regex("""\[.+]""").find(
|
||||||
|
URL(galleryInfoUrl).readText()
|
||||||
|
)?.value ?: "[]"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (images.size > galleryInfo.size)
|
||||||
|
galleryInfo.addAll(arrayOfNulls(images.size - galleryInfo.size))
|
||||||
|
|
||||||
|
return Reader(title, (images zip galleryInfo).map {
|
||||||
|
ReaderItem(it.first, it.second)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package xyz.quaver.hitomi
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
|
||||||
import kotlinx.serialization.list
|
|
||||||
import org.jsoup.Jsoup
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
fun getReferer(galleryID: Int) = "https://hitomi.la/reader/$galleryID.html"
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class GalleryInfo(
|
|
||||||
val width: Int,
|
|
||||||
val haswebp: Int,
|
|
||||||
val name: String,
|
|
||||||
val height: Int
|
|
||||||
)
|
|
||||||
@Serializable
|
|
||||||
data class ReaderItem(
|
|
||||||
val url: String,
|
|
||||||
val galleryInfo: GalleryInfo?
|
|
||||||
)
|
|
||||||
typealias Reader = List<ReaderItem>
|
|
||||||
//Set header `Referer` to reader url to avoid 403 error
|
|
||||||
fun getReader(galleryID: Int) : Reader {
|
|
||||||
val readerUrl = "https://hitomi.la/reader/$galleryID.html"
|
|
||||||
val galleryInfoUrl = "https://ltn.hitomi.la/galleries/$galleryID.js"
|
|
||||||
|
|
||||||
try {
|
|
||||||
val doc = Jsoup.connect(readerUrl).get()
|
|
||||||
|
|
||||||
val images = doc.select(".img-url").map {
|
|
||||||
protocol + urlFromURL(it.text())
|
|
||||||
}
|
|
||||||
|
|
||||||
val galleryInfo = ArrayList<GalleryInfo?>()
|
|
||||||
|
|
||||||
galleryInfo.addAll(
|
|
||||||
Json(JsonConfiguration.Stable).parse(
|
|
||||||
GalleryInfo.serializer().list,
|
|
||||||
Regex("""\[.+]""").find(
|
|
||||||
URL(galleryInfoUrl).readText()
|
|
||||||
)?.value ?: "[]"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (images.size > galleryInfo.size)
|
|
||||||
galleryInfo.addAll(arrayOfNulls(images.size - galleryInfo.size))
|
|
||||||
|
|
||||||
return (images zip galleryInfo).map {
|
|
||||||
ReaderItem(it.first, it.second)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 tom5079
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.hitomi
|
package xyz.quaver.hitomi
|
||||||
|
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
val searchDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
|
fun doSearch(query: String, sortByPopularity: Boolean = false) : List<Int> {
|
||||||
fun doSearch(query: String) : List<Int> {
|
|
||||||
val terms = query
|
val terms = query
|
||||||
.trim()
|
.trim()
|
||||||
.replace(Regex("""^\?"""), "")
|
.replace(Regex("""^\?"""), "")
|
||||||
@@ -27,7 +42,20 @@ fun doSearch(query: String) : List<Int> {
|
|||||||
positiveTerms.push(term)
|
positiveTerms.push(term)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val positiveResults = positiveTerms.map {
|
||||||
|
CoroutineScope(Dispatchers.IO).async {
|
||||||
|
getGalleryIDsForQuery(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val negativeResults = negativeTerms.map {
|
||||||
|
CoroutineScope(Dispatchers.IO).async {
|
||||||
|
getGalleryIDsForQuery(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var results = when {
|
var results = when {
|
||||||
|
sortByPopularity -> getGalleryIDsFromNozomi(null, "popular", "all")
|
||||||
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(null, "index", "all")
|
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(null, "index", "all")
|
||||||
else -> getGalleryIDsForQuery(positiveTerms.poll())
|
else -> getGalleryIDsForQuery(positiveTerms.poll())
|
||||||
}
|
}
|
||||||
@@ -42,25 +70,19 @@ fun doSearch(query: String) : List<Int> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//positive results
|
//positive results
|
||||||
positiveTerms.map {
|
positiveResults.forEach {
|
||||||
launch(searchDispatcher) {
|
val result = it.await()
|
||||||
val newResults = getGalleryIDsForQuery(it)
|
|
||||||
filterPositive(newResults.sorted())
|
filterPositive(result.sorted())
|
||||||
}
|
|
||||||
}.forEach {
|
|
||||||
it.join()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//negative results
|
//negative results
|
||||||
negativeTerms.map {
|
negativeResults.forEach {
|
||||||
launch(searchDispatcher) {
|
val result = it.await()
|
||||||
filterNegative(getGalleryIDsForQuery(it).sorted())
|
|
||||||
}
|
filterNegative(result.sorted())
|
||||||
}.forEach {
|
|
||||||
it.join()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 tom5079
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.hitomi
|
package xyz.quaver.hitomi
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
@@ -163,8 +180,10 @@ fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : List
|
|||||||
|
|
||||||
val nozomi = ArrayList<Int>()
|
val nozomi = ArrayList<Int>()
|
||||||
|
|
||||||
|
val bytes = inputStream.readBytes()
|
||||||
|
|
||||||
val arrayBuffer = ByteBuffer
|
val arrayBuffer = ByteBuffer
|
||||||
.wrap(inputStream.readBytes())
|
.wrap(bytes)
|
||||||
.order(ByteOrder.BIG_ENDIAN)
|
.order(ByteOrder.BIG_ENDIAN)
|
||||||
|
|
||||||
while (arrayBuffer.hasRemaining())
|
while (arrayBuffer.hasRemaining())
|
||||||
|
|||||||
@@ -1,9 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 tom5079
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.hiyobi
|
package xyz.quaver.hiyobi
|
||||||
|
|
||||||
import kotlinx.io.IOException
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
import kotlinx.serialization.json.content
|
import kotlinx.serialization.json.content
|
||||||
|
import org.jsoup.Jsoup
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.hitomi.ReaderItem
|
import xyz.quaver.hitomi.ReaderItem
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
@@ -12,13 +28,15 @@ import javax.net.ssl.HttpsURLConnection
|
|||||||
const val hiyobi = "xn--9w3b15m8vo.asia"
|
const val hiyobi = "xn--9w3b15m8vo.asia"
|
||||||
const val user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"
|
const val user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"
|
||||||
|
|
||||||
var cookie: String = ""
|
class HiyobiReader(title: String, readerItems: List<ReaderItem>) : Reader(title, readerItems)
|
||||||
get() {
|
|
||||||
if (field.isEmpty())
|
|
||||||
field = renewCookie()
|
|
||||||
|
|
||||||
return field
|
var cookie: String = ""
|
||||||
}
|
get() {
|
||||||
|
if (field.isEmpty())
|
||||||
|
field = renewCookie()
|
||||||
|
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
fun renewCookie() : String {
|
fun renewCookie() : String {
|
||||||
val url = "https://$hiyobi/"
|
val url = "https://$hiyobi/"
|
||||||
@@ -35,26 +53,25 @@ fun renewCookie() : String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getReader(galleryId: Int) : Reader {
|
fun getReader(galleryID: Int) : Reader {
|
||||||
val url = "https://$hiyobi/data/json/${galleryId}_list.json"
|
val reader = "https://$hiyobi/reader/$galleryID"
|
||||||
|
val url = "https://$hiyobi/data/json/${galleryID}_list.json"
|
||||||
|
|
||||||
try {
|
val title = Jsoup.connect(reader).get().title()
|
||||||
val json = Json(JsonConfiguration.Stable).parseJson(
|
|
||||||
with(URL(url).openConnection() as HttpsURLConnection) {
|
|
||||||
setRequestProperty("User-Agent", user_agent)
|
|
||||||
setRequestProperty("Cookie", cookie)
|
|
||||||
connectTimeout = 2000
|
|
||||||
connect()
|
|
||||||
|
|
||||||
inputStream.bufferedReader().use { it.readText() }
|
val json = Json(JsonConfiguration.Stable).parseJson(
|
||||||
}
|
with(URL(url).openConnection() as HttpsURLConnection) {
|
||||||
)
|
setRequestProperty("User-Agent", user_agent)
|
||||||
|
setRequestProperty("Cookie", cookie)
|
||||||
|
connectTimeout = 2000
|
||||||
|
connect()
|
||||||
|
|
||||||
return json.jsonArray.map {
|
inputStream.bufferedReader().use { it.readText() }
|
||||||
val name = it.jsonObject["name"]!!.content
|
|
||||||
ReaderItem("https://$hiyobi/data/$galleryId/$name", null)
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
)
|
||||||
return emptyList()
|
|
||||||
}
|
return Reader(title, json.jsonArray.map {
|
||||||
|
val name = it.jsonObject["name"]!!.content
|
||||||
|
ReaderItem("https://$hiyobi/data/$galleryID/$name", null)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 tom5079
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("UNUSED_VARIABLE")
|
||||||
|
|
||||||
package xyz.quaver.hitomi
|
package xyz.quaver.hitomi
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.net.InetAddress
|
|
||||||
import java.net.UnknownHostException
|
|
||||||
|
|
||||||
|
|
||||||
class UnitTest {
|
class UnitTest {
|
||||||
@Test
|
@Test
|
||||||
@@ -11,21 +26,11 @@ class UnitTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getByIp(host: String): InetAddress {
|
|
||||||
try {
|
|
||||||
return InetAddress.getByName(host)
|
|
||||||
} catch (e: UnknownHostException) {
|
|
||||||
// unlikely
|
|
||||||
throw RuntimeException(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_nozomi() {
|
fun test_nozomi() {
|
||||||
val nozomi = fetchNozomi(start = 0, count = 5)
|
val nozomi = getGalleryIDsFromNozomi(null, "popular", "all")
|
||||||
|
|
||||||
nozomi.first
|
print(nozomi.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -44,7 +49,7 @@ class UnitTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_doSearch() {
|
fun test_doSearch() {
|
||||||
val r = doSearch("female:loli female:bondage language:korean -male:yaoi -male:guro -female:guro")
|
val r = doSearch("female:loli female:bondage language:korean -male:yaoi -male:guro -female:guro", true)
|
||||||
|
|
||||||
print(r.size)
|
print(r.size)
|
||||||
}
|
}
|
||||||
@@ -72,8 +77,6 @@ class UnitTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_hiyobi() {
|
fun test_hiyobi() {
|
||||||
xyz.quaver.hiyobi.getReader(1415416).forEach {
|
|
||||||
println(it.url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user