tauri-plugin-widgets 0.5.0

Tauri plugin for App Widgets on Android, iOS, and macOS (WidgetKit); Windows Widgets Board (Adaptive Cards) + desktop webview; Linux desktop webview with X11 DESKTOP pin.
Documentation
package git.s00d.widgets

import android.content.Context

/** Shared storage contract keys (0.4 — no legacy `__widget_config__`). */
object WidgetStoreKeys {
    const val CONFIG_PREFIX = "config:"
    const val PENDING_ACTIONS = "pending_actions"
    const val META_NONCE = "__meta_nonce__"
    const val META_UPDATED_AT = "__meta_updated_at__"
    const val META_PREFS = "__tauri_widget_meta__"
    const val RECEIPTS_PREFS = "__tauri_widget_receipts__"
    const val KEY_ACTIVE_GROUP = "active_group"
    const val KEY_ACTIVE_WIDGET_ID = "active_widget_id"
    const val KEY_REGISTERED = "registered_kinds"
    const val INSTANCE_WIDGET_ID_PREFIX = "widgetId:"
    const val INSTANCE_GROUP_PREFIX = "group:"

    fun configKey(widgetId: String): String = CONFIG_PREFIX + widgetId

    fun isConfigKey(key: String): Boolean = key.startsWith(CONFIG_PREFIX)

    fun instanceWidgetIdKey(appWidgetId: Int): String = INSTANCE_WIDGET_ID_PREFIX + appWidgetId

    fun instanceGroupKey(appWidgetId: Int): String = INSTANCE_GROUP_PREFIX + appWidgetId

    fun hasAnyConfig(prefs: android.content.SharedPreferences): Boolean {
        return prefs.all.keys.any { isConfigKey(it) }
    }

    fun metaPrefs(context: Context) =
        context.getSharedPreferences(META_PREFS, Context.MODE_PRIVATE)

    fun mappedWidgetId(context: Context, appWidgetId: Int): String? {
        if (appWidgetId < 0) return null
        return metaPrefs(context).getString(instanceWidgetIdKey(appWidgetId), null)
    }

    fun mappedGroup(context: Context, appWidgetId: Int): String? {
        if (appWidgetId < 0) return null
        return metaPrefs(context).getString(instanceGroupKey(appWidgetId), null)
    }

    /** Bind an AppWidget instance to a logical widgetId (+ group). */
    fun bindInstance(context: Context, appWidgetId: Int, widgetId: String, group: String?) {
        if (appWidgetId < 0 || widgetId.isBlank()) return
        val ed = metaPrefs(context).edit()
            .putString(instanceWidgetIdKey(appWidgetId), widgetId)
        if (!group.isNullOrBlank()) {
            ed.putString(instanceGroupKey(appWidgetId), group)
        }
        ed.apply()
    }

    /** Clear binding when an AppWidgetId is deleted / recycled. */
    fun clearInstance(context: Context, appWidgetId: Int) {
        if (appWidgetId < 0) return
        metaPrefs(context).edit()
            .remove(instanceWidgetIdKey(appWidgetId))
            .remove(instanceGroupKey(appWidgetId))
            .apply()
    }

    fun resolveGroup(context: Context, appWidgetId: Int = -1): String {
        mappedGroup(context, appWidgetId)?.takeIf { it.isNotBlank() }?.let { mapped ->
            val prefs = context.getSharedPreferences(mapped, Context.MODE_PRIVATE)
            if (hasAnyConfig(prefs)) return mapped
        }

        val active = metaPrefs(context).getString(KEY_ACTIVE_GROUP, null)
        if (!active.isNullOrBlank()) {
            val activePrefs = context.getSharedPreferences(active, Context.MODE_PRIVATE)
            if (hasAnyConfig(activePrefs)) return active
        }

        val packageName = context.packageName
        val packageNameHyphen = packageName.replace('_', '-')
        val metaGroup = runCatching {
            val ai = context.packageManager.getReceiverInfo(
                android.content.ComponentName(context, "git.s00d.widgets.TauriGlanceWidgetReceiver"),
                android.content.pm.PackageManager.GET_META_DATA,
            )
            ai.metaData?.getString("tauri_widget_group")
        }.getOrNull()
        val candidates = listOfNotNull(
            metaGroup?.takeIf { it.isNotBlank() },
            packageName,
            "group.$packageName",
            packageNameHyphen,
            "group.$packageNameHyphen",
        ).distinct()
        for (group in candidates) {
            val prefs = context.getSharedPreferences(group, Context.MODE_PRIVATE)
            if (hasAnyConfig(prefs)) return group
        }
        return candidates.first()
    }

    fun resolveWidgetId(context: Context, appWidgetId: Int = -1): String {
        mappedWidgetId(context, appWidgetId)?.takeIf { it.isNotBlank() }?.let { return it }
        return metaPrefs(context).getString(KEY_ACTIVE_WIDGET_ID, null) ?: "default"
    }
}