Shortcut fix Oreo (#1026)
* Moved to Android O Shortcutmanager * Re-added possibility to change icon shape pre oreo.
This commit is contained in:
parent
5c662b1ae1
commit
deec65446f
4 changed files with 105 additions and 72 deletions
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
|
@ -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,18 +293,19 @@ 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 }
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
|
|
||||||
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
|
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
|
||||||
.showDialog(router)
|
.showDialog(router)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,23 +327,15 @@ 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
|
0 -> request.transform(CropCircleTransformation(activity)).toIcon()
|
||||||
// i = 2: Square icon
|
1 -> request.transform(RoundedCornersTransformation(activity, 5, 0)).toIcon()
|
||||||
// i = 3: Star icon (because boredom)
|
2 -> request.transform(CropSquareTransformation(activity)).toIcon()
|
||||||
fun getIcon(i: Int): Bitmap? {
|
3 -> request.transform(CenterCrop(activity),
|
||||||
return when (i) {
|
MaskTransformation(activity, R.drawable.mask_star)).toIcon()
|
||||||
0 -> request.transform(CropCircleTransformation(activity)).toIcon()
|
else -> null
|
||||||
1 -> request.transform(RoundedCornersTransformation(activity, 5, 0)).toIcon()
|
|
||||||
2 -> request.transform(CropSquareTransformation(activity)).toIcon()
|
|
||||||
3 -> request.transform(CenterCrop(activity),
|
|
||||||
MaskTransformation(activity, R.drawable.mask_star)).toIcon()
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val dialog = MaterialDialog.Builder(activity)
|
val dialog = MaterialDialog.Builder(activity)
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Reference in a new issue