Browse Source

Fixes + API 16 support

Bram van de Kerkhof 8 years ago
parent
commit
c2b113ac0a
28 changed files with 219 additions and 278 deletions
  1. 1 1
      app/src/main/AndroidManifest.xml
  2. 18 28
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt
  3. 0 132
      app/src/main/java/eu/kanade/tachiyomi/data/download/ImageNotifier.kt
  4. 25 11
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
  5. 31 68
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
  6. 30 13
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/notification/ImageNotificationReceiver.kt
  7. 93 0
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/notification/ImageNotifier.kt
  8. 8 2
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt
  9. 11 7
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt
  10. BIN
      app/src/main/res/drawable-hdpi/ic_delete_grey_24dp.png
  11. BIN
      app/src/main/res/drawable-hdpi/ic_insert_photo_white_24dp.png
  12. BIN
      app/src/main/res/drawable-hdpi/ic_share_grey_24dp.png
  13. BIN
      app/src/main/res/drawable-mdpi/ic_delete_grey_24dp.png
  14. BIN
      app/src/main/res/drawable-mdpi/ic_insert_photo_white_24dp.png
  15. BIN
      app/src/main/res/drawable-mdpi/ic_share_grey_24dp.png
  16. BIN
      app/src/main/res/drawable-xhdpi/ic_delete_grey_24dp.png
  17. BIN
      app/src/main/res/drawable-xhdpi/ic_insert_photo_white_24dp.png
  18. BIN
      app/src/main/res/drawable-xhdpi/ic_share_grey_24dp.png
  19. BIN
      app/src/main/res/drawable-xxhdpi/ic_delete_grey_24dp.png
  20. BIN
      app/src/main/res/drawable-xxhdpi/ic_insert_photo_white_24dp.png
  21. BIN
      app/src/main/res/drawable-xxhdpi/ic_share_grey_24dp.png
  22. BIN
      app/src/main/res/drawable-xxxhdpi/ic_delete_grey_24dp.png
  23. BIN
      app/src/main/res/drawable-xxxhdpi/ic_insert_photo_white_24dp.png
  24. BIN
      app/src/main/res/drawable-xxxhdpi/ic_share_grey_24dp.png
  25. 0 9
      app/src/main/res/drawable/ic_insert_photo_black_24dp.xml
  26. BIN
      app/src/main/res/drawable/ic_insert_photo_white_24dp.png
  27. 0 2
      app/src/main/res/values/keys.xml
  28. 2 5
      app/src/main/res/values/strings.xml

+ 1 - 1
app/src/main/AndroidManifest.xml

@@ -88,7 +88,7 @@
 
         <receiver android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver" />
 
-        <receiver android:name=".data.download.ImageNotificationReceiver" />
+        <receiver android:name=".ui.reader.notification.ImageNotificationReceiver" />
 
         <meta-data
             android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"

+ 18 - 28
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt

@@ -41,11 +41,6 @@ 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.
@@ -53,7 +48,7 @@ class DownloadNotifier(private val context: Context) {
      * @param queue the queue containing downloads.
      */
     internal fun onProgressChange(queue: DownloadQueue) {
-        if (multipleDownloadThreads && showNotification)
+        if (multipleDownloadThreads)
             doOnProgressChange(null, queue)
     }
 
@@ -65,7 +60,7 @@ class DownloadNotifier(private val context: Context) {
      * @param queue the queue containing downloads
      */
     internal fun onProgressChange(download: Download, queue: DownloadQueue) {
-        if (!multipleDownloadThreads && showNotification)
+        if (!multipleDownloadThreads)
             doOnProgressChange(download, queue)
     }
 
@@ -131,18 +126,17 @@ class DownloadNotifier(private val context: Context) {
      * @param download download object containing download information
      */
     private fun onComplete(download: Download?) {
-        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())
+        // 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())
+
         // Reset initial values
         isDownloading = false
         initialQueueSize = 0
@@ -163,17 +157,13 @@ class DownloadNotifier(private val context: Context) {
      */
     internal fun onError(error: String? = null, chapter: String? = null) {
         // Create notification
-        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))
+        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())
         // Reset download information
         onClear()
         isDownloading = false

+ 0 - 132
app/src/main/java/eu/kanade/tachiyomi/data/download/ImageNotifier.kt

@@ -1,132 +0,0 @@
-package eu.kanade.tachiyomi.data.download
-
-import android.content.Context
-import android.graphics.Bitmap
-import android.support.v4.app.NotificationCompat
-import com.bumptech.glide.Glide
-import com.bumptech.glide.request.animation.GlideAnimation
-import com.bumptech.glide.request.target.SimpleTarget
-import eu.kanade.tachiyomi.Constants
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.util.notificationManager
-import java.io.File
-
-
-class ImageNotifier(private val context: Context) {
-    /**
-     * Notification builder.
-     */
-    private val notificationBuilder = NotificationCompat.Builder(context)
-
-    /**
-     * Id of the notification.
-     */
-    private val notificationId: Int
-        get() = Constants.NOTIFICATION_DOWNLOAD_IMAGE_ID
-
-    /**
-     * Status of download. Used for correct notification icon.
-     */
-    private var isDownloading = false
-
-    /**
-     * Called when download progress changes.
-     * @param progress progress value in range [0,100]
-     */
-    fun onProgressChange(progress: Int) {
-        with(notificationBuilder) {
-            if (!isDownloading) {
-                setContentTitle(context.getString(R.string.saving_picture))
-                setSmallIcon(android.R.drawable.stat_sys_download)
-                setLargeIcon(null)
-                setStyle(null)
-                // Clear old actions if they exist
-                if (!mActions.isEmpty())
-                    mActions.clear()
-                isDownloading = true
-            }
-
-            setProgress(100, progress, false)
-        }
-        // Displays the progress bar on notification
-        context.notificationManager.notify(notificationId, notificationBuilder.build())
-    }
-
-    /**
-     * Called when image download is complete
-     * @param file image file containing downloaded page image
-     */
-    fun onComplete(file: File) {
-        with(notificationBuilder) {
-            if (isDownloading) {
-                setProgress(0, 0, false)
-                isDownloading = false
-            }
-            setContentTitle(context.getString(R.string.picture_saved))
-            setSmallIcon(R.drawable.ic_insert_photo_black_24dp)
-            Glide.with(context).load(file).asBitmap().into(object : SimpleTarget<Bitmap>(96, 96) {
-                /**
-                 * The method that will be called when the resource load has finished.
-                 * @param resource the loaded resource.
-                 */
-                override fun onResourceReady(resource: Bitmap?, glideAnimation: GlideAnimation<in Bitmap>?) {
-                    setLargeIcon(resource)
-                    context.notificationManager.notify(notificationId, notificationBuilder.build())
-                }
-            })
-            Glide.with(context).load(file).asBitmap().into(object : SimpleTarget<Bitmap>(720, 1280) {
-                /**
-                 * The method that will be called when the resource load has finished.
-                 * @param resource the loaded resource.
-                 */
-                override fun onResourceReady(resource: Bitmap?, glideAnimation: GlideAnimation<in Bitmap>?) {
-                    setStyle(NotificationCompat.BigPictureStyle().bigPicture(resource))
-                    context.notificationManager.notify(notificationId, notificationBuilder.build())
-                }
-            })
-
-            setAutoCancel(true)
-
-            // Clear old actions if they exist
-            if (!mActions.isEmpty())
-                mActions.clear()
-
-            setContentIntent(ImageNotificationReceiver.showImageIntent(context, file.absolutePath))
-            // Share action
-            addAction(R.drawable.ic_share_white_24dp,
-                    context.getString(R.string.action_share),
-                    ImageNotificationReceiver.shareImageIntent(context, file.absolutePath, notificationId))
-            // Delete action
-            addAction(R.drawable.ic_delete_white_24dp,
-                    context.getString(R.string.action_delete),
-                    ImageNotificationReceiver.deleteImageIntent(context, file.absolutePath, notificationId))
-        }
-        // Displays the progress bar on notification
-        context.notificationManager.notify(notificationId, notificationBuilder.build())
-    }
-
-    /**
-     * Clears the notification message
-     */
-    internal fun onClear() {
-        context.notificationManager.cancel(notificationId)
-    }
-
-
-    /**
-     * Called on error while downloading image
-     * @param error string containing error information
-     */
-    internal fun onError(error: String?) {
-        // Create notification
-        with(notificationBuilder) {
-            setContentTitle(context.getString(R.string.download_notifier_title_error))
-            setContentText(error ?: context.getString(R.string.download_notifier_unkown_error))
-            setSmallIcon(android.R.drawable.ic_menu_report_image)
-            setProgress(0, 0, false)
-        }
-        context.notificationManager.notify(notificationId, notificationBuilder.build())
-        isDownloading = false
-    }
-
-}

+ 25 - 11
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt

@@ -5,6 +5,7 @@ import android.content.Intent
 import android.content.pm.ActivityInfo
 import android.content.res.Configuration
 import android.graphics.Color
+import android.net.Uri
 import android.os.Build
 import android.os.Build.VERSION_CODES.KITKAT
 import android.os.Bundle
@@ -229,19 +230,17 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
     }
 
     fun onLongPress(page: Page) {
-        MaterialDialog.Builder(this).apply {
-            title = "Choose"
-            items(R.array.reader_image_options)
+        MaterialDialog.Builder(this)
+            .title(getString(R.string.options))
+            .items(R.array.reader_image_options)
                     .itemsIds(R.array.reader_image_options_values)
-            itemsCallback { materialDialog, view, i, charSequence ->
+            .itemsCallback { materialDialog, view, i, charSequence ->
                 when (i) {
                     0 -> presenter.setCover(page)
-                    1 -> presenter.shareImage(page)
+                    1 -> shareImage(page)
                     2 -> presenter.savePage(page)
                 }
-
             }.show()
-        }
     }
 
     /**
@@ -409,16 +408,16 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
 
     private fun setRotation(rotation: Int) {
         when (rotation) {
-        // Rotation free
+            // Rotation free
             1 -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
-        // Lock in current rotation
+            // Lock in current rotation
             2 -> {
                 val currentOrientation = resources.configuration.orientation
                 setRotation(if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) 3 else 4)
             }
-        // Lock in portrait
+            // Lock in portrait
             3 -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
-        // Lock in landscape
+            // Lock in landscape
             4 -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
         }
     }
@@ -471,6 +470,21 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
         }
     }
 
+    /**
+     * Start a share intent that lets user share image
+     *
+     * @param page page object containing image information.
+     */
+    fun shareImage(page: 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"
+        }
+        startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.action_share)))
+    }
+
     /**
      * Sets the brightness of the screen. Range is [-75, 100].
      * From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.

+ 31 - 68
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt

@@ -1,7 +1,5 @@
 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
@@ -13,15 +11,14 @@ import eu.kanade.tachiyomi.data.database.models.History
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.database.models.MangaSync
 import eu.kanade.tachiyomi.data.download.DownloadManager
-import eu.kanade.tachiyomi.data.download.ImageNotifier
 import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
 import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService
-import eu.kanade.tachiyomi.data.network.NetworkHelper
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.data.source.SourceManager
 import eu.kanade.tachiyomi.data.source.model.Page
 import eu.kanade.tachiyomi.data.source.online.OnlineSource
 import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
+import eu.kanade.tachiyomi.ui.reader.notification.ImageNotifier
 import eu.kanade.tachiyomi.util.RetryWithDelay
 import eu.kanade.tachiyomi.util.SharedData
 import eu.kanade.tachiyomi.util.toast
@@ -33,19 +30,12 @@ import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
 import java.io.File
 import java.io.IOException
-import java.io.InputStream
 import java.util.*
 
 /**
  * Presenter of [ReaderActivity].
  */
 class ReaderPresenter : BasePresenter<ReaderActivity>() {
-
-    /**
-     * Network helper
-     */
-    private val network: NetworkHelper by injectLazy()
-
     /**
      * Preferences.
      */
@@ -108,11 +98,6 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
      */
     private val source by lazy { sourceManager.get(manga.source)!! }
 
-    /**
-     *
-     */
-    val imageNotifier by lazy { ImageNotifier(context) }
-
     /**
      * Directory of pictures
      */
@@ -398,13 +383,13 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
             if (chapter.read) {
                 val removeAfterReadSlots = prefs.removeAfterReadSlots()
                 when (removeAfterReadSlots) {
-                // Setting disabled
+                    // Setting disabled
                     -1 -> {
                         /**Empty function**/
                     }
-                // Remove current read chapter
+                    // Remove current read chapter
                     0 -> deleteChapter(chapter, manga)
-                // Remove previous chapter specified by user in settings.
+                    // Remove previous chapter specified by user in settings.
                     else -> getAdjacentChaptersStrategy(chapter, removeAfterReadSlots)
                             .first?.let { deleteChapter(it, manga) }
                 }
@@ -548,43 +533,21 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
      * Update cover with page file.
      */
     internal fun setCover(page: Page) {
-            // Update cover to selected file, show error if something went wrong
-            try {
-                if (editCoverWithStream(File(page.imagePath).inputStream(), manga)) {
+        try {
+            if (manga.favorite) {
+                if (manga.thumbnail_url != null) {
+                    coverCache.copyToCache(manga.thumbnail_url!!, File(page.imagePath).inputStream())
                     context.toast(R.string.cover_updated)
                 } else {
-                    throw Exception("Stream copy failed")
+                    throw Exception("Image url not found")
                 }
-            } catch(e: Exception) {
-                context.toast(R.string.notification_manga_update_failed)
-                Timber.e(e.message)
+            } else {
+                context.toast(R.string.notification_first_add_to_library)
             }
-    }
-
-    /**
-     * Called to copy image to cache
-     * @param inputStream the new cover.
-     * @param manga the manga edited.
-     * @return true if the cover is updated, false otherwise
-     */
-    @Throws(IOException::class)
-    private fun editCoverWithStream(inputStream: InputStream, manga: Manga): Boolean {
-        if (manga.thumbnail_url != null && manga.favorite) {
-            coverCache.copyToCache(manga.thumbnail_url!!, inputStream)
-            return true
+        } catch (error: Exception) {
+            context.toast(R.string.notification_cover_update_failed)
+            Timber.e(error)
         }
-        return false
-    }
-
-    fun shareImage(page: 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 })
     }
 
     /**
@@ -593,6 +556,9 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
      */
     @Throws(IOException::class)
     internal fun savePage(page: Page) {
+        // Used to show image notification
+        val imageNotifier = ImageNotifier(context)
+
         // Location of image file.
         val inputFile = File(page.imagePath)
 
@@ -602,23 +568,20 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
 
         //Remove the notification if already exist (user feedback)
         imageNotifier.onClear()
-
-        //Check if file doesn't already exist
-        if (destFile.exists()) {
-            imageNotifier.onComplete(destFile)
-        } else {
-            if (inputFile.exists()) {
-                // Copy file
-                Observable.fromCallable { inputFile.copyTo(destFile) }
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .subscribeOn(Schedulers.io())
-                        .subscribe(
-                                { imageNotifier.onComplete(it) },
-                                { error ->
-                                    Timber.e(error.message)
-                                    imageNotifier.onError(error.message)
-                                })
-            }
+        if (inputFile.exists()) {
+            // Copy file
+            Observable.fromCallable { inputFile.copyTo(destFile, true) }
+                    .subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .subscribe(
+                            {
+                                // Show notification
+                                imageNotifier.onComplete(it)
+                            },
+                            { error ->
+                                Timber.e(error)
+                                imageNotifier.onError(error.message)
+                            })
         }
     }
 }

+ 30 - 13
app/src/main/java/eu/kanade/tachiyomi/data/download/ImageNotificationReceiver.kt → app/src/main/java/eu/kanade/tachiyomi/ui/reader/notification/ImageNotificationReceiver.kt

@@ -1,4 +1,4 @@
-package eu.kanade.tachiyomi.data.download
+package eu.kanade.tachiyomi.ui.reader.notification
 
 import android.app.PendingIntent
 import android.content.BroadcastReceiver
@@ -9,6 +9,10 @@ import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.util.notificationManager
 import java.io.File
 
+/**
+ * The BroadcastReceiver of [ImageNotifier]
+ * Intent calls should be made from this class.
+ */
 class ImageNotificationReceiver : BroadcastReceiver() {
     override fun onReceive(context: Context, intent: Intent) {
         when (intent.action) {
@@ -25,23 +29,36 @@ class ImageNotificationReceiver : BroadcastReceiver() {
         }
     }
 
-    fun deleteImage(path: String) {
+    /**
+     * Called to delete image
+     * @param path path of file
+     */
+    private fun deleteImage(path: String) {
         val file = File(path)
         if (file.exists()) file.delete()
     }
 
-    fun shareImage(context: Context, path: String) {
+    /**
+     * Called to start share intent to share image
+     * @param context context of application
+     * @param path path of file
+     */
+    private fun shareImage(context: Context, path: String) {
         val shareIntent = Intent().apply {
             action = Intent.ACTION_SEND
             putExtra(Intent.EXTRA_STREAM, Uri.parse(path))
-            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 })
     }
 
-    fun showImage(context: Context, path: String) {
+    /**
+     * Called to show image in gallery application
+     * @param context context of application
+     * @param path path of file
+     */
+    private fun showImage(context: Context, path: String) {
         val intent = Intent().apply {
             action = Intent.ACTION_VIEW
             flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
@@ -51,17 +68,17 @@ class ImageNotificationReceiver : BroadcastReceiver() {
     }
 
     companion object {
-        const val ACTION_SHARE_IMAGE = "eu.kanade.SHARE_IMAGE"
+        private const val ACTION_SHARE_IMAGE = "eu.kanade.SHARE_IMAGE"
 
-        const val ACTION_SHOW_IMAGE = "eu.kanade.SHOW_IMAGE"
+        private const val ACTION_SHOW_IMAGE = "eu.kanade.SHOW_IMAGE"
 
-        const val ACTION_DELETE_IMAGE = "eu.kanade.DELETE_IMAGE"
+        private const val ACTION_DELETE_IMAGE = "eu.kanade.DELETE_IMAGE"
 
-        const val EXTRA_FILE_LOCATION = "file_location"
+        private const val EXTRA_FILE_LOCATION = "file_location"
 
-        const val NOTIFICATION_ID = "notification_id"
+        private const val NOTIFICATION_ID = "notification_id"
 
-        fun shareImageIntent(context: Context, path: String, notificationId: Int): PendingIntent {
+        internal fun shareImageIntent(context: Context, path: String, notificationId: Int): PendingIntent {
             val intent = Intent(context, ImageNotificationReceiver::class.java).apply {
                 action = ACTION_SHARE_IMAGE
                 putExtra(EXTRA_FILE_LOCATION, path)
@@ -70,7 +87,7 @@ class ImageNotificationReceiver : BroadcastReceiver() {
             return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
         }
 
-        fun showImageIntent(context: Context, path: String): PendingIntent {
+        internal fun showImageIntent(context: Context, path: String): PendingIntent {
             val intent = Intent(context, ImageNotificationReceiver::class.java).apply {
                 action = ACTION_SHOW_IMAGE
                 putExtra(EXTRA_FILE_LOCATION, path)
@@ -78,7 +95,7 @@ class ImageNotificationReceiver : BroadcastReceiver() {
             return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
         }
 
-        fun deleteImageIntent(context: Context, path: String, notificationId: Int): PendingIntent {
+        internal fun deleteImageIntent(context: Context, path: String, notificationId: Int): PendingIntent {
             val intent = Intent(context, ImageNotificationReceiver::class.java).apply {
                 action = ACTION_DELETE_IMAGE
                 putExtra(EXTRA_FILE_LOCATION, path)

+ 93 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/reader/notification/ImageNotifier.kt

@@ -0,0 +1,93 @@
+package eu.kanade.tachiyomi.ui.reader.notification
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.support.v4.app.NotificationCompat
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import com.bumptech.glide.request.animation.GlideAnimation
+import com.bumptech.glide.request.target.SimpleTarget
+import eu.kanade.tachiyomi.Constants
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.util.notificationManager
+import java.io.File
+
+/**
+ * Class used to show BigPictureStyle notifications
+ */
+class ImageNotifier(private val context: Context) {
+    /**
+     * Notification builder.
+     */
+    private val notificationBuilder = NotificationCompat.Builder(context)
+
+    /**
+     * Id of the notification.
+     */
+    private val notificationId: Int
+        get() = Constants.NOTIFICATION_DOWNLOAD_IMAGE_ID
+
+    /**
+     * Called when image download/copy is complete
+     * @param file image file containing downloaded page image
+     */
+    fun onComplete(file: File) {
+        with(notificationBuilder) {
+            Glide.with(context).load(file).asBitmap().diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true).into(object : SimpleTarget<Bitmap>(720, 1280) {
+                /**
+                 * The method that will be called when the resource load has finished.
+                 * @param resource the loaded resource.
+                 */
+                override fun onResourceReady(resource: Bitmap?, glideAnimation: GlideAnimation<in Bitmap>?) {
+                    setContentTitle(context.getString(R.string.picture_saved))
+                    setSmallIcon(R.drawable.ic_insert_photo_white_24dp)
+                    setStyle(NotificationCompat.BigPictureStyle().bigPicture(resource))
+                    setLargeIcon(resource)
+                    setAutoCancel(true)
+                    // Clear old actions if they exist
+                    if (!mActions.isEmpty())
+                        mActions.clear()
+
+                    setContentIntent(ImageNotificationReceiver.showImageIntent(context, file.absolutePath))
+                    // Share action
+                    addAction(R.drawable.ic_share_grey_24dp,
+                            context.getString(R.string.action_share),
+                            ImageNotificationReceiver.shareImageIntent(context, file.absolutePath, notificationId))
+                    // Delete action
+                    addAction(R.drawable.ic_delete_grey_24dp,
+                            context.getString(R.string.action_delete),
+                            ImageNotificationReceiver.deleteImageIntent(context, file.absolutePath, notificationId))
+                    updateNotification()
+                }
+            })
+        }
+    }
+
+    /**
+     * Clears the notification message
+     */
+    fun onClear() {
+        context.notificationManager.cancel(notificationId)
+    }
+
+    private fun updateNotification() {
+        // Displays the progress bar on notification
+        context.notificationManager.notify(notificationId, notificationBuilder.build())
+    }
+
+
+    /**
+     * Called on error while downloading image
+     * @param error string containing error information
+     */
+    fun onError(error: String?) {
+        // Create notification
+        with(notificationBuilder) {
+            setContentTitle(context.getString(R.string.download_notifier_title_error))
+            setContentText(error ?: context.getString(R.string.unknown_error))
+            setSmallIcon(android.R.drawable.ic_menu_report_image)
+        }
+        updateNotification()
+    }
+
+}

+ 8 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt

@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.ui.reader.ReaderChapter
 import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
 import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
 import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader
+import eu.kanade.tachiyomi.util.toast
 import rx.subscriptions.CompositeSubscription
 
 /**
@@ -187,8 +188,13 @@ abstract class PagerReader : BaseReader() {
             }
 
             override fun onLongPress(e: MotionEvent?) {
-                super.onLongPress(e)
-                readerActivity.onLongPress(adapter.pages!![pager.currentItem])
+                if (isAdded) {
+                    val page = adapter.pages.getOrNull(pager.currentItem)
+                    if (page != null)
+                        readerActivity.onLongPress(page)
+                    else
+                        context.toast(getString(R.string.unknown_error))
+                }
             }
         })
     }

+ 11 - 7
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt

@@ -6,9 +6,11 @@ import android.view.*
 import android.view.GestureDetector.SimpleOnGestureListener
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.source.model.Page
 import eu.kanade.tachiyomi.ui.reader.ReaderChapter
 import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
+import eu.kanade.tachiyomi.util.toast
 import eu.kanade.tachiyomi.widget.PreCachingLayoutManager
 import rx.subscriptions.CompositeSubscription
 
@@ -142,17 +144,21 @@ class WebtoonReader : BaseReader() {
             }
 
             override fun onLongPress(e: MotionEvent) {
-                super.onLongPress(e)
-                val a = recycler.findChildViewUnder(e.rawX, e.rawY)
-                val i = recycler.getChildAdapterPosition(a)
-                readerActivity.onLongPress(adapter.getItem(i))
+                if (isAdded) {
+                    val child = recycler.findChildViewUnder(e.rawX, e.rawY)
+                    val position = recycler.getChildAdapterPosition(child)
+                    val page = adapter.pages?.getOrNull(position)
+                    if (page != null)
+                        readerActivity.onLongPress(page)
+                    else
+                        context.toast(getString(R.string.unknown_error))
+                }
             }
         })
     }
 
     /**
      * Called when a new chapter is set in [BaseReader].
-     *
      * @param chapter the chapter set.
      * @param currentPage the initial page to display.
      */
@@ -167,7 +173,6 @@ class WebtoonReader : BaseReader() {
 
     /**
      * Called when a chapter is appended in [BaseReader].
-     *
      * @param chapter the chapter appended.
      */
     override fun onChapterAppended(chapter: ReaderChapter) {
@@ -191,7 +196,6 @@ class WebtoonReader : BaseReader() {
 
     /**
      * Sets the active page.
-     *
      * @param pageNumber the index of the page from [pages].
      */
     override fun setActivePage(pageNumber: Int) {

BIN
app/src/main/res/drawable-hdpi/ic_delete_grey_24dp.png


BIN
app/src/main/res/drawable-hdpi/ic_insert_photo_white_24dp.png


BIN
app/src/main/res/drawable-hdpi/ic_share_grey_24dp.png


BIN
app/src/main/res/drawable-mdpi/ic_delete_grey_24dp.png


BIN
app/src/main/res/drawable-mdpi/ic_insert_photo_white_24dp.png


BIN
app/src/main/res/drawable-mdpi/ic_share_grey_24dp.png


BIN
app/src/main/res/drawable-xhdpi/ic_delete_grey_24dp.png


BIN
app/src/main/res/drawable-xhdpi/ic_insert_photo_white_24dp.png


BIN
app/src/main/res/drawable-xhdpi/ic_share_grey_24dp.png


BIN
app/src/main/res/drawable-xxhdpi/ic_delete_grey_24dp.png


BIN
app/src/main/res/drawable-xxhdpi/ic_insert_photo_white_24dp.png


BIN
app/src/main/res/drawable-xxhdpi/ic_share_grey_24dp.png


BIN
app/src/main/res/drawable-xxxhdpi/ic_delete_grey_24dp.png


BIN
app/src/main/res/drawable-xxxhdpi/ic_insert_photo_white_24dp.png


BIN
app/src/main/res/drawable-xxxhdpi/ic_share_grey_24dp.png


+ 0 - 9
app/src/main/res/drawable/ic_insert_photo_black_24dp.xml

@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
-</vector>

BIN
app/src/main/res/drawable/ic_insert_photo_white_24dp.png


+ 0 - 2
app/src/main/res/values/keys.xml

@@ -48,8 +48,6 @@
     <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>

+ 2 - 5
app/src/main/res/values/strings.xml

@@ -267,11 +267,6 @@
     <string name="status">Status</string>
     <string name="chapters">Chapters</string>
 
-    <!-- Reader Activity -->
-    <string name="custom_filter">Custom filter</string>
-    <string name="save_page">Download page</string>
-    <string name="set_as_cover">Set as cover</string>
-    <string name="cover_updated">Cover updated</string>
     <!-- Dialog remove recently view -->
     <string name="dialog_remove_recently_description">This will remove the read date of this chapter. Are you sure?</string>
     <string name="dialog_remove_recently_reset">Reset all chapters for this manga</string>
@@ -279,6 +274,7 @@
     <!-- Image notifier -->
     <string name="picture_saved">Picture saved</string>
     <string name="saving_picture">Saving picture</string>
+    <string name="options">Options</string>
 
     <!-- Reader activity -->
     <string name="custom_filter">Custom filter</string>
@@ -315,6 +311,7 @@
     <string name="notification_no_new_chapters">No new chapters found</string>
     <string name="notification_new_chapters">New chapters found for:</string>
     <string name="notification_manga_update_failed">Failed to update manga:</string>
+    <string name="notification_cover_update_failed">Failed to update cover</string>
     <string name="notification_first_add_to_library">Please add the manga to your library before doing this</string>
     <string name="notification_not_connected_to_ac_title">Sync canceled</string>
     <string name="notification_not_connected_to_ac_body">Not connected to AC power</string>