Notification Improvements (#594)

* Download notifier improvements

* Notification improvements

Added a Notification Service.

Added a Notification Activity Handler.

* Removed service. Everything is now managed by single broadcast

* Fixed some flags

* Fixed ReaderActivity call

* Code review

* Added Handler. Removed dismiss onDestroy
This commit is contained in:
Bram van de Kerkhof
2017-01-20 21:18:15 +01:00
committed by inorichi
parent 52c50398b8
commit c445ea90ba
32 changed files with 993 additions and 394 deletions

View File

@@ -2,15 +2,17 @@ package eu.kanade.tachiyomi.ui.download
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.view.*
import android.view.Menu
import android.view.MenuItem
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.source.model.Page
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
import eu.kanade.tachiyomi.util.plusAssign
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_download_queue.*
import kotlinx.android.synthetic.main.toolbar.*
import nucleus.factory.RequiresPresenter
import rx.Observable
import rx.Subscription
@@ -20,19 +22,18 @@ import java.util.*
import java.util.concurrent.TimeUnit
/**
* Fragment that shows the currently active downloads.
* Activity that shows the currently active downloads.
* Uses R.layout.fragment_download_queue.
*/
@RequiresPresenter(DownloadPresenter::class)
class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
class DownloadActivity : BaseRxActivity<DownloadPresenter>() {
/**
* Adapter containing the active downloads.
*/
private lateinit var adapter: DownloadAdapter
/**
* Subscription list to be cleared during [onDestroyView].
* Subscription list to be cleared during [onDestroy].
*/
private val subscriptions by lazy { CompositeSubscription() }
@@ -46,38 +47,22 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
*/
private var isRunning: Boolean = false
companion object {
/**
* Creates a new instance of this fragment.
*
* @return a new instance of [DownloadFragment].
*/
fun newInstance(): DownloadFragment {
return DownloadFragment()
}
}
override fun onCreate(savedState: Bundle?) {
setAppTheme()
super.onCreate(savedState)
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View {
return inflater.inflate(R.layout.fragment_download_queue, container, false)
}
override fun onViewCreated(view: View, savedState: Bundle?) {
setContentView(R.layout.activity_download_manager)
setupToolbar(toolbar)
setToolbarTitle(R.string.label_download_queue)
// Check if download queue is empty and update information accordingly.
setInformationView()
// Initialize adapter.
adapter = DownloadAdapter(activity)
adapter = DownloadAdapter(this)
recycler.adapter = adapter
// Set the layout manager for the recycler and fixed size.
recycler.layoutManager = LinearLayoutManager(activity)
recycler.layoutManager = LinearLayoutManager(this)
recycler.setHasFixedSize(true)
// Suscribe to changes
@@ -94,20 +79,21 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
.subscribe { onUpdateDownloadedPages(it) }
}
override fun onDestroyView() {
override fun onDestroy() {
for (subscription in progressSubscriptions.values) {
subscription.unsubscribe()
}
progressSubscriptions.clear()
subscriptions.clear()
super.onDestroyView()
super.onDestroy()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.download_queue, menu)
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.download_queue, menu)
return true
}
override fun onPrepareOptionsMenu(menu: Menu) {
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
// Set start button visibility.
menu.findItem(R.id.start_queue).isVisible = !isRunning && !presenter.downloadQueue.isEmpty()
@@ -116,14 +102,18 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
// Set clear button visibility.
menu.findItem(R.id.clear_queue).isVisible = !presenter.downloadQueue.isEmpty()
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.start_queue -> DownloadService.start(activity)
R.id.pause_queue -> DownloadService.stop(activity)
R.id.start_queue -> DownloadService.start(this)
R.id.pause_queue -> {
DownloadService.stop(this)
presenter.pauseDownloads()
}
R.id.clear_queue -> {
DownloadService.stop(activity)
DownloadService.stop(this)
presenter.clearQueue()
}
else -> return super.onOptionsItemSelected(item)
@@ -198,7 +188,7 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
*/
private fun onQueueStatusChange(running: Boolean) {
isRunning = running
activity.supportInvalidateOptionsMenu()
supportInvalidateOptionsMenu()
// Check if download queue is empty and update information accordingly.
setInformationView()
@@ -210,7 +200,7 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
* @param downloads the downloads from the queue.
*/
fun onNextDownloads(downloads: List<Download>) {
activity.supportInvalidateOptionsMenu()
supportInvalidateOptionsMenu()
setInformationView()
adapter.setItems(downloads)
}
@@ -247,8 +237,11 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
* Set information view when queue is empty
*/
private fun setInformationView() {
(activity as MainActivity).updateEmptyView(presenter.downloadQueue.isEmpty(),
updateEmptyView(presenter.downloadQueue.isEmpty(),
R.string.information_no_downloads, R.drawable.ic_file_download_black_128dp)
}
fun updateEmptyView(show: Boolean, textResource: Int, drawable: Int) {
if (show) empty_view.show(drawable, textResource) else empty_view.hide()
}
}

View File

@@ -12,9 +12,9 @@ import uy.kohesive.injekt.injectLazy
import java.util.*
/**
* Presenter of [DownloadFragment].
* Presenter of [DownloadActivity].
*/
class DownloadPresenter : BasePresenter<DownloadFragment>() {
class DownloadPresenter : BasePresenter<DownloadActivity>() {
/**
* Download manager.
@@ -33,7 +33,7 @@ class DownloadPresenter : BasePresenter<DownloadFragment>() {
downloadQueue.getUpdatedObservable()
.observeOn(AndroidSchedulers.mainThread())
.map { ArrayList(it) }
.subscribeLatestCache(DownloadFragment::onNextDownloads, { view, error ->
.subscribeLatestCache(DownloadActivity::onNextDownloads, { view, error ->
Timber.e(error)
})
}
@@ -48,6 +48,13 @@ class DownloadPresenter : BasePresenter<DownloadFragment>() {
.onBackpressureBuffer()
}
/**
* Pauses the download queue.
*/
fun pauseDownloads() {
downloadManager.pauseDownloads()
}
/**
* Clears the download queue.
*/
@@ -55,4 +62,4 @@ class DownloadPresenter : BasePresenter<DownloadFragment>() {
downloadManager.clearQueue()
}
}
}

View File

@@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.backup.BackupFragment
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.catalogue.CatalogueFragment
import eu.kanade.tachiyomi.ui.download.DownloadFragment
import eu.kanade.tachiyomi.ui.download.DownloadActivity
import eu.kanade.tachiyomi.ui.latest_updates.LatestUpdatesFragment
import eu.kanade.tachiyomi.ui.library.LibraryFragment
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersFragment
@@ -63,7 +63,7 @@ class MainActivity : BaseActivity() {
R.id.nav_drawer_recently_read -> setFragment(RecentlyReadFragment.newInstance(), id)
R.id.nav_drawer_catalogues -> setFragment(CatalogueFragment.newInstance(), id)
R.id.nav_drawer_latest_updates -> setFragment(LatestUpdatesFragment.newInstance(), id)
R.id.nav_drawer_downloads -> setFragment(DownloadFragment.newInstance(), id)
R.id.nav_drawer_downloads -> startActivity(Intent(this, DownloadActivity::class.java))
R.id.nav_drawer_settings -> {
val intent = Intent(this, SettingsActivity::class.java)
startActivityForResult(intent, REQUEST_OPEN_SETTINGS)

View File

@@ -19,7 +19,7 @@ import eu.kanade.tachiyomi.data.source.online.OnlineSource
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackUpdateService
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.reader.notification.ImageNotifier
import eu.kanade.tachiyomi.ui.reader.SaveImageNotifier
import eu.kanade.tachiyomi.util.DiskUtil
import eu.kanade.tachiyomi.util.RetryWithDelay
import eu.kanade.tachiyomi.util.SharedData
@@ -562,7 +562,7 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
return
// Used to show image notification.
val imageNotifier = ImageNotifier(context)
val imageNotifier = SaveImageNotifier(context)
// Remove the notification if it already exists (user feedback).
imageNotifier.onClear()

View File

@@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.ui.reader.notification
package eu.kanade.tachiyomi.ui.reader
import android.content.Context
import android.graphics.Bitmap
@@ -7,13 +7,15 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.kanade.tachiyomi.Constants
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationHandler
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.util.notificationManager
import java.io.File
/**
* Class used to show BigPictureStyle notifications
*/
class ImageNotifier(private val context: Context) {
class SaveImageNotifier(private val context: Context) {
/**
* Notification builder.
*/
@@ -58,15 +60,15 @@ class ImageNotifier(private val context: Context) {
if (!mActions.isEmpty())
mActions.clear()
setContentIntent(ImageNotificationReceiver.showImageIntent(context, file))
setContentIntent(NotificationHandler.openImagePendingActivity(context, file))
// Share action
addAction(R.drawable.ic_share_grey_24dp,
context.getString(R.string.action_share),
ImageNotificationReceiver.shareImageIntent(context, file))
context.getString(R.string.action_share),
NotificationReceiver.shareImagePendingBroadcast(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))
NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId))
updateNotification()
}

View File

@@ -1,84 +0,0 @@
package eu.kanade.tachiyomi.ui.reader.notification
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.support.v4.content.FileProvider
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.util.notificationManager
import java.io.File
import eu.kanade.tachiyomi.Constants.NOTIFICATION_DOWNLOAD_IMAGE_ID as defaultNotification
/**
* 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) {
ACTION_DELETE_IMAGE -> {
deleteImage(intent.getStringExtra(EXTRA_FILE_LOCATION))
context.notificationManager.cancel(intent.getIntExtra(NOTIFICATION_ID, defaultNotification))
}
}
}
/**
* Called to delete image
*
* @param path path of file
*/
private fun deleteImage(path: String) {
val file = File(path)
if (file.exists()) file.delete()
}
companion object {
private const val ACTION_DELETE_IMAGE = "eu.kanade.DELETE_IMAGE"
private const val EXTRA_FILE_LOCATION = "file_location"
private const val NOTIFICATION_ID = "notification_id"
/**
* Called to start share intent to share image
*
* @param context context of application
* @param file file that contains image
*/
internal fun shareImageIntent(context: Context, file: File): PendingIntent {
val intent = Intent(Intent.ACTION_SEND).apply {
val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file)
putExtra(Intent.EXTRA_STREAM, uri)
type = "image/*"
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
/**
* Called to show image in gallery application
*
* @param context context of application
* @param file file that contains image
*/
internal fun showImageIntent(context: Context, file: File): PendingIntent {
val intent = Intent(Intent.ACTION_VIEW).apply {
val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file)
setDataAndType(uri, "image/*")
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
return PendingIntent.getActivity(context, 0, intent, 0)
}
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)
putExtra(NOTIFICATION_ID, notificationId)
}
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
}
}