|
@@ -1,6 +1,7 @@
|
|
|
package eu.kanade.tachiyomi.ui.reader
|
|
|
|
|
|
import android.app.Application
|
|
|
+import android.net.Uri
|
|
|
import android.os.Bundle
|
|
|
import com.jakewharton.rxrelay.BehaviorRelay
|
|
|
import eu.kanade.tachiyomi.R
|
|
@@ -10,6 +11,9 @@ import eu.kanade.tachiyomi.data.database.models.History
|
|
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
|
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
|
+import eu.kanade.tachiyomi.data.saver.Image
|
|
|
+import eu.kanade.tachiyomi.data.saver.ImageSaver
|
|
|
+import eu.kanade.tachiyomi.data.saver.Location
|
|
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
|
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore
|
|
|
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingUpdateJob
|
|
@@ -28,11 +32,10 @@ import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
|
|
import eu.kanade.tachiyomi.util.isLocal
|
|
|
import eu.kanade.tachiyomi.util.lang.byteSize
|
|
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
|
+import eu.kanade.tachiyomi.util.lang.launchUI
|
|
|
import eu.kanade.tachiyomi.util.lang.takeBytes
|
|
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
|
|
-import eu.kanade.tachiyomi.util.storage.getPicturesDir
|
|
|
-import eu.kanade.tachiyomi.util.storage.getTempShareDir
|
|
|
-import eu.kanade.tachiyomi.util.system.ImageUtil
|
|
|
+import eu.kanade.tachiyomi.util.storage.cacheImageDir
|
|
|
import eu.kanade.tachiyomi.util.system.isOnline
|
|
|
import eu.kanade.tachiyomi.util.system.logcat
|
|
|
import eu.kanade.tachiyomi.util.updateCoverLastModified
|
|
@@ -45,7 +48,7 @@ import rx.android.schedulers.AndroidSchedulers
|
|
|
import rx.schedulers.Schedulers
|
|
|
import uy.kohesive.injekt.Injekt
|
|
|
import uy.kohesive.injekt.api.get
|
|
|
-import java.io.File
|
|
|
+import uy.kohesive.injekt.injectLazy
|
|
|
import java.util.Date
|
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
|
@@ -92,6 +95,8 @@ class ReaderPresenter(
|
|
|
*/
|
|
|
private val isLoadingAdjacentChapterRelay = BehaviorRelay.create<Boolean>()
|
|
|
|
|
|
+ private val imageSaver: ImageSaver by injectLazy()
|
|
|
+
|
|
|
/**
|
|
|
* Chapter list for the active manga. It's retrieved lazily and should be accessed for the first
|
|
|
* time in a background thread to avoid blocking the UI.
|
|
@@ -560,32 +565,6 @@ class ReaderPresenter(
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Saves the image of this [page] in the given [directory] and returns the file location.
|
|
|
- */
|
|
|
- private fun saveImage(page: ReaderPage, directory: File, manga: Manga): File {
|
|
|
- val stream = page.stream!!
|
|
|
- val type = ImageUtil.findImageType(stream) ?: throw Exception("Not an image")
|
|
|
-
|
|
|
- directory.mkdirs()
|
|
|
-
|
|
|
- val chapter = page.chapter.chapter
|
|
|
-
|
|
|
- // Build destination file.
|
|
|
- val filenameSuffix = " - ${page.number}.${type.extension}"
|
|
|
- val filename = DiskUtil.buildValidFilename(
|
|
|
- "${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize())
|
|
|
- ) + filenameSuffix
|
|
|
-
|
|
|
- val destFile = File(directory, filename)
|
|
|
- stream().use { input ->
|
|
|
- destFile.outputStream().use { output ->
|
|
|
- input.copyTo(output)
|
|
|
- }
|
|
|
- }
|
|
|
- return destFile
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* Saves the image of this [page] on the pictures directory and notifies the UI of the result.
|
|
|
* There's also a notification to allow sharing the image somewhere else or deleting it.
|
|
@@ -593,32 +572,42 @@ class ReaderPresenter(
|
|
|
fun saveImage(page: ReaderPage) {
|
|
|
if (page.status != Page.READY) return
|
|
|
val manga = manga ?: return
|
|
|
- val context = Injekt.get<Application>()
|
|
|
|
|
|
+ val context = Injekt.get<Application>()
|
|
|
val notifier = SaveImageNotifier(context)
|
|
|
notifier.onClear()
|
|
|
|
|
|
+ // Generate filename
|
|
|
+ val chapter = page.chapter.chapter
|
|
|
+ val filenameSuffix = " - ${page.number}"
|
|
|
+ val filename = DiskUtil.buildValidFilename(
|
|
|
+ "${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize())
|
|
|
+ ) + filenameSuffix
|
|
|
+
|
|
|
// Pictures directory.
|
|
|
- val baseDir = getPicturesDir(context).absolutePath
|
|
|
- val destDir = if (preferences.folderPerManga()) {
|
|
|
- File(baseDir + File.separator + DiskUtil.buildValidFilename(manga.title))
|
|
|
- } else {
|
|
|
- File(baseDir)
|
|
|
- }
|
|
|
+ val relativePath = if (preferences.folderPerManga()) DiskUtil.buildValidFilename(manga.title) else ""
|
|
|
|
|
|
// Copy file in background.
|
|
|
- Observable.fromCallable { saveImage(page, destDir, manga) }
|
|
|
- .doOnNext { file ->
|
|
|
- DiskUtil.scanMedia(context, file)
|
|
|
- notifier.onComplete(file)
|
|
|
+
|
|
|
+ try {
|
|
|
+ presenterScope.launchIO {
|
|
|
+ val uri = imageSaver.save(
|
|
|
+ image = Image.Page(
|
|
|
+ inputStream = page.stream!!,
|
|
|
+ name = filename,
|
|
|
+ location = Location.Pictures.create(relativePath)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ launchUI {
|
|
|
+ DiskUtil.scanMedia(context, uri)
|
|
|
+ notifier.onComplete(uri)
|
|
|
+ view!!.onSaveImageResult(SaveImageResult.Success(uri))
|
|
|
+ }
|
|
|
}
|
|
|
- .doOnError { notifier.onError(it.message) }
|
|
|
- .subscribeOn(Schedulers.io())
|
|
|
- .observeOn(AndroidSchedulers.mainThread())
|
|
|
- .subscribeFirst(
|
|
|
- { view, file -> view.onSaveImageResult(SaveImageResult.Success(file)) },
|
|
|
- { view, error -> view.onSaveImageResult(SaveImageResult.Error(error)) }
|
|
|
- )
|
|
|
+ } catch (e: Throwable) {
|
|
|
+ notifier.onError(e.message)
|
|
|
+ view!!.onSaveImageResult(SaveImageResult.Error(e))
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -631,18 +620,27 @@ class ReaderPresenter(
|
|
|
fun shareImage(page: ReaderPage) {
|
|
|
if (page.status != Page.READY) return
|
|
|
val manga = manga ?: return
|
|
|
- val context = Injekt.get<Application>()
|
|
|
|
|
|
- val destDir = getTempShareDir(context)
|
|
|
-
|
|
|
- Observable.fromCallable { destDir.deleteRecursively() } // Keep only the last shared file
|
|
|
- .map { saveImage(page, destDir, manga) }
|
|
|
- .subscribeOn(Schedulers.io())
|
|
|
- .observeOn(AndroidSchedulers.mainThread())
|
|
|
- .subscribeFirst(
|
|
|
- { view, file -> view.onShareImageResult(file, page) },
|
|
|
- { _, _ -> /* Empty */ }
|
|
|
- )
|
|
|
+ val context = Injekt.get<Application>()
|
|
|
+ val destDir = context.cacheImageDir
|
|
|
+
|
|
|
+ try {
|
|
|
+ presenterScope.launchIO {
|
|
|
+ destDir.deleteRecursively()
|
|
|
+ val uri = imageSaver.save(
|
|
|
+ image = Image.Page(
|
|
|
+ inputStream = page.stream!!,
|
|
|
+ name = manga.title,
|
|
|
+ location = Location.Cache
|
|
|
+ )
|
|
|
+ )
|
|
|
+ launchUI {
|
|
|
+ view!!.onShareImageResult(uri, page)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e: Throwable) {
|
|
|
+ logcat(LogPriority.ERROR, e)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -691,7 +689,7 @@ class ReaderPresenter(
|
|
|
* Results of the save image feature.
|
|
|
*/
|
|
|
sealed class SaveImageResult {
|
|
|
- class Success(val file: File) : SaveImageResult()
|
|
|
+ class Success(val uri: Uri) : SaveImageResult()
|
|
|
class Error(val error: Throwable) : SaveImageResult()
|
|
|
}
|
|
|
|