Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 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
8784db1
Prioritize original repo field over registry lookup in getRepositoryF…
netmindz Feb 28, 2026
c8ba0c6
Never replace a build with a release name, with platform-only based b…
netmindz Feb 28, 2026
654a7b1
Merge branch 'dev' into copilot/update-repo-tracking-system
netmindz Mar 10, 2026
3b4b6a6
Fix null release name
netmindz Mar 10, 2026
77e39ec
Fix merge typos
netmindz Mar 10, 2026
fa0839b
Refactor DevicesDatabase instance variable and update DeviceWebsocket…
netmindz Mar 10, 2026
70f44a8
Fix Asset entity schema mismatch - add defaultValue annotation
Copilot Mar 11, 2026
473e21d
Fix SQL syntax to match Room's expected schema format
Copilot Mar 11, 2026
3553ecb
Bump version
netmindz Mar 11, 2026
c409a49
Merge remote-tracking branch 'origin/copilot/update-repo-tracking-sys…
netmindz Mar 11, 2026
238bd18
Update database schema and migration for Asset table
netmindz Mar 11, 2026
a40cad4
Enhance repository tracking: add queries to fetch assets and versions…
netmindz Mar 11, 2026
d901cd6
Fix lint errors: proper formatting and typo corrections
Copilot Mar 12, 2026
c7adcde
Fix remaining lint issues: remove trailing whitespace and split long …
Copilot Mar 12, 2026
cb183a1
spotlessApply
netmindz Mar 12, 2026
0d660a5
Address :app:detekt issues
netmindz Mar 12, 2026
e24751d
Address final :app:detekt issues
netmindz Mar 12, 2026
cbeef95
Update app/src/main/java/ca/cgagnier/wlednativeandroid/service/update…
netmindz Mar 12, 2026
0d1f4a4
Merge branch 'dev' into pr/125
Moustachauve Mar 14, 2026
f455dbb
chore: Restore missing database schema version 10
Moustachauve Mar 14, 2026
39bfdda
Fix race condition and remove redundant migration
Copilot Mar 14, 2026
0ed8a84
Enhance device tracking: update database schema to include repository…
netmindz Mar 14, 2026
1daeeea
apply code style formatting
netmindz Mar 14, 2026
4c646bc
fix detekt warning for complexity
netmindz Mar 14, 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,228 @@
{
"formatVersion": 1,
"database": {
"version": 10,
"identityHash": "afe44fe308d1a4e01e11eb17839728ef",
"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, `repository` TEXT NOT NULL DEFAULT 'wled/WLED', 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"
},
{
"fieldPath": "repository",
"columnName": "repository",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'wled/WLED'"
}
],
"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 DEFAULT 'wled/WLED', `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,
"defaultValue": "'wled/WLED'"
},
{
"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, 'afe44fe308d1a4e01e11eb17839728ef')"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ 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,
),
],
Expand All @@ -19,6 +19,8 @@ data class Asset(

@ColumnInfo(index = true)
val versionTagName: String,
@ColumnInfo(index = true, defaultValue = "'wled/WLED'")
val repository: String,
val name: String,
val size: Long,
val downloadUrl: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ data class Device(

@ColumnInfo(defaultValue = "0")
val lastSeen: Long = System.currentTimeMillis(),

@ColumnInfo(defaultValue = "'wled/WLED'")
val repository: String = "wled/WLED",
) : Parcelable {

fun getDeviceUrl(): String = "http://$address"
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"],
)
Comment thread
Moustachauve marked this conversation as resolved.
data class Version(
@PrimaryKey
val tagName: String,
@ColumnInfo(defaultValue = "'wled/WLED'")
val repository: String,
Comment thread
Moustachauve marked this conversation as resolved.
val name: String,
val description: String,
val isPrerelease: Boolean,
Expand All @@ -17,6 +20,7 @@ data class Version(
companion object {
fun getPreviewVersion(): Version = 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,
Comment on lines +22 to +23
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Adding the repo field to the Info data class is a critical change to support dynamic repository tracking. This allows the application to retrieve the repository information directly from the device's API.

@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 @@ -24,4 +24,7 @@ interface AssetDao {

@Query("DELETE FROM asset")
suspend fun deleteAll()

@Query("SELECT * FROM asset WHERE repository = :repository")
suspend fun getAssetsByRepository(repository: String): List<Asset>
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ 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

@Database(
entities = [
Device::class,
Version::class,
Asset::class,
],
version = 9,
version = 10,
exportSchema = true,
autoMigrations = [
AutoMigration(from = 1, to = 2),
Expand All @@ -42,13 +43,15 @@ abstract class DevicesDatabase : RoomDatabase() {
private var instance: DevicesDatabase? = null

fun getDatabase(context: Context): DevicesDatabase = instance ?: synchronized(this) {
val newInstance = Room.databaseBuilder(
val instance = Room.databaseBuilder(
context.applicationContext,
DevicesDatabase::class.java,
"devices_database",
).build()
instance = newInstance
newInstance
)
.addMigrations(MIGRATION_9_10)
.build()
this.instance = instance
instance
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,37 @@ interface VersionDao {
@Query("DELETE FROM version")
suspend fun deleteAll()

@Query("SELECT * FROM version WHERE repository = :repository")
suspend fun getVersionsByRepository(repository: String): List<Version>

@Transaction
@Query(
"""
SELECT * FROM version
WHERE isPrerelease = 0 AND tagName != '$IGNORED_TAG'
ORDER BY publishedDate DESC LIMIT 1
WHERE repository = :repository
AND isPrerelease = 0
AND tagName != '$IGNORED_TAG'
ORDER BY publishedDate DESC
LIMIT 1
""",
)
suspend fun getLatestStableVersionWithAssets(): VersionWithAssets?
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?
Comment on lines +67 to +68
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The getVersionByTagName query is correctly updated to include repository as a parameter. This is crucial for uniquely identifying a version when multiple repositories might have the same tag name.


@Transaction
@Query("SELECT * FROM version")
Expand Down
Loading
Loading