Shortcut fix Oreo (#1026)

* Moved to Android O Shortcutmanager

* Re-added possibility to change icon shape pre oreo.
This commit is contained in:
Bram van de Kerkhof 2017-10-10 12:05:33 +02:00 committed by inorichi
parent 5c662b1ae1
commit deec65446f
4 changed files with 105 additions and 72 deletions

View File

@ -24,7 +24,6 @@
android:launchMode="singleTop"> android:launchMode="singleTop">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<meta-data android:name="android.app.shortcuts" <meta-data android:name="android.app.shortcuts"

View File

@ -41,6 +41,8 @@ class NotificationReceiver : BroadcastReceiver() {
ACTION_RESUME_DOWNLOADS -> DownloadService.start(context) ACTION_RESUME_DOWNLOADS -> DownloadService.start(context)
// Clear the download queue // Clear the download queue
ACTION_CLEAR_DOWNLOADS -> downloadManager.clearQueue(true) ACTION_CLEAR_DOWNLOADS -> downloadManager.clearQueue(true)
// Show message notification created
ACTION_SHORTCUT_CREATED -> context.toast(R.string.shortcut_created)
// Launch share activity and dismiss notification // Launch share activity and dismiss notification
ACTION_SHARE_IMAGE -> shareImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION), ACTION_SHARE_IMAGE -> shareImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1))
@ -161,6 +163,9 @@ class NotificationReceiver : BroadcastReceiver() {
// Called to clear downloads. // Called to clear downloads.
private const val ACTION_CLEAR_DOWNLOADS = "$ID.$NAME.ACTION_CLEAR_DOWNLOADS" private const val ACTION_CLEAR_DOWNLOADS = "$ID.$NAME.ACTION_CLEAR_DOWNLOADS"
// Called to notify user shortcut is created.
private const val ACTION_SHORTCUT_CREATED = "$ID.$NAME.ACTION_SHORTCUT_CREATED"
// Called to dismiss notification. // Called to dismiss notification.
private const val ACTION_DISMISS_NOTIFICATION = "$ID.$NAME.ACTION_DISMISS_NOTIFICATION" private const val ACTION_DISMISS_NOTIFICATION = "$ID.$NAME.ACTION_DISMISS_NOTIFICATION"
@ -199,6 +204,13 @@ class NotificationReceiver : BroadcastReceiver() {
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
} }
internal fun shortcutCreatedBroadcast(context: Context) : PendingIntent {
val intent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_SHORTCUT_CREATED
}
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
/** /**
* Returns [PendingIntent] that starts a service which dismissed the notification * Returns [PendingIntent] that starts a service which dismissed the notification
* *

View File

@ -1,14 +1,18 @@
package eu.kanade.tachiyomi.ui.manga.info package eu.kanade.tachiyomi.ui.manga.info
import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.customtabs.CustomTabsIntent import android.support.customtabs.CustomTabsIntent
import android.support.v4.content.pm.ShortcutInfoCompat
import android.support.v4.content.pm.ShortcutManagerCompat
import android.support.v4.graphics.drawable.IconCompat
import android.view.* import android.view.*
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.bumptech.glide.BitmapRequestBuilder import com.bumptech.glide.BitmapRequestBuilder
import com.bumptech.glide.BitmapTypeRequest
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.CenterCrop
@ -17,6 +21,7 @@ import com.jakewharton.rxbinding.view.clicks
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
@ -181,7 +186,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
/** /**
* Toggles the favorite status and asks for confirmation to delete downloaded chapters. * Toggles the favorite status and asks for confirmation to delete downloaded chapters.
*/ */
fun toggleFavorite() { private fun toggleFavorite() {
val view = view val view = view
val isNowFavorite = presenter.toggleFavorite() val isNowFavorite = presenter.toggleFavorite()
@ -197,7 +202,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
/** /**
* Open the manga in browser. * Open the manga in browser.
*/ */
fun openInBrowser() { private fun openInBrowser() {
val context = view?.context ?: return val context = view?.context ?: return
val source = presenter.source as? HttpSource ?: return val source = presenter.source as? HttpSource ?: return
@ -288,11 +293,11 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
if (manga.favorite) { if (manga.favorite) {
val categories = presenter.getCategories() val categories = presenter.getCategories()
val defaultCategory = categories.find { it.id == preferences.defaultCategory() } val defaultCategory = categories.find { it.id == preferences.defaultCategory() }
if (defaultCategory != null) { when {
presenter.moveMangaToCategory(manga, defaultCategory) defaultCategory != null -> presenter.moveMangaToCategory(manga, defaultCategory)
} else if (categories.size <= 1) { // default or the one from the user categories.size <= 1 -> // default or the one from the user
presenter.moveMangaToCategory(manga, categories.firstOrNull()) presenter.moveMangaToCategory(manga, categories.firstOrNull())
} else { else -> {
val ids = presenter.getMangaCategoryIds(manga) val ids = presenter.getMangaCategoryIds(manga)
val preselected = ids.mapNotNull { id -> val preselected = ids.mapNotNull { id ->
categories.indexOfFirst { it.id == id }.takeIf { it != -1 } categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
@ -303,6 +308,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
} }
} }
} }
}
override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) { override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {
val manga = mangas.firstOrNull() ?: return val manga = mangas.firstOrNull() ?: return
@ -310,38 +316,10 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
} }
/** /**
* Add the manga to the home screen * Choose the shape of the icon
* Only use for pre Oreo devices.
*/ */
fun addToHomeScreen() { private fun chooseIconDialog() {
val activity = activity ?: return
val mangaControllerArgs = parentController?.args ?: return
val shortcutIntent = activity.intent
.setAction(MainActivity.SHORTCUT_MANGA)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(MangaController.MANGA_EXTRA,
mangaControllerArgs.getLong(MangaController.MANGA_EXTRA))
val addIntent = Intent("com.android.launcher.action.INSTALL_SHORTCUT")
.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent)
//Set shortcut title
val dialog = MaterialDialog.Builder(activity)
.title(R.string.shortcut_title)
.input("", presenter.manga.title, { _, text ->
//Set shortcut title
addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, text.toString())
reshapeIconBitmap(addIntent,
Glide.with(activity).load(presenter.manga).asBitmap())
})
.negativeText(android.R.string.cancel)
.show()
untilDestroySubscriptions.add(Subscriptions.create { dialog.dismiss() })
}
fun reshapeIconBitmap(addIntent: Intent, request: BitmapTypeRequest<out Any>) {
val activity = activity ?: return val activity = activity ?: return
val modes = intArrayOf(R.string.circular_icon, val modes = intArrayOf(R.string.circular_icon,
@ -349,16 +327,9 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
R.string.square_icon, R.string.square_icon,
R.string.star_icon) R.string.star_icon)
fun BitmapRequestBuilder<out Any, Bitmap>.toIcon(): Bitmap { val request = Glide.with(activity).load(presenter.manga).asBitmap()
return this.into(96, 96).get()
}
// i = 0: Circular icon fun getIcon(i: Int): Bitmap? = when (i) {
// i = 1: Rounded icon
// i = 2: Square icon
// i = 3: Star icon (because boredom)
fun getIcon(i: Int): Bitmap? {
return when (i) {
0 -> request.transform(CropCircleTransformation(activity)).toIcon() 0 -> request.transform(CropCircleTransformation(activity)).toIcon()
1 -> request.transform(RoundedCornersTransformation(activity, 5, 0)).toIcon() 1 -> request.transform(RoundedCornersTransformation(activity, 5, 0)).toIcon()
2 -> request.transform(CropSquareTransformation(activity)).toIcon() 2 -> request.transform(CropSquareTransformation(activity)).toIcon()
@ -366,7 +337,6 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
MaskTransformation(activity, R.drawable.mask_star)).toIcon() MaskTransformation(activity, R.drawable.mask_star)).toIcon()
else -> null else -> null
} }
}
val dialog = MaterialDialog.Builder(activity) val dialog = MaterialDialog.Builder(activity)
.title(R.string.icon_shape) .title(R.string.icon_shape)
@ -377,7 +347,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ icon -> .subscribe({ icon ->
if (icon != null) createShortcut(addIntent, icon) if (icon != null) createShortcut(icon)
}, { }, {
activity.toast(R.string.icon_creation_fail) activity.toast(R.string.icon_creation_fail)
}) })
@ -387,16 +357,67 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
untilDestroySubscriptions.add(Subscriptions.create { dialog.dismiss() }) untilDestroySubscriptions.add(Subscriptions.create { dialog.dismiss() })
} }
fun createShortcut(addIntent: Intent, icon: Bitmap) { private fun BitmapRequestBuilder<out Any, Bitmap>.toIcon() = this.into(96,96).get()
val activity = activity ?: return
//Send shortcut intent /**
addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon) * Create shortcut using ShortcutManager.
activity.sendBroadcast(addIntent) */
//Go to launcher to show this shiny new shortcut! private fun createShortcut(icon: Bitmap) {
val startMain = Intent(Intent.ACTION_MAIN) val activity = activity ?: return
startMain.addCategory(Intent.CATEGORY_HOME).flags = Intent.FLAG_ACTIVITY_NEW_TASK val mangaControllerArgs = parentController?.args ?: return
startActivity(startMain)
// Create the shortcut intent.
val shortcutIntent = activity.intent
.setAction(MainActivity.SHORTCUT_MANGA)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(MangaController.MANGA_EXTRA,
mangaControllerArgs.getLong(MangaController.MANGA_EXTRA))
// Check if shortcut placement is supported
if (ShortcutManagerCompat.isRequestPinShortcutSupported(activity)) {
// Create shortcut info
val pinShortcutInfo = ShortcutInfoCompat.Builder(activity, "manga-shortcut-${presenter.manga.title}-${presenter.source.name}")
.setShortLabel(presenter.manga.title)
.setIcon(IconCompat.createWithBitmap(icon))
.setIntent(shortcutIntent).build()
val successCallback: PendingIntent
successCallback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create the CallbackIntent.
val pinnedShortcutCallbackIntent = ShortcutManagerCompat.createShortcutResultIntent(activity, pinShortcutInfo)
// Configure the intent so that the broadcast receiver gets the callback successfully.
PendingIntent.getBroadcast(activity, 0, pinnedShortcutCallbackIntent, 0)
} else{
NotificationReceiver.shortcutCreatedBroadcast(activity)
} }
// Request shortcut.
ShortcutManagerCompat.requestPinShortcut(activity, pinShortcutInfo,
successCallback.intentSender)
}
}
/**
* Add a shortcut of the manga to the home screen
*/
private fun addToHomeScreen() {
// Get bitmap icon
val bitmap = Glide.with(activity).load(presenter.manga).asBitmap()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
Observable.fromCallable {
bitmap.toIcon()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({icon ->
createShortcut(icon)
})
}else{
chooseIconDialog()
}
}
} }

View File

@ -311,6 +311,7 @@
<string name="square_icon">Square icon</string> <string name="square_icon">Square icon</string>
<string name="star_icon">Star icon</string> <string name="star_icon">Star icon</string>
<string name="shortcut_title">Shortcut title</string> <string name="shortcut_title">Shortcut title</string>
<string name="shortcut_created">Shortcut was added to home screen.</string>
<string name="icon_shape">Icon shape</string> <string name="icon_shape">Icon shape</string>
<string name="icon_creation_fail">Failed to create shortcut!</string> <string name="icon_creation_fail">Failed to create shortcut!</string>
<string name="delete_downloads_for_manga">Delete downloaded chapters?</string> <string name="delete_downloads_for_manga">Delete downloaded chapters?</string>