Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b7e70ff
Initial plan
Copilot Feb 7, 2026
c526ea0
Add multi-repository support for WLED updates
Copilot Feb 7, 2026
c5a901d
Fix code issues found in review
Copilot Feb 7, 2026
1129f0c
Fix incorrect version reference for repo field
Copilot Feb 7, 2026
aef5a63
Restore UpdateSourceRegistry as fallback for missing repo field
Copilot Feb 7, 2026
b89efb4
Fix checkForUpdates to refresh all discovered device repositories
Copilot Feb 7, 2026
cd0ce47
Fix checkForUpdates to only refresh selected device's repository
Copilot Feb 7, 2026
a2d0b82
Update app/src/main/java/ca/cgagnier/wlednativeandroid/service/update…
netmindz Feb 7, 2026
24258a2
Update app/src/main/java/ca/cgagnier/wlednativeandroid/ui/homeScreen/…
netmindz Feb 7, 2026
f04e26b
Centralize DEFAULT_REPO constant to avoid duplication
Copilot Feb 7, 2026
48a299b
Fix data loss in database migration 9→10
Copilot Feb 7, 2026
a026381
Fix manual ReleaseService instantiation in DeviceEditViewModel
Copilot Feb 7, 2026
f5b6653
Add MoonMudules to UpdateSourceRegistry
netmindz Feb 21, 2026
fb1472a
Update app/src/main/java/ca/cgagnier/wlednativeandroid/service/update…
netmindz Feb 21, 2026
3381ba2
More robust UpdateSourceDefinition matching
netmindz Feb 21, 2026
08c88a1
Revert accidental change to agp version
netmindz Feb 21, 2026
b354211
Fix issues with database migration
netmindz Feb 21, 2026
35444a7
Merge branch 'refs/heads/MoonModules' into copilot/update-repo-tracki…
netmindz Feb 21, 2026
fc30b52
Attempt at fix for "ReleaseService is being instantiated directly her…
netmindz Feb 21, 2026
0d03fad
Handle forks using different names than WLED for their binaries
netmindz Feb 21, 2026
d15c838
Prefer UpdateSourceRegistry over repo field pending checking of accur…
netmindz Feb 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
{
"formatVersion": 1,
"database": {
"version": 11,
"identityHash": "2760473c3744dbf6f299b09b0c84a05d",
"entities": [
{
"tableName": "Device2",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`macAddress` TEXT NOT NULL, `address` TEXT NOT NULL, `isHidden` INTEGER NOT NULL, `originalName` TEXT NOT NULL DEFAULT '', `customName` TEXT NOT NULL DEFAULT '', `skipUpdateTag` TEXT NOT NULL DEFAULT '', `branch` TEXT NOT NULL DEFAULT 'UNKNOWN', `lastSeen` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`macAddress`))",
"fields": [
{
"fieldPath": "macAddress",
"columnName": "macAddress",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "address",
"columnName": "address",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isHidden",
"columnName": "isHidden",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "originalName",
"columnName": "originalName",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "customName",
"columnName": "customName",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "skipUpdateTag",
"columnName": "skipUpdateTag",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "branch",
"columnName": "branch",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'UNKNOWN'"
},
{
"fieldPath": "lastSeen",
"columnName": "lastSeen",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"macAddress"
]
}
},
{
"tableName": "Version",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tagName` TEXT NOT NULL, `repository` TEXT NOT NULL DEFAULT 'wled/WLED', `name` TEXT NOT NULL, `description` TEXT NOT NULL, `isPrerelease` INTEGER NOT NULL, `publishedDate` TEXT NOT NULL, `htmlUrl` TEXT NOT NULL, PRIMARY KEY(`tagName`, `repository`))",
"fields": [
{
"fieldPath": "tagName",
"columnName": "tagName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "repository",
"columnName": "repository",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'wled/WLED'"
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isPrerelease",
"columnName": "isPrerelease",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "publishedDate",
"columnName": "publishedDate",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "htmlUrl",
"columnName": "htmlUrl",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"tagName",
"repository"
]
}
},
{
"tableName": "Asset",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`versionTagName` TEXT NOT NULL, `repository` TEXT NOT NULL, `name` TEXT NOT NULL, `size` INTEGER NOT NULL, `downloadUrl` TEXT NOT NULL, `assetId` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`versionTagName`, `repository`, `name`), FOREIGN KEY(`versionTagName`, `repository`) REFERENCES `Version`(`tagName`, `repository`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "versionTagName",
"columnName": "versionTagName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "repository",
"columnName": "repository",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "downloadUrl",
"columnName": "downloadUrl",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "assetId",
"columnName": "assetId",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"versionTagName",
"repository",
"name"
]
},
"indices": [
{
"name": "index_Asset_versionTagName",
"unique": false,
"columnNames": [
"versionTagName"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Asset_versionTagName` ON `${TABLE_NAME}` (`versionTagName`)"
},
{
"name": "index_Asset_repository",
"unique": false,
"columnNames": [
"repository"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Asset_repository` ON `${TABLE_NAME}` (`repository`)"
}
],
"foreignKeys": [
{
"table": "Version",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"versionTagName",
"repository"
],
"referencedColumns": [
"tagName",
"repository"
]
}
]
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2760473c3744dbf6f299b09b0c84a05d')"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ import androidx.room.Entity
import androidx.room.ForeignKey

@Entity(
primaryKeys = ["versionTagName", "name"],
primaryKeys = ["versionTagName", "repository", "name"],
foreignKeys = [ForeignKey(
entity = Version::class,
parentColumns = arrayOf("tagName"),
childColumns = arrayOf("versionTagName"),
parentColumns = arrayOf("tagName", "repository"),
childColumns = arrayOf("versionTagName", "repository"),
onDelete = ForeignKey.CASCADE
)]
)
data class Asset(

@ColumnInfo(index = true)
val versionTagName: String,
@ColumnInfo(index = true)
val repository: String,
val name: String,
val size: Long,
val downloadUrl: String,
Expand Down
10 changes: 7 additions & 3 deletions app/src/main/java/ca/cgagnier/wlednativeandroid/model/Version.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package ca.cgagnier.wlednativeandroid.model

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity
@Entity(
primaryKeys = ["tagName", "repository"]
)
data class Version(
@PrimaryKey
val tagName: String,
@ColumnInfo(defaultValue = "'wled/WLED'")
val repository: String,
val name: String,
val description: String,
val isPrerelease: Boolean,
Expand All @@ -18,6 +21,7 @@ data class Version(
fun getPreviewVersion(): Version {
return Version(
tagName = "v1.0.0",
repository = "wled/WLED",
name = "new version",
description = "this is a test version",
isPrerelease = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ data class Info(
@param:Json(name = "cn") val codeName: String? = null,
// Added in 0.15
@param:Json(name = "release") val release: String? = null,
// Added in 0.15.2
@param:Json(name = "repo") val repo: String? = null,
@param:Json(name = "name") val name: String,
@param:Json(name = "str") val syncToggleReceive: Boolean? = null,
@param:Json(name = "udpport") val udpPort: Int? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ import ca.cgagnier.wlednativeandroid.model.Device
import ca.cgagnier.wlednativeandroid.model.Version
import ca.cgagnier.wlednativeandroid.repository.migrations.DbMigration7To8
import ca.cgagnier.wlednativeandroid.repository.migrations.DbMigration8To9
import ca.cgagnier.wlednativeandroid.repository.migrations.MIGRATION_9_10
import ca.cgagnier.wlednativeandroid.repository.migrations.MIGRATION_10_11

@Database(
entities = [
Device::class,
Version::class,
Asset::class,
],
version = 9,
version = 11,
exportSchema = true,
autoMigrations = [
AutoMigration(from = 1, to = 2),
Expand Down Expand Up @@ -47,7 +49,9 @@ abstract class DevicesDatabase : RoomDatabase() {
context.applicationContext,
DevicesDatabase::class.java,
"devices_database"
).build()
)
.addMigrations(MIGRATION_9_10, MIGRATION_10_11)
.build()
INSTANCE = instance
instance
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,16 @@ interface VersionDao {
suspend fun deleteAll()

@Transaction
@Query("SELECT * FROM version WHERE isPrerelease = 0 AND tagName != '$IGNORED_TAG' ORDER BY publishedDate DESC LIMIT 1")
suspend fun getLatestStableVersionWithAssets(): VersionWithAssets?
@Query("SELECT * FROM version WHERE repository = :repository AND isPrerelease = 0 AND tagName != '$IGNORED_TAG' ORDER BY publishedDate DESC LIMIT 1")
suspend fun getLatestStableVersionWithAssets(repository: String): VersionWithAssets?

@Transaction
@Query("SELECT * FROM version WHERE tagName != '$IGNORED_TAG' ORDER BY publishedDate DESC LIMIT 1")
suspend fun getLatestBetaVersionWithAssets(): VersionWithAssets?
@Query("SELECT * FROM version WHERE repository = :repository AND tagName != '$IGNORED_TAG' ORDER BY publishedDate DESC LIMIT 1")
suspend fun getLatestBetaVersionWithAssets(repository: String): VersionWithAssets?

@Transaction
@Query("SELECT * FROM version WHERE tagName = :tagName LIMIT 1")
suspend fun getVersionByTagName(tagName: String): VersionWithAssets?
@Query("SELECT * FROM version WHERE repository = :repository AND tagName = :tagName LIMIT 1")
suspend fun getVersionByTagName(repository: String, tagName: String): VersionWithAssets?

@Transaction
@Query("SELECT * FROM version")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ class VersionWithAssetsRepository @Inject constructor(
}
}

suspend fun getLatestStableVersionWithAssets(): VersionWithAssets? {
return versionDao.getLatestStableVersionWithAssets()
suspend fun getLatestStableVersionWithAssets(repository: String): VersionWithAssets? {
return versionDao.getLatestStableVersionWithAssets(repository)
}

suspend fun getLatestBetaVersionWithAssets(): VersionWithAssets? {
return versionDao.getLatestBetaVersionWithAssets()
suspend fun getLatestBetaVersionWithAssets(repository: String): VersionWithAssets? {
return versionDao.getLatestBetaVersionWithAssets(repository)
}

suspend fun getVersionByTag(tagName: String): VersionWithAssets? {
return versionDao.getVersionByTagName(tagName)
suspend fun getVersionByTag(repository: String, tagName: String): VersionWithAssets? {
return versionDao.getVersionByTagName(repository, tagName)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ca.cgagnier.wlednativeandroid.repository.migrations

import android.util.Log
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase

private const val TAG = "DbMigration10To11"

/**
* Migration from 10->11 removes the old Version and Asset tables after data has been migrated
* to the new schema with repository tracking support.
*/
val MIGRATION_10_11 = object : Migration(10, 11) {
override fun migrate(db: SupportSQLiteDatabase) {
Log.i(TAG, "Starting migration from 10 to 11")

// Drop the old tables if they exist (cleanup from previous migration)
db.execSQL("DROP TABLE IF EXISTS Version_old")
db.execSQL("DROP TABLE IF EXISTS Asset_old")

Log.i(TAG, "Migration from 10 to 11 complete!")
}
}
Loading
Loading