Added code to prevent OutOfMemory error. Made notification optional. Can now save image on long press. Bug fixes

This commit is contained in:
Bram van de Kerkhof 2016-10-06 21:51:51 +02:00
parent 1210691fdd
commit 4975787afa
14 changed files with 180 additions and 48 deletions

View File

@ -47,6 +47,8 @@ class DownloadManager(
private val threadsSubject = BehaviorSubject.create<Int>() private val threadsSubject = BehaviorSubject.create<Int>()
private var threadsSubscription: Subscription? = null private var threadsSubscription: Subscription? = null
private var notificationSubscription: Subscription? = null
val queue = DownloadQueue() val queue = DownloadQueue()
val imageFilenameRegex = "[^\\sa-zA-Z0-9.-]".toRegex() val imageFilenameRegex = "[^\\sa-zA-Z0-9.-]".toRegex()
@ -66,6 +68,12 @@ class DownloadManager(
downloadNotifier.multipleDownloadThreads = it > 1 downloadNotifier.multipleDownloadThreads = it > 1
} }
notificationSubscription = preferences.showMangaDownloadNotification().asObservable()
.subscribe {
downloadNotifier.onClear()
downloadNotifier.showNotification = it
}
downloadsSubscription = downloadsQueueSubject.flatMap { Observable.from(it) } downloadsSubscription = downloadsQueueSubject.flatMap { Observable.from(it) }
.lift(DynamicConcurrentMergeOperator<Download, Download>({ downloadChapter(it) }, threadsSubject)) .lift(DynamicConcurrentMergeOperator<Download, Download>({ downloadChapter(it) }, threadsSubject))
.onBackpressureBuffer() .onBackpressureBuffer()
@ -107,6 +115,10 @@ class DownloadManager(
threadsSubscription?.unsubscribe() threadsSubscription?.unsubscribe()
} }
if (notificationSubscription != null) {
notificationSubscription?.unsubscribe()
}
} }
// Create a download object for every chapter and add them to the downloads queue // Create a download object for every chapter and add them to the downloads queue

View File

@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.util.notificationManager import eu.kanade.tachiyomi.util.notificationManager
import eu.kanade.tachiyomi.util.toast
/** /**
* DownloadNotifier is used to show notifications when downloading one or multiple chapters. * 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 internal var multipleDownloadThreads = false
/**
* Value determining if notification should be shown
*/
internal var showNotification = true
/** /**
* Called when download progress changes. * Called when download progress changes.
* Note: Only accepted when multi download active. * Note: Only accepted when multi download active.
@ -47,10 +53,9 @@ class DownloadNotifier(private val context: Context) {
* @param queue the queue containing downloads. * @param queue the queue containing downloads.
*/ */
internal fun onProgressChange(queue: DownloadQueue) { internal fun onProgressChange(queue: DownloadQueue) {
if (multipleDownloadThreads) { if (multipleDownloadThreads && showNotification)
doOnProgressChange(null, queue) doOnProgressChange(null, queue)
} }
}
/** /**
* Called when download progress changes * Called when download progress changes
@ -60,10 +65,9 @@ class DownloadNotifier(private val context: Context) {
* @param queue the queue containing downloads * @param queue the queue containing downloads
*/ */
internal fun onProgressChange(download: Download, queue: DownloadQueue) { internal fun onProgressChange(download: Download, queue: DownloadQueue) {
if (!multipleDownloadThreads) { if (!multipleDownloadThreads && showNotification)
doOnProgressChange(download, queue) doOnProgressChange(download, queue)
} }
}
/** /**
* Show notification progress of chapter * Show notification progress of chapter
@ -127,6 +131,7 @@ class DownloadNotifier(private val context: Context) {
* @param download download object containing download information * @param download download object containing download information
*/ */
private fun onComplete(download: Download?) { private fun onComplete(download: Download?) {
if (showNotification) {
// Create notification. // Create notification.
with(notificationBuilder) { with(notificationBuilder) {
setContentTitle(download?.chapter?.name ?: context.getString(R.string.app_name)) setContentTitle(download?.chapter?.name ?: context.getString(R.string.app_name))
@ -137,7 +142,7 @@ class DownloadNotifier(private val context: Context) {
// Show notification. // Show notification.
context.notificationManager.notify(notificationId, notificationBuilder.build()) context.notificationManager.notify(notificationId, notificationBuilder.build())
}
// Reset initial values // Reset initial values
isDownloading = false isDownloading = false
initialQueueSize = 0 initialQueueSize = 0
@ -158,6 +163,7 @@ class DownloadNotifier(private val context: Context) {
*/ */
internal fun onError(error: String? = null, chapter: String? = null) { internal fun onError(error: String? = null, chapter: String? = null) {
// Create notification // Create notification
if (showNotification) {
with(notificationBuilder) { with(notificationBuilder) {
setContentTitle(chapter ?: context.getString(R.string.download_notifier_title_error)) setContentTitle(chapter ?: context.getString(R.string.download_notifier_title_error))
setContentText(error ?: context.getString(R.string.download_notifier_unkown_error)) setContentText(error ?: context.getString(R.string.download_notifier_unkown_error))
@ -165,7 +171,9 @@ class DownloadNotifier(private val context: Context) {
setProgress(0, 0, false) setProgress(0, 0, false)
} }
context.notificationManager.notify(Constants.NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID, notificationBuilder.build()) context.notificationManager.notify(Constants.NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID, notificationBuilder.build())
} else {
context.toast(error ?: context.getString(R.string.download_notifier_unkown_error))
}
// Reset download information // Reset download information
onClear() onClear()
isDownloading = false isDownloading = false

View File

@ -1,11 +1,10 @@
package eu.kanade.tachiyomi.data.download package eu.kanade.tachiyomi.data.download
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationCompat
import eu.kanade.tachiyomi.Constants import eu.kanade.tachiyomi.Constants
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.decodeSampledBitmap
import eu.kanade.tachiyomi.util.notificationManager import eu.kanade.tachiyomi.util.notificationManager
import java.io.File import java.io.File
@ -52,9 +51,9 @@ class ImageNotifier(private val context: Context) {
/** /**
* Called when image download is complete * 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) { with(notificationBuilder) {
if (isDownloading) { if (isDownloading) {
setProgress(0, 0, false) setProgress(0, 0, false)
@ -62,8 +61,8 @@ class ImageNotifier(private val context: Context) {
} }
setContentTitle(context.getString(R.string.picture_saved)) setContentTitle(context.getString(R.string.picture_saved))
setSmallIcon(R.drawable.ic_insert_photo_black_24dp) setSmallIcon(R.drawable.ic_insert_photo_black_24dp)
setLargeIcon(bitmap) setLargeIcon(file.decodeSampledBitmap(100, 100))
setStyle(NotificationCompat.BigPictureStyle().bigPicture(bitmap)) setStyle(NotificationCompat.BigPictureStyle().bigPicture(file.decodeSampledBitmap(1024, 1024)))
setAutoCancel(true) setAutoCancel(true)
// Clear old actions if they exist // Clear old actions if they exist
@ -84,10 +83,6 @@ class ImageNotifier(private val context: Context) {
context.notificationManager.notify(notificationId, notificationBuilder.build()) context.notificationManager.notify(notificationId, notificationBuilder.build())
} }
fun onComplete(file: File) {
onComplete(convertToBitmap(file), file)
}
/** /**
* Clears the notification message * Clears the notification message
*/ */
@ -112,13 +107,4 @@ class ImageNotifier(private val context: Context) {
isDownloading = false 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)
}
} }

View File

@ -72,6 +72,10 @@ class PreferenceKeys(context: Context) {
val removeAfterMarkedAsRead = context.getString(R.string.pref_remove_after_marked_as_read_key) 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 libraryUpdateInterval = context.getString(R.string.pref_library_update_interval_key)
val libraryUpdateRestriction = context.getString(R.string.pref_library_update_restriction_key) val libraryUpdateRestriction = context.getString(R.string.pref_library_update_restriction_key)

View File

@ -122,6 +122,10 @@ class PreferencesHelper(context: Context) {
fun removeAfterMarkedAsRead() = prefs.getBoolean(keys.removeAfterMarkedAsRead, false) 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 libraryUpdateInterval() = rxPrefs.getInteger(keys.libraryUpdateInterval, 0)
fun libraryUpdateRestriction() = prefs.getStringSet(keys.libraryUpdateRestriction, emptySet()) fun libraryUpdateRestriction() = prefs.getStringSet(keys.libraryUpdateRestriction, emptySet())

View File

@ -145,8 +145,6 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
when (item.itemId) { when (item.itemId) {
R.id.action_settings -> ReaderSettingsDialog().show(supportFragmentManager, "settings") R.id.action_settings -> ReaderSettingsDialog().show(supportFragmentManager, "settings")
R.id.action_custom_filter -> ReaderCustomFilterDialog().show(supportFragmentManager, "filter") 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) else -> return super.onOptionsItemSelected(item)
} }
return true return true
@ -230,6 +228,22 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
// Ignore // 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. * Called from the presenter at startup, allowing to prepare the selected reader.
*/ */

View File

@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.ui.reader package eu.kanade.tachiyomi.ui.reader
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment import android.os.Environment
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -576,6 +578,19 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
return false 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 * Save page to local storage
* @throws IOException * @throws IOException
@ -595,7 +610,10 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
//Check if file doesn't already exist //Check if file doesn't already exist
if (destFile.exists()) { if (destFile.exists()) {
if (prefs.showSavePageNotification())
imageNotifier.onComplete(destFile) imageNotifier.onComplete(destFile)
else
context.toast(context.getString(R.string.page_downloaded, destFile.path))
} else { } else {
if (inputFile.exists()) { if (inputFile.exists()) {
// Copy file // Copy file
@ -606,7 +624,10 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
{ imageNotifier.onComplete(it) }, { imageNotifier.onComplete(it) },
{ error -> { error ->
Timber.e(error.message) Timber.e(error.message)
if (prefs.showSavePageNotification())
imageNotifier.onError(error.message) imageNotifier.onError(error.message)
else
context.toast(error.message)
}) })
} }
} }

View File

@ -185,6 +185,11 @@ abstract class PagerReader : BaseReader() {
} }
return true return true
} }
override fun onLongPress(e: MotionEvent?) {
super.onLongPress(e)
readerActivity.onLongPress()
}
}) })
} }

View File

@ -140,6 +140,11 @@ class WebtoonReader : BaseReader() {
} }
return true return true
} }
override fun onLongPress(e: MotionEvent?) {
super.onLongPress(e)
readerActivity.onLongPress()
}
}) })
} }

View 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
}

View File

@ -174,4 +174,16 @@
<item>3</item> <item>3</item>
</string-array> </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> </resources>

View File

@ -48,7 +48,8 @@
<string name="pref_download_only_over_wifi_key">pref_download_only_over_wifi_key</string> <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_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_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_last_used_category_key">last_used_category</string>
<string name="pref_source_languages">pref_source_languages</string> <string name="pref_source_languages">pref_source_languages</string>

View File

@ -281,6 +281,11 @@
<!-- Reader activity --> <!-- Reader activity -->
<string name="custom_filter">Custom filter</string> <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="downloading">Downloading…</string>
<string name="download_progress">Downloaded %1$d%%</string> <string name="download_progress">Downloaded %1$d%%</string>
<string name="chapter_progress">Page: %1$d</string> <string name="chapter_progress">Page: %1$d</string>

View File

@ -40,6 +40,21 @@
android:summary="%s" android:summary="%s"
android:title="@string/pref_remove_after_read" /> 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>
</PreferenceScreen> </PreferenceScreen>