diff --git a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
index f6cbca02..79db10d2 100644
--- a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
@@ -7,7 +7,12 @@ import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
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.net.URL
+import javax.net.ssl.HttpsURLConnection
/**
* Instrumented test, which will execute on an Android device.
@@ -35,6 +40,17 @@ class ExampleInstrumentedTest {
@Test
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())
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 766a3ca7..e8cbecd4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
package="xyz.quaver.pupil">
+
-
-
-
-
diff --git a/app/src/main/java/xyz/quaver/pupil/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/MainActivity.kt
index d35b14b4..28079aaf 100644
--- a/app/src/main/java/xyz/quaver/pupil/MainActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/MainActivity.kt
@@ -1,12 +1,14 @@
package xyz.quaver.pupil
+import android.Manifest
import android.content.Intent
+import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
+import android.os.Environment
import android.preference.PreferenceManager
import android.text.*
import android.text.style.AlignmentSpan
-import android.util.Log
import android.view.*
import android.widget.EditText
import android.widget.ImageView
@@ -15,16 +17,20 @@ import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.GravityCompat
import com.arlib.floatingsearchview.FloatingSearchView
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
import com.arlib.floatingsearchview.util.view.SearchInputView
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_content.*
import kotlinx.android.synthetic.main.dialog_galleryblock.view.*
import kotlinx.coroutines.*
+import kotlinx.io.IOException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
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.util.*
import java.io.File
+import java.io.FileInputStream
import java.io.FileOutputStream
import java.net.URL
import java.util.*
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList
import kotlin.math.roundToInt
@@ -68,6 +77,8 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ checkPermissions()
+
val preference = PreferenceManager.getDefaultSharedPreferences(this)
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() {
var prevP1 = 0
main_appbar_layout.addOnOffsetChangedListener(
@@ -280,12 +296,7 @@ class MainActivity : AppCompatActivity() {
loadBlocks()
}
R.id.main_drawer_help -> {
- AlertDialog.Builder(this@MainActivity).apply {
- title = getString(R.string.help_dialog_title)
- setMessage(R.string.help_dialog_message)
-
- setPositiveButton(android.R.string.ok) { _, _ -> }
- }.show()
+ startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
}
R.id.main_drawer_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)
setOnClickListener {
val downloader = GalleryDownloader.get(galleryBlock.id)
- if (downloader == null) {
+ if (downloader == null)
GalleryDownloader(context, galleryBlock, true).start()
- downloads.add(galleryBlock.id)
- } else {
+ else {
downloader.cancel()
downloader.clearNotification()
}
@@ -377,11 +387,91 @@ class MainActivity : AppCompatActivity() {
this?.cancelAndJoin()
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()
+ 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()
- (adapter as GalleryBlockAdapter).completeFlag.put(galleryBlock.id, false)
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ReaderActivity.kt
index c92e65b0..57cc02cd 100644
--- a/app/src/main/java/xyz/quaver/pupil/ReaderActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ReaderActivity.kt
@@ -2,6 +2,7 @@ package xyz.quaver.pupil
import android.graphics.drawable.Drawable
import android.os.Bundle
+import android.os.Environment
import android.view.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
@@ -26,6 +27,11 @@ import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.pupil.adapters.ReaderAdapter
import xyz.quaver.pupil.util.GalleryDownloader
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() {
@@ -163,6 +169,7 @@ class ReaderActivity : AppCompatActivity() {
onProgressHandler = {
CoroutineScope(Dispatchers.Main).launch {
reader_progressbar.progress = it
+ menu?.findItem(R.id.reader_menu_use_hiyobi)?.isVisible = true
}
}
onDownloadedHandler = {
@@ -274,6 +281,14 @@ class ReaderActivity : AppCompatActivity() {
if (!downloader.download)
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) {
diff --git a/app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt b/app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt
index 7f941162..9f7b30be 100644
--- a/app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt
@@ -12,6 +12,7 @@ import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import kotlinx.android.synthetic.main.dialog_default_query.view.*
@@ -95,6 +96,33 @@ class SettingsActivity : AppCompatActivity() {
true
}
}
+ with(findPreference("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("clear_history")) {
this ?: return@with
diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt
index c75529b7..ed8aa83d 100644
--- a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt
+++ b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt
@@ -68,16 +68,30 @@ class GalleryBlockAdapter(private val galleries: List 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)
- .parse(ReaderItem.serializer().list, readerCache.readText())
+ .parse(ReaderItem.serializer().list, readerCache.invoke().readText())
with(galleryblock_progressbar) {
max = reader.size
- progress = imageCache.list()?.size ?: 0
+ progress = imageCache.invoke().list()?.size ?: 0
visibility = View.VISIBLE
}
@@ -89,9 +103,9 @@ class GalleryBlockAdapter(private val galleries: List it
+ else -> File(cacheDir, "imageCache/${galleryBlock.id}/reader.json")
+ }
+ }
if (cache.exists()) {
val cached = json.parse(serializer, cache.readText())
@@ -90,7 +115,10 @@ class GalleryDownloader(
useHiyobi -> {
xyz.quaver.hiyobi.getReader(galleryBlock.id).let {
when {
- it.isEmpty() -> getReader(galleryBlock.id)
+ it.isEmpty() -> {
+ useHiyobi = false
+ getReader(galleryBlock.id)
+ }
else -> it
}
}
@@ -148,12 +176,21 @@ class GalleryDownloader(
val name = "$index".padStart(4, '0')
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())
try {
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())
cache.parentFile.mkdirs()
@@ -189,8 +226,35 @@ class GalleryDownloader(
.setContentText(getString(R.string.reader_notification_complete))
.setProgress(0, 0, false)
- if (download)
- notificationManager.notify(galleryBlock.id, notificationBuilder.build())
+ if (download) {
+ 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
}
@@ -207,6 +271,8 @@ class GalleryDownloader(
suspend fun cancelAndJoin() {
downloadJob?.cancelAndJoin()
+
+ remove(galleryBlock.id)
}
fun invokeOnReaderLoaded() {
@@ -243,4 +309,69 @@ class GalleryDownloader(
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()
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_export.xml b/app/src/main/res/drawable/ic_export.xml
new file mode 100644
index 00000000..32d0ca69
--- /dev/null
+++ b/app/src/main/res/drawable/ic_export.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_reader.xml b/app/src/main/res/layout/activity_reader.xml
index 9e49df68..e9827e8d 100644
--- a/app/src/main/res/layout/activity_reader.xml
+++ b/app/src/main/res/layout/activity_reader.xml
@@ -51,6 +51,14 @@
app:fab_label="@string/reader_fab_download"
app:fab_size="mini"/>
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/reader.xml b/app/src/main/res/menu/reader.xml
index 2eb9eaaa..22a61f0f 100644
--- a/app/src/main/res/menu/reader.xml
+++ b/app/src/main/res/menu/reader.xml
@@ -2,6 +2,12 @@