Merge pull request #6 from tom5079/development

Version 2.5
This commit is contained in:
tom5079
2019-06-07 12:05:06 +09:00
committed by GitHub
18 changed files with 416 additions and 62 deletions

View File

@@ -7,7 +7,12 @@ import androidx.test.platform.app.InstrumentationRegistry
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
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.getReader
import xyz.quaver.hiyobi.user_agent
import java.io.File import java.io.File
import java.net.URL
import javax.net.ssl.HttpsURLConnection
/** /**
* Instrumented test, which will execute on an Android device. * Instrumented test, which will execute on an Android device.
@@ -35,6 +40,17 @@ class ExampleInstrumentedTest {
@Test @Test
fun test_doSearch() { fun test_doSearch() {
val reader = getReader(1426382)
val data: ByteArray
with(URL(reader[0].url).openConnection() as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie)
data = inputStream.readBytes()
}
Log.d("Pupil", data.size.toString())
} }
} }

View File

@@ -3,6 +3,7 @@
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"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
@@ -14,16 +15,6 @@
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:name=".Pupil"> android:name=".Pupil">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="xyz.quaver.pupil.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths"/>
</provider>
<activity android:name=".ReaderActivity" <activity android:name=".ReaderActivity"
android:parentActivityName=".MainActivity" android:parentActivityName=".MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize"/> android:configChanges="keyboardHidden|orientation|screenSize"/>

View File

@@ -1,12 +1,14 @@
package xyz.quaver.pupil package xyz.quaver.pupil
import android.Manifest
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.text.* import android.text.*
import android.text.style.AlignmentSpan import android.text.style.AlignmentSpan
import android.util.Log
import android.view.* import android.view.*
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView import android.widget.ImageView
@@ -15,16 +17,20 @@ 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.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import com.arlib.floatingsearchview.FloatingSearchView import com.arlib.floatingsearchview.FloatingSearchView
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
import com.arlib.floatingsearchview.util.view.SearchInputView import com.arlib.floatingsearchview.util.view.SearchInputView
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main_content.* import kotlinx.android.synthetic.main.activity_main_content.*
import kotlinx.android.synthetic.main.dialog_galleryblock.view.* import kotlinx.android.synthetic.main.dialog_galleryblock.view.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
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.JsonObject import kotlinx.serialization.json.JsonObject
@@ -35,9 +41,12 @@ import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.types.TagSuggestion import xyz.quaver.pupil.types.TagSuggestion
import xyz.quaver.pupil.util.* import xyz.quaver.pupil.util.*
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.net.URL import java.net.URL
import java.util.* import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -68,6 +77,8 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
checkPermissions()
val preference = PreferenceManager.getDefaultSharedPreferences(this) val preference = PreferenceManager.getDefaultSharedPreferences(this)
if (Locale.getDefault().language == "ko") { if (Locale.getDefault().language == "ko") {
@@ -238,6 +249,11 @@ class MainActivity : AppCompatActivity() {
} }
} }
private fun checkPermissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 13489)
}
private fun initView() { private fun initView() {
var prevP1 = 0 var prevP1 = 0
main_appbar_layout.addOnOffsetChangedListener( main_appbar_layout.addOnOffsetChangedListener(
@@ -280,12 +296,7 @@ class MainActivity : AppCompatActivity() {
loadBlocks() loadBlocks()
} }
R.id.main_drawer_help -> { R.id.main_drawer_help -> {
AlertDialog.Builder(this@MainActivity).apply { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
title = getString(R.string.help_dialog_title)
setMessage(R.string.help_dialog_message)
setPositiveButton(android.R.string.ok) { _, _ -> }
}.show()
} }
R.id.main_drawer_github -> { R.id.main_drawer_github -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github)))) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github))))
@@ -359,10 +370,9 @@ class MainActivity : AppCompatActivity() {
isEnabled = !(adapter as GalleryBlockAdapter).completeFlag.get(galleryBlock.id, false) isEnabled = !(adapter as GalleryBlockAdapter).completeFlag.get(galleryBlock.id, false)
setOnClickListener { setOnClickListener {
val downloader = GalleryDownloader.get(galleryBlock.id) val downloader = GalleryDownloader.get(galleryBlock.id)
if (downloader == null) { if (downloader == null)
GalleryDownloader(context, galleryBlock, true).start() GalleryDownloader(context, galleryBlock, true).start()
downloads.add(galleryBlock.id) else {
} else {
downloader.cancel() downloader.cancel()
downloader.clearNotification() downloader.clearNotification()
} }
@@ -377,11 +387,91 @@ class MainActivity : AppCompatActivity() {
this?.cancelAndJoin() this?.cancelAndJoin()
this?.clearNotification() this?.clearNotification()
} }
val cache = File(cacheDir, "imageCache/${galleryBlock.id}/images/") val cache = File(cacheDir, "imageCache/${galleryBlock.id}")
val data = File(ContextCompat.getDataDir(this@MainActivity), "images/${galleryBlock.id}")
cache.deleteRecursively() cache.deleteRecursively()
data.deleteRecursively()
downloads.remove(galleryBlock.id)
if (mode == Mode.DOWNLOAD) {
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query)
loadBlocks()
}
}
(adapter as GalleryBlockAdapter).completeFlag.put(galleryBlock.id, false)
}
dialog.dismiss()
}
with(view.main_dialog_export) {
val images = File(ContextCompat.getDataDir(this@MainActivity), "images/${galleryBlock.id}/images").let {
when {
it.exists() -> it
else -> File(cacheDir, "imageCache/${galleryBlock.id}/images")
}
}
isEnabled = images.exists()
setOnClickListener {
CoroutineScope(Dispatchers.Default).launch {
val preference = PreferenceManager.getDefaultSharedPreferences(context)
val zip = preference.getBoolean("export_zip", false)
if (zip) {
var target = File(Environment.getExternalStorageDirectory(), "Pupil/${galleryBlock.id} ${galleryBlock.title}.zip")
try {
target.createNewFile()
} catch (e: IOException) {
target = File(Environment.getExternalStorageDirectory(), "Pupil/${galleryBlock.id}.zip")
try {
target.createNewFile()
} catch (e: IOException) {
Snackbar.make(main_layout, getString(R.string.main_export_error), Snackbar.LENGTH_LONG).show()
return@launch
}
}
FileOutputStream(target).use { targetStream ->
ZipOutputStream(targetStream).use {zipStream ->
images.listFiles().forEach {
zipStream.putNextEntry(ZipEntry(it.name))
FileInputStream(it).use { fileStream ->
fileStream.copyTo(zipStream)
}
}
}
}
} else {
var target = File(Environment.getExternalStorageDirectory(), "Pupil/${galleryBlock.id} ${galleryBlock.title}")
try {
target.canonicalPath
} catch (e: IOException) {
target = File(Environment.getExternalStorageDirectory(), "Pupil/${galleryBlock.id}")
try {
target.canonicalPath
} catch (e: IOException) {
Snackbar.make(main_layout, getString(R.string.main_export_error), Snackbar.LENGTH_LONG).show()
return@launch
}
}
images.copyRecursively(target, true)
}
Snackbar.make(main_layout, getString(R.string.main_export_complete), Snackbar.LENGTH_LONG).show()
}
dialog.dismiss() dialog.dismiss()
(adapter as GalleryBlockAdapter).completeFlag.put(galleryBlock.id, false)
} }
} }

View File

@@ -2,6 +2,7 @@ package xyz.quaver.pupil
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.view.* import android.view.*
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@@ -26,6 +27,11 @@ import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.pupil.adapters.ReaderAdapter import xyz.quaver.pupil.adapters.ReaderAdapter
import xyz.quaver.pupil.util.GalleryDownloader import xyz.quaver.pupil.util.GalleryDownloader
import xyz.quaver.pupil.util.ItemClickSupport import xyz.quaver.pupil.util.ItemClickSupport
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
class ReaderActivity : AppCompatActivity() { class ReaderActivity : AppCompatActivity() {
@@ -163,6 +169,7 @@ class ReaderActivity : AppCompatActivity() {
onProgressHandler = { onProgressHandler = {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
reader_progressbar.progress = it reader_progressbar.progress = it
menu?.findItem(R.id.reader_menu_use_hiyobi)?.isVisible = true
} }
} }
onDownloadedHandler = { onDownloadedHandler = {
@@ -274,6 +281,14 @@ class ReaderActivity : AppCompatActivity() {
if (!downloader.download) if (!downloader.download)
downloader.clearNotification() downloader.clearNotification()
} }
reader_fab_export.setOnClickListener {
downloader.export( {
Snackbar.make(reader_layout, getString(R.string.main_export_complete), Snackbar.LENGTH_LONG).show()
}, {
Snackbar.make(reader_layout, getString(R.string.main_export_error), Snackbar.LENGTH_LONG).show()
})
}
} }
private fun fullscreen(isFullscreen: Boolean) { private fun fullscreen(isFullscreen: Boolean) {

View File

@@ -12,6 +12,7 @@ import android.widget.LinearLayout
import android.widget.TextView 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.core.content.ContextCompat
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import kotlinx.android.synthetic.main.dialog_default_query.view.* import kotlinx.android.synthetic.main.dialog_default_query.view.*
@@ -95,6 +96,33 @@ class SettingsActivity : AppCompatActivity() {
true true
} }
} }
with(findPreference<Preference>("delete_downloads")) {
this ?: return@with
val dir = File(ContextCompat.getDataDir(context), "images")
summary = getCacheSize(dir)
setOnPreferenceClickListener {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_clear_downloads_alert_message)
setPositiveButton(android.R.string.yes) { _, _ ->
if (dir.exists())
dir.deleteRecursively()
val downloads = (activity!!.application as Pupil).downloads
downloads.clear()
summary = getCacheSize(dir)
}
setNegativeButton(android.R.string.no) { _, _ -> }
}.show()
true
}
}
with(findPreference<Preference>("clear_history")) { with(findPreference<Preference>("clear_history")) {
this ?: return@with this ?: return@with

View File

@@ -68,16 +68,30 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
} }
//Check cache //Check cache
val readerCache = File(context.cacheDir, "imageCache/${gallery.id}/reader.json") val readerCache = {
val imageCache = File(context.cacheDir, "imageCache/${gallery.id}/images") File(ContextCompat.getDataDir(context), "images/${gallery.id}/reader.json").let {
when {
it.exists() -> it
else -> File(context.cacheDir, "imageCache/${gallery.id}/reader.json")
}
}
}
val imageCache = {
File(ContextCompat.getDataDir(context), "images/${gallery.id}/images").let {
when {
it.exists() -> it
else -> File(context.cacheDir, "imageCache/${gallery.id}/images")
}
}
}
if (readerCache.exists()) { if (readerCache.invoke().exists()) {
val reader = Json(JsonConfiguration.Stable) val reader = Json(JsonConfiguration.Stable)
.parse(ReaderItem.serializer().list, readerCache.readText()) .parse(ReaderItem.serializer().list, readerCache.invoke().readText())
with(galleryblock_progressbar) { with(galleryblock_progressbar) {
max = reader.size max = reader.size
progress = imageCache.list()?.size ?: 0 progress = imageCache.invoke().list()?.size ?: 0
visibility = View.VISIBLE visibility = View.VISIBLE
} }
@@ -89,9 +103,9 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
val refresh = Timer(false).schedule(0, 1000) { val refresh = Timer(false).schedule(0, 1000) {
post { post {
with(view.galleryblock_progressbar) { with(view.galleryblock_progressbar) {
progress = imageCache.list()?.size ?: 0 progress = imageCache.invoke().list()?.size ?: 0
if (!readerCache.exists()) { if (!readerCache.invoke().exists()) {
visibility = View.GONE visibility = View.GONE
max = 0 max = 0
progress = 0 progress = 0
@@ -100,7 +114,7 @@ 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.readText()) .parse(ReaderItem.serializer().list, readerCache.invoke().readText())
max = reader.size max = reader.size
visibility = View.VISIBLE visibility = View.VISIBLE
} }

View File

@@ -4,10 +4,13 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.Intent import android.content.Intent
import android.os.Environment
import android.util.Log
import android.util.SparseArray import android.util.SparseArray
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.TaskStackBuilder import androidx.core.app.TaskStackBuilder
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.io.IOException import kotlinx.io.IOException
@@ -15,12 +18,18 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list import kotlinx.serialization.list
import xyz.quaver.hitomi.* import xyz.quaver.hitomi.*
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.user_agent
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.ReaderActivity import xyz.quaver.pupil.ReaderActivity
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.net.URL import java.net.URL
import java.util.* import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.concurrent.schedule import kotlin.concurrent.schedule
@@ -31,14 +40,27 @@ class GalleryDownloader(
_notify: Boolean = false _notify: Boolean = false
) : ContextWrapper(base) { ) : ContextWrapper(base) {
private val downloads = (applicationContext as Pupil).downloads
var useHiyobi = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("use_hiyobi", false)
var download: Boolean = false var download: Boolean = false
set(value) { set(value) {
if (value) { if (value) {
field = true field = true
notificationManager.notify(galleryBlock.id, notificationBuilder.build()) notificationManager.notify(galleryBlock.id, notificationBuilder.build())
val data = File(ContextCompat.getDataDir(this), "images/${galleryBlock.id}")
val cache = File(cacheDir, "imageCache/${galleryBlock.id}")
if (cache.exists() && !data.exists()) {
cache.copyRecursively(data, true)
cache.deleteRecursively()
}
if (!reader.isActive && downloadJob?.isActive != true) if (!reader.isActive && downloadJob?.isActive != true)
field = false field = false
downloads.add(galleryBlock.id)
} else { } else {
field = false field = false
} }
@@ -70,11 +92,14 @@ class GalleryDownloader(
download = _notify download = _notify
val json = Json(JsonConfiguration.Stable) val json = Json(JsonConfiguration.Stable)
val serializer = ReaderItem.serializer().list val serializer = ReaderItem.serializer().list
val preference = PreferenceManager.getDefaultSharedPreferences(this@GalleryDownloader)
val useHiyobi = preference.getBoolean("use_hiyobi", false)
//Check cache //Check cache
val cache = File(cacheDir, "imageCache/${galleryBlock.id}/reader.json") val cache = File(ContextCompat.getDataDir(this@GalleryDownloader), "images/${galleryBlock.id}/reader.json").let {
when {
it.exists() -> it
else -> File(cacheDir, "imageCache/${galleryBlock.id}/reader.json")
}
}
if (cache.exists()) { if (cache.exists()) {
val cached = json.parse(serializer, cache.readText()) val cached = json.parse(serializer, cache.readText())
@@ -90,7 +115,10 @@ class GalleryDownloader(
useHiyobi -> { useHiyobi -> {
xyz.quaver.hiyobi.getReader(galleryBlock.id).let { xyz.quaver.hiyobi.getReader(galleryBlock.id).let {
when { when {
it.isEmpty() -> getReader(galleryBlock.id) it.isEmpty() -> {
useHiyobi = false
getReader(galleryBlock.id)
}
else -> it else -> it
} }
} }
@@ -148,12 +176,21 @@ class GalleryDownloader(
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(cacheDir, "/imageCache/${galleryBlock.id}/images/$name.$ext") val cache = File(ContextCompat.getDataDir(this@GalleryDownloader), "images/${galleryBlock.id}/images/$name.$ext").let {
when {
it.exists() -> it
else -> File(cacheDir, "/imageCache/${galleryBlock.id}/images/$name.$ext")
}
}
if (!cache.exists()) if (!cache.exists())
try { try {
with(URL(url).openConnection() as HttpsURLConnection) { with(URL(url).openConnection() as HttpsURLConnection) {
setRequestProperty("Referer", getReferer(galleryBlock.id)) if (useHiyobi) {
setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie)
} else
setRequestProperty("Referer", getReferer(galleryBlock.id))
if (!cache.parentFile.exists()) if (!cache.parentFile.exists())
cache.parentFile.mkdirs() cache.parentFile.mkdirs()
@@ -189,8 +226,35 @@ class GalleryDownloader(
.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) {
notificationManager.notify(galleryBlock.id, notificationBuilder.build()) File(cacheDir, "imageCache/${galleryBlock.id}").let {
if (it.exists()) {
it.copyRecursively(
File(ContextCompat.getDataDir(this@GalleryDownloader), "images/${galleryBlock.id}"),
true
)
it.deleteRecursively()
}
}
val preference = PreferenceManager.getDefaultSharedPreferences(this@GalleryDownloader)
val autoExport = preference.getBoolean("auto_export", false)
if (autoExport) {
export({
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
}, {
notificationBuilder
.setContentTitle(galleryBlock.title)
.setContentText(getString(R.string.main_export_error))
.setProgress(0, 0, false)
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
})
} else {
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
}
}
download = false download = false
} }
@@ -207,6 +271,8 @@ class GalleryDownloader(
suspend fun cancelAndJoin() { suspend fun cancelAndJoin() {
downloadJob?.cancelAndJoin() downloadJob?.cancelAndJoin()
remove(galleryBlock.id)
} }
fun invokeOnReaderLoaded() { fun invokeOnReaderLoaded() {
@@ -243,4 +309,69 @@ class GalleryDownloader(
notificationManager = NotificationManagerCompat.from(this) notificationManager = NotificationManagerCompat.from(this)
} }
fun export(onSuccess: (() -> Unit)? = null, onError: (() -> Unit)? = null) {
val images = File(ContextCompat.getDataDir(this), "images/${galleryBlock.id}/images").let {
when {
it.exists() -> it
else -> File(cacheDir, "imageCache/${galleryBlock.id}/images")
}
}
if (!images.exists())
return
CoroutineScope(Dispatchers.Default).launch {
val preference = PreferenceManager.getDefaultSharedPreferences(this@GalleryDownloader)
val zip = preference.getBoolean("export_zip", false)
if (zip) {
var target = File(Environment.getExternalStorageDirectory(), "Pupil/${galleryBlock.id} ${galleryBlock.title}.zip")
try {
target.createNewFile()
} catch (e: IOException) {
target = File(Environment.getExternalStorageDirectory(), "Pupil/${galleryBlock.id}.zip")
try {
target.createNewFile()
} catch (e: IOException) {
onError?.invoke()
return@launch
}
}
FileOutputStream(target).use { targetStream ->
ZipOutputStream(targetStream).use { zipStream ->
images.listFiles().forEach {
zipStream.putNextEntry(ZipEntry(it.name))
FileInputStream(it).use { fileStream ->
fileStream.copyTo(zipStream)
}
}
}
}
} else {
var target = File(Environment.getExternalStorageDirectory(), "Pupil/${galleryBlock.id} ${galleryBlock.title}")
try {
target.canonicalPath
} catch (e: IOException) {
target = File(Environment.getExternalStorageDirectory(), "Pupil/${galleryBlock.id}")
try {
target.canonicalPath
} catch (e: IOException) {
onError?.invoke()
return@launch
}
}
images.copyRecursively(target, true)
}
onSuccess?.invoke()
}
}
} }

View File

@@ -0,0 +1,8 @@
<!-- drawable/export.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="M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z" />
</vector>

View File

@@ -51,6 +51,14 @@
app:fab_label="@string/reader_fab_download" app:fab_label="@string/reader_fab_download"
app:fab_size="mini"/> app:fab_size="mini"/>
<com.github.clans.fab.FloatingActionButton
android:id="@+id/reader_fab_export"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_export"
app:fab_label="@string/main_dialog_export"
app:fab_size="mini"/>
<com.github.clans.fab.FloatingActionButton <com.github.clans.fab.FloatingActionButton
android:id="@+id/reader_fab_fullscreen" android:id="@+id/reader_fab_fullscreen"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -20,4 +20,12 @@
android:text="@string/main_dialog_delete" android:text="@string/main_dialog_delete"
app:layout_constraintTop_toBottomOf="@id/main_dialog_download"/> app:layout_constraintTop_toBottomOf="@id/main_dialog_download"/>
<Button
android:id="@+id/main_dialog_export"
style="?borderlessButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/main_dialog_export"
app:layout_constraintTop_toBottomOf="@id/main_dialog_delete"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -2,6 +2,12 @@
<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/reader_menu_use_hiyobi"
android:title=""
android:icon="@drawable/ic_hiyobi"
app:showAsAction="ifRoom"
android:visible="false"/>
<item android:id="@+id/reader_menu_page_indicator" <item android:id="@+id/reader_menu_page_indicator"
android:title="@string/page_indicator_placeholder" android:title="@string/page_indicator_placeholder"
app:showAsAction="always|withText"/> app:showAsAction="always|withText"/>

View File

@@ -9,8 +9,8 @@
<string name="search_hint_with_page">ギャラリー検索</string> <string name="search_hint_with_page">ギャラリー検索</string>
<string name="settings_cache_title">キャッシュ</string> <string name="settings_cache_title">キャッシュ</string>
<string name="settings_clear_image_cache">イメージキャッシュクリア</string> <string name="settings_clear_image_cache">イメージキャッシュクリア</string>
<string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string> <string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。\n実行しますか?</string>
<string name="settings_clear_cache_summary">キャッシュサイズ: %1$d%2$s</string> <string name="settings_clear_cache_summary">サイズ: %1$d%2$s</string>
<string name="settings_default_query">デフォルトキーワード</string> <string name="settings_default_query">デフォルトキーワード</string>
<string name="settings_galleries_per_page">一回にロードするギャラリー数</string> <string name="settings_galleries_per_page">一回にロードするギャラリー数</string>
<string name="settings_search_title">検索設定</string> <string name="settings_search_title">検索設定</string>
@@ -19,7 +19,6 @@
<string name="update_title">新しいアップデートがあります</string> <string name="update_title">新しいアップデートがあります</string>
<string name="warning">注意</string> <string name="warning">注意</string>
<string name="settings_miscellaneous_title">その他</string> <string name="settings_miscellaneous_title">その他</string>
<string name="settings_use_hiyobi_summary">ロード速度を向上させるためhiyobi.meからイメージロード</string>
<string name="settings_use_hiyobi_title">hiyobi.meからロード</string> <string name="settings_use_hiyobi_title">hiyobi.meからロード</string>
<string name="settings_clear_history">履歴を削除</string> <string name="settings_clear_history">履歴を削除</string>
<string name="settings_clear_history_alert_message">履歴を削除しますか?</string> <string name="settings_clear_history_alert_message">履歴を削除しますか?</string>
@@ -41,8 +40,6 @@
<string name="main_drawer_group_contact_help">ヘルプ</string> <string name="main_drawer_group_contact_help">ヘルプ</string>
<string name="main_drawer_group_contact_github">Github</string> <string name="main_drawer_group_contact_github">Github</string>
<string name="main_drawer_group_contact_email">メールを送る</string> <string name="main_drawer_group_contact_email">メールを送る</string>
<string name="help_dialog_title">準備中</string>
<string name="help_dialog_message">準備中です。</string>
<string name="reader_fab_fullscreen">フルスクリーン</string> <string name="reader_fab_fullscreen">フルスクリーン</string>
<string name="channel_download">ダウンロード</string> <string name="channel_download">ダウンロード</string>
<string name="channel_download_description">ダウンロードの進行を通知</string> <string name="channel_download_description">ダウンロードの進行を通知</string>
@@ -59,4 +56,15 @@
<string name="main_move">%1$dページへ移動</string> <string name="main_move">%1$dページへ移動</string>
<string name="https_block_alert_title">(Korean only)</string> <string name="https_block_alert_title">(Korean only)</string>
<string name="https_block_alert">(Korean only)</string> <string name="https_block_alert">(Korean only)</string>
<string name="main_dialog_export">ギャラリーエクスポート</string>
<string name="main_export_complete">エクスポート完了</string>
<string name="main_export_open_folder">フォルダを開く</string>
<string name="main_export_error">エクスポートエラーが発生しました</string>
<string name="settings_export_zip_title">zipエクスポート</string>
<string name="settings_export_zip_summary">イメージフォルダの代わりzipファイルでエクスポート</string>
<string name="settings_auto_export_title">自動エクスポート</string>
<string name="settings_auto_export_summary">ダウンロード完了後自動的にエクスポート</string>
<string name="settings_clear_downloads">ダウンロード削除</string>
<string name="settings_clear_downloads_alert_message">ダウンロードしたギャラリーを全て削除します。\n実行しますか</string>
<string name="settings_use_hiyobi_summary">ロード速度を向上させるため可能であればhiyobi.meからイメージロード</string>
</resources> </resources>

View File

@@ -7,8 +7,8 @@
<string name="search_hint_with_page">갤러리 검색</string> <string name="search_hint_with_page">갤러리 검색</string>
<string name="settings_default_query">기본 검색어</string> <string name="settings_default_query">기본 검색어</string>
<string name="settings_clear_image_cache">이미지 캐시 정리하기</string> <string name="settings_clear_image_cache">이미지 캐시 정리하기</string>
<string name="settings_clear_cache_alert_message">캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까?</string> <string name="settings_clear_cache_alert_message">캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다.\n계속하시겠습니까?</string>
<string name="settings_clear_cache_summary">현재 캐시 사용량: %1$d%2$s</string> <string name="settings_clear_cache_summary">사용량: %1$d%2$s</string>
<string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string> <string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string>
<string name="settings_search_title">검색 설정</string> <string name="settings_search_title">검색 설정</string>
<string name="settings_title">설정</string> <string name="settings_title">설정</string>
@@ -19,7 +19,6 @@
<string name="main_search">검색</string> <string name="main_search">검색</string>
<string name="settings_cache_title">캐시</string> <string name="settings_cache_title">캐시</string>
<string name="settings_miscellaneous_title">기타</string> <string name="settings_miscellaneous_title">기타</string>
<string name="settings_use_hiyobi_summary">속도 향상을 위해 가능하면 hiyobi.me에서 이미지 로드</string>
<string name="settings_use_hiyobi_title">hiyobi.me 사용</string> <string name="settings_use_hiyobi_title">hiyobi.me 사용</string>
<string name="settings_clear_history">기록 삭제</string> <string name="settings_clear_history">기록 삭제</string>
<string name="settings_clear_history_alert_message">기록을 삭제하시겠습니까?</string> <string name="settings_clear_history_alert_message">기록을 삭제하시겠습니까?</string>
@@ -41,8 +40,6 @@
<string name="main_drawer_group_contact_help">도움말</string> <string name="main_drawer_group_contact_help">도움말</string>
<string name="main_drawer_group_contact_homepage">홈페이지</string> <string name="main_drawer_group_contact_homepage">홈페이지</string>
<string name="main_drawer_group_contact_title">문의</string> <string name="main_drawer_group_contact_title">문의</string>
<string name="help_dialog_title">준비 중</string>
<string name="help_dialog_message">준비중입니다.</string>
<string name="reader_fab_fullscreen">전체 화면</string> <string name="reader_fab_fullscreen">전체 화면</string>
<string name="channel_download">다운로드</string> <string name="channel_download">다운로드</string>
<string name="channel_download_description">다운로드 상태 알림</string> <string name="channel_download_description">다운로드 상태 알림</string>
@@ -59,4 +56,15 @@
<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 차단으로 접속이 안 되는 경우가 발생하고 있습니다\n이 경우 플레이스토어에서 SNIper앱을 이용하시면 정상이용이 가능합니다.</string>
<string name="main_dialog_export">갤러리 내보내기</string>
<string name="main_export_complete">내보내기 완료</string>
<string name="main_export_open_folder">폴더 열기</string>
<string name="main_export_error">내보내기 오류가 발생했습니다</string>
<string name="settings_export_zip_title">zip 파일로 내보내기</string>
<string name="settings_export_zip_summary">이미지 폴더 대신 zip 파일로 내보내기</string>
<string name="settings_auto_export_title">자동 내보내기</string>
<string name="settings_auto_export_summary">다운로드가 끝난 후 자동 내보내기</string>
<string name="settings_clear_downloads">다운로드 삭제</string>
<string name="settings_clear_downloads_alert_message">다운로드 된 만화를 모두 삭제합니다.\n계속하시겠습니까?</string>
<string name="settings_use_hiyobi_summary">속도 향상을 위해 가능하면 hiyobi.me에서 이미지 로드</string>
</resources> </resources>

View File

@@ -5,6 +5,7 @@
<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">https://tom5079.github.io/Pupil</string> <string name="home_page" translatable="false">https://tom5079.github.io/Pupil</string>
<string name="help" translatable="false">https://tom5079.github.io/Pupil/2019/06/02/manual-kr.html</string>
<string name="github" translatable="false">https://github.com/tom5079/Pupil-issue/issues/new/choose</string> <string name="github" translatable="false">https://github.com/tom5079/Pupil-issue/issues/new/choose</string>
<string name="email" translatable="false">mailto:pupil.hentai@gmail.com</string> <string name="email" translatable="false">mailto:pupil.hentai@gmail.com</string>
@@ -46,9 +47,11 @@
<string name="main_move">Move to page %1$d</string> <string name="main_move">Move to page %1$d</string>
<string name="main_dialog_delete">Delete this gallery</string> <string name="main_dialog_delete">Delete this gallery</string>
<string name="main_dialog_export">Export this gallery</string>
<string name="help_dialog_title">WIP</string> <string name="main_export_complete">Export completed</string>
<string name="help_dialog_message">While in progress!</string> <string name="main_export_open_folder">Open Folder</string>
<string name="main_export_error">Error occurred during export</string>
<string name="update_title">Update available</string> <string name="update_title">Update available</string>
<string name="update_download_started">Download started</string> <string name="update_download_started">Download started</string>
@@ -76,14 +79,20 @@
<string name="settings_default_query">Default query</string> <string name="settings_default_query">Default query</string>
<string name="settings_cache_title">Cache</string> <string name="settings_cache_title">Cache</string>
<string name="settings_clear_image_cache">Clear image cache</string> <string name="settings_clear_image_cache">Clear image cache</string>
<string name="settings_clear_cache_summary">Currently using %1$d%2$s of cache</string> <string name="settings_clear_cache_summary">Currently using %1$d%2$s</string>
<string name="settings_clear_cache_alert_message">Deleting cache can affect image loading speed. Do you want to continue?</string> <string name="settings_clear_cache_alert_message">Deleting cache can affect image loading speed.\nDo you want to continue?</string>
<string name="settings_clear_downloads">Clear downloads</string>
<string name="settings_clear_downloads_alert_message">Delete all downloaded galleries.\nDo you want to continue?</string>
<string name="settings_clear_history">Clear history</string> <string name="settings_clear_history">Clear history</string>
<string name="settings_clear_history_alert_message">Do you want to clear histories?</string> <string name="settings_clear_history_alert_message">Do you want to clear histories?</string>
<string name="settings_clear_history_summary">%1$d histories saved</string> <string name="settings_clear_history_summary">%1$d histories saved</string>
<string name="settings_miscellaneous_title">Miscellaneous</string> <string name="settings_miscellaneous_title">Miscellaneous</string>
<string name="settings_use_hiyobi_title">Use hiyobi.me</string> <string name="settings_use_hiyobi_title">Use hiyobi.me</string>
<string name="settings_use_hiyobi_summary">Load images from hiyobi.me to improve loading speed (if available)</string> <string name="settings_use_hiyobi_summary">Load images from hiyobi.me to improve loading speed (if available)</string>
<string name="settings_export_zip_title">Export zip</string>
<string name="settings_export_zip_summary">Export to zip instead of image folder</string>
<string name="settings_auto_export_title">Auto Export</string>
<string name="settings_auto_export_summary">Automatically exports galleries when download is finished</string>
<string name="settings_security_mode_title">Enable security mode</string> <string name="settings_security_mode_title">Enable security mode</string>
<string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string> <string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="Download" path="Download"/>
</paths>

View File

@@ -28,6 +28,10 @@
app:title="@string/settings_clear_image_cache" app:title="@string/settings_clear_image_cache"
app:key="delete_image_cache"/> app:key="delete_image_cache"/>
<Preference
app:title="@string/settings_clear_downloads"
app:key="delete_downloads"/>
<Preference <Preference
app:title="@string/settings_clear_history" app:title="@string/settings_clear_history"
app:key="clear_history"/> app:key="clear_history"/>
@@ -42,6 +46,16 @@
app:title="@string/settings_use_hiyobi_title" app:title="@string/settings_use_hiyobi_title"
app:summary="@string/settings_use_hiyobi_summary"/> app:summary="@string/settings_use_hiyobi_summary"/>
<SwitchPreference
app:key="export_zip"
app:title="@string/settings_export_zip_title"
app:summary="@string/settings_export_zip_summary"/>
<SwitchPreference
app:key="auto_export"
app:title="@string/settings_auto_export_title"
app:summary="@string/settings_auto_export_summary"/>
<SwitchPreference <SwitchPreference
app:key="security_mode" app:key="security_mode"
app:title="@string/settings_security_mode_title" app:title="@string/settings_security_mode_title"

View File

@@ -13,6 +13,12 @@ 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 = "" 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/"
@@ -32,9 +38,6 @@ fun renewCookie() : String {
fun getReader(galleryId: Int) : Reader { fun getReader(galleryId: Int) : Reader {
val url = "https://$hiyobi/data/json/${galleryId}_list.json" val url = "https://$hiyobi/data/json/${galleryId}_list.json"
if (cookie.isEmpty())
cookie = renewCookie()
try { try {
val json = Json(JsonConfiguration.Stable).parseJson( val json = Json(JsonConfiguration.Stable).parseJson(
with(URL(url).openConnection() as HttpsURLConnection) { with(URL(url).openConnection() as HttpsURLConnection) {

View File

@@ -14,8 +14,7 @@ class UnitTest {
fun test_nozomi() { fun test_nozomi() {
val nozomi = fetchNozomi(start = 0, count = 5) val nozomi = fetchNozomi(start = 0, count = 5)
for (n in nozomi) nozomi.first
println(n)
} }
@Test @Test
@@ -62,6 +61,8 @@ class UnitTest {
@Test @Test
fun test_hiyobi() { fun test_hiyobi() {
print(xyz.quaver.hiyobi.getReader(1415416).size) xyz.quaver.hiyobi.getReader(1415416).forEach {
println(it.url)
}
} }
} }