Adding a button in new release notification to open GitHub release page

Shows also while downloading/error/installed

Should help those who can't complete their downloads for some reason

There's also refactoring from upstream in this, including tapping on the notification for an intent. However the intent here opens the app with the release notes dialog
This commit is contained in:
Jays2Kings 2021-04-27 23:03:17 -04:00
parent 0b4f83d83e
commit 77c080cc47
8 changed files with 124 additions and 43 deletions

View File

@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.setting.AboutController
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
@ -278,6 +279,8 @@ class NotificationReceiver : BroadcastReceiver() {
// Called to cancel library update. // Called to cancel library update.
private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE" private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE"
private const val ACTION_CANCEL_UPDATE_DOWNLOAD = "$ID.$NAME.CANCEL_UPDATE_DOWNLOAD"
// Called to mark as read // Called to mark as read
private const val ACTION_MARK_AS_READ = "$ID.$NAME.MARK_AS_READ" private const val ACTION_MARK_AS_READ = "$ID.$NAME.MARK_AS_READ"
@ -471,6 +474,27 @@ class NotificationReceiver : BroadcastReceiver() {
) )
} }
/**
* Returns [PendingIntent] that opens the manga details controller.
*
* @param context context of application
* @param manga manga of chapter
*/
internal fun openUpdatePendingActivity(context: Context, notes: String, downloadLink: String):
PendingIntent {
val newIntent =
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_UPDATE_NOTES)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
.putExtra(AboutController.NewUpdateDialogController.BODY_KEY, notes)
.putExtra(AboutController.NewUpdateDialogController.URL_KEY, downloadLink)
return PendingIntent.getActivity(
context,
downloadLink.hashCode(),
newIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
/** /**
* Returns [PendingIntent] that opens the manga details controller. * Returns [PendingIntent] that opens the manga details controller.
* *

View File

@ -9,4 +9,6 @@ interface Release {
* @return download link of latest release. * @return download link of latest release.
*/ */
val downloadLink: String val downloadLink: String
val releaseLink: String
} }

View File

@ -1,8 +1,6 @@
package eu.kanade.tachiyomi.data.updater package eu.kanade.tachiyomi.data.updater
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
@ -11,9 +9,7 @@ import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -22,37 +18,19 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) { CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result = coroutineScope { override suspend fun doWork(): Result = coroutineScope {
val result = try { try {
UpdateChecker.getUpdateChecker().checkForUpdate() val result = UpdateChecker.getUpdateChecker().checkForUpdate()
if (result is UpdateResult.NewUpdate<*>) {
UpdaterNotifier(context).promptUpdate(
result.release.info,
result.release.downloadLink,
result.release.releaseLink
)
}
Result.success()
} catch (e: Exception) { } catch (e: Exception) {
Result.failure() Result.failure()
} }
if (result is UpdateResult.NewUpdate<*>) {
val url = result.release.downloadLink
val intent = Intent(context, UpdaterService::class.java).apply {
putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url)
}
NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON).update {
setContentTitle(context.getString(R.string.app_name))
setContentText(context.getString(R.string.update_available))
setSmallIcon(android.R.drawable.stat_sys_download_done)
color = context.getResourceColor(R.attr.colorAccent)
// Download action
addAction(
android.R.drawable.stat_sys_download_done,
context.getString(R.string.download),
PendingIntent.getService(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
}
Result.success()
} }
fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) { fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) {

View File

@ -1,13 +1,17 @@
package eu.kanade.tachiyomi.data.updater package eu.kanade.tachiyomi.data.updater
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.data.notification.NotificationHandler
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
/** /**
@ -20,10 +24,14 @@ internal class UpdaterNotifier(private val context: Context) {
/** /**
* Builder to manage notifications. * Builder to manage notifications.
*/ */
private val notification by lazy { private val notificationBuilder by lazy {
NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON) NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON)
} }
companion object {
var releasePageUrl: String? = null
}
/** /**
* Call to show notification. * Call to show notification.
* *
@ -33,20 +41,68 @@ internal class UpdaterNotifier(private val context: Context) {
context.notificationManager.notify(id, build()) context.notificationManager.notify(id, build())
} }
fun promptUpdate(body: String, url: String, releaseUrl: String) {
val intent = Intent(context, UpdaterService::class.java).apply {
putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url)
}
val pendingIntent = NotificationReceiver.openUpdatePendingActivity(context, body, url)
releasePageUrl = releaseUrl
with(notificationBuilder) {
setContentTitle(context.getString(R.string.app_name))
setContentText(context.getString(R.string.new_version_available))
setContentIntent(pendingIntent)
setAutoCancel(true)
setSmallIcon(android.R.drawable.stat_sys_download_done)
color = context.getResourceColor(R.attr.colorAccent)
clearActions()
// Download action
addAction(
android.R.drawable.stat_sys_download_done,
context.getString(R.string.download),
PendingIntent.getService(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
)
addReleasePageAction()
}
notificationBuilder.show()
}
private fun NotificationCompat.Builder.addReleasePageAction() {
releasePageUrl?.let { releaseUrl ->
val releaseIntent = Intent(Intent.ACTION_VIEW, releaseUrl.toUri()).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
addAction(
R.drawable.ic_new_releases_24dp,
context.getString(R.string.release_page),
PendingIntent.getActivity(context, releaseUrl.hashCode(), releaseIntent, PendingIntent.FLAG_UPDATE_CURRENT)
)
}
}
/** /**
* Call when apk download starts. * Call when apk download starts.
* *
* @param title tile of notification. * @param title tile of notification.
*/ */
fun onDownloadStarted(title: String): NotificationCompat.Builder { fun onDownloadStarted(title: String): NotificationCompat.Builder {
with(notification) { with(notificationBuilder) {
setContentTitle(title) setContentTitle(title)
setContentText(context.getString(R.string.downloading)) setContentText(context.getString(R.string.downloading))
setSmallIcon(android.R.drawable.stat_sys_download) setSmallIcon(android.R.drawable.stat_sys_download)
setAutoCancel(false)
setOngoing(true) setOngoing(true)
clearActions()
addReleasePageAction()
} }
notification.show() notificationBuilder.show()
return notification return notificationBuilder
} }
/** /**
@ -55,11 +111,11 @@ internal class UpdaterNotifier(private val context: Context) {
* @param progress progress of download (xx%/100). * @param progress progress of download (xx%/100).
*/ */
fun onProgressChange(progress: Int) { fun onProgressChange(progress: Int) {
with(notification) { with(notificationBuilder) {
setProgress(100, progress, false) setProgress(100, progress, false)
setOnlyAlertOnce(true) setOnlyAlertOnce(true)
} }
notification.show() notificationBuilder.show()
} }
/** /**
@ -68,13 +124,15 @@ internal class UpdaterNotifier(private val context: Context) {
* @param uri path location of apk. * @param uri path location of apk.
*/ */
fun onDownloadFinished(uri: Uri) { fun onDownloadFinished(uri: Uri) {
with(notification) { with(notificationBuilder) {
setContentText(context.getString(R.string.download_complete)) setContentText(context.getString(R.string.download_complete))
setSmallIcon(android.R.drawable.stat_sys_download_done) setSmallIcon(android.R.drawable.stat_sys_download_done)
setAutoCancel(false)
setOnlyAlertOnce(false) setOnlyAlertOnce(false)
setProgress(0, 0, false) setProgress(0, 0, false)
// Install action // Install action
setContentIntent(NotificationHandler.installApkPendingActivity(context, uri)) setContentIntent(NotificationHandler.installApkPendingActivity(context, uri))
clearActions()
addAction( addAction(
R.drawable.ic_system_update_24dp, R.drawable.ic_system_update_24dp,
context.getString(R.string.install), context.getString(R.string.install),
@ -86,8 +144,9 @@ internal class UpdaterNotifier(private val context: Context) {
context.getString(R.string.cancel), context.getString(R.string.cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER) NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)
) )
addReleasePageAction()
} }
notification.show() notificationBuilder.show()
} }
/** /**
@ -96,12 +155,14 @@ internal class UpdaterNotifier(private val context: Context) {
* @param url web location of apk to download. * @param url web location of apk to download.
*/ */
fun onDownloadError(url: String) { fun onDownloadError(url: String) {
with(notification) { with(notificationBuilder) {
setContentText(context.getString(R.string.download_error)) setContentText(context.getString(R.string.download_error))
setSmallIcon(android.R.drawable.stat_sys_warning) setSmallIcon(android.R.drawable.stat_sys_warning)
setOnlyAlertOnce(false) setOnlyAlertOnce(false)
setAutoCancel(false)
setProgress(0, 0, false) setProgress(0, 0, false)
color = ContextCompat.getColor(context, R.color.colorAccent) color = ContextCompat.getColor(context, R.color.colorAccent)
clearActions()
// Retry action // Retry action
addAction( addAction(
R.drawable.ic_refresh_24dp, R.drawable.ic_refresh_24dp,
@ -114,7 +175,9 @@ internal class UpdaterNotifier(private val context: Context) {
context.getString(R.string.cancel), context.getString(R.string.cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER) NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)
) )
addReleasePageAction()
}
notificationBuilder.show(Notifications.ID_UPDATER)
} }
notification.show(Notifications.ID_UPDATER)
} }
} }

View File

@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.data.updater.Release
class GithubRelease( class GithubRelease(
@SerializedName("tag_name") val version: String, @SerializedName("tag_name") val version: String,
@SerializedName("body") override val info: String, @SerializedName("body") override val info: String,
@SerializedName("html_url") override val releaseLink: String,
@SerializedName("assets") private val assets: List<Assets> @SerializedName("assets") private val assets: List<Assets>
) : Release { ) : Release {

View File

@ -49,6 +49,7 @@ import eu.kanade.tachiyomi.data.preference.asImmediateFlowIn
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.updater.UpdateChecker import eu.kanade.tachiyomi.data.updater.UpdateChecker
import eu.kanade.tachiyomi.data.updater.UpdateResult import eu.kanade.tachiyomi.data.updater.UpdateResult
import eu.kanade.tachiyomi.data.updater.UpdaterNotifier
import eu.kanade.tachiyomi.databinding.MainActivityBinding import eu.kanade.tachiyomi.databinding.MainActivityBinding
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.ui.base.MaterialMenuSheet import eu.kanade.tachiyomi.ui.base.MaterialMenuSheet
@ -497,6 +498,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
// Create confirmation window // Create confirmation window
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
UpdaterNotifier.releasePageUrl = result.release.releaseLink
AboutController.NewUpdateDialogController(body, url).showDialog(router) AboutController.NewUpdateDialogController(body, url).showDialog(router)
} }
} }
@ -570,6 +572,13 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
if (router.backstack.isEmpty()) binding.bottomNav.selectedItemId = R.id.nav_library if (router.backstack.isEmpty()) binding.bottomNav.selectedItemId = R.id.nav_library
router.pushController(MangaDetailsController(extras).withFadeTransaction()) router.pushController(MangaDetailsController(extras).withFadeTransaction())
} }
SHORTCUT_UPDATE_NOTES -> {
val extras = intent.extras ?: return false
if (router.backstack.isEmpty()) binding.bottomNav.selectedItemId = R.id.nav_library
if (router.backstack.lastOrNull()?.controller() !is AboutController.NewUpdateDialogController) {
AboutController.NewUpdateDialogController(extras).showDialog(router)
}
}
SHORTCUT_SOURCE -> { SHORTCUT_SOURCE -> {
val extras = intent.extras ?: return false val extras = intent.extras ?: return false
if (router.backstack.isEmpty()) binding.bottomNav.selectedItemId = R.id.nav_library if (router.backstack.isEmpty()) binding.bottomNav.selectedItemId = R.id.nav_library
@ -882,6 +891,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
const val SHORTCUT_BROWSE = "eu.kanade.tachiyomi.SHOW_BROWSE" const val SHORTCUT_BROWSE = "eu.kanade.tachiyomi.SHOW_BROWSE"
const val SHORTCUT_DOWNLOADS = "eu.kanade.tachiyomi.SHOW_DOWNLOADS" const val SHORTCUT_DOWNLOADS = "eu.kanade.tachiyomi.SHOW_DOWNLOADS"
const val SHORTCUT_MANGA = "eu.kanade.tachiyomi.SHOW_MANGA" const val SHORTCUT_MANGA = "eu.kanade.tachiyomi.SHOW_MANGA"
const val SHORTCUT_UPDATE_NOTES = "eu.kanade.tachiyomi.SHOW_UPDATE_NOTES"
const val SHORTCUT_SOURCE = "eu.kanade.tachiyomi.SHOW_SOURCE" const val SHORTCUT_SOURCE = "eu.kanade.tachiyomi.SHOW_SOURCE"
const val SHORTCUT_READER_SETTINGS = "eu.kanade.tachiyomi.READER_SETTINGS" const val SHORTCUT_READER_SETTINGS = "eu.kanade.tachiyomi.READER_SETTINGS"
const val SHORTCUT_EXTENSIONS = "eu.kanade.tachiyomi.EXTENSIONS" const val SHORTCUT_EXTENSIONS = "eu.kanade.tachiyomi.EXTENSIONS"

View File

@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.updater.UpdateChecker import eu.kanade.tachiyomi.data.updater.UpdateChecker
import eu.kanade.tachiyomi.data.updater.UpdateResult import eu.kanade.tachiyomi.data.updater.UpdateResult
import eu.kanade.tachiyomi.data.updater.UpdaterNotifier
import eu.kanade.tachiyomi.data.updater.UpdaterService import eu.kanade.tachiyomi.data.updater.UpdaterService
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.util.lang.toTimestampString import eu.kanade.tachiyomi.util.lang.toTimestampString
@ -166,6 +167,7 @@ class AboutController : SettingsController() {
// Create confirmation window // Create confirmation window
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
UpdaterNotifier.releasePageUrl = result.release.releaseLink
NewUpdateDialogController(body, url).showDialog(router) NewUpdateDialogController(body, url).showDialog(router)
} }
} }
@ -202,7 +204,7 @@ class AboutController : SettingsController() {
.negativeButton(R.string.ignore) .negativeButton(R.string.ignore)
} }
private companion object { companion object {
const val BODY_KEY = "NewUpdateDialogController.body" const val BODY_KEY = "NewUpdateDialogController.body"
const val URL_KEY = "NewUpdateDialogController.key" const val URL_KEY = "NewUpdateDialogController.key"
} }

View File

@ -108,6 +108,7 @@
<string name="new_version_available">New version available!</string> <string name="new_version_available">New version available!</string>
<string name="no_new_updates_available">No new updates available</string> <string name="no_new_updates_available">No new updates available</string>
<string name="searching_for_updates">Searching for updates…</string> <string name="searching_for_updates">Searching for updates…</string>
<string name="release_page">Release page</string>
<!-- Main Screens --> <!-- Main Screens -->