Compare commits

...

37 Commits

Author SHA1 Message Date
Pupil
9922a9f82a Merge pull request #41 from tom5079/hotfix-40
Fixes #40
2019-12-19 09:37:23 +09:00
tom5079
445b9b4673 Fixes #40 2019-12-19 09:36:51 +09:00
tom5079
0ef7b358e0 Fixes #40 2019-12-19 09:33:10 +09:00
tom5079
2d3fb75576 Fixes wierd crash 2019-12-14 17:04:43 +09:00
tom5079
d55ff6d68e Fixes wierd crash 2019-12-14 17:04:04 +09:00
Pupil
079654a9c7 Merge pull request #36 from tom5079/Pupil-35
fixes #35
2019-12-14 16:56:58 +09:00
tom5079
30263c6260 fixes #35
warning: this can cause OOM
2019-12-14 16:54:59 +09:00
tom5079
ceaa930623 bug fix 2019-12-13 20:03:11 +09:00
tom5079
6a8539106b bug fix 2019-12-13 20:01:45 +09:00
tom5079
7a24c3c08e bug fix 2019-12-13 19:50:14 +09:00
tom5079
251abeb090 Merge remote-tracking branch 'origin/development' into development 2019-12-13 19:43:42 +09:00
Pupil
a61fe9f98c Merge pull request #33 from tom5079/Pupil-29
Pupil-29
2019-12-13 19:42:44 +09:00
tom5079
d29c7bf91a Apply update on startup 2019-12-13 19:30:19 +09:00
tom5079
ed4911c441 Updated serialization library 2019-12-13 18:39:12 +09:00
tom5079
d40b4f3748 Added update logic for outdated readers 2019-12-12 20:14:55 +09:00
tom5079
f3c4fe1914 Merge remote-tracking branch 'origin/development' into development 2019-12-11 20:23:45 +09:00
tom5079
55ee841bd0 resolves #31 2019-12-11 20:23:18 +09:00
tom5079
657fb488ee fixed #31 on libpupil
#TODO: fix app side to completely resolve the issue
2019-12-11 20:23:18 +09:00
tom5079
4eef0b93fb bug fix 2019-12-11 20:23:17 +09:00
Pupil
f2be56435c Merge pull request #32 from tom5079/Pupil-31 2019-12-11 20:08:45 +09:00
tom5079
fa6b3ad7ba resolves #31 2019-12-11 20:03:55 +09:00
tom5079
52c05e6888 fixed #31 on libpupil
#TODO: fix app side to completely resolve the issue
2019-12-11 19:52:25 +09:00
tom5079
865bf0ba83 Added code for differentiating readers 2019-12-09 10:33:26 +09:00
tom5079
3f827d1bad bug fix 2019-12-09 10:15:53 +09:00
tom5079
0561d5f55c Added code for saved reader 2019-12-09 09:36:36 +09:00
Pupil
1bf2e1dacc Merge pull request #30 from tom5079/Pupil-24
fixed #24
2019-12-08 18:45:21 +09:00
tom5079
db5a221b56 kotlin plugin update 2019-12-08 18:10:35 +09:00
tom5079
295285f132 Turned off development only option 2019-12-02 19:04:09 +09:00
tom5079
5052b6c074 Removed folder opening feature due to its unstability 2019-12-02 18:55:38 +09:00
tom5079
f98f45dc54 Pupil-24 Absence of backing up favorites feature 2019-12-01 16:58:29 +09:00
tom5079
8d16950f46 Issue #27 fix 2019-11-30 16:10:47 +09:00
tom5079
74033b9f4a Issue #27 fix 2019-11-30 16:10:09 +09:00
tom5079
e497d47374 Fix for bug caused by changed hiyobi domain 2019-11-30 15:10:25 +09:00
tom5079
a97af59260 Potential fix for memory issues 2019-11-30 15:09:53 +09:00
tom5079
2197de98ea Potential fix for too large bitmap crash 2019-11-25 19:39:17 +09:00
tom5079
c004c7f71a Fixed bug fetching old galleries from hiyobi 2019-11-15 19:47:09 +09:00
tom5079
69fc3ad4e8 typo 2019-11-02 21:50:03 +09:00
26 changed files with 322 additions and 72 deletions

View File

@@ -13,8 +13,8 @@ android {
applicationId "xyz.quaver.pupil" applicationId "xyz.quaver.pupil"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 30 versionCode 31
versionName "4.1" versionName "4.2-beta2-hotfix1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@@ -25,7 +25,7 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
buildTypes.each { buildTypes.each {
it.buildConfigField('boolean', 'PRERELEASE', 'false') it.buildConfigField('boolean', 'PRERELEASE', 'true')
it.buildConfigField('boolean', 'CENSOR', 'false') it.buildConfigField('boolean', 'CENSOR', 'false')
} }
} }
@@ -46,7 +46,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.preference:preference:1.1.0'
@@ -55,12 +55,12 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "androidx.biometric:biometric:1.0.0-rc02" implementation "androidx.biometric:biometric:1.0.0"
implementation 'com.android.support:multidex:1.0.3' implementation 'com.android.support:multidex:1.0.3'
implementation "com.daimajia.swipelayout:library:1.2.0@aar" implementation "com.daimajia.swipelayout:library:1.2.0@aar"
implementation 'com.google.android.material:material:1.2.0-alpha01' implementation 'com.google.android.material:material:1.2.0-alpha02'
implementation 'com.google.firebase:firebase-core:17.2.1' implementation 'com.google.firebase:firebase-core:17.2.1'
implementation 'com.google.firebase:firebase-perf:19.0.1' implementation 'com.google.firebase:firebase-perf:19.0.3'
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'

View File

@@ -1 +1 @@
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":30,"versionName":"4.1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":31,"versionName":"4.2-beta2-hotfix1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]

View File

@@ -25,6 +25,10 @@ import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule import androidx.test.rule.ActivityTestRule
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.json.JsonObject
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -33,6 +37,9 @@ import xyz.quaver.hiyobi.createImgList
import xyz.quaver.hiyobi.getReader import xyz.quaver.hiyobi.getReader
import xyz.quaver.hiyobi.user_agent import xyz.quaver.hiyobi.user_agent
import xyz.quaver.pupil.ui.LockActivity import xyz.quaver.pupil.ui.LockActivity
import xyz.quaver.pupil.util.getDownloadDirectory
import xyz.quaver.pupil.util.updateOldReaderGalleries
import java.io.File
import java.net.URL import java.net.URL
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
@@ -48,6 +55,7 @@ class ExampleInstrumentedTest {
fun useAppContext() { fun useAppContext() {
// Context of the app under test. // Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext val appContext = InstrumentationRegistry.getInstrumentation().targetContext
Log.i("PUPILD", getDownloadDirectory(appContext).absolutePath ?: "")
assertEquals("xyz.quaver.pupil", appContext.packageName) assertEquals("xyz.quaver.pupil", appContext.packageName)
} }
@@ -57,13 +65,11 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext val appContext = InstrumentationRegistry.getInstrumentation().targetContext
activityTestRule.launchActivity(Intent()) activityTestRule.launchActivity(Intent())
while(true);
} }
@Test @Test
fun test_doSearch() { fun test_doSearch() {
val reader = getReader(1426382) val reader = getReader( 1426382)
val data: ByteArray val data: ByteArray
@@ -76,4 +82,38 @@ class ExampleInstrumentedTest {
Log.d("Pupil", data.size.toString()) Log.d("Pupil", data.size.toString())
} }
@UseExperimental(ImplicitReflectionSerializer::class)
@Test
fun test_deleteCodeFromReader() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val json = Json(JsonConfiguration.Stable)
listOf(
getDownloadDirectory(context),
File(context.cacheDir, "imageCache")
).forEach { root ->
root.listFiles()?.forEach gallery@{ gallery ->
val reader = json.parseJson(File(gallery, "reader.json").apply {
if (!exists())
return@gallery
}.readText())
.jsonObject.toMutableMap()
Log.d("PUPILD", gallery.name)
reader.remove("code")
File(gallery, "reader.json").writeText(JsonObject(reader).toString())
}
}
}
@Test
fun test_updateOldReader() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
updateOldReaderGalleries(context)
}
} }

View File

@@ -20,6 +20,18 @@
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:replace="android:theme"> tools:replace="android:theme">
<provider
android:authorities="${applicationId}.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
<activity android:name=".ui.LockActivity"/> <activity android:name=".ui.LockActivity"/>
<activity <activity
android:name=".ui.ReaderActivity" android:name=".ui.ReaderActivity"

View File

@@ -30,7 +30,11 @@ import androidx.preference.PreferenceManager
import com.google.android.gms.common.GooglePlayServicesNotAvailableException import com.google.android.gms.common.GooglePlayServicesNotAvailableException
import com.google.android.gms.common.GooglePlayServicesRepairableException import com.google.android.gms.common.GooglePlayServicesRepairableException
import com.google.android.gms.security.ProviderInstaller import com.google.android.gms.security.ProviderInstaller
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import xyz.quaver.pupil.util.Histories import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.updateOldReaderGalleries
import java.io.File import java.io.File
class Pupil : MultiDexApplication() { class Pupil : MultiDexApplication() {
@@ -78,6 +82,10 @@ class Pupil : MultiDexApplication() {
false -> AppCompatDelegate.MODE_NIGHT_NO false -> AppCompatDelegate.MODE_NIGHT_NO
}) })
CoroutineScope(Dispatchers.IO).launch {
updateOldReaderGalleries(this@Pupil)
}
super.onCreate() super.onCreate()
} }

View File

@@ -337,7 +337,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
holder.view.galleryblock_download.text = when(GalleryDownloader.get(gallery.first.id)) { holder.view.galleryblock_download.text = when(GalleryDownloader.get(gallery.first.id)) {
null -> holder.view.context.getString(R.string.main_download) null -> holder.view.context.getString(R.string.main_download)
else -> holder.view.context.getString(R.string.main_cancel_download) else -> holder.view.context.getString(android.R.string.cancel)
} }
} }

View File

@@ -49,12 +49,17 @@ class ReaderAdapter(private val glide: RequestManager,
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.view as ImageView holder.view as ImageView
if (isFullScreen)
holder.view.layoutParams.height = RecyclerView.LayoutParams.MATCH_PARENT
else
holder.view.layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT
glide glide
.load(File(getCachedGallery(holder.view.context, galleryID), images[position])) .load(File(getCachedGallery(holder.view.context, galleryID), images[position]))
.dontTransform()
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true) .skipMemoryCache(true)
.error(R.drawable.image_broken_variant) .error(R.drawable.image_broken_variant)
.dontTransform()
.apply { .apply {
if (BuildConfig.CENSOR) if (BuildConfig.CENSOR)
override(5, 8) override(5, 8)

View File

@@ -576,6 +576,17 @@ class MainActivity : AppCompatActivity() {
} }
} }
histories.remove(galleryID)
if (this@MainActivity.mode == Mode.HISTORY) {
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
}
completeFlag.put(galleryID, false) completeFlag.put(galleryID, false)
} }

View File

@@ -32,10 +32,15 @@ import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.dialog_default_query.view.* import kotlinx.android.synthetic.main.dialog_default_query.view.*
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.parseList
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.Tags import xyz.quaver.pupil.types.Tags
@@ -43,10 +48,13 @@ import xyz.quaver.pupil.util.Lock
import xyz.quaver.pupil.util.LockManager import xyz.quaver.pupil.util.LockManager
import xyz.quaver.pupil.util.getDownloadDirectory import xyz.quaver.pupil.util.getDownloadDirectory
import java.io.File import java.io.File
import java.nio.charset.Charset
import java.util.*
class SettingsActivity : AppCompatActivity() { class SettingsActivity : AppCompatActivity() {
val REQUEST_LOCK = 38238 val REQUEST_LOCK = 38238
val REQUEST_RESTORE = 16546
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -154,7 +162,7 @@ class SettingsActivity : AppCompatActivity() {
with(findPreference<Preference>("delete_downloads")) { with(findPreference<Preference>("delete_downloads")) {
this!! this!!
val dir = getDownloadDirectory(context)!! val dir = getDownloadDirectory(context)
summary = getDirSize(dir) summary = getDirSize(dir)
@@ -278,7 +286,7 @@ class SettingsActivity : AppCompatActivity() {
s ?: return s ?: return
if (s.any { it.isUpperCase() }) if (s.any { it.isUpperCase() })
s.replace(0, s.length, s.toString().toLowerCase()) s.replace(0, s.length, s.toString().toLowerCase(Locale.getDefault()))
} }
}) })
} }
@@ -352,6 +360,37 @@ class SettingsActivity : AppCompatActivity() {
true true
} }
} }
with(findPreference<Preference>("backup")) {
this!!
onPreferenceClickListener = Preference.OnPreferenceClickListener {
File(ContextCompat.getDataDir(context), "favorites.json").copyTo(
File(getDownloadDirectory(context), "favorites.json"),
true
)
Snackbar.make(this@SettingsFragment.listView, R.string.settings_backup_snackbar, Snackbar.LENGTH_LONG)
.show()
true
}
}
with(findPreference<Preference>("restore")) {
this!!
onPreferenceClickListener = Preference.OnPreferenceClickListener {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
activity?.startActivityForResult(intent, (activity as SettingsActivity).REQUEST_RESTORE)
true
}
}
} }
} }
@@ -415,6 +454,7 @@ class SettingsActivity : AppCompatActivity() {
return true return true
} }
@UseExperimental(ImplicitReflectionSerializer::class)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when(requestCode) { when(requestCode) {
REQUEST_LOCK -> { REQUEST_LOCK -> {
@@ -426,6 +466,33 @@ class SettingsActivity : AppCompatActivity() {
.commitAllowingStateLoss() .commitAllowingStateLoss()
} }
} }
REQUEST_RESTORE -> {
if (resultCode == Activity.RESULT_OK) {
val uri = data?.data ?: return
try {
val json = contentResolver.openInputStream(uri).use { inputStream ->
inputStream!!
inputStream.readBytes().toString(Charset.defaultCharset())
}
(application as Pupil).favorites.addAll(Json.parseList<Int>(json).also {
Snackbar.make(
window.decorView,
getString(R.string.settings_restore_successful, it.size),
Snackbar.LENGTH_LONG
).show()
})
} catch (e: Exception) {
Snackbar.make(
window.decorView,
R.string.settings_restore_failed,
Snackbar.LENGTH_LONG
).show()
}
}
}
else -> super.onActivityResult(requestCode, resultCode, data) else -> super.onActivityResult(requestCode, resultCode, data)
} }
} }

View File

@@ -31,7 +31,6 @@ import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import xyz.quaver.availableInHiyobi
import xyz.quaver.hitomi.Reader import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.getReader import xyz.quaver.hitomi.getReader
import xyz.quaver.hitomi.getReferer import xyz.quaver.hitomi.getReferer
@@ -124,7 +123,7 @@ class GalleryDownloader(
val cached = json.parse(serializer, cache.readText()) val cached = json.parse(serializer, cache.readText())
if (cached.galleryInfo.isNotEmpty()) { if (cached.galleryInfo.isNotEmpty()) {
useHiyobi = availableInHiyobi(galleryID) useHiyobi = cached.code == Reader.Code.HIYOBI
onReaderLoadedHandler?.invoke(cached) onReaderLoadedHandler?.invoke(cached)
@@ -187,15 +186,11 @@ class GalleryDownloader(
async(Dispatchers.IO) { async(Dispatchers.IO) {
val url = when(useHiyobi) { val url = when(useHiyobi) {
true -> createImgList(galleryID, reader)[index].path true -> createImgList(galleryID, reader)[index].path
false -> when (galleryInfo.haswebp) { false -> when {
1 -> webpUrlFromUrl( (!galleryInfo.hash.isNullOrBlank()) and (galleryInfo.haswebp == 1) ->
urlFromUrlFromHash( urlFromUrlFromHash(galleryID, galleryInfo, "webp")
galleryID, else ->
galleryInfo, urlFromUrlFromHash(galleryID, galleryInfo)
true
)
)
else -> urlFromUrlFromHash(galleryID, galleryInfo)
} }
} }

View File

@@ -33,9 +33,9 @@ fun getCachedGallery(context: Context, galleryID: Int): File {
} }
@Suppress("DEPRECATION") @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")!!
else else
File(Environment.getExternalStorageDirectory(), "Pupil") File(Environment.getExternalStorageDirectory(), "Pupil")
} }

View File

@@ -18,8 +18,14 @@
package xyz.quaver.pupil.util package xyz.quaver.pupil.util
import android.content.Context
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.internal.EnumSerializer
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import xyz.quaver.availableInHiyobi
import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.BuildConfig import xyz.quaver.pupil.BuildConfig
import java.io.File
import java.net.URL import java.net.URL
fun getReleases(url: String) : JsonArray { fun getReleases(url: String) : JsonArray {
@@ -62,3 +68,58 @@ fun getApkUrl(releases: JsonObject) : Pair<String?, String?>? {
Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content) Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content)
} }
} }
fun getOldReaderGalleries(context: Context) : List<File> {
val oldGallery = mutableListOf<File>()
listOf(
getDownloadDirectory(context),
File(context.cacheDir, "imageCache")
).forEach { root ->
root.listFiles()?.forEach { gallery ->
File(gallery, "reader.json").let { readerFile ->
if (!readerFile.exists())
return@let
try {
Json(JsonConfiguration.Stable).parseJson(readerFile.readText())
.jsonObject.let { reader ->
if (!reader.contains("code"))
oldGallery.add(gallery)
}
} catch (e: Exception) {
// do nothing
}
}
}
}
return oldGallery
}
@UseExperimental(InternalSerializationApi::class)
fun updateOldReaderGalleries(context: Context) {
val json = Json(JsonConfiguration.Stable)
getOldReaderGalleries(context).forEach { gallery ->
val reader = json.parseJson(File(gallery, "reader.json").apply {
if (!exists())
return@forEach
}.readText())
.jsonObject.toMutableMap()
val codeSerializer = EnumSerializer(Reader.Code::class)
reader["code"] = when {
(File(gallery, "images").list()?.
all { !it.endsWith("webp") } ?: return@forEach) &&
availableInHiyobi(gallery.name.toIntOrNull() ?: return@forEach)
-> json.toJson(codeSerializer, Reader.Code.HIYOBI)
else -> json.toJson(codeSerializer, Reader.Code.HITOMI)
}
File(gallery, "reader.json").writeText(JsonObject(reader).toString())
}
}

View File

@@ -26,18 +26,11 @@
android:background="@color/dark_gray" android:background="@color/dark_gray"
tools:context=".ui.ReaderActivity"> tools:context=".ui.ReaderActivity">
<LinearLayout <androidx.recyclerview.widget.RecyclerView
android:id="@+id/reader_recyclerview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_gravity="center_vertical"> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/reader_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -22,6 +22,4 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="100dp" android:minHeight="100dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"/>
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>

View File

@@ -104,5 +104,10 @@
<string name="reader_help">ヘルプ</string> <string name="reader_help">ヘルプ</string>
<string name="main_delete">削除</string> <string name="main_delete">削除</string>
<string name="main_download">ダウンロード</string> <string name="main_download">ダウンロード</string>
<string name="main_cancel_download">キャンセル</string> <string name="settings_backup_title">お気に入りバックアップ</string>
<string name="settings_restore_title">お気に入り復元</string>
<string name="settings_backup_snackbar">バックアップファイルを作成しました</string>
<string name="settings_backup_checkout">確認</string>
<string name="settings_restore_failed">復元に失敗しました</string>
<string name="settings_restore_successful">%1$d項目を復元しました</string>
</resources> </resources>

View File

@@ -66,7 +66,7 @@
<string name="main_open_gallery_by_id">갤러리 번호로 열기</string> <string name="main_open_gallery_by_id">갤러리 번호로 열기</string>
<string name="main_open_gallery_by_id_error">갤러리를 찾지 못했습니다</string> <string name="main_open_gallery_by_id_error">갤러리를 찾지 못했습니다</string>
<string name="settings_storage">저장 공간</string> <string name="settings_storage">저장 공간</string>
<string name="main_drawer_grouop_contact_discord">디스코드</string> <string name="main_drawer_grouop_contact_discord">디스코드</string>
<string name="settings_app_lock">앱 잠금</string> <string name="settings_app_lock">앱 잠금</string>
<string name="settings_app_lock_type">앱 잠금 종류</string> <string name="settings_app_lock_type">앱 잠금 종류</string>
<string name="settings_app_version_title">앱 버전</string> <string name="settings_app_version_title">앱 버전</string>
@@ -104,5 +104,10 @@
<string name="reader_help">도움말</string> <string name="reader_help">도움말</string>
<string name="main_delete">삭제</string> <string name="main_delete">삭제</string>
<string name="main_download">다운로드</string> <string name="main_download">다운로드</string>
<string name="main_cancel_download">취소</string> <string name="settings_backup_title">즐겨찾기 백업</string>
<string name="settings_restore_title">즐겨찾기 복원</string>
<string name="settings_backup_snackbar">백업 파일을 생성하였습니다</string>
<string name="settings_backup_checkout">확인</string>
<string name="settings_restore_failed">복원에 실패했습니다</string>
<string name="settings_restore_successful">%1$d개 항목을 복원했습니다</string>
</resources> </resources>

View File

@@ -72,7 +72,6 @@
<string name="main_export_error">Error occurred during export</string> <string name="main_export_error">Error occurred during export</string>
<string name="main_download">DOWNLOAD</string> <string name="main_download">DOWNLOAD</string>
<string name="main_cancel_download">CANCEL</string>
<string name="main_delete">DELETE</string> <string name="main_delete">DELETE</string>
<string name="update_title">Update available</string> <string name="update_title">Update available</string>
@@ -133,6 +132,12 @@
<string name="settings_dark_mode_summary">Protect yourself against light attacks!</string> <string name="settings_dark_mode_summary">Protect yourself against light attacks!</string>
<string name="settings_nomedia_title">Hide image from gallery</string> <string name="settings_nomedia_title">Hide image from gallery</string>
<string name="settings_nomedia_summary">Hides image from gallery</string> <string name="settings_nomedia_summary">Hides image from gallery</string>
<string name="settings_backup_title">Backup favorites</string>
<string name="settings_backup_snackbar">Backup file created</string>
<string name="settings_backup_checkout">Check out</string>
<string name="settings_restore_title">Restore favorites</string>
<string name="settings_restore_failed">Restore failed</string>
<string name="settings_restore_successful">%1$d entries restored</string>
<string name="settings_lock_none">None</string> <string name="settings_lock_none">None</string>
<string name="settings_lock_pattern">Pattern</string> <string name="settings_lock_pattern">Pattern</string>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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/>.
-->
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external" path="/"/>
</paths>

View File

@@ -73,6 +73,15 @@
app:key="nomedia" app:key="nomedia"
app:title="@string/settings_nomedia_title" app:title="@string/settings_nomedia_title"
app:summary="@string/settings_nomedia_title"/> app:summary="@string/settings_nomedia_title"/>
<Preference
app:key="backup"
app:title="@string/settings_backup_title"/>
<Preference
app:key="restore"
app:title="@string/settings_restore_title"/>
</PreferenceCategory> </PreferenceCategory>
</androidx.preference.PreferenceScreen> </androidx.preference.PreferenceScreen>

View File

@@ -20,22 +20,19 @@
package xyz.quaver.pupil package xyz.quaver.pupil
import org.junit.Test
/** /**
* Example local unit test, which will execute on the development machine (host). * Example local unit test, which will execute on the development machine (host).
* *
* See [testing documentation](http://d.android.com/tools/testing). * See [testing documentation](http://d.android.com/tools/testing).
*/ */
import org.junit.Test
class ExampleUnitTest { class ExampleUnitTest {
@Test @Test
fun test() { fun test() {
val current = "0.1"
val latest = "0.2"
print(current < latest)
} }
} }

View File

@@ -1,18 +1,18 @@
// 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.50' ext.kotlin_version = '1.3.61'
repositories { repositories {
google() google()
jcenter() jcenter()
maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.fabric.io/public' }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.1' classpath 'com.android.tools.build:gradle:3.5.3'
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.3.2' classpath 'com.google.gms:google-services:4.3.3'
// 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'

View File

@@ -6,7 +6,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
implementation 'org.jsoup:jsoup:1.11.3' implementation 'org.jsoup:jsoup:1.11.3'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
} }

View File

@@ -51,7 +51,7 @@ fun subdomainFromGalleryID(g: Int) : String {
fun subdomainFromURL(url: String, base: String? = null) : String { fun subdomainFromURL(url: String, base: String? = null) : String {
var retval = "a" var retval = "a"
if (!base.isNullOrEmpty()) if (!base.isNullOrBlank())
retval = base retval = base
val r = Regex("""/galleries/\d*(\d)/""") val r = Regex("""/galleries/\d*(\d)/""")
@@ -60,7 +60,7 @@ fun subdomainFromURL(url: String, base: String? = null) : String {
if (m == null) { if (m == null) {
b = 16 b = 16
val r2 = Regex("""/images/[0-9a-f]/([0-9a-f]{2})/""") val r2 = Regex("""/[0-9a-f]/([0-9a-f]{2})/""")
m = r2.find(url) m = r2.find(url)
if (m == null) if (m == null)
return retval return retval
@@ -79,17 +79,20 @@ fun urlFromURL(url: String, base: String? = null) : String {
fun fullPathFromHash(hash: String?) : String? { fun fullPathFromHash(hash: String?) : String? {
return when { return when {
hash?.length ?: 0 < 3 -> hash (hash?.length ?: 0) < 3 -> hash
else -> hash!!.replace(Regex("^.*(..)(.)$"), "$2/$1/$hash") else -> hash!!.replace(Regex("^.*(..)(.)$"), "$2/$1/$hash")
} }
} }
fun urlFromHash(galleryID: Int, image: GalleryInfo, oldMethod: Boolean) : String { fun urlFromHash(galleryID: Int, image: GalleryInfo, webp: String? = null) : String {
val ext = webp ?: image.name.split('.').last()
return when { return when {
oldMethod or image.hash.isNullOrEmpty() -> "$protocol//a.hitomi.la/galleries/$galleryID/${image.name}" image.hash.isNullOrBlank() ->
else -> "$protocol//a.hitomi.la/images/${fullPathFromHash(image.hash)}.${image.name.split('.').last()}" "$protocol//a.hitomi.la/galleries/$galleryID/${image.name}"
else ->
"$protocol//a.hitomi.la/${webp?:"images"}/${fullPathFromHash(image.hash)}.$ext"
} }
} }
fun urlFromUrlFromHash(galleryID: Int, image: GalleryInfo, oldMethod: Boolean = false) = fun urlFromUrlFromHash(galleryID: Int, image: GalleryInfo, webp: String? = null) =
urlFromURL(urlFromHash(galleryID, image, oldMethod)) urlFromURL(urlFromHash(galleryID, image, webp))

View File

@@ -26,13 +26,19 @@ fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
data class GalleryInfo( data class GalleryInfo(
val width: Int, val width: Int,
val hash: String? = null, val hash: String? = null,
val haswebp: Int, val haswebp: Int = 0,
val name: String, val name: String,
val height: Int val height: Int
) )
@Serializable @Serializable
open class Reader(val title: String, val galleryInfo: List<GalleryInfo>) data class Reader(val code: Code, val title: String, val galleryInfo: List<GalleryInfo>) {
enum class Code {
HITOMI,
HIYOBI,
SORALA
}
}
//Set header `Referer` to reader url to avoid 403 error //Set header `Referer` to reader url to avoid 403 error
fun getReader(galleryID: Int) : Reader { fun getReader(galleryID: Int) : Reader {
@@ -40,5 +46,5 @@ fun getReader(galleryID: Int) : Reader {
val doc = Jsoup.connect(readerUrl).get() val doc = Jsoup.connect(readerUrl).get()
return Reader(doc.title(), getGalleryInfo(galleryID)) return Reader(Reader.Code.HITOMI, doc.title(), getGalleryInfo(galleryID))
} }

View File

@@ -26,7 +26,7 @@ import xyz.quaver.hitomi.protocol
import java.net.URL import java.net.URL
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
const val hiyobi = "xn--9w3b15m8vo.asia" const val hiyobi = "hiyobi.me"
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 = "" var cookie: String = ""
@@ -76,7 +76,7 @@ fun getReader(galleryID: Int) : Reader {
} }
) )
return Reader(title, galleryInfo) return Reader(Reader.Code.HIYOBI, title, galleryInfo)
} }
fun createImgList(galleryID: Int, reader: Reader) = fun createImgList(galleryID: Int, reader: Reader) =

View File

@@ -20,6 +20,7 @@ package xyz.quaver.hitomi
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import xyz.quaver.availableInHiyobi
class UnitTest { class UnitTest {
@Test @Test
@@ -86,10 +87,17 @@ class UnitTest {
@Test @Test
fun test_urlFromUrlFromHash() { fun test_urlFromUrlFromHash() {
val url = urlFromUrlFromHash(1510702, GalleryInfo( val url = urlFromUrlFromHash(1531795, GalleryInfo(
210, "56e9e1b8bb72194777ed93fee11b06070b905039dd11348b070bcf1793aaed7b", 1, "6.jpg", 300 212, "719d46a7556be0d0021c5105878507129b5b3308b02cf67f18901b69dbb3b5ef", 1, "00.jpg", 300
)) ), "webp")
print(url) print(url)
} }
@Test
fun test_availableInHiyobi() {
val result = availableInHiyobi(1272781)
print(result)
}
} }