diff --git a/plugins/toaster_utils/toaster_utils_android/android/build.gradle b/plugins/toaster_utils/toaster_utils_android/android/build.gradle index 51da051..e19c4d8 100644 --- a/plugins/toaster_utils/toaster_utils_android/android/build.gradle +++ b/plugins/toaster_utils/toaster_utils_android/android/build.gradle @@ -48,10 +48,14 @@ android { useJUnitPlatform() testLogging { - events "passed", "skipped", "failed", "standardOut", "standardError" - outputs.upToDateWhen {false} - showStandardStreams = true + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen { false } + showStandardStreams = true } } } -} \ No newline at end of file + + buildFeatures { + buildConfig = true + } +} diff --git a/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/Messages.g.kt b/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/Messages.g.kt index 7437186..b3eb165 100644 --- a/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/Messages.g.kt +++ b/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/Messages.g.kt @@ -1,4 +1,4 @@ -// Copyright (c) 2026, Org Mars3142 +// Copyright (c) 2026, mars3142 // Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -34,6 +34,150 @@ private object MessagesPigeonUtils { ) } } + fun doubleEquals(a: Double, b: Double): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN()) + } + + fun floatEquals(a: Float, b: Float): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN()) + } + + fun doubleHash(d: Double): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (d == 0.0) 0.0 else d + val bits = java.lang.Double.doubleToLongBits(normalized) + return (bits xor (bits ushr 32)).toInt() + } + + fun floatHash(f: Float): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (f == 0.0f) 0.0f else f + return java.lang.Float.floatToIntBits(normalized) + } + + fun deepEquals(a: Any?, b: Any?): Boolean { + if (a === b) { + return true + } + if (a == null || b == null) { + return false + } + if (a is ByteArray && b is ByteArray) { + return a.contentEquals(b) + } + if (a is IntArray && b is IntArray) { + return a.contentEquals(b) + } + if (a is LongArray && b is LongArray) { + return a.contentEquals(b) + } + if (a is DoubleArray && b is DoubleArray) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!doubleEquals(a[i], b[i])) return false + } + return true + } + if (a is FloatArray && b is FloatArray) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!floatEquals(a[i], b[i])) return false + } + return true + } + if (a is Array<*> && b is Array<*>) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!deepEquals(a[i], b[i])) return false + } + return true + } + if (a is List<*> && b is List<*>) { + if (a.size != b.size) return false + val iterA = a.iterator() + val iterB = b.iterator() + while (iterA.hasNext() && iterB.hasNext()) { + if (!deepEquals(iterA.next(), iterB.next())) return false + } + return true + } + if (a is Map<*, *> && b is Map<*, *>) { + if (a.size != b.size) return false + for (entry in a) { + val key = entry.key + var found = false + for (bEntry in b) { + if (deepEquals(key, bEntry.key)) { + if (deepEquals(entry.value, bEntry.value)) { + found = true + break + } else { + return false + } + } + } + if (!found) return false + } + return true + } + if (a is Double && b is Double) { + return doubleEquals(a, b) + } + if (a is Float && b is Float) { + return floatEquals(a, b) + } + return a == b + } + + fun deepHash(value: Any?): Int { + return when (value) { + null -> 0 + is ByteArray -> value.contentHashCode() + is IntArray -> value.contentHashCode() + is LongArray -> value.contentHashCode() + is DoubleArray -> { + var result = 1 + for (item in value) { + result = 31 * result + doubleHash(item) + } + result + } + is FloatArray -> { + var result = 1 + for (item in value) { + result = 31 * result + floatHash(item) + } + result + } + is Array<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is List<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is Map<*, *> -> { + var result = 0 + for (entry in value) { + result += ((deepHash(entry.key) * 31) xor deepHash(entry.value)) + } + result + } + is Double -> doubleHash(value) + is Float -> floatHash(value) + else -> value.hashCode() + } + } + } /** @@ -47,12 +191,126 @@ class FlutterError ( override val message: String? = null, val details: Any? = null ) : RuntimeException() + +/** Generated class from Pigeon that represents data sent in messages. */ +data class App ( + val packageName: String, + val appName: String, + val installedBy: String, + val icon: ByteArray +) + { + companion object { + fun fromList(pigeonVar_list: List): App { + val packageName = pigeonVar_list[0] as String + val appName = pigeonVar_list[1] as String + val installedBy = pigeonVar_list[2] as String + val icon = pigeonVar_list[3] as ByteArray + return App(packageName, appName, installedBy, icon) + } + } + fun toList(): List { + return listOf( + packageName, + appName, + installedBy, + icon, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as App + return MessagesPigeonUtils.deepEquals(this.packageName, other.packageName) && MessagesPigeonUtils.deepEquals(this.appName, other.appName) && MessagesPigeonUtils.deepEquals(this.installedBy, other.installedBy) && MessagesPigeonUtils.deepEquals(this.icon, other.icon) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + MessagesPigeonUtils.deepHash(this.packageName) + result = 31 * result + MessagesPigeonUtils.deepHash(this.appName) + result = 31 * result + MessagesPigeonUtils.deepHash(this.installedBy) + result = 31 * result + MessagesPigeonUtils.deepHash(this.icon) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class Toast ( + val app: App, + val message: String, + val timestamp: Long, + val duration: Long +) + { + companion object { + fun fromList(pigeonVar_list: List): Toast { + val app = pigeonVar_list[0] as App + val message = pigeonVar_list[1] as String + val timestamp = pigeonVar_list[2] as Long + val duration = pigeonVar_list[3] as Long + return Toast(app, message, timestamp, duration) + } + } + fun toList(): List { + return listOf( + app, + message, + timestamp, + duration, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as Toast + return MessagesPigeonUtils.deepEquals(this.app, other.app) && MessagesPigeonUtils.deepEquals(this.message, other.message) && MessagesPigeonUtils.deepEquals(this.timestamp, other.timestamp) && MessagesPigeonUtils.deepEquals(this.duration, other.duration) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + MessagesPigeonUtils.deepHash(this.app) + result = 31 * result + MessagesPigeonUtils.deepHash(this.message) + result = 31 * result + MessagesPigeonUtils.deepHash(this.timestamp) + result = 31 * result + MessagesPigeonUtils.deepHash(this.duration) + return result + } +} private open class MessagesPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { - return super.readValueOfType(type, buffer) + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as? List)?.let { + App.fromList(it) + } + } + 130.toByte() -> { + return (readValue(buffer) as? List)?.let { + Toast.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } } override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { - super.writeValue(stream, value) + when (value) { + is App -> { + stream.write(129) + writeValue(stream, value.toList()) + } + is Toast -> { + stream.write(130) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } } } @@ -60,6 +318,13 @@ private open class MessagesPigeonCodec : StandardMessageCodec() { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface ToasterUtilsApi { fun getPlatformName(callback: (Result) -> Unit) + fun getInstalledApps(callback: (Result>) -> Unit) + fun getInstalledApp(packageName: String, callback: (Result) -> Unit) + fun getToasts(packageName: String, callback: (Result>) -> Unit) + fun getToastFiltered(packageName: String, from: Long, callback: (Result>) -> Unit) + fun isServiceRunning(callback: (Result) -> Unit) + fun showSettings(callback: (Result) -> Unit) + fun exampleToast(callback: (Result) -> Unit) companion object { /** The codec used by ToasterUtilsApi. */ @@ -88,6 +353,137 @@ interface ToasterUtilsApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getInstalledApps$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.getInstalledApps{ result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getInstalledApp$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val packageNameArg = args[0] as String + api.getInstalledApp(packageNameArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getToasts$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val packageNameArg = args[0] as String + api.getToasts(packageNameArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getToastFiltered$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val packageNameArg = args[0] as String + val fromArg = args[1] as Long + api.getToastFiltered(packageNameArg, fromArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.isServiceRunning$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.isServiceRunning{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.showSettings$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.showSettings{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + reply.reply(MessagesPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.exampleToast$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.exampleToast{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + reply.reply(MessagesPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } } } } diff --git a/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/ToasterUtilsPlugin.kt b/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/ToasterUtilsPlugin.kt index 06bbd0a..0aa25a1 100644 --- a/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/ToasterUtilsPlugin.kt +++ b/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/ToasterUtilsPlugin.kt @@ -1,14 +1,20 @@ package org.mars3142 +import App +import Toast import ToasterUtilsApi +import android.content.Context +import android.content.Intent +import android.os.Handler +import android.os.Looper +import android.provider.Settings import io.flutter.embedding.engine.plugins.FlutterPlugin class ToasterUtilsPlugin : FlutterPlugin, ToasterUtilsApi { - companion object { - private const val TAG = "ToasterUtilsPlugin" - } + private var context: Context? = null override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + context = binding.applicationContext ToasterUtilsApi.setUp(binding.binaryMessenger, this) } @@ -19,4 +25,43 @@ class ToasterUtilsPlugin : FlutterPlugin, ToasterUtilsApi { override fun getPlatformName(callback: (Result) -> Unit) { callback(Result.success("Android ${android.os.Build.VERSION.RELEASE}")) } -} \ No newline at end of file + + override fun getInstalledApps(callback: (Result>) -> Unit) { + callback(Result.success(emptyList())) + } + + override fun getInstalledApp(packageName: String, callback: (Result) -> Unit) { + callback(Result.success(null)) + } + + override fun getToasts(packageName: String, callback: (Result>) -> Unit) { + callback(Result.success(emptyList())) + } + + override fun getToastFiltered( + packageName: String, + from: Long, + callback: (Result>) -> Unit + ) { + callback(Result.success(emptyList())) + } + + override fun isServiceRunning(callback: (Result) -> Unit) { + callback(Result.success(true)) + } + + override fun showSettings(callback: (Result) -> Unit) { + context?.startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)) + callback(Result.success(Unit)) + } + + override fun exampleToast(callback: (Result) -> Unit) { + context?.let { ctx -> + Handler(Looper.getMainLooper()).post { + android.widget.Toast.makeText(ctx, R.string.example_toast, android.widget.Toast.LENGTH_LONG) + .show() + } + } + callback(Result.success(Unit)) + } +} diff --git a/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/toaster/database/DatabaseHelper.kt b/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/toaster/database/DatabaseHelper.kt new file mode 100644 index 0000000..ff907a4 --- /dev/null +++ b/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/toaster/database/DatabaseHelper.kt @@ -0,0 +1,31 @@ +package org.mars3142.toaster.database + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import android.util.Log + +class DatabaseHelper(context: Context?) : + SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { + + companion object { + private const val DATABASE_NAME = "toaster.db3" + private const val DATABASE_VERSION = 1 + private const val TAG = "DatabaseHelper" + } + + override fun onCreate(db: SQLiteDatabase) { + Log.v(TAG, "create database") + ToasterTable.onCreate(db) + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + Log.v(TAG, "Upgrading database from version $oldVersion to $newVersion") + ToasterTable.onUpgrade(db, oldVersion, newVersion) + } + + override fun onDowngrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { + Log.v(TAG, "Downgrading database from version $oldVersion to $newVersion") + ToasterTable.onDowngrade(db, oldVersion, newVersion) + } +} diff --git a/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/toaster/database/ToasterProvider.kt b/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/toaster/database/ToasterProvider.kt new file mode 100644 index 0000000..61c76e2 --- /dev/null +++ b/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/toaster/database/ToasterProvider.kt @@ -0,0 +1,164 @@ +package org.mars3142.toaster.database + +import android.content.ContentProvider +import android.content.ContentUris +import android.content.ContentValues +import android.content.UriMatcher +import android.database.Cursor +import android.database.SQLException +import android.database.sqlite.SQLiteQueryBuilder +import android.net.Uri +import android.provider.BaseColumns +import android.util.Log +import io.flutter.BuildConfig + +class ToasterProvider : ContentProvider() { + + companion object { + const val AUTHORITY: String = "org.mars3142.toaster" + + private val TAG = ToasterProvider::class.java.simpleName + private const val TOASTER = 1 + private const val TOASTER_ID = 2 + private const val PACKAGE = 3 + + private val toasterMap = hashMapOf( + BaseColumns._ID to BaseColumns._ID, + ToasterTable.TIMESTAMP to ToasterTable.TIMESTAMP, + ToasterTable.MESSAGE to ToasterTable.MESSAGE, + ToasterTable.PACKAGE to ToasterTable.PACKAGE, + ToasterTable.VERSIONCODE to ToasterTable.VERSIONCODE, + ToasterTable.VERSIONNAME to ToasterTable.VERSIONNAME, + BaseColumns._COUNT to BaseColumns._COUNT, + ) + + private val packageMap = hashMapOf( + ToasterTable.PACKAGE to ToasterTable.PACKAGE, + ) + + private val mUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { + addURI(AUTHORITY, "toaster", TOASTER) + addURI(AUTHORITY, "toaster/#", TOASTER_ID) + addURI(AUTHORITY, "packages", PACKAGE) + } + } + + private var dbHelper: DatabaseHelper? = null + + override fun onCreate(): Boolean { + dbHelper = DatabaseHelper(context) + return true + } + + override fun query( + uri: Uri, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ): Cursor? { + if (BuildConfig.DEBUG) { + Log.v(TAG, "query: uri=$uri projection=${projection.contentToString()} selection=[$selection] args=${selectionArgs.contentToString()} order=[$sortOrder]") + } + + val queryBuilder = SQLiteQueryBuilder() + val effectiveSortOrder = when (mUriMatcher.match(uri)) { + TOASTER -> { + queryBuilder.tables = ToasterTable.TABLENAME + queryBuilder.projectionMap = toasterMap + sortOrder ?: "${ToasterTable.TIMESTAMP} DESC" + } + TOASTER_ID -> { + queryBuilder.tables = ToasterTable.TABLENAME + queryBuilder.projectionMap = toasterMap + queryBuilder.appendWhere("${BaseColumns._ID} = ${uri.lastPathSegment}") + sortOrder + } + PACKAGE -> { + queryBuilder.tables = ToasterTable.TABLENAME + queryBuilder.projectionMap = packageMap + queryBuilder.isDistinct = true + sortOrder ?: "${ToasterTable.PACKAGE} ASC" + } + else -> throw IllegalArgumentException("Unknown URI: $uri") + } + + val database = dbHelper?.readableDatabase ?: return null + val cursor = queryBuilder.query(database, projection, selection, selectionArgs, null, null, effectiveSortOrder) + context?.let { cursor?.setNotificationUri(it.contentResolver, uri) } + return cursor + } + + override fun getType(uri: Uri): String { + if (BuildConfig.DEBUG) Log.v(TAG, "getType: uri=$uri") + return when (mUriMatcher.match(uri)) { + TOASTER -> ToasterTable.CONTENT_TYPE + TOASTER_ID -> ToasterTable.CONTENT_ITEM_TYPE + else -> throw IllegalArgumentException("Unknown URI: $uri") + } + } + + @Synchronized + override fun insert(uri: Uri, initialValues: ContentValues?): Uri? { + if (BuildConfig.DEBUG) Log.v(TAG, "insert: uri=$uri initialValues=[$initialValues]") + + if (mUriMatcher.match(uri) != TOASTER) throw IllegalArgumentException("Unknown URI: $uri") + + val values = initialValues?.let { ContentValues(it) } ?: ContentValues() + val rowId = dbHelper?.writableDatabase?.insert(ToasterTable.TABLENAME, null, values) ?: 0L + if (rowId > 0) { + context?.contentResolver?.notifyChange(uri, null) + return ContentUris.withAppendedId(ToasterTable.TOASTER_URI, rowId) + } + throw SQLException("Failed to insert row into $uri") + } + + @Synchronized + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { + if (BuildConfig.DEBUG) { + Log.v(TAG, "delete: uri=$uri selection=[$selection] args=${selectionArgs.contentToString()}") + } + + val database = dbHelper?.writableDatabase ?: return 0 + val count = when (mUriMatcher.match(uri)) { + TOASTER -> database.delete(ToasterTable.TABLENAME, selection, selectionArgs) + TOASTER_ID -> { + val finalWhere = buildString { + append("${BaseColumns._ID} = ${uri.lastPathSegment}") + if (selection != null) append(" AND $selection") + } + database.delete(ToasterTable.TABLENAME, finalWhere, selectionArgs) + } + else -> throw IllegalArgumentException("Unknown URI: $uri") + } + context?.contentResolver?.notifyChange(uri, null) + return count + } + + @Synchronized + override fun update( + uri: Uri, + values: ContentValues?, + selection: String?, + selectionArgs: Array? + ): Int { + if (BuildConfig.DEBUG) { + Log.v(TAG, "update: uri=$uri values=[$values] selection=[$selection] args=${selectionArgs.contentToString()}") + } + + val database = dbHelper?.writableDatabase ?: return 0 + val count = when (mUriMatcher.match(uri)) { + TOASTER -> database.update(ToasterTable.TABLENAME, values, selection, selectionArgs) + TOASTER_ID -> { + val finalWhere = buildString { + append("${BaseColumns._ID} = ${uri.lastPathSegment}") + if (selection != null) append(" AND $selection") + } + database.update(ToasterTable.TABLENAME, values, finalWhere, selectionArgs) + } + else -> throw IllegalArgumentException("Unknown URI: $uri") + } + context?.contentResolver?.notifyChange(uri, null) + return count + } +} diff --git a/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/toaster/database/ToasterTable.kt b/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/toaster/database/ToasterTable.kt new file mode 100644 index 0000000..d6fdd3b --- /dev/null +++ b/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/toaster/database/ToasterTable.kt @@ -0,0 +1,52 @@ +package org.mars3142.toaster.database + +import android.content.ContentResolver +import android.database.SQLException +import android.database.sqlite.SQLiteDatabase +import android.net.Uri +import android.provider.BaseColumns +import androidx.core.net.toUri + +object ToasterTable : BaseColumns { + const val TABLENAME: String = "toaster" + const val TIMESTAMP: String = "timestamp" + const val PACKAGE: String = "package" + const val MESSAGE: String = "message" + const val VERSIONCODE: String = "version_code" + const val VERSIONNAME: String = "version_name" + val TOASTER_URI: Uri = "content://${ToasterProvider.AUTHORITY}/toaster".toUri() + val PACKAGE_URI: Uri = "content://${ToasterProvider.AUTHORITY}/packages".toUri() + val CONTENT_TYPE: String = "${ContentResolver.CURSOR_DIR_BASE_TYPE}/vnd.mars3142.content.toaster" + val CONTENT_ITEM_TYPE: String = "${ContentResolver.CURSOR_ITEM_BASE_TYPE}/vnd.mars3142.content.toaster" + + fun onCreate(db: SQLiteDatabase) { + db.execSQL( + """ + CREATE TABLE $TABLENAME (${BaseColumns._ID} INTEGER PRIMARY KEY AUTOINCREMENT, + $TIMESTAMP LONG, $PACKAGE TEXT, $MESSAGE TEXT, $VERSIONCODE INTEGER, + $VERSIONNAME TEXT ); + """ + ) + } + + fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + if (newVersion > oldVersion) { + when (oldVersion) { + 0 -> try { + db.execSQL("ALTER TABLE $TABLENAME ADD COLUMN $VERSIONCODE INTEGER;") + db.execSQL("ALTER TABLE $TABLENAME ADD COLUMN $VERSIONNAME TEXT;") + } catch (ex: SQLException) { + // upgrade already done + } + + else -> { + db.execSQL("DROP TABLE IF EXISTS $TABLENAME") + onCreate(db) + } + } + } + } + + @Suppress("UNUSED_PARAMETER") + fun onDowngrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {} +} diff --git a/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/toaster/services/ToasterService.kt b/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/toaster/services/ToasterService.kt new file mode 100644 index 0000000..d142751 --- /dev/null +++ b/plugins/toaster_utils/toaster_utils_android/android/src/main/kotlin/org/mars3142/toaster/services/ToasterService.kt @@ -0,0 +1,36 @@ +package org.mars3142.toaster.services + +import android.accessibilityservice.AccessibilityService +import android.annotation.SuppressLint +import android.app.Notification +import android.content.ContentValues +import android.content.Intent +import android.util.Log +import android.view.accessibility.AccessibilityEvent +import org.mars3142.toaster.database.ToasterTable + +@SuppressLint("AccessibilityPolicy") +class ToasterService : AccessibilityService() { + companion object { + private val TAG = ToasterService::class.java.simpleName + } + + override fun onAccessibilityEvent(event: AccessibilityEvent) { + if (event.eventType != AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) { + Log.d(TAG, "Unexpected event type") + return + } + if (event.parcelableData !is Notification) return + + val message = event.text.joinToString("\n") { it.toString() } + val cv = ContentValues().apply { + put(ToasterTable.PACKAGE, event.packageName as String?) + put(ToasterTable.MESSAGE, message) + put(ToasterTable.TIMESTAMP, System.currentTimeMillis()) + } + contentResolver.insert(ToasterTable.TOASTER_URI, cv) + sendBroadcast(Intent("org.mars3142.toaster.APPWIDGET_UPDATE")) + } + + override fun onInterrupt() {} +} diff --git a/plugins/toaster_utils/toaster_utils_android/android/src/main/res/values/strings.xml b/plugins/toaster_utils/toaster_utils_android/android/src/main/res/values/strings.xml new file mode 100644 index 0000000..c05d41d --- /dev/null +++ b/plugins/toaster_utils/toaster_utils_android/android/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + This is just an example toast. + diff --git a/plugins/toaster_utils/toaster_utils_android/lib/src/messages.g.dart b/plugins/toaster_utils/toaster_utils_android/lib/src/messages.g.dart index b5af842..c093848 100644 --- a/plugins/toaster_utils/toaster_utils_android/lib/src/messages.g.dart +++ b/plugins/toaster_utils/toaster_utils_android/lib/src/messages.g.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2026, Org Mars3142 +// Copyright (c) 2026, mars3142 // Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: unused_import, unused_shown_name @@ -35,6 +35,178 @@ Object? _extractReplyValueOrThrow( return replyList.firstOrNull; } +bool _deepEquals(Object? a, Object? b) { + if (identical(a, b)) { + return true; + } + if (a is double && b is double) { + if (a.isNaN && b.isNaN) { + return true; + } + return a == b; + } + if (a is List && b is List) { + return a.length == b.length && + a.indexed + .every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); + } + if (a is Map && b is Map) { + if (a.length != b.length) { + return false; + } + for (final MapEntry entryA in a.entries) { + bool found = false; + for (final MapEntry entryB in b.entries) { + if (_deepEquals(entryA.key, entryB.key)) { + if (_deepEquals(entryA.value, entryB.value)) { + found = true; + break; + } else { + return false; + } + } + } + if (!found) { + return false; + } + } + return true; + } + return a == b; +} + +int _deepHash(Object? value) { + if (value is List) { + return Object.hashAll(value.map(_deepHash)); + } + if (value is Map) { + int result = 0; + for (final MapEntry entry in value.entries) { + result += (_deepHash(entry.key) * 31) ^ _deepHash(entry.value); + } + return result; + } + if (value is double && value.isNaN) { + // Normalize NaN to a consistent hash. + return 0x7FF8000000000000.hashCode; + } + if (value is double && value == 0.0) { + // Normalize -0.0 to 0.0 so they have the same hash code. + return 0.0.hashCode; + } + return value.hashCode; +} + + +class App { + App({ + required this.packageName, + required this.appName, + required this.installedBy, + required this.icon, + }); + + String packageName; + + String appName; + + String installedBy; + + Uint8List icon; + + List _toList() { + return [ + packageName, + appName, + installedBy, + icon, + ]; + } + + Object encode() { + return _toList(); } + + static App decode(Object result) { + result as List; + return App( + packageName: result[0]! as String, + appName: result[1]! as String, + installedBy: result[2]! as String, + icon: result[3]! as Uint8List, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! App || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(packageName, other.packageName) && _deepEquals(appName, other.appName) && _deepEquals(installedBy, other.installedBy) && _deepEquals(icon, other.icon); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class Toast { + Toast({ + required this.app, + required this.message, + required this.timestamp, + required this.duration, + }); + + App app; + + String message; + + int timestamp; + + int duration; + + List _toList() { + return [ + app, + message, + timestamp, + duration, + ]; + } + + Object encode() { + return _toList(); } + + static Toast decode(Object result) { + result as List; + return Toast( + app: result[0]! as App, + message: result[1]! as String, + timestamp: result[2]! as int, + duration: result[3]! as int, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! Toast || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(app, other.app) && _deepEquals(message, other.message) && _deepEquals(timestamp, other.timestamp) && _deepEquals(duration, other.duration); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} class _PigeonCodec extends StandardMessageCodec { @@ -44,6 +216,12 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); + } else if (value is App) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is Toast) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -52,6 +230,10 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { + case 129: + return App.decode(readValue(buffer)!); + case 130: + return Toast.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -89,4 +271,135 @@ class ToasterUtilsApi { ; return pigeonVar_replyValue as String?; } + + Future> getInstalledApps() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getInstalledApps$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ) + ; + return (pigeonVar_replyValue! as List).cast(); + } + + Future getInstalledApp(String packageName) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getInstalledApp$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([packageName]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ) + ; + return pigeonVar_replyValue as App?; + } + + Future> getToasts(String packageName) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getToasts$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([packageName]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ) + ; + return (pigeonVar_replyValue! as List).cast(); + } + + Future> getToastFiltered(String packageName, int from) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getToastFiltered$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([packageName, from]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ) + ; + return (pigeonVar_replyValue! as List).cast(); + } + + Future isServiceRunning() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.isServiceRunning$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ) + ; + return pigeonVar_replyValue! as bool; + } + + Future showSettings() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.showSettings$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ) + ; + } + + Future exampleToast() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.exampleToast$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ) + ; + } } diff --git a/plugins/toaster_utils/toaster_utils_android/pigeons/copyright.txt b/plugins/toaster_utils/toaster_utils_android/pigeons/copyright.txt index 3d7f53c..76b95ad 100644 --- a/plugins/toaster_utils/toaster_utils_android/pigeons/copyright.txt +++ b/plugins/toaster_utils/toaster_utils_android/pigeons/copyright.txt @@ -1 +1 @@ -Copyright (c) 2026, Org Mars3142 +Copyright (c) 2026, mars3142 diff --git a/plugins/toaster_utils/toaster_utils_android/pigeons/messages.dart b/plugins/toaster_utils/toaster_utils_android/pigeons/messages.dart index 89a8682..986a1f1 100644 --- a/plugins/toaster_utils/toaster_utils_android/pigeons/messages.dart +++ b/plugins/toaster_utils/toaster_utils_android/pigeons/messages.dart @@ -16,4 +16,43 @@ import 'package:pigeon/pigeon.dart'; abstract class ToasterUtilsApi { @async String? getPlatformName(); + + @async + List getInstalledApps(); + + @async + App? getInstalledApp(String packageName); + + @async + List getToasts(String packageName); + + @async + List getToastFiltered(String packageName, int from); + + @async + bool isServiceRunning(); + + @async + void showSettings(); + + @async + void exampleToast(); +} + +class App { + const App(this.packageName, this.appName, this.installedBy, this.icon); + + final String packageName; + final String appName; + final String installedBy; + final Uint8List icon; +} + +class Toast { + const Toast(this.app, this.message, this.timestamp, this.duration); + + final App app; + final String message; + final int timestamp; + final int duration; } diff --git a/plugins/toaster_utils/toaster_utils_ios/ios/toaster_utils_ios/Sources/toaster_utils_ios/Messages.g.swift b/plugins/toaster_utils/toaster_utils_ios/ios/toaster_utils_ios/Sources/toaster_utils_ios/Messages.g.swift index d8d079b..65ea288 100644 --- a/plugins/toaster_utils/toaster_utils_ios/ios/toaster_utils_ios/Sources/toaster_utils_ios/Messages.g.swift +++ b/plugins/toaster_utils/toaster_utils_ios/ios/toaster_utils_ios/Sources/toaster_utils_ios/Messages.g.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2026, Org mars3142 +// Copyright (c) 2026, mars3142 // Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon @@ -65,11 +65,233 @@ private func nilOrValue(_ value: Any?) -> T? { return value as! T? } +private func doubleEqualsMessages(_ lhs: Double, _ rhs: Double) -> Bool { + return (lhs.isNaN && rhs.isNaN) || lhs == rhs +} + +private func doubleHashMessages(_ value: Double, _ hasher: inout Hasher) { + if value.isNaN { + hasher.combine(0x7FF8000000000000) + } else { + // Normalize -0.0 to 0.0 + hasher.combine(value == 0 ? 0 : value) + } +} + +func deepEqualsMessages(_ lhs: Any?, _ rhs: Any?) -> Bool { + let cleanLhs = nilOrValue(lhs) as Any? + let cleanRhs = nilOrValue(rhs) as Any? + switch (cleanLhs, cleanRhs) { + case (nil, nil): + return true + + case (nil, _), (_, nil): + return false + + case (let lhs as AnyObject, let rhs as AnyObject) where lhs === rhs: + return true + + case is (Void, Void): + return true + + case (let lhsArray, let rhsArray) as ([Any?], [Any?]): + guard lhsArray.count == rhsArray.count else { return false } + for (index, element) in lhsArray.enumerated() { + if !deepEqualsMessages(element, rhsArray[index]) { + return false + } + } + return true + + case (let lhsArray, let rhsArray) as ([Double], [Double]): + guard lhsArray.count == rhsArray.count else { return false } + for (index, element) in lhsArray.enumerated() { + if !doubleEqualsMessages(element, rhsArray[index]) { + return false + } + } + return true + + case (let lhsDictionary, let rhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]): + guard lhsDictionary.count == rhsDictionary.count else { return false } + for (lhsKey, lhsValue) in lhsDictionary { + var found = false + for (rhsKey, rhsValue) in rhsDictionary { + if deepEqualsMessages(lhsKey, rhsKey) { + if deepEqualsMessages(lhsValue, rhsValue) { + found = true + break + } else { + return false + } + } + } + if !found { return false } + } + return true + + case (let lhs as Double, let rhs as Double): + return doubleEqualsMessages(lhs, rhs) + + case (let lhsHashable, let rhsHashable) as (AnyHashable, AnyHashable): + return lhsHashable == rhsHashable + + default: + return false + } +} + +func deepHashMessages(value: Any?, hasher: inout Hasher) { + let cleanValue = nilOrValue(value) as Any? + if let cleanValue = cleanValue { + if let doubleValue = cleanValue as? Double { + doubleHashMessages(doubleValue, &hasher) + } else if let valueList = cleanValue as? [Any?] { + for item in valueList { + deepHashMessages(value: item, hasher: &hasher) + } + } else if let valueList = cleanValue as? [Double] { + for item in valueList { + doubleHashMessages(item, &hasher) + } + } else if let valueDict = cleanValue as? [AnyHashable: Any?] { + var result = 0 + for (key, value) in valueDict { + var entryKeyHasher = Hasher() + deepHashMessages(value: key, hasher: &entryKeyHasher) + var entryValueHasher = Hasher() + deepHashMessages(value: value, hasher: &entryValueHasher) + result = result &+ ((entryKeyHasher.finalize() &* 31) ^ entryValueHasher.finalize()) + } + hasher.combine(result) + } else if let hashableValue = cleanValue as? AnyHashable { + hasher.combine(hashableValue) + } else { + hasher.combine(String(describing: cleanValue)) + } + } else { + hasher.combine(0) + } +} + + +/// Generated class from Pigeon that represents data sent in messages. +struct App: Hashable { + var packageName: String + var appName: String + var installedBy: String + var icon: FlutterStandardTypedData + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> App? { + let packageName = pigeonVar_list[0] as! String + let appName = pigeonVar_list[1] as! String + let installedBy = pigeonVar_list[2] as! String + let icon = pigeonVar_list[3] as! FlutterStandardTypedData + + return App( + packageName: packageName, + appName: appName, + installedBy: installedBy, + icon: icon + ) + } + func toList() -> [Any?] { + return [ + packageName, + appName, + installedBy, + icon, + ] + } + static func == (lhs: App, rhs: App) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsMessages(lhs.packageName, rhs.packageName) && deepEqualsMessages(lhs.appName, rhs.appName) && deepEqualsMessages(lhs.installedBy, rhs.installedBy) && deepEqualsMessages(lhs.icon, rhs.icon) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("App") + deepHashMessages(value: packageName, hasher: &hasher) + deepHashMessages(value: appName, hasher: &hasher) + deepHashMessages(value: installedBy, hasher: &hasher) + deepHashMessages(value: icon, hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct Toast: Hashable { + var app: App + var message: String + var timestamp: Int64 + var duration: Int64 + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> Toast? { + let app = pigeonVar_list[0] as! App + let message = pigeonVar_list[1] as! String + let timestamp = pigeonVar_list[2] as! Int64 + let duration = pigeonVar_list[3] as! Int64 + + return Toast( + app: app, + message: message, + timestamp: timestamp, + duration: duration + ) + } + func toList() -> [Any?] { + return [ + app, + message, + timestamp, + duration, + ] + } + static func == (lhs: Toast, rhs: Toast) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsMessages(lhs.app, rhs.app) && deepEqualsMessages(lhs.message, rhs.message) && deepEqualsMessages(lhs.timestamp, rhs.timestamp) && deepEqualsMessages(lhs.duration, rhs.duration) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("Toast") + deepHashMessages(value: app, hasher: &hasher) + deepHashMessages(value: message, hasher: &hasher) + deepHashMessages(value: timestamp, hasher: &hasher) + deepHashMessages(value: duration, hasher: &hasher) + } +} private class MessagesPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + return App.fromList(self.readValue() as! [Any?]) + case 130: + return Toast.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } } private class MessagesPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? App { + super.writeByte(129) + super.writeValue(value.toList()) + } else if let value = value as? Toast { + super.writeByte(130) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } } private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { @@ -90,6 +312,13 @@ class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol ToasterUtilsApi { func getPlatformName(completion: @escaping (Result) -> Void) + func getInstalledApps(completion: @escaping (Result<[App], Error>) -> Void) + func getInstalledApp(packageName: String, completion: @escaping (Result) -> Void) + func getToasts(packageName: String, completion: @escaping (Result<[Toast], Error>) -> Void) + func getToastFiltered(packageName: String, from: Int64, completion: @escaping (Result<[Toast], Error>) -> Void) + func isServiceRunning(completion: @escaping (Result) -> Void) + func showSettings(completion: @escaping (Result) -> Void) + func exampleToast(completion: @escaping (Result) -> Void) } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -113,5 +342,117 @@ class ToasterUtilsApiSetup { } else { getPlatformNameChannel.setMessageHandler(nil) } + let getInstalledAppsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getInstalledApps\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getInstalledAppsChannel.setMessageHandler { _, reply in + api.getInstalledApps { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getInstalledAppsChannel.setMessageHandler(nil) + } + let getInstalledAppChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getInstalledApp\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getInstalledAppChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let packageNameArg = args[0] as! String + api.getInstalledApp(packageName: packageNameArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getInstalledAppChannel.setMessageHandler(nil) + } + let getToastsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getToasts\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getToastsChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let packageNameArg = args[0] as! String + api.getToasts(packageName: packageNameArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getToastsChannel.setMessageHandler(nil) + } + let getToastFilteredChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getToastFiltered\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getToastFilteredChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let packageNameArg = args[0] as! String + let fromArg = args[1] as! Int64 + api.getToastFiltered(packageName: packageNameArg, from: fromArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getToastFilteredChannel.setMessageHandler(nil) + } + let isServiceRunningChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.isServiceRunning\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + isServiceRunningChannel.setMessageHandler { _, reply in + api.isServiceRunning { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + isServiceRunningChannel.setMessageHandler(nil) + } + let showSettingsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.showSettings\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + showSettingsChannel.setMessageHandler { _, reply in + api.showSettings { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + showSettingsChannel.setMessageHandler(nil) + } + let exampleToastChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.exampleToast\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + exampleToastChannel.setMessageHandler { _, reply in + api.exampleToast { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + exampleToastChannel.setMessageHandler(nil) + } } } diff --git a/plugins/toaster_utils/toaster_utils_ios/lib/src/messages.g.dart b/plugins/toaster_utils/toaster_utils_ios/lib/src/messages.g.dart index cb97588..c093848 100644 --- a/plugins/toaster_utils/toaster_utils_ios/lib/src/messages.g.dart +++ b/plugins/toaster_utils/toaster_utils_ios/lib/src/messages.g.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2026, Org mars3142 +// Copyright (c) 2026, mars3142 // Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: unused_import, unused_shown_name @@ -35,6 +35,178 @@ Object? _extractReplyValueOrThrow( return replyList.firstOrNull; } +bool _deepEquals(Object? a, Object? b) { + if (identical(a, b)) { + return true; + } + if (a is double && b is double) { + if (a.isNaN && b.isNaN) { + return true; + } + return a == b; + } + if (a is List && b is List) { + return a.length == b.length && + a.indexed + .every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); + } + if (a is Map && b is Map) { + if (a.length != b.length) { + return false; + } + for (final MapEntry entryA in a.entries) { + bool found = false; + for (final MapEntry entryB in b.entries) { + if (_deepEquals(entryA.key, entryB.key)) { + if (_deepEquals(entryA.value, entryB.value)) { + found = true; + break; + } else { + return false; + } + } + } + if (!found) { + return false; + } + } + return true; + } + return a == b; +} + +int _deepHash(Object? value) { + if (value is List) { + return Object.hashAll(value.map(_deepHash)); + } + if (value is Map) { + int result = 0; + for (final MapEntry entry in value.entries) { + result += (_deepHash(entry.key) * 31) ^ _deepHash(entry.value); + } + return result; + } + if (value is double && value.isNaN) { + // Normalize NaN to a consistent hash. + return 0x7FF8000000000000.hashCode; + } + if (value is double && value == 0.0) { + // Normalize -0.0 to 0.0 so they have the same hash code. + return 0.0.hashCode; + } + return value.hashCode; +} + + +class App { + App({ + required this.packageName, + required this.appName, + required this.installedBy, + required this.icon, + }); + + String packageName; + + String appName; + + String installedBy; + + Uint8List icon; + + List _toList() { + return [ + packageName, + appName, + installedBy, + icon, + ]; + } + + Object encode() { + return _toList(); } + + static App decode(Object result) { + result as List; + return App( + packageName: result[0]! as String, + appName: result[1]! as String, + installedBy: result[2]! as String, + icon: result[3]! as Uint8List, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! App || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(packageName, other.packageName) && _deepEquals(appName, other.appName) && _deepEquals(installedBy, other.installedBy) && _deepEquals(icon, other.icon); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class Toast { + Toast({ + required this.app, + required this.message, + required this.timestamp, + required this.duration, + }); + + App app; + + String message; + + int timestamp; + + int duration; + + List _toList() { + return [ + app, + message, + timestamp, + duration, + ]; + } + + Object encode() { + return _toList(); } + + static Toast decode(Object result) { + result as List; + return Toast( + app: result[0]! as App, + message: result[1]! as String, + timestamp: result[2]! as int, + duration: result[3]! as int, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! Toast || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(app, other.app) && _deepEquals(message, other.message) && _deepEquals(timestamp, other.timestamp) && _deepEquals(duration, other.duration); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} class _PigeonCodec extends StandardMessageCodec { @@ -44,6 +216,12 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); + } else if (value is App) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is Toast) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -52,6 +230,10 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { + case 129: + return App.decode(readValue(buffer)!); + case 130: + return Toast.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -89,4 +271,135 @@ class ToasterUtilsApi { ; return pigeonVar_replyValue as String?; } + + Future> getInstalledApps() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getInstalledApps$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ) + ; + return (pigeonVar_replyValue! as List).cast(); + } + + Future getInstalledApp(String packageName) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getInstalledApp$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([packageName]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ) + ; + return pigeonVar_replyValue as App?; + } + + Future> getToasts(String packageName) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getToasts$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([packageName]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ) + ; + return (pigeonVar_replyValue! as List).cast(); + } + + Future> getToastFiltered(String packageName, int from) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getToastFiltered$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([packageName, from]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ) + ; + return (pigeonVar_replyValue! as List).cast(); + } + + Future isServiceRunning() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.isServiceRunning$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ) + ; + return pigeonVar_replyValue! as bool; + } + + Future showSettings() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.showSettings$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ) + ; + } + + Future exampleToast() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.exampleToast$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ) + ; + } } diff --git a/plugins/toaster_utils/toaster_utils_ios/pigeons/copyright.txt b/plugins/toaster_utils/toaster_utils_ios/pigeons/copyright.txt index 7a8c0eb..76b95ad 100644 --- a/plugins/toaster_utils/toaster_utils_ios/pigeons/copyright.txt +++ b/plugins/toaster_utils/toaster_utils_ios/pigeons/copyright.txt @@ -1 +1 @@ -Copyright (c) 2026, Org mars3142 +Copyright (c) 2026, mars3142 diff --git a/plugins/toaster_utils/toaster_utils_ios/pigeons/messages.dart b/plugins/toaster_utils/toaster_utils_ios/pigeons/messages.dart index 3497af0..2234df6 100644 --- a/plugins/toaster_utils/toaster_utils_ios/pigeons/messages.dart +++ b/plugins/toaster_utils/toaster_utils_ios/pigeons/messages.dart @@ -16,4 +16,43 @@ import 'package:pigeon/pigeon.dart'; abstract class ToasterUtilsApi { @async String? getPlatformName(); + + @async + List getInstalledApps(); + + @async + App? getInstalledApp(String packageName); + + @async + List getToasts(String packageName); + + @async + List getToastFiltered(String packageName, int from); + + @async + bool isServiceRunning(); + + @async + void showSettings(); + + @async + void exampleToast(); +} + +class App { + const App(this.packageName, this.appName, this.installedBy, this.icon); + + final String packageName; + final String appName; + final String installedBy; + final Uint8List icon; +} + +class Toast { + const Toast(this.app, this.message, this.timestamp, this.duration); + + final App app; + final String message; + final int timestamp; + final int duration; } diff --git a/plugins/toaster_utils/toaster_utils_macos/lib/src/messages.g.dart b/plugins/toaster_utils/toaster_utils_macos/lib/src/messages.g.dart index cb97588..c093848 100644 --- a/plugins/toaster_utils/toaster_utils_macos/lib/src/messages.g.dart +++ b/plugins/toaster_utils/toaster_utils_macos/lib/src/messages.g.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2026, Org mars3142 +// Copyright (c) 2026, mars3142 // Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: unused_import, unused_shown_name @@ -35,6 +35,178 @@ Object? _extractReplyValueOrThrow( return replyList.firstOrNull; } +bool _deepEquals(Object? a, Object? b) { + if (identical(a, b)) { + return true; + } + if (a is double && b is double) { + if (a.isNaN && b.isNaN) { + return true; + } + return a == b; + } + if (a is List && b is List) { + return a.length == b.length && + a.indexed + .every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); + } + if (a is Map && b is Map) { + if (a.length != b.length) { + return false; + } + for (final MapEntry entryA in a.entries) { + bool found = false; + for (final MapEntry entryB in b.entries) { + if (_deepEquals(entryA.key, entryB.key)) { + if (_deepEquals(entryA.value, entryB.value)) { + found = true; + break; + } else { + return false; + } + } + } + if (!found) { + return false; + } + } + return true; + } + return a == b; +} + +int _deepHash(Object? value) { + if (value is List) { + return Object.hashAll(value.map(_deepHash)); + } + if (value is Map) { + int result = 0; + for (final MapEntry entry in value.entries) { + result += (_deepHash(entry.key) * 31) ^ _deepHash(entry.value); + } + return result; + } + if (value is double && value.isNaN) { + // Normalize NaN to a consistent hash. + return 0x7FF8000000000000.hashCode; + } + if (value is double && value == 0.0) { + // Normalize -0.0 to 0.0 so they have the same hash code. + return 0.0.hashCode; + } + return value.hashCode; +} + + +class App { + App({ + required this.packageName, + required this.appName, + required this.installedBy, + required this.icon, + }); + + String packageName; + + String appName; + + String installedBy; + + Uint8List icon; + + List _toList() { + return [ + packageName, + appName, + installedBy, + icon, + ]; + } + + Object encode() { + return _toList(); } + + static App decode(Object result) { + result as List; + return App( + packageName: result[0]! as String, + appName: result[1]! as String, + installedBy: result[2]! as String, + icon: result[3]! as Uint8List, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! App || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(packageName, other.packageName) && _deepEquals(appName, other.appName) && _deepEquals(installedBy, other.installedBy) && _deepEquals(icon, other.icon); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class Toast { + Toast({ + required this.app, + required this.message, + required this.timestamp, + required this.duration, + }); + + App app; + + String message; + + int timestamp; + + int duration; + + List _toList() { + return [ + app, + message, + timestamp, + duration, + ]; + } + + Object encode() { + return _toList(); } + + static Toast decode(Object result) { + result as List; + return Toast( + app: result[0]! as App, + message: result[1]! as String, + timestamp: result[2]! as int, + duration: result[3]! as int, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! Toast || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(app, other.app) && _deepEquals(message, other.message) && _deepEquals(timestamp, other.timestamp) && _deepEquals(duration, other.duration); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} class _PigeonCodec extends StandardMessageCodec { @@ -44,6 +216,12 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); + } else if (value is App) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is Toast) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -52,6 +230,10 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { + case 129: + return App.decode(readValue(buffer)!); + case 130: + return Toast.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -89,4 +271,135 @@ class ToasterUtilsApi { ; return pigeonVar_replyValue as String?; } + + Future> getInstalledApps() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getInstalledApps$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ) + ; + return (pigeonVar_replyValue! as List).cast(); + } + + Future getInstalledApp(String packageName) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getInstalledApp$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([packageName]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ) + ; + return pigeonVar_replyValue as App?; + } + + Future> getToasts(String packageName) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getToasts$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([packageName]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ) + ; + return (pigeonVar_replyValue! as List).cast(); + } + + Future> getToastFiltered(String packageName, int from) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getToastFiltered$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([packageName, from]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ) + ; + return (pigeonVar_replyValue! as List).cast(); + } + + Future isServiceRunning() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.isServiceRunning$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ) + ; + return pigeonVar_replyValue! as bool; + } + + Future showSettings() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.showSettings$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ) + ; + } + + Future exampleToast() async { + final pigeonVar_channelName = 'dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.exampleToast$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ) + ; + } } diff --git a/plugins/toaster_utils/toaster_utils_macos/macos/toaster_utils_macos/Sources/toaster_utils_macos/Messages.g.swift b/plugins/toaster_utils/toaster_utils_macos/macos/toaster_utils_macos/Sources/toaster_utils_macos/Messages.g.swift index d8d079b..65ea288 100644 --- a/plugins/toaster_utils/toaster_utils_macos/macos/toaster_utils_macos/Sources/toaster_utils_macos/Messages.g.swift +++ b/plugins/toaster_utils/toaster_utils_macos/macos/toaster_utils_macos/Sources/toaster_utils_macos/Messages.g.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2026, Org mars3142 +// Copyright (c) 2026, mars3142 // Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon @@ -65,11 +65,233 @@ private func nilOrValue(_ value: Any?) -> T? { return value as! T? } +private func doubleEqualsMessages(_ lhs: Double, _ rhs: Double) -> Bool { + return (lhs.isNaN && rhs.isNaN) || lhs == rhs +} + +private func doubleHashMessages(_ value: Double, _ hasher: inout Hasher) { + if value.isNaN { + hasher.combine(0x7FF8000000000000) + } else { + // Normalize -0.0 to 0.0 + hasher.combine(value == 0 ? 0 : value) + } +} + +func deepEqualsMessages(_ lhs: Any?, _ rhs: Any?) -> Bool { + let cleanLhs = nilOrValue(lhs) as Any? + let cleanRhs = nilOrValue(rhs) as Any? + switch (cleanLhs, cleanRhs) { + case (nil, nil): + return true + + case (nil, _), (_, nil): + return false + + case (let lhs as AnyObject, let rhs as AnyObject) where lhs === rhs: + return true + + case is (Void, Void): + return true + + case (let lhsArray, let rhsArray) as ([Any?], [Any?]): + guard lhsArray.count == rhsArray.count else { return false } + for (index, element) in lhsArray.enumerated() { + if !deepEqualsMessages(element, rhsArray[index]) { + return false + } + } + return true + + case (let lhsArray, let rhsArray) as ([Double], [Double]): + guard lhsArray.count == rhsArray.count else { return false } + for (index, element) in lhsArray.enumerated() { + if !doubleEqualsMessages(element, rhsArray[index]) { + return false + } + } + return true + + case (let lhsDictionary, let rhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]): + guard lhsDictionary.count == rhsDictionary.count else { return false } + for (lhsKey, lhsValue) in lhsDictionary { + var found = false + for (rhsKey, rhsValue) in rhsDictionary { + if deepEqualsMessages(lhsKey, rhsKey) { + if deepEqualsMessages(lhsValue, rhsValue) { + found = true + break + } else { + return false + } + } + } + if !found { return false } + } + return true + + case (let lhs as Double, let rhs as Double): + return doubleEqualsMessages(lhs, rhs) + + case (let lhsHashable, let rhsHashable) as (AnyHashable, AnyHashable): + return lhsHashable == rhsHashable + + default: + return false + } +} + +func deepHashMessages(value: Any?, hasher: inout Hasher) { + let cleanValue = nilOrValue(value) as Any? + if let cleanValue = cleanValue { + if let doubleValue = cleanValue as? Double { + doubleHashMessages(doubleValue, &hasher) + } else if let valueList = cleanValue as? [Any?] { + for item in valueList { + deepHashMessages(value: item, hasher: &hasher) + } + } else if let valueList = cleanValue as? [Double] { + for item in valueList { + doubleHashMessages(item, &hasher) + } + } else if let valueDict = cleanValue as? [AnyHashable: Any?] { + var result = 0 + for (key, value) in valueDict { + var entryKeyHasher = Hasher() + deepHashMessages(value: key, hasher: &entryKeyHasher) + var entryValueHasher = Hasher() + deepHashMessages(value: value, hasher: &entryValueHasher) + result = result &+ ((entryKeyHasher.finalize() &* 31) ^ entryValueHasher.finalize()) + } + hasher.combine(result) + } else if let hashableValue = cleanValue as? AnyHashable { + hasher.combine(hashableValue) + } else { + hasher.combine(String(describing: cleanValue)) + } + } else { + hasher.combine(0) + } +} + + +/// Generated class from Pigeon that represents data sent in messages. +struct App: Hashable { + var packageName: String + var appName: String + var installedBy: String + var icon: FlutterStandardTypedData + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> App? { + let packageName = pigeonVar_list[0] as! String + let appName = pigeonVar_list[1] as! String + let installedBy = pigeonVar_list[2] as! String + let icon = pigeonVar_list[3] as! FlutterStandardTypedData + + return App( + packageName: packageName, + appName: appName, + installedBy: installedBy, + icon: icon + ) + } + func toList() -> [Any?] { + return [ + packageName, + appName, + installedBy, + icon, + ] + } + static func == (lhs: App, rhs: App) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsMessages(lhs.packageName, rhs.packageName) && deepEqualsMessages(lhs.appName, rhs.appName) && deepEqualsMessages(lhs.installedBy, rhs.installedBy) && deepEqualsMessages(lhs.icon, rhs.icon) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("App") + deepHashMessages(value: packageName, hasher: &hasher) + deepHashMessages(value: appName, hasher: &hasher) + deepHashMessages(value: installedBy, hasher: &hasher) + deepHashMessages(value: icon, hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct Toast: Hashable { + var app: App + var message: String + var timestamp: Int64 + var duration: Int64 + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> Toast? { + let app = pigeonVar_list[0] as! App + let message = pigeonVar_list[1] as! String + let timestamp = pigeonVar_list[2] as! Int64 + let duration = pigeonVar_list[3] as! Int64 + + return Toast( + app: app, + message: message, + timestamp: timestamp, + duration: duration + ) + } + func toList() -> [Any?] { + return [ + app, + message, + timestamp, + duration, + ] + } + static func == (lhs: Toast, rhs: Toast) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsMessages(lhs.app, rhs.app) && deepEqualsMessages(lhs.message, rhs.message) && deepEqualsMessages(lhs.timestamp, rhs.timestamp) && deepEqualsMessages(lhs.duration, rhs.duration) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("Toast") + deepHashMessages(value: app, hasher: &hasher) + deepHashMessages(value: message, hasher: &hasher) + deepHashMessages(value: timestamp, hasher: &hasher) + deepHashMessages(value: duration, hasher: &hasher) + } +} private class MessagesPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + return App.fromList(self.readValue() as! [Any?]) + case 130: + return Toast.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } } private class MessagesPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? App { + super.writeByte(129) + super.writeValue(value.toList()) + } else if let value = value as? Toast { + super.writeByte(130) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } } private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { @@ -90,6 +312,13 @@ class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol ToasterUtilsApi { func getPlatformName(completion: @escaping (Result) -> Void) + func getInstalledApps(completion: @escaping (Result<[App], Error>) -> Void) + func getInstalledApp(packageName: String, completion: @escaping (Result) -> Void) + func getToasts(packageName: String, completion: @escaping (Result<[Toast], Error>) -> Void) + func getToastFiltered(packageName: String, from: Int64, completion: @escaping (Result<[Toast], Error>) -> Void) + func isServiceRunning(completion: @escaping (Result) -> Void) + func showSettings(completion: @escaping (Result) -> Void) + func exampleToast(completion: @escaping (Result) -> Void) } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -113,5 +342,117 @@ class ToasterUtilsApiSetup { } else { getPlatformNameChannel.setMessageHandler(nil) } + let getInstalledAppsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getInstalledApps\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getInstalledAppsChannel.setMessageHandler { _, reply in + api.getInstalledApps { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getInstalledAppsChannel.setMessageHandler(nil) + } + let getInstalledAppChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getInstalledApp\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getInstalledAppChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let packageNameArg = args[0] as! String + api.getInstalledApp(packageName: packageNameArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getInstalledAppChannel.setMessageHandler(nil) + } + let getToastsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getToasts\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getToastsChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let packageNameArg = args[0] as! String + api.getToasts(packageName: packageNameArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getToastsChannel.setMessageHandler(nil) + } + let getToastFilteredChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.getToastFiltered\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getToastFilteredChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let packageNameArg = args[0] as! String + let fromArg = args[1] as! Int64 + api.getToastFiltered(packageName: packageNameArg, from: fromArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getToastFilteredChannel.setMessageHandler(nil) + } + let isServiceRunningChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.isServiceRunning\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + isServiceRunningChannel.setMessageHandler { _, reply in + api.isServiceRunning { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + isServiceRunningChannel.setMessageHandler(nil) + } + let showSettingsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.showSettings\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + showSettingsChannel.setMessageHandler { _, reply in + api.showSettings { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + showSettingsChannel.setMessageHandler(nil) + } + let exampleToastChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.toaster_utils.ToasterUtilsApi.exampleToast\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + exampleToastChannel.setMessageHandler { _, reply in + api.exampleToast { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + exampleToastChannel.setMessageHandler(nil) + } } } diff --git a/plugins/toaster_utils/toaster_utils_macos/pigeons/copyright.txt b/plugins/toaster_utils/toaster_utils_macos/pigeons/copyright.txt index 7a8c0eb..76b95ad 100644 --- a/plugins/toaster_utils/toaster_utils_macos/pigeons/copyright.txt +++ b/plugins/toaster_utils/toaster_utils_macos/pigeons/copyright.txt @@ -1 +1 @@ -Copyright (c) 2026, Org mars3142 +Copyright (c) 2026, mars3142 diff --git a/plugins/toaster_utils/toaster_utils_macos/pigeons/messages.dart b/plugins/toaster_utils/toaster_utils_macos/pigeons/messages.dart index c7ee93a..a71f863 100644 --- a/plugins/toaster_utils/toaster_utils_macos/pigeons/messages.dart +++ b/plugins/toaster_utils/toaster_utils_macos/pigeons/messages.dart @@ -16,4 +16,43 @@ import 'package:pigeon/pigeon.dart'; abstract class ToasterUtilsApi { @async String? getPlatformName(); + + @async + List getInstalledApps(); + + @async + App? getInstalledApp(String packageName); + + @async + List getToasts(String packageName); + + @async + List getToastFiltered(String packageName, int from); + + @async + bool isServiceRunning(); + + @async + void showSettings(); + + @async + void exampleToast(); +} + +class App { + const App(this.packageName, this.appName, this.installedBy, this.icon); + + final String packageName; + final String appName; + final String installedBy; + final Uint8List icon; +} + +class Toast { + const Toast(this.app, this.message, this.timestamp, this.duration); + + final App app; + final String message; + final int timestamp; + final int duration; }