Sfoglia il codice sorgente

Minor extension update cleanup, default to on

arkon 5 anni fa
parent
commit
6da350aee6

+ 4 - 0
app/src/main/java/eu/kanade/tachiyomi/Migrations.kt

@@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.data.preference.getOrDefault
 import eu.kanade.tachiyomi.data.updater.UpdaterJob
+import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
 import java.io.File
 
 object Migrations {
@@ -27,6 +28,9 @@ object Migrations {
                 if (BuildConfig.INCLUDE_UPDATER && preferences.automaticUpdates()) {
                     UpdaterJob.setupTask(context)
                 }
+                if (preferences.automaticExtUpdates().getOrDefault()) {
+                    ExtensionUpdateJob.setupTask(context)
+                }
                 return false
             }
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt

@@ -26,7 +26,7 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
     }
 
     companion object {
-        const val TAG = "BackupCreator"
+        private const val TAG = "BackupCreator"
 
         fun setupTask(context: Context, prefInterval: Int? = null) {
             val preferences = Injekt.get<PreferencesHelper>()

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt

@@ -23,7 +23,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
     }
 
     companion object {
-        const val TAG = "LibraryUpdate"
+        private const val TAG = "LibraryUpdate"
 
         fun setupTask(context: Context, prefInterval: Int? = null) {
             val preferences = Injekt.get<PreferencesHelper>()

+ 8 - 10
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt

@@ -410,11 +410,9 @@ class NotificationReceiver : BroadcastReceiver() {
         internal fun markAsReadPendingBroadcast(
             context: Context,
             manga: Manga,
-            chapters:
-                    Array<Chapter>,
+            chapters: Array<Chapter>,
             groupId: Int
-        ):
-                PendingIntent {
+        ): PendingIntent {
             val newIntent = Intent(context, NotificationReceiver::class.java).apply {
                 action = ACTION_MARK_AS_READ
                 putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray())
@@ -442,14 +440,14 @@ class NotificationReceiver : BroadcastReceiver() {
          * Returns [PendingIntent] that opens the extensions controller.
          *
          * @param context context of application
+         * @return [PendingIntent]
          */
         internal fun openExtensionsPendingActivity(context: Context): PendingIntent {
-            val newIntent =
-                Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_EXTENSIONS)
-                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
-            return PendingIntent.getActivity(
-                context, 0, newIntent, PendingIntent.FLAG_UPDATE_CURRENT
-            )
+            val intent = Intent(context, MainActivity::class.java).apply {
+                action = MainActivity.SHORTCUT_EXTENSIONS
+                addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+            }
+            return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
         }
     }
 }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -190,7 +190,7 @@ class PreferencesHelper(val context: Context) {
 
     fun automaticUpdates() = prefs.getBoolean(Keys.automaticUpdates, true)
 
-    fun automaticExtUpdates() = rxPrefs.getBoolean(Keys.automaticExtUpdates, false)
+    fun automaticExtUpdates() = rxPrefs.getBoolean(Keys.automaticExtUpdates, true)
 
     fun extensionUpdatesCount() = rxPrefs.getInteger("ext_updates_count", 0)
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt

@@ -55,7 +55,7 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
     }
 
     companion object {
-        const val TAG = "UpdateChecker"
+        private const val TAG = "UpdateChecker"
 
         fun setupTask(context: Context) {
             val constraints = Constraints.Builder()

+ 8 - 4
app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt

@@ -187,7 +187,7 @@ class ExtensionManager(
         if (changed) {
             installedExtensions = mutInstalledExtensions
         }
-        preferences.extensionUpdatesCount().set(installedExtensions.count { it.hasUpdate })
+        updatePendingUpdatesCount()
     }
 
     /**
@@ -318,12 +318,12 @@ class ExtensionManager(
 
         override fun onExtensionInstalled(extension: Extension.Installed) {
             registerNewExtension(extension.withUpdateCheck())
-            preferences.extensionUpdatesCount().set(installedExtensions.count { it.hasUpdate })
+            updatePendingUpdatesCount()
         }
 
         override fun onExtensionUpdated(extension: Extension.Installed) {
             registerUpdatedExtension(extension.withUpdateCheck())
-            preferences.extensionUpdatesCount().set(installedExtensions.count { it.hasUpdate })
+            updatePendingUpdatesCount()
         }
 
         override fun onExtensionUntrusted(extension: Extension.Untrusted) {
@@ -332,7 +332,7 @@ class ExtensionManager(
 
         override fun onPackageUninstalled(pkgName: String) {
             unregisterExtension(pkgName)
-            preferences.extensionUpdatesCount().set(installedExtensions.count { it.hasUpdate })
+            updatePendingUpdatesCount()
         }
     }
 
@@ -346,4 +346,8 @@ class ExtensionManager(
         }
         return this
     }
+
+    private fun updatePendingUpdatesCount() {
+        preferences.extensionUpdatesCount().set(installedExtensions.count { it.hasUpdate })
+    }
 }

+ 25 - 24
app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt

@@ -31,36 +31,37 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
         } catch (e: Exception) {
             return@coroutineScope Result.failure()
         }
+
         if (pendingUpdates.isNotEmpty()) {
-            val names = pendingUpdates.map { it.name }
-            NotificationManagerCompat.from(context).apply {
-                notify(Notifications.ID_UPDATES_TO_EXTS,
-                    context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) {
-                        setContentTitle(
-                            context.resources.getQuantityString(
-                                R.plurals.update_check_notification_ext_updates,
-                                names.size,
-                                names.size
-                            )
-                        )
-                        val extNames = names.joinToString(", ")
-                        setContentText(extNames)
-                        setStyle(NotificationCompat.BigTextStyle().bigText(extNames))
-                        setSmallIcon(R.drawable.ic_extension_24dp)
-                        setContentIntent(
-                            NotificationReceiver.openExtensionsPendingActivity(
-                                context
-                            )
-                        )
-                        setAutoCancel(true)
-                    })
-            }
+            createUpdateNotification(pendingUpdates.map { it.name })
         }
+
         Result.success()
     }
 
+    private fun createUpdateNotification(names: List<String>) {
+        NotificationManagerCompat.from(context).apply {
+            notify(Notifications.ID_UPDATES_TO_EXTS,
+                context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) {
+                    setContentTitle(
+                        context.resources.getQuantityString(
+                            R.plurals.update_check_notification_ext_updates,
+                            names.size,
+                            names.size
+                        )
+                    )
+                    val extNames = names.joinToString(", ")
+                    setContentText(extNames)
+                    setStyle(NotificationCompat.BigTextStyle().bigText(extNames))
+                    setSmallIcon(R.drawable.ic_extension_24dp)
+                    setContentIntent(NotificationReceiver.openExtensionsPendingActivity(context))
+                    setAutoCancel(true)
+                })
+        }
+    }
+
     companion object {
-        const val TAG = "ExtensionUpdate"
+        private const val TAG = "ExtensionUpdate"
 
         fun setupTask(context: Context, forceAutoUpdateJob: Boolean? = null) {
             val preferences = Injekt.get<PreferencesHelper>()

+ 12 - 3
app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt

@@ -7,12 +7,14 @@ import com.github.salomonbrys.kotson.int
 import com.github.salomonbrys.kotson.string
 import com.google.gson.Gson
 import com.google.gson.JsonArray
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 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.NetworkHelper
 import eu.kanade.tachiyomi.network.await
+import java.util.Date
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 import okhttp3.Response
@@ -21,6 +23,7 @@ import uy.kohesive.injekt.injectLazy
 internal class ExtensionGithubApi {
 
     private val network: NetworkHelper by injectLazy()
+    private val preferences: PreferencesHelper by injectLazy()
 
     private val gson: Gson by injectLazy()
 
@@ -33,23 +36,29 @@ internal class ExtensionGithubApi {
     }
 
     suspend fun checkForUpdates(context: Context): List<Extension.Installed> {
+        val call = GET(EXT_URL)
+
         return withContext(Dispatchers.IO) {
-            val call = GET(EXT_URL)
             val response = network.client.newCall(call).await()
 
+            preferences.lastExtCheck().set(Date().time)
+
             if (response.isSuccessful) {
                 val extensions = parseResponse(response)
-                val extensionsWithUpdate = mutableListOf<Extension.Installed>()
 
                 val installedExtensions = ExtensionLoader.loadExtensions(context)
                     .filterIsInstance<LoadResult.Success>()
                     .map { it.extension }
+
+                val extensionsWithUpdate = mutableListOf<Extension.Installed>()
                 for (installedExt in installedExtensions) {
                     val pkgName = installedExt.pkgName
                     val availableExt = extensions.find { it.pkgName == pkgName } ?: continue
 
                     val hasUpdate = availableExt.versionCode > installedExt.versionCode
-                    if (hasUpdate) extensionsWithUpdate.add(installedExt)
+                    if (hasUpdate) {
+                        extensionsWithUpdate.add(installedExt)
+                    }
                 }
 
                 extensionsWithUpdate

+ 3 - 4
app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt

@@ -36,6 +36,8 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
         FlexibleAdapter.OnItemLongClickListener,
         ExtensionTrustDialog.Listener {
 
+    private val preferences: PreferencesHelper = Injekt.get()
+
     /**
      * Adapter containing the list of manga from the catalogue.
      */
@@ -92,7 +94,6 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
             }
             R.id.action_auto_check -> {
                 item.isChecked = !item.isChecked
-                val preferences: PreferencesHelper = Injekt.get()
                 preferences.automaticExtUpdates().set(item.isChecked)
                 ExtensionUpdateJob.setupTask(activity!!, item.isChecked)
             }
@@ -150,9 +151,7 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
         // Fixes problem with the overflow icon showing up in lieu of search
         searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() })
 
-        val autoItem = menu.findItem(R.id.action_auto_check)
-        val preferences: PreferencesHelper = Injekt.get()
-        autoItem.isChecked = preferences.automaticExtUpdates().getOrDefault()
+        menu.findItem(R.id.action_auto_check).isChecked = preferences.automaticExtUpdates().getOrDefault()
     }
 
     override fun onItemClick(view: View, position: Int): Boolean {

+ 18 - 20
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

@@ -13,7 +13,6 @@ import com.bluelinelabs.conductor.RouterTransaction
 import eu.kanade.tachiyomi.Migrations
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.notification.NotificationReceiver
-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.ui.base.activity.BaseActivity
@@ -39,7 +38,6 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
 import timber.log.Timber
-import uy.kohesive.injekt.injectLazy
 
 class MainActivity : BaseActivity() {
 
@@ -152,33 +150,33 @@ class MainActivity : BaseActivity() {
         }
     }
 
+    override fun onResume() {
+        super.onResume()
+        getExtensionUpdates()
+    }
+
     private fun setExtensionsBadge() {
         val updates = preferences.extensionUpdatesCount().getOrDefault()
         if (updates > 0) {
-            val badge = bottom_nav.getOrCreateBadge(R.id.nav_more)
-            badge.number = updates
+            bottom_nav.getOrCreateBadge(R.id.nav_more).number = updates
         } else {
             bottom_nav.removeBadge(R.id.nav_more)
         }
     }
 
-    override fun onResume() {
-        super.onResume()
-        getExtensionUpdates()
-    }
-
     private fun getExtensionUpdates() {
-        if (Date().time >= preferences.lastExtCheck().getOrDefault() +
-            TimeUnit.HOURS.toMillis(2)) {
-            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) {
-                    Timber.e(e)
-                }
+        // Limit checks to once every 2 hours at most
+        val now = Date().time
+        if (now < preferences.lastExtCheck().getOrDefault() + TimeUnit.HOURS.toMillis(2)) {
+            return
+        }
+
+        GlobalScope.launch(Dispatchers.IO) {
+            try {
+                val pendingUpdates = ExtensionGithubApi().checkForUpdates(this@MainActivity)
+                preferences.extensionUpdatesCount().set(pendingUpdates.size)
+            } catch (e: Exception) {
+                Timber.e(e)
             }
         }
     }

+ 7 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt

@@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.ui.more
 
 import androidx.preference.PreferenceScreen
 import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
 import eu.kanade.tachiyomi.ui.base.controller.RootController
 import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
 import eu.kanade.tachiyomi.ui.download.DownloadController
@@ -9,7 +11,7 @@ import eu.kanade.tachiyomi.ui.extension.ExtensionController
 import eu.kanade.tachiyomi.ui.migration.MigrationController
 import eu.kanade.tachiyomi.ui.setting.SettingsController
 import eu.kanade.tachiyomi.ui.setting.SettingsMainController
-import eu.kanade.tachiyomi.util.preference.extensionPreference
+import eu.kanade.tachiyomi.util.preference.badgePreference
 import eu.kanade.tachiyomi.util.preference.iconRes
 import eu.kanade.tachiyomi.util.preference.iconTint
 import eu.kanade.tachiyomi.util.preference.onClick
@@ -18,6 +20,8 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory
 import eu.kanade.tachiyomi.util.preference.titleRes
 import eu.kanade.tachiyomi.util.system.getResourceColor
 import eu.kanade.tachiyomi.util.system.openInBrowser
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
 
 class MoreController : SettingsController(), RootController {
 
@@ -26,10 +30,11 @@ class MoreController : SettingsController(), RootController {
 
         val tintColor = context.getResourceColor(R.attr.colorAccent)
 
-        extensionPreference {
+        badgePreference {
             titleRes = R.string.label_extensions
             iconRes = R.drawable.ic_extension_24dp
             iconTint = tintColor
+            setBadge(Injekt.get<PreferencesHelper>().extensionUpdatesCount().getOrDefault())
             onClick {
                 router.pushController(ExtensionController().withFadeTransaction())
             }

+ 3 - 3
app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceDSL.kt

@@ -13,7 +13,7 @@ import androidx.preference.PreferenceManager
 import androidx.preference.PreferenceScreen
 import androidx.preference.SwitchPreferenceCompat
 import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
-import eu.kanade.tachiyomi.widget.preference.ExtensionPreference
+import eu.kanade.tachiyomi.widget.preference.BadgePreference
 import eu.kanade.tachiyomi.widget.preference.IntListPreference
 import eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory
 
@@ -57,8 +57,8 @@ inline fun PreferenceGroup.multiSelectListPreference(block: (@DSL MultiSelectLis
     return initThenAdd(MultiSelectListPreference(context), block).also(::initDialog)
 }
 
-inline fun PreferenceGroup.extensionPreference(block: (@DSL Preference).() -> Unit): ExtensionPreference {
-    return initThenAdd(ExtensionPreference(context), block)
+inline fun PreferenceGroup.badgePreference(block: (@DSL BadgePreference).() -> Unit): BadgePreference {
+    return initThenAdd(BadgePreference(context), block)
 }
 
 inline fun PreferenceScreen.preferenceCategory(block: (@DSL PreferenceCategory).() -> Unit): PreferenceCategory {

+ 37 - 0
app/src/main/java/eu/kanade/tachiyomi/widget/preference/BadgePreference.kt

@@ -0,0 +1,37 @@
+package eu.kanade.tachiyomi.widget.preference
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.util.view.gone
+import eu.kanade.tachiyomi.util.view.visible
+import kotlinx.android.synthetic.main.pref_badge.view.badge
+
+class BadgePreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+    Preference(context, attrs) {
+
+    private var badgeNumber: Int = 0
+
+    init {
+        widgetLayoutResource = R.layout.pref_badge
+    }
+
+    override fun onBindViewHolder(holder: PreferenceViewHolder) {
+        super.onBindViewHolder(holder)
+
+        if (badgeNumber > 0) {
+            holder.itemView.badge.text = badgeNumber.toString()
+            holder.itemView.badge.visible()
+        } else {
+            holder.itemView.badge.text = null
+            holder.itemView.badge.gone()
+        }
+    }
+
+    fun setBadge(number: Int) {
+        this.badgeNumber = number
+        notifyChanged()
+    }
+}

+ 0 - 37
app/src/main/java/eu/kanade/tachiyomi/widget/preference/ExtensionPreference.kt

@@ -1,37 +0,0 @@
-package eu.kanade.tachiyomi.widget.preference
-
-import android.content.Context
-import android.util.AttributeSet
-import androidx.preference.Preference
-import androidx.preference.PreferenceViewHolder
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.preference.PreferencesHelper
-import eu.kanade.tachiyomi.data.preference.getOrDefault
-import eu.kanade.tachiyomi.util.view.gone
-import eu.kanade.tachiyomi.util.view.visible
-import kotlinx.android.synthetic.main.preference_update_text.view.*
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-
-class ExtensionPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
-    Preference(context, attrs) {
-
-    init {
-        widgetLayoutResource = R.layout.preference_update_text
-    }
-
-    override fun onBindViewHolder(holder: PreferenceViewHolder) {
-        super.onBindViewHolder(holder)
-
-        val extUpdateText = holder.itemView.textView
-
-        val updates = Injekt.get<PreferencesHelper>().extensionUpdatesCount().getOrDefault()
-        if (updates > 0) {
-            extUpdateText.text = updates.toString()
-            extUpdateText.visible()
-        } else {
-            extUpdateText.text = null
-            extUpdateText.gone()
-        }
-    }
-}

+ 5 - 5
app/src/main/res/drawable/round_textview_background.xml

@@ -1,9 +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"/>
+    <corners android:radius="13dp" />
     <size
-        android:height="25dp"
-        android:width="25dp" />
-    <solid android:color="@color/material_red_900"/>
-</shape>
+        android:width="25dp"
+        android:height="25dp" />
+    <solid android:color="?attr/colorError" />
+</shape>

+ 5 - 10
app/src/main/res/layout/preference_update_text.xml → app/src/main/res/layout/pref_badge.xml

@@ -1,17 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/textView"
+    android:id="@+id/badge"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:gravity="center"
-    android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
     android:background="@drawable/round_textview_background"
-    android:textColor="#FFFFFF"
-    android:layout_marginTop="12dp"
-    android:layout_marginBottom="12dp"
+    android:gravity="center"
+    android:padding="3dp"
+    android:textAppearance="@style/TextAppearance.MaterialComponents.Badge"
     android:textStyle="bold"
-    tools:text="3"
-    android:layout_marginStart="12dp"
-    android:paddingStart="3dp"
-    android:paddingEnd="3dp"/>
+    tools:text="3" />

+ 2 - 2
app/src/main/res/menu/extension_main.xml

@@ -18,8 +18,8 @@
 
     <item
         android:id="@+id/action_auto_check"
-        android:title="@string/action_auto_check_extensions"
         android:checkable="true"
-        app:showAsAction="never"/>
+        android:title="@string/pref_enable_automatic_extension_updates"
+        app:showAsAction="never" />
 
 </menu>

+ 2 - 2
app/src/main/res/values/strings.xml

@@ -211,7 +211,7 @@
     <string name="ext_version_info">Version: %1$s</string>
     <string name="ext_language_info">Language: %1$s</string>
     <string name="ext_empty_preferences">No preferences to edit for this extension</string>
-    <string name="action_auto_check_extensions">Auto-check for updates</string>
+    <string name="pref_enable_automatic_extension_updates">Check for extension updates</string>
 
       <!-- Reader section -->
     <string name="pref_fullscreen">Fullscreen</string>
@@ -601,6 +601,6 @@
     <string name="channel_library">Library</string>
     <string name="channel_downloader">Downloader</string>
     <string name="channel_new_chapters">Chapter updates</string>
-    <string name="channel_ext_updates">Extension Updates</string>
+    <string name="channel_ext_updates">Extension updates</string>
 
 </resources>