Added code to prevent OutOfMemory error. Made notification optional. Can now save image on long press. Bug fixes
This commit is contained in:
parent
1210691fdd
commit
4975787afa
14 changed files with 180 additions and 48 deletions
|
@ -47,6 +47,8 @@ class DownloadManager(
|
|||
private val threadsSubject = BehaviorSubject.create<Int>()
|
||||
private var threadsSubscription: Subscription? = null
|
||||
|
||||
private var notificationSubscription: Subscription? = null
|
||||
|
||||
val queue = DownloadQueue()
|
||||
|
||||
val imageFilenameRegex = "[^\\sa-zA-Z0-9.-]".toRegex()
|
||||
|
@ -66,6 +68,12 @@ class DownloadManager(
|
|||
downloadNotifier.multipleDownloadThreads = it > 1
|
||||
}
|
||||
|
||||
notificationSubscription = preferences.showMangaDownloadNotification().asObservable()
|
||||
.subscribe {
|
||||
downloadNotifier.onClear()
|
||||
downloadNotifier.showNotification = it
|
||||
}
|
||||
|
||||
downloadsSubscription = downloadsQueueSubject.flatMap { Observable.from(it) }
|
||||
.lift(DynamicConcurrentMergeOperator<Download, Download>({ downloadChapter(it) }, threadsSubject))
|
||||
.onBackpressureBuffer()
|
||||
|
@ -107,6 +115,10 @@ class DownloadManager(
|
|||
threadsSubscription?.unsubscribe()
|
||||
}
|
||||
|
||||
if (notificationSubscription != null) {
|
||||
notificationSubscription?.unsubscribe()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Create a download object for every chapter and add them to the downloads queue
|
||||
|
@ -188,7 +200,7 @@ class DownloadManager(
|
|||
DiskUtils.createDirectory(download.directory)
|
||||
|
||||
val pageListObservable: Observable<List<Page>> = if (download.pages == null)
|
||||
// Pull page list from network and add them to download object
|
||||
// Pull page list from network and add them to download object
|
||||
download.source.fetchPageListFromNetwork(download.chapter)
|
||||
.doOnNext { pages ->
|
||||
download.pages = pages
|
||||
|
|
|
@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.R
|
|||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
||||
import eu.kanade.tachiyomi.util.notificationManager
|
||||
import eu.kanade.tachiyomi.util.toast
|
||||
|
||||
/**
|
||||
* DownloadNotifier is used to show notifications when downloading one or multiple chapters.
|
||||
|
@ -40,6 +41,11 @@ class DownloadNotifier(private val context: Context) {
|
|||
*/
|
||||
internal var multipleDownloadThreads = false
|
||||
|
||||
/**
|
||||
* Value determining if notification should be shown
|
||||
*/
|
||||
internal var showNotification = true
|
||||
|
||||
/**
|
||||
* Called when download progress changes.
|
||||
* Note: Only accepted when multi download active.
|
||||
|
@ -47,9 +53,8 @@ class DownloadNotifier(private val context: Context) {
|
|||
* @param queue the queue containing downloads.
|
||||
*/
|
||||
internal fun onProgressChange(queue: DownloadQueue) {
|
||||
if (multipleDownloadThreads) {
|
||||
if (multipleDownloadThreads && showNotification)
|
||||
doOnProgressChange(null, queue)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,9 +65,8 @@ class DownloadNotifier(private val context: Context) {
|
|||
* @param queue the queue containing downloads
|
||||
*/
|
||||
internal fun onProgressChange(download: Download, queue: DownloadQueue) {
|
||||
if (!multipleDownloadThreads) {
|
||||
if (!multipleDownloadThreads && showNotification)
|
||||
doOnProgressChange(download, queue)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,7 +90,7 @@ class DownloadNotifier(private val context: Context) {
|
|||
}
|
||||
|
||||
// Create notification
|
||||
with (notificationBuilder) {
|
||||
with(notificationBuilder) {
|
||||
// Check if icon needs refresh
|
||||
if (!isDownloading) {
|
||||
setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
|
@ -127,17 +131,18 @@ class DownloadNotifier(private val context: Context) {
|
|||
* @param download download object containing download information
|
||||
*/
|
||||
private fun onComplete(download: Download?) {
|
||||
// Create notification.
|
||||
with(notificationBuilder) {
|
||||
setContentTitle(download?.chapter?.name ?: context.getString(R.string.app_name))
|
||||
setContentText(context.getString(R.string.update_check_notification_download_complete))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
setProgress(0, 0, false)
|
||||
if (showNotification) {
|
||||
// Create notification.
|
||||
with(notificationBuilder) {
|
||||
setContentTitle(download?.chapter?.name ?: context.getString(R.string.app_name))
|
||||
setContentText(context.getString(R.string.update_check_notification_download_complete))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
setProgress(0, 0, false)
|
||||
}
|
||||
|
||||
// Show notification.
|
||||
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
||||
}
|
||||
|
||||
// Show notification.
|
||||
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
||||
|
||||
// Reset initial values
|
||||
isDownloading = false
|
||||
initialQueueSize = 0
|
||||
|
@ -158,14 +163,17 @@ class DownloadNotifier(private val context: Context) {
|
|||
*/
|
||||
internal fun onError(error: String? = null, chapter: String? = null) {
|
||||
// Create notification
|
||||
with(notificationBuilder) {
|
||||
setContentTitle(chapter ?: context.getString(R.string.download_notifier_title_error))
|
||||
setContentText(error ?: context.getString(R.string.download_notifier_unkown_error))
|
||||
setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||
setProgress(0, 0, false)
|
||||
if (showNotification) {
|
||||
with(notificationBuilder) {
|
||||
setContentTitle(chapter ?: context.getString(R.string.download_notifier_title_error))
|
||||
setContentText(error ?: context.getString(R.string.download_notifier_unkown_error))
|
||||
setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||
setProgress(0, 0, false)
|
||||
}
|
||||
context.notificationManager.notify(Constants.NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID, notificationBuilder.build())
|
||||
} else {
|
||||
context.toast(error ?: context.getString(R.string.download_notifier_unkown_error))
|
||||
}
|
||||
context.notificationManager.notify(Constants.NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID, notificationBuilder.build())
|
||||
|
||||
// Reset download information
|
||||
onClear()
|
||||
isDownloading = false
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package eu.kanade.tachiyomi.data.download
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import eu.kanade.tachiyomi.Constants
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.decodeSampledBitmap
|
||||
import eu.kanade.tachiyomi.util.notificationManager
|
||||
import java.io.File
|
||||
|
||||
|
@ -52,9 +51,9 @@ class ImageNotifier(private val context: Context) {
|
|||
|
||||
/**
|
||||
* Called when image download is complete
|
||||
* @param bitmap image file containing downloaded page image
|
||||
* @param file image file containing downloaded page image
|
||||
*/
|
||||
fun onComplete(bitmap: Bitmap, file: File) {
|
||||
fun onComplete(file: File) {
|
||||
with(notificationBuilder) {
|
||||
if (isDownloading) {
|
||||
setProgress(0, 0, false)
|
||||
|
@ -62,8 +61,8 @@ class ImageNotifier(private val context: Context) {
|
|||
}
|
||||
setContentTitle(context.getString(R.string.picture_saved))
|
||||
setSmallIcon(R.drawable.ic_insert_photo_black_24dp)
|
||||
setLargeIcon(bitmap)
|
||||
setStyle(NotificationCompat.BigPictureStyle().bigPicture(bitmap))
|
||||
setLargeIcon(file.decodeSampledBitmap(100, 100))
|
||||
setStyle(NotificationCompat.BigPictureStyle().bigPicture(file.decodeSampledBitmap(1024, 1024)))
|
||||
setAutoCancel(true)
|
||||
|
||||
// Clear old actions if they exist
|
||||
|
@ -84,10 +83,6 @@ class ImageNotifier(private val context: Context) {
|
|||
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
||||
}
|
||||
|
||||
fun onComplete(file: File) {
|
||||
onComplete(convertToBitmap(file), file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the notification message
|
||||
*/
|
||||
|
@ -112,13 +107,4 @@ class ImageNotifier(private val context: Context) {
|
|||
isDownloading = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts file to bitmap
|
||||
*/
|
||||
fun convertToBitmap(image: File): Bitmap {
|
||||
val options = BitmapFactory.Options()
|
||||
options.inPreferredConfig = Bitmap.Config.ARGB_8888
|
||||
return BitmapFactory.decodeFile(image.absolutePath, options)
|
||||
}
|
||||
|
||||
}
|
|
@ -72,6 +72,10 @@ class PreferenceKeys(context: Context) {
|
|||
|
||||
val removeAfterMarkedAsRead = context.getString(R.string.pref_remove_after_marked_as_read_key)
|
||||
|
||||
val showMangaDownloadNotification = context.getString(R.string.pref_notifications_manga_download_key)
|
||||
|
||||
val showSavePageNotification = context.getString(R.string.pref_notifications_single_page_key)
|
||||
|
||||
val libraryUpdateInterval = context.getString(R.string.pref_library_update_interval_key)
|
||||
|
||||
val libraryUpdateRestriction = context.getString(R.string.pref_library_update_restriction_key)
|
||||
|
|
|
@ -122,6 +122,10 @@ class PreferencesHelper(context: Context) {
|
|||
|
||||
fun removeAfterMarkedAsRead() = prefs.getBoolean(keys.removeAfterMarkedAsRead, false)
|
||||
|
||||
fun showMangaDownloadNotification() = rxPrefs.getBoolean(keys.showMangaDownloadNotification, true)
|
||||
|
||||
fun showSavePageNotification() = prefs.getBoolean(keys.showSavePageNotification, false)
|
||||
|
||||
fun libraryUpdateInterval() = rxPrefs.getInteger(keys.libraryUpdateInterval, 0)
|
||||
|
||||
fun libraryUpdateRestriction() = prefs.getStringSet(keys.libraryUpdateRestriction, emptySet())
|
||||
|
|
|
@ -145,8 +145,6 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||
when (item.itemId) {
|
||||
R.id.action_settings -> ReaderSettingsDialog().show(supportFragmentManager, "settings")
|
||||
R.id.action_custom_filter -> ReaderCustomFilterDialog().show(supportFragmentManager, "filter")
|
||||
R.id.action_save_page -> presenter.savePage()
|
||||
R.id.action_set_as_cover -> presenter.setCover()
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
return true
|
||||
|
@ -230,6 +228,22 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||
// Ignore
|
||||
}
|
||||
|
||||
fun onLongPress() {
|
||||
MaterialDialog.Builder(this).apply {
|
||||
title = "Choose"
|
||||
items(R.array.reader_image_options)
|
||||
.itemsIds(R.array.reader_image_options_values)
|
||||
itemsCallback { materialDialog, view, i, charSequence ->
|
||||
when (i) {
|
||||
0 -> presenter.setCover()
|
||||
1 -> presenter.shareImage()
|
||||
2 -> presenter.savePage()
|
||||
}
|
||||
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the presenter at startup, allowing to prepare the selected reader.
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package eu.kanade.tachiyomi.ui.reader
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
@ -576,6 +578,19 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||
return false
|
||||
}
|
||||
|
||||
fun shareImage() {
|
||||
chapter.pages?.get(chapter.last_page_read)?.let { page ->
|
||||
val shareIntent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_STREAM, Uri.parse(page.imagePath))
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
type = "image/jpeg"
|
||||
}
|
||||
context.startActivity(Intent.createChooser(shareIntent, context.resources.getText(R.string.action_share))
|
||||
.apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save page to local storage
|
||||
* @throws IOException
|
||||
|
@ -595,7 +610,10 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||
|
||||
//Check if file doesn't already exist
|
||||
if (destFile.exists()) {
|
||||
imageNotifier.onComplete(destFile)
|
||||
if (prefs.showSavePageNotification())
|
||||
imageNotifier.onComplete(destFile)
|
||||
else
|
||||
context.toast(context.getString(R.string.page_downloaded, destFile.path))
|
||||
} else {
|
||||
if (inputFile.exists()) {
|
||||
// Copy file
|
||||
|
@ -606,7 +624,10 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||
{ imageNotifier.onComplete(it) },
|
||||
{ error ->
|
||||
Timber.e(error.message)
|
||||
imageNotifier.onError(error.message)
|
||||
if (prefs.showSavePageNotification())
|
||||
imageNotifier.onError(error.message)
|
||||
else
|
||||
context.toast(error.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -185,6 +185,11 @@ abstract class PagerReader : BaseReader() {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onLongPress(e: MotionEvent?) {
|
||||
super.onLongPress(e)
|
||||
readerActivity.onLongPress()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -140,6 +140,11 @@ class WebtoonReader : BaseReader() {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onLongPress(e: MotionEvent?) {
|
||||
super.onLongPress(e)
|
||||
readerActivity.onLongPress()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
40
app/src/main/java/eu/kanade/tachiyomi/util/FileExtensions.kt
Normal file
40
app/src/main/java/eu/kanade/tachiyomi/util/FileExtensions.kt
Normal file
|
@ -0,0 +1,40 @@
|
|||
package eu.kanade.tachiyomi.util
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import java.io.File
|
||||
|
||||
fun File.decodeSampledBitmap(reqWidth: Int, reqHeight: Int): Bitmap {
|
||||
// First decode with inJustDecodeBounds=true to check dimensions
|
||||
val options = BitmapFactory.Options()
|
||||
options.inJustDecodeBounds = true
|
||||
BitmapFactory.decodeFile(this.absolutePath, options)
|
||||
|
||||
// Calculate inSampleSize
|
||||
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
|
||||
|
||||
// Decode bitmap with inSampleSize set
|
||||
options.inJustDecodeBounds = false;
|
||||
return BitmapFactory.decodeFile(this.absolutePath, options)
|
||||
}
|
||||
|
||||
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
|
||||
// Raw height and width of image
|
||||
val height = options.outHeight
|
||||
val width = options.outWidth
|
||||
var inSampleSize = 1
|
||||
|
||||
if (height > reqHeight || width > reqWidth) {
|
||||
|
||||
val halfHeight = height / 2
|
||||
val halfWidth = width / 2
|
||||
|
||||
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
||||
// height and width larger than the requested height and width.
|
||||
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
|
||||
inSampleSize *= 2
|
||||
}
|
||||
}
|
||||
|
||||
return inSampleSize
|
||||
}
|
|
@ -174,4 +174,16 @@
|
|||
<item>3</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="reader_image_options">
|
||||
<item>@string/set_as_cover</item>
|
||||
<item>@string/share_image</item>
|
||||
<item>@string/save_image</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="reader_image_options_values">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
|
@ -48,7 +48,8 @@
|
|||
<string name="pref_download_only_over_wifi_key">pref_download_only_over_wifi_key</string>
|
||||
<string name="pref_remove_after_marked_as_read_key">pref_remove_after_marked_as_read_key</string>
|
||||
<string name="pref_category_remove_after_read_key">pref_category_remove_after_read_key</string>
|
||||
|
||||
<string name="pref_notifications_single_page_key">notifications_single_page</string>
|
||||
<string name="pref_notifications_manga_download_key">notifications_manga_download</string>
|
||||
<string name="pref_last_used_category_key">last_used_category</string>
|
||||
|
||||
<string name="pref_source_languages">pref_source_languages</string>
|
||||
|
|
|
@ -281,6 +281,11 @@
|
|||
|
||||
<!-- Reader activity -->
|
||||
<string name="custom_filter">Custom filter</string>
|
||||
<string name="set_as_cover">Set as cover</string>
|
||||
<string name="share_image">Share image</string>
|
||||
<string name="save_image">Save image</string>
|
||||
<string name="cover_updated">Cover updated</string>
|
||||
<string name="page_downloaded">Page copied to %1$s</string>
|
||||
<string name="downloading">Downloading…</string>
|
||||
<string name="download_progress">Downloaded %1$d%%</string>
|
||||
<string name="chapter_progress">Page: %1$d</string>
|
||||
|
|
|
@ -40,6 +40,21 @@
|
|||
android:summary="%s"
|
||||
android:title="@string/pref_remove_after_read" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_notifications" />
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="@string/pref_notifications_manga_download_key"
|
||||
android:title="@string/pref_notifications_manga_download" />
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="@string/pref_notifications_single_page_key"
|
||||
android:title="@string/pref_notifications_single_page" />
|
||||
|
||||
|
||||
</PreferenceScreen>
|
||||
|
||||
</PreferenceScreen>
|
Reference in a new issue