Final Final fix for extension notifation + badge

Using coroutine for notifcation job
Now setting a badge beside extension in drawer to signify updates, this is updated every hour or when the api is hit
This commit is contained in:
Jay 2020-01-13 04:16:05 -08:00
parent 8332a45028
commit 09bb216cda
10 changed files with 164 additions and 49 deletions

View File

@ -201,6 +201,10 @@ class PreferencesHelper(val context: Context) {
fun refreshCoversToo() = rxPrefs.getBoolean(Keys.refreshCoversToo, true) fun refreshCoversToo() = rxPrefs.getBoolean(Keys.refreshCoversToo, true)
fun extensionUpdatesCount() = rxPrefs.getInteger("ext_updates_count", 0)
fun lastExtCheck() = rxPrefs.getLong("last_ext_check", 0)
fun upgradeFilters() { fun upgradeFilters() {
val filterDl = rxPrefs.getBoolean(Keys.filterDownloaded, false).getOrDefault() val filterDl = rxPrefs.getBoolean(Keys.filterDownloaded, false).getOrDefault()
val filterUn = rxPrefs.getBoolean(Keys.filterUnread, false).getOrDefault() val filterUn = rxPrefs.getBoolean(Keys.filterUnread, false).getOrDefault()

View File

@ -161,7 +161,7 @@ class ExtensionManager(
private fun updatedInstalledExtensionsStatuses(availableExtensions: List<Extension.Available>) { private fun updatedInstalledExtensionsStatuses(availableExtensions: List<Extension.Available>) {
val mutInstalledExtensions = installedExtensions.toMutableList() val mutInstalledExtensions = installedExtensions.toMutableList()
var changed = false var changed = false
var hasUpdateCount = 0
for ((index, installedExt) in mutInstalledExtensions.withIndex()) { for ((index, installedExt) in mutInstalledExtensions.withIndex()) {
val pkgName = installedExt.pkgName val pkgName = installedExt.pkgName
val availableExt = availableExtensions.find { it.pkgName == pkgName } val availableExt = availableExtensions.find { it.pkgName == pkgName }
@ -175,6 +175,7 @@ class ExtensionManager(
val hasUpdate = availableExt.versionCode > installedExt.versionCode val hasUpdate = availableExt.versionCode > installedExt.versionCode
if (installedExt.hasUpdate != hasUpdate) { if (installedExt.hasUpdate != hasUpdate) {
mutInstalledExtensions[index] = installedExt.copy(hasUpdate = hasUpdate) mutInstalledExtensions[index] = installedExt.copy(hasUpdate = hasUpdate)
hasUpdateCount++
changed = true changed = true
} }
} }
@ -182,6 +183,7 @@ class ExtensionManager(
if (changed) { if (changed) {
installedExtensions = mutInstalledExtensions installedExtensions = mutInstalledExtensions
} }
preferences.extensionUpdatesCount().set(installedExtensions.count { it.hasUpdate })
} }
/** /**
@ -312,10 +314,12 @@ class ExtensionManager(
override fun onExtensionInstalled(extension: Extension.Installed) { override fun onExtensionInstalled(extension: Extension.Installed) {
registerNewExtension(extension.withUpdateCheck()) registerNewExtension(extension.withUpdateCheck())
preferences.extensionUpdatesCount().set(installedExtensions.count { it.hasUpdate })
} }
override fun onExtensionUpdated(extension: Extension.Installed) { override fun onExtensionUpdated(extension: Extension.Installed) {
registerUpdatedExtension(extension.withUpdateCheck()) registerUpdatedExtension(extension.withUpdateCheck())
preferences.extensionUpdatesCount().set(installedExtensions.count { it.hasUpdate })
} }
override fun onExtensionUntrusted(extension: Extension.Untrusted) { override fun onExtensionUntrusted(extension: Extension.Untrusted) {
@ -324,6 +328,7 @@ class ExtensionManager(
override fun onPackageUninstalled(pkgName: String) { override fun onPackageUninstalled(pkgName: String) {
unregisterExtension(pkgName) unregisterExtension(pkgName)
preferences.extensionUpdatesCount().set(installedExtensions.count { it.hasUpdate })
} }
} }

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension package eu.kanade.tachiyomi.extension
import android.content.Context
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -10,61 +11,55 @@ import com.evernote.android.job.JobRequest
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
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.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.util.notification import eu.kanade.tachiyomi.util.notification
import rx.Observable import kotlinx.coroutines.Dispatchers
import rx.android.schedulers.AndroidSchedulers import kotlinx.coroutines.GlobalScope
import rx.schedulers.Schedulers import kotlinx.coroutines.launch
import timber.log.Timber import uy.kohesive.injekt.injectLazy
import uy.kohesive.injekt.Injekt import java.lang.Exception
import uy.kohesive.injekt.api.get import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class ExtensionUpdateJob : Job() { class ExtensionUpdateJob : Job() {
override fun onRunJob(params: Params): Result { override fun onRunJob(params: Params): Result {
val extensionManager: ExtensionManager = Injekt.get() GlobalScope.launch(Dispatchers.IO) {
extensionManager.findAvailableExtensions() val pendingUpdates = ExtensionGithubApi().checkforUpdates(context)
if (pendingUpdates.isNotEmpty()) {
extensionManager.getInstalledExtensionsObservable().map { list -> val names = pendingUpdates.map { it.name }
val pendingUpdates = list.filter { it.hasUpdate } val preferences: PreferencesHelper by injectLazy()
if (pendingUpdates.isNotEmpty()) { preferences.extensionUpdatesCount().set(pendingUpdates.size)
val names = pendingUpdates.map { it.name } NotificationManagerCompat.from(context).apply {
NotificationManagerCompat.from(context).apply { notify(Notifications.ID_UPDATES_TO_EXTS,
notify(Notifications.ID_UPDATES_TO_EXTS, context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) {
context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) { setContentTitle(
setContentTitle( context.resources.getQuantityString(
context.resources.getQuantityString( R.plurals.update_check_notification_ext_updates, names
R.plurals.update_check_notification_ext_updates, names .size, names.size
.size, names.size
)
) )
val extNames = if (names.size > 5) { )
"${names.take(4).joinToString(", ")}, " + context.getString( val extNames = if (names.size > 5) {
R.string.notification_and_n_more, (names.size - 4) "${names.take(4).joinToString(", ")}, " + context.getString(
) R.string.notification_and_n_more, (names.size - 4)
} else names.joinToString(", ")
setContentText(extNames)
setStyle(NotificationCompat.BigTextStyle().bigText(extNames))
setSmallIcon(R.drawable.ic_extension_update)
color = ContextCompat.getColor(context, R.color.colorAccentLight)
setContentIntent(
NotificationReceiver.openExtensionsPendingActivity(
context
)
) )
setAutoCancel(true) } else names.joinToString(", ")
}) setContentText(extNames)
} setStyle(NotificationCompat.BigTextStyle().bigText(extNames))
setSmallIcon(R.drawable.ic_extension_update)
color = ContextCompat.getColor(context, R.color.colorAccentLight)
setContentIntent(
NotificationReceiver.openExtensionsPendingActivity(
context
)
)
setAutoCancel(true)
})
} }
Result.SUCCESS }
}.take(1) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({
}, {
Timber.e(it)
}, {
})
return Result.SUCCESS return Result.SUCCESS
} }

View File

@ -1,18 +1,26 @@
package eu.kanade.tachiyomi.extension.api package eu.kanade.tachiyomi.extension.api
import android.content.Context
import com.github.salomonbrys.kotson.fromJson import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.get import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.int import com.github.salomonbrys.kotson.int
import com.github.salomonbrys.kotson.string import com.github.salomonbrys.kotson.string
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonArray import com.google.gson.JsonArray
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.LoadResult
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.withContext
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.lang.Exception
internal class ExtensionGithubApi { internal class ExtensionGithubApi {
@ -31,6 +39,35 @@ internal class ExtensionGithubApi {
.map(::parseResponse) .map(::parseResponse)
} }
suspend fun checkforUpdates(context: Context): List<Extension.Installed> {
return withContext(Dispatchers.IO) {
val call = GET("$repoUrl/index.json")
val response = client.newCall(call).execute()
if (response.isSuccessful) {
val extensions = parseResponse(response)
val extensionsWithUpdate = mutableListOf<Extension.Installed>()
val installedExtensions = ExtensionLoader.loadExtensions(context)
.filterIsInstance<LoadResult.Success>()
.map { it.extension }
val mutInstalledExtensions = installedExtensions.toMutableList()
for (installedExt in mutInstalledExtensions) {
val pkgName = installedExt.pkgName
val availableExt = extensions.find { it.pkgName == pkgName } ?: continue
val hasUpdate = availableExt.versionCode > installedExt.versionCode
if (hasUpdate) extensionsWithUpdate.add(installedExt)
}
extensionsWithUpdate
} else {
response.close()
throw Exception("Failed to get extensions")
}
}
}
private fun parseResponse(response: Response): List<Extension.Available> { private fun parseResponse(response: Response): List<Extension.Available> {
val text = response.body?.use { it.string() } ?: return emptyList() val text = response.body?.use { it.string() } ?: return emptyList()

View File

@ -82,7 +82,9 @@ internal class ExtensionInstaller(private val context: Context) {
// Always notify on main thread // Always notify on main thread
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
// Always remove the download when unsubscribed // Always remove the download when unsubscribed
.doOnUnsubscribe { deleteDownload(pkgName) } .doOnUnsubscribe {
deleteDownload(pkgName)
}
} }
/** /**

View File

@ -7,6 +7,7 @@ import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Color import android.graphics.Color
import android.graphics.Rect import android.graphics.Rect
import android.graphics.Typeface
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.MotionEvent import android.view.MotionEvent
@ -15,6 +16,7 @@ import android.view.ViewGroup
import android.webkit.WebView import android.webkit.WebView
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
@ -34,6 +36,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationReceiver 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.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
@ -51,6 +54,7 @@ import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import eu.kanade.tachiyomi.ui.setting.SettingsMainController import eu.kanade.tachiyomi.ui.setting.SettingsMainController
import eu.kanade.tachiyomi.util.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.util.gone
import eu.kanade.tachiyomi.util.launchUI import eu.kanade.tachiyomi.util.launchUI
import eu.kanade.tachiyomi.util.marginBottom import eu.kanade.tachiyomi.util.marginBottom
import eu.kanade.tachiyomi.util.marginTop import eu.kanade.tachiyomi.util.marginTop
@ -58,11 +62,16 @@ import eu.kanade.tachiyomi.util.openInBrowser
import eu.kanade.tachiyomi.util.updateLayoutParams import eu.kanade.tachiyomi.util.updateLayoutParams
import eu.kanade.tachiyomi.util.updatePadding import eu.kanade.tachiyomi.util.updatePadding
import eu.kanade.tachiyomi.util.updatePaddingRelative import eu.kanade.tachiyomi.util.updatePaddingRelative
import eu.kanade.tachiyomi.util.visible
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Date import java.util.Date
import java.util.concurrent.TimeUnit
class MainActivity : BaseActivity() { class MainActivity : BaseActivity() {
@ -276,10 +285,32 @@ class MainActivity : BaseActivity() {
ChangelogDialogController().showDialog(router) ChangelogDialogController().showDialog(router)
} }
} }
preferences.extensionUpdatesCount().asObservable().subscribe {
setExtensionsBadge()
}
setExtensionsBadge()
}
fun setExtensionsBadge() {
val extUpdateText: TextView = nav_view.menu.findItem(
R.id.nav_drawer_extensions
)?.actionView as? TextView ?: return
val updates = preferences.extensionUpdatesCount().getOrDefault()
if (updates > 0) {
extUpdateText.text = updates.toString()
extUpdateText.visible()
}
else {
extUpdateText.text = null
extUpdateText.gone()
}
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
getExtensionUpdates()
val useBiometrics = preferences.useBiometrics().getOrDefault() val useBiometrics = preferences.useBiometrics().getOrDefault()
if (useBiometrics && BiometricManager.from(this) if (useBiometrics && BiometricManager.from(this)
.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) { .canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
@ -294,6 +325,21 @@ class MainActivity : BaseActivity() {
preferences.useBiometrics().set(false) preferences.useBiometrics().set(false)
} }
private fun getExtensionUpdates() {
if (Date().time >= preferences.lastExtCheck().getOrDefault() +
TimeUnit.HOURS.toMillis(1)) {
GlobalScope.launch(Dispatchers.IO) {
val preferences: PreferencesHelper by injectLazy()
try {
val pendingUpdates = ExtensionGithubApi().checkforUpdates(this@MainActivity)
preferences.extensionUpdatesCount().set(pendingUpdates.size)
preferences.lastExtCheck().set(Date().time)
} catch (e: java.lang.Exception) { }
}
}
}
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
if (!handleIntentAction(intent)) { if (!handleIntentAction(intent)) {
super.onNewIntent(intent) super.onNewIntent(intent)

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="13dp"/>
<size
android:height="25dp"
android:width="25dp" />
<solid android:color="@color/drawerPrimary"/>
</shape>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:background="@drawable/round_textview_background"
android:textColor="#FFFFFF"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:textStyle="bold"
android:layout_marginStart="12dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"/>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<group android:id="@+id/group_feature" <group android:id="@+id/group_feature"
android:checkableBehavior="single"> android:checkableBehavior="single">
<item <item
@ -22,6 +23,7 @@
<item <item
android:id="@+id/nav_drawer_extensions" android:id="@+id/nav_drawer_extensions"
android:icon="@drawable/ic_extension_black_24dp" android:icon="@drawable/ic_extension_black_24dp"
app:actionLayout="@layout/menu_counter"
android:title="@string/label_extensions"/> android:title="@string/label_extensions"/>
<item <item
android:id="@+id/nav_drawer_downloads" android:id="@+id/nav_drawer_downloads"

View File

@ -566,6 +566,7 @@
<item quantity="other">For %d titles</item> <item quantity="other">For %d titles</item>
</plurals> </plurals>
<string name="notification_and_n_more">and %1$d more chapters.</string> <string name="notification_and_n_more">and %1$d more chapters.</string>
<string name="notification_and_n_more_ext">and %1$d more extensions.</string>
<string name="notification_cover_update_failed">Failed to update cover</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_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_title">Sync canceled</string>