diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/model/Asset.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/model/Asset.kt index 77d21794..e3148155 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/model/Asset.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/model/Asset.kt @@ -5,11 +5,11 @@ 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 )] ) @@ -17,6 +17,8 @@ data class Asset( @ColumnInfo(index = true) val versionTagName: String, + @ColumnInfo(index = true) + val repository: String, val name: String, val size: Long, val downloadUrl: String, diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/model/Version.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/model/Version.kt index 11c1d6f7..5a09a118 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/model/Version.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/model/Version.kt @@ -3,10 +3,12 @@ package ca.cgagnier.wlednativeandroid.model import androidx.room.Entity import androidx.room.PrimaryKey -@Entity +@Entity( + primaryKeys = ["tagName", "repository"] +) data class Version( - @PrimaryKey val tagName: String, + val repository: String, val name: String, val description: String, val isPrerelease: Boolean, @@ -18,6 +20,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, diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/model/wledapi/Info.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/model/wledapi/Info.kt index d0f0a7e1..f2ae6b27 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/model/wledapi/Info.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/model/wledapi/Info.kt @@ -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, diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/DevicesDatabase.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/DevicesDatabase.kt index c63995b3..46645db2 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/DevicesDatabase.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/DevicesDatabase.kt @@ -11,6 +11,8 @@ 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.DbMigration9To10 +import ca.cgagnier.wlednativeandroid.repository.migrations.DbMigration10To11 @Database( entities = [ @@ -18,7 +20,7 @@ import ca.cgagnier.wlednativeandroid.repository.migrations.DbMigration8To9 Version::class, Asset::class, ], - version = 9, + version = 11, exportSchema = true, autoMigrations = [ AutoMigration(from = 1, to = 2), @@ -29,6 +31,8 @@ import ca.cgagnier.wlednativeandroid.repository.migrations.DbMigration8To9 AutoMigration(from = 6, to = 7), AutoMigration(from = 7, to = 8, spec = DbMigration7To8::class), AutoMigration(from = 8, to = 9, spec = DbMigration8To9::class), + AutoMigration(from = 9, to = 10, spec = DbMigration9To10::class), + AutoMigration(from = 10, to = 11, spec = DbMigration10To11::class), ] ) @TypeConverters(Converters::class) diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionDao.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionDao.kt index 4744dcce..18800cf3 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionDao.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionDao.kt @@ -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") diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt index 6cef3a63..835fac0b 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/VersionWithAssetsRepository.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/migrations/DbMigration10To11.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/migrations/DbMigration10To11.kt new file mode 100644 index 00000000..b7bd19ec --- /dev/null +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/migrations/DbMigration10To11.kt @@ -0,0 +1,12 @@ +package ca.cgagnier.wlednativeandroid.repository.migrations + +import androidx.room.DeleteTable +import androidx.room.migration.AutoMigrationSpec + +/** + * Migration from 10->11 removes the old Version and Asset tables after data has been migrated + * to the new schema with repository tracking support. + */ +@DeleteTable(tableName = "Version_old") +@DeleteTable(tableName = "Asset_old") +class DbMigration10To11 : AutoMigrationSpec diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/migrations/DbMigration9To10.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/migrations/DbMigration9To10.kt new file mode 100644 index 00000000..611191ce --- /dev/null +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/repository/migrations/DbMigration9To10.kt @@ -0,0 +1,105 @@ +package ca.cgagnier.wlednativeandroid.repository.migrations + +import android.util.Log +import androidx.room.RenameTable +import androidx.room.migration.AutoMigrationSpec +import androidx.sqlite.db.SupportSQLiteDatabase + +private const val TAG = "DbMigration9To10" + +/** + * Migration from 9->10 adds repository information to Version and Asset tables + * to support tracking releases from multiple WLED repositories/forks. + * + * We rename the old tables, create new ones with repository field, + * copy existing data with default repository "wled/WLED", then drop the old tables. + */ +@RenameTable(fromTableName = "Version", toTableName = "Version_old") +@RenameTable(fromTableName = "Asset", toTableName = "Asset_old") +class DbMigration9To10 : AutoMigrationSpec { + override fun onPostMigrate(db: SupportSQLiteDatabase) { + Log.i(TAG, "onPostMigrate starting - migrating Version and Asset data") + + // Migrate Version table + val originalVersionCountCursor = db.query("SELECT COUNT(*) FROM Version_old") + var originalVersionCount = 0 + if (originalVersionCountCursor.moveToFirst()) { + originalVersionCount = originalVersionCountCursor.getInt(0) + } + originalVersionCountCursor.close() + Log.i(TAG, "Total versions in old 'Version' table: $originalVersionCount") + + // Copy data from Version_old to Version with default repository + db.execSQL( + """ + INSERT OR IGNORE INTO Version ( + tagName, + repository, + name, + description, + isPrerelease, + publishedDate, + htmlUrl + ) + SELECT + tagName, + 'wled/WLED' AS repository, + name, + description, + isPrerelease, + publishedDate, + htmlUrl + FROM Version_old + """.trimIndent() + ) + + val migratedVersionCountCursor = db.query("SELECT COUNT(*) FROM Version") + var migratedVersionCount = 0 + if (migratedVersionCountCursor.moveToFirst()) { + migratedVersionCount = migratedVersionCountCursor.getInt(0) + } + migratedVersionCountCursor.close() + Log.i(TAG, "Versions migrated to new table: $migratedVersionCount") + + // Migrate Asset table + val originalAssetCountCursor = db.query("SELECT COUNT(*) FROM Asset_old") + var originalAssetCount = 0 + if (originalAssetCountCursor.moveToFirst()) { + originalAssetCount = originalAssetCountCursor.getInt(0) + } + originalAssetCountCursor.close() + Log.i(TAG, "Total assets in old 'Asset' table: $originalAssetCount") + + // Copy data from Asset_old to Asset with default repository + db.execSQL( + """ + INSERT OR IGNORE INTO Asset ( + versionTagName, + repository, + name, + size, + downloadUrl, + assetId + ) + SELECT + versionTagName, + 'wled/WLED' AS repository, + name, + size, + downloadUrl, + assetId + FROM Asset_old + """.trimIndent() + ) + + val migratedAssetCountCursor = db.query("SELECT COUNT(*) FROM Asset") + var migratedAssetCount = 0 + if (migratedAssetCountCursor.moveToFirst()) { + migratedAssetCount = migratedAssetCountCursor.getInt(0) + } + migratedAssetCountCursor.close() + Log.i(TAG, "Assets migrated to new table: $migratedAssetCount") + + Log.i(TAG, "onPostMigrate done! Migration is complete.") + } +} diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/service/api/github/GithubApi.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/service/api/github/GithubApi.kt index 6a2013d7..0845c662 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/service/api/github/GithubApi.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/service/api/github/GithubApi.kt @@ -18,12 +18,12 @@ import javax.inject.Singleton @Singleton class GithubApi @Inject constructor(private val apiEndpoints: GithubApiEndpoints) { - suspend fun getAllReleases(): Result> { - Log.d(TAG, "retrieving latest release") + suspend fun getAllReleases(repoOwner: String, repoName: String): Result> { + Log.d(TAG, "retrieving latest releases from $repoOwner/$repoName") return try { - Result.success(apiEndpoints.getAllReleases(REPO_OWNER, REPO_NAME)) + Result.success(apiEndpoints.getAllReleases(repoOwner, repoName)) } catch (e: Exception) { - Log.w(TAG, "Error retrieving releases: ${e.message}") + Log.w(TAG, "Error retrieving releases from $repoOwner/$repoName: ${e.message}") Result.failure(e) } } @@ -33,8 +33,9 @@ class GithubApi @Inject constructor(private val apiEndpoints: GithubApiEndpoints ): Flow = flow { try { emit(DownloadState.Downloading(0)) + val (repoOwner, repoName) = ca.cgagnier.wlednativeandroid.service.update.splitRepository(asset.repository) val responseBody = - apiEndpoints.downloadReleaseBinary(REPO_OWNER, REPO_NAME, asset.assetId) + apiEndpoints.downloadReleaseBinary(repoOwner, repoName, asset.assetId) emitAll(responseBody.saveFile(targetFile)) } catch (e: Exception) { emit(DownloadState.Failed(e)) @@ -69,7 +70,5 @@ class GithubApi @Inject constructor(private val apiEndpoints: GithubApiEndpoints companion object { private const val TAG = "github-release" - const val REPO_OWNER = "Aircoookie" - const val REPO_NAME = "WLED" } } \ No newline at end of file diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/service/update/DeviceUpdateManager.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/service/update/DeviceUpdateManager.kt index dfa6b2d0..8f50bc5e 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/service/update/DeviceUpdateManager.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/service/update/DeviceUpdateManager.kt @@ -30,16 +30,15 @@ class DeviceUpdateManager @Inject constructor( .map { (info, branch, skipUpdateTag) -> if (info == null) return@map null - val source = UpdateSourceRegistry.getSource(info) ?: return@map null + val repository = getRepositoryFromInfo(info) Log.d( TAG, - "Checking for software update for ${deviceWithState.device.macAddress} on ${source.githubOwner}:${source.githubRepo}" + "Checking for software update for ${deviceWithState.device.macAddress} on $repository" ) releaseService.getNewerReleaseTag( deviceInfo = info, branch = branch, ignoreVersion = skipUpdateTag, - updateSourceDefinition = source, ) } } diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/service/update/ReleaseService.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/service/update/ReleaseService.kt index adfdfc77..8288c8fb 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/service/update/ReleaseService.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/service/update/ReleaseService.kt @@ -13,8 +13,10 @@ import ca.cgagnier.wlednativeandroid.service.api.github.GithubApi import com.vdurmont.semver4j.Semver import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import javax.inject.Inject private const val TAG = "updateService" +const val DEFAULT_REPO = "wled/WLED" enum class UpdateSourceType { OFFICIAL_WLED, QUINLED, CUSTOM @@ -32,7 +34,7 @@ object UpdateSourceRegistry { UpdateSourceDefinition( type = UpdateSourceType.OFFICIAL_WLED, brandPattern = "WLED", - githubOwner = "Aircoookie", + githubOwner = "wled", githubRepo = "WLED" ), UpdateSourceDefinition( type = UpdateSourceType.QUINLED, @@ -49,7 +51,44 @@ object UpdateSourceRegistry { } } -class ReleaseService(private val versionWithAssetsRepository: VersionWithAssetsRepository) { +/** + * Extracts repository from device info using a three-tier fallback strategy: + * 1. First: Use the repo field if available (format: "owner/name") - added in WLED 0.15.2 + * 2. Second: Use UpdateSourceRegistry based on brand pattern matching + * 3. Third: Default to "wled/WLED" + */ +fun getRepositoryFromInfo(info: Info): String { + // First priority: Use the repo field if present (WLED 0.15.2+) + if (!info.repo.isNullOrBlank()) { + return info.repo + } + + // Second priority: Use brand-based registry lookup + val source = UpdateSourceRegistry.getSource(info) + if (source != null) { + return "${source.githubOwner}/${source.githubRepo}" + } + + // Final fallback: Default repository + return DEFAULT_REPO +} + +/** + * Splits a repository string (e.g., "owner/name") into owner and name parts for API calls. + * Returns a pair of (owner, name). Defaults to ("wled", "WLED") if format is invalid. + */ +fun splitRepository(repository: String): Pair { + val parts = repository.split("/") + if (parts.size == 2 && parts[0].isNotBlank() && parts[1].isNotBlank()) { + return Pair(parts[0], parts[1]) + } else { + Log.w(TAG, "Invalid repo format: $repository, using default") + val defaultParts = DEFAULT_REPO.split("/") + return Pair(defaultParts[0], defaultParts[1]) + } +} + +class ReleaseService @Inject constructor(private val versionWithAssetsRepository: VersionWithAssetsRepository) { /** * If a new version is available, returns the version tag of it. @@ -65,20 +104,16 @@ class ReleaseService(private val versionWithAssetsRepository: VersionWithAssetsR deviceInfo: Info, branch: Branch, ignoreVersion: String, - updateSourceDefinition: UpdateSourceDefinition, ): String? { if (deviceInfo.version.isNullOrEmpty()) { return null } - if (deviceInfo.brand != updateSourceDefinition.brandPattern) { - return null - } if (!deviceInfo.isOtaEnabled) { return null } - // TODO: Modify this to use repositoryOwner and repositoryName - val latestVersion = getLatestVersionWithAssets(branch) ?: return null + val repository = getRepositoryFromInfo(deviceInfo) + val latestVersion = getLatestVersionWithAssets(repository, branch) ?: return null val latestTagName = latestVersion.version.tagName if (latestTagName == ignoreVersion) { @@ -124,37 +159,53 @@ class ReleaseService(private val versionWithAssetsRepository: VersionWithAssetsR return null } - private suspend fun getLatestVersionWithAssets(branch: Branch): VersionWithAssets? { + private suspend fun getLatestVersionWithAssets( + repository: String, + branch: Branch + ): VersionWithAssets? { if (branch == Branch.BETA) { - return versionWithAssetsRepository.getLatestBetaVersionWithAssets() + return versionWithAssetsRepository.getLatestBetaVersionWithAssets(repository) } - return versionWithAssetsRepository.getLatestStableVersionWithAssets() + return versionWithAssetsRepository.getLatestStableVersionWithAssets(repository) } - suspend fun refreshVersions(githubApi: GithubApi) = withContext(Dispatchers.IO) { - githubApi.getAllReleases().onFailure { exception -> - Log.w(TAG, "Failed to refresh versions from Github", exception) - return@onFailure - }.onSuccess { allVersions -> - if (allVersions.isEmpty()) { - Log.w(TAG, "GitHub returned 0 releases. Skipping DB update to preserve cache.") - return@onSuccess - } - val (versions, assets) = withContext(Dispatchers.Default) { - val v = allVersions.map { createVersion(it) } - val a = allVersions.flatMap { createAssetsForVersion(it) } - Pair(v, a) + /** + * Refreshes versions from multiple repositories. + * Gets a list of unique repositories, then fetches releases for each. + */ + suspend fun refreshVersions(githubApi: GithubApi, repositories: Set) = withContext(Dispatchers.IO) { + val allVersions = mutableListOf() + val allAssets = mutableListOf() + + for (repository in repositories) { + val (repoOwner, repoName) = splitRepository(repository) + Log.i(TAG, "Fetching releases from $repository") + githubApi.getAllReleases(repoOwner, repoName).onFailure { exception -> + Log.w(TAG, "Failed to refresh versions from $repository", exception) + }.onSuccess { releases -> + if (releases.isEmpty()) { + Log.w(TAG, "GitHub returned 0 releases for $repository.") + } else { + val versions = releases.map { createVersion(it, repository) } + val assets = releases.flatMap { createAssetsForVersion(it, repository) } + allVersions.addAll(versions) + allAssets.addAll(assets) + Log.i(TAG, "Added ${versions.size} versions and ${assets.size} assets from $repository") + } } + } - Log.i(TAG, "Replacing DB with ${versions.size} versions and ${assets.size} assets") - versionWithAssetsRepository.replaceAll(versions, assets) + if (allVersions.isNotEmpty()) { + Log.i(TAG, "Replacing DB with ${allVersions.size} versions and ${allAssets.size} assets total") + versionWithAssetsRepository.replaceAll(allVersions, allAssets) } } - private fun createVersion(version: Release): Version { + private fun createVersion(version: Release, repository: String): Version { return Version( sanitizeTagName(version.tagName), + repository, version.name, version.body, version.prerelease, @@ -163,13 +214,14 @@ class ReleaseService(private val versionWithAssetsRepository: VersionWithAssetsR ) } - private fun createAssetsForVersion(version: Release): List { + private fun createAssetsForVersion(version: Release, repository: String): List { val assetsModels = mutableListOf() val sanitizedTagName = sanitizeTagName(version.tagName) for (asset in version.assets) { assetsModels.add( Asset( sanitizedTagName, + repository, asset.name, asset.size, asset.browserDownloadUrl, diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/ui/MainViewModel.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/ui/MainViewModel.kt index eb7f1dc3..edef6eb9 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/ui/MainViewModel.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/ui/MainViewModel.kt @@ -3,9 +3,13 @@ package ca.cgagnier.wlednativeandroid.ui import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import ca.cgagnier.wlednativeandroid.repository.DeviceRepository import ca.cgagnier.wlednativeandroid.repository.UserPreferencesRepository import ca.cgagnier.wlednativeandroid.service.api.github.GithubApi +import ca.cgagnier.wlednativeandroid.service.update.DEFAULT_REPO import ca.cgagnier.wlednativeandroid.service.update.ReleaseService +import ca.cgagnier.wlednativeandroid.service.update.getRepositoryFromInfo +import ca.cgagnier.wlednativeandroid.service.websocket.WebsocketClient import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first @@ -19,7 +23,9 @@ private const val TAG = "MainViewModel" class MainViewModel @Inject constructor( private val userPreferencesRepository: UserPreferencesRepository, private val releaseService: ReleaseService, - private val githubApi: GithubApi + private val githubApi: GithubApi, + private val deviceRepository: DeviceRepository, + private val websocketClients: Map ) : ViewModel() { fun downloadUpdateMetadata() { @@ -30,7 +36,22 @@ class MainViewModel @Inject constructor( Log.i(TAG, "Not updating version list since it was done recently.") return@launch } - releaseService.refreshVersions(githubApi) + + // Collect unique repositories from all connected devices + val repositories = mutableSetOf() + repositories.add(DEFAULT_REPO) // Always include the default WLED repository + + websocketClients.values.forEach { client -> + val info = client.deviceState.stateInfo.value?.info + if (info != null) { + val repo = getRepositoryFromInfo(info) + repositories.add(repo) + Log.d(TAG, "Found device using repository: $repo") + } + } + + Log.i(TAG, "Refreshing versions from ${repositories.size} repositories: $repositories") + releaseService.refreshVersions(githubApi, repositories) // Set the next date to check in minimum 24 hours from now. userPreferencesRepository.updateLastUpdateCheckDate(now + DAYS.toMillis(1)) } diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/ui/homeScreen/deviceEdit/DeviceEdit.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/ui/homeScreen/deviceEdit/DeviceEdit.kt index 78536c74..fa7fd9b8 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/ui/homeScreen/deviceEdit/DeviceEdit.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/ui/homeScreen/deviceEdit/DeviceEdit.kt @@ -168,7 +168,7 @@ fun DeviceEdit( device, currentUpdateTag, seeUpdateDetails = { - viewModel.showUpdateDetails(currentUpdateTag) + viewModel.showUpdateDetails(device.device, device.stateInfo.value, currentUpdateTag) } ) } else { diff --git a/app/src/main/java/ca/cgagnier/wlednativeandroid/ui/homeScreen/deviceEdit/DeviceEditViewModel.kt b/app/src/main/java/ca/cgagnier/wlednativeandroid/ui/homeScreen/deviceEdit/DeviceEditViewModel.kt index b82f7f17..c10ccb1f 100644 --- a/app/src/main/java/ca/cgagnier/wlednativeandroid/ui/homeScreen/deviceEdit/DeviceEditViewModel.kt +++ b/app/src/main/java/ca/cgagnier/wlednativeandroid/ui/homeScreen/deviceEdit/DeviceEditViewModel.kt @@ -6,10 +6,14 @@ import androidx.lifecycle.viewModelScope import ca.cgagnier.wlednativeandroid.model.Branch import ca.cgagnier.wlednativeandroid.model.Device import ca.cgagnier.wlednativeandroid.model.VersionWithAssets +import ca.cgagnier.wlednativeandroid.model.wledapi.DeviceStateInfo import ca.cgagnier.wlednativeandroid.repository.DeviceRepository import ca.cgagnier.wlednativeandroid.repository.VersionWithAssetsRepository import ca.cgagnier.wlednativeandroid.service.api.github.GithubApi +import ca.cgagnier.wlednativeandroid.service.update.DEFAULT_REPO import ca.cgagnier.wlednativeandroid.service.update.ReleaseService +import ca.cgagnier.wlednativeandroid.service.update.getRepositoryFromInfo +import ca.cgagnier.wlednativeandroid.service.websocket.WebsocketClient import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -23,7 +27,9 @@ const val TAG = "DeviceEditViewModel" class DeviceEditViewModel @Inject constructor( private val repository: DeviceRepository, private val versionWithAssetsRepository: VersionWithAssetsRepository, - private val githubApi: GithubApi + private val githubApi: GithubApi, + private val websocketClients: Map, + private val releaseService: ReleaseService ) : ViewModel() { private var _updateDetailsVersion: MutableStateFlow = MutableStateFlow(null) @@ -68,8 +74,10 @@ class DeviceEditViewModel @Inject constructor( repository.update(updatedDevice) } - fun showUpdateDetails(version: String) = viewModelScope.launch(Dispatchers.IO) { - _updateDetailsVersion.value = versionWithAssetsRepository.getVersionByTag(version) + fun showUpdateDetails(device: Device, deviceStateInfo: DeviceStateInfo?, version: String) = viewModelScope.launch(Dispatchers.IO) { + // Extract repository from device info, defaulting to "wled/WLED" + val repository = deviceStateInfo?.info?.let { getRepositoryFromInfo(it) } ?: DEFAULT_REPO + _updateDetailsVersion.value = versionWithAssetsRepository.getVersionByTag(repository, version) } fun hideUpdateDetails() { @@ -108,8 +116,23 @@ class DeviceEditViewModel @Inject constructor( val updatedDevice = device.copy(skipUpdateTag = "") repository.update(updatedDevice) try { - val releaseService = ReleaseService(versionWithAssetsRepository) - releaseService.refreshVersions(githubApi) + // Get repository for this specific device + val repositories = mutableSetOf() + repositories.add(DEFAULT_REPO) // Always include the default WLED repository + + // Look up the specific device's websocket client to get its repository + val client = websocketClients[device.macAddress] + val info = client?.deviceState?.stateInfo?.value?.info + if (info != null) { + val repo = getRepositoryFromInfo(info) + repositories.add(repo) + Log.d(TAG, "Refreshing versions for device repository: $repo") + } else { + Log.d(TAG, "Device info not available, using default repository only") + } + + Log.i(TAG, "Refreshing versions from ${repositories.size} repositories: $repositories") + releaseService.refreshVersions(githubApi, repositories) } finally { _isCheckingUpdates.value = false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7e2b3ca1..a1b08553 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.13.2" +agp = "8.1.3" composeBom = "2025.12.01" converterMoshi = "3.0.0" core = "4.6.2"