mirror of
https://github.com/usetrmnl/trmnl-android.git
synced 2026-04-29 13:35:26 -07:00
feat: auto-migrate usetrmnl.com to trmnl.com for TRMNL devices
- Add domain migration logic in TrmnlDeviceConfigDataStore.deviceConfigFlow - Automatically detects and updates usetrmnl.com -> trmnl.com for TRMNL device type - Migration runs once on config load and persists updated URL - Only affects TRMNL devices (BYOS custom URLs unchanged) - Fixes issue where existing users kept old usetrmnl.com URLs after #240 - See: https://github.com/usetrmnl/trmnl-android/issues/240
This commit is contained in:
@@ -103,7 +103,7 @@ android {
|
||||
// Use fake API response for local development and testing purposes.
|
||||
// ℹ️ To override during local development, change this value to `"false"`
|
||||
// or, you can change the value in the `RepositoryConfigProvider`
|
||||
buildConfigField("Boolean", "USE_FAKE_API", "true")
|
||||
buildConfigField("Boolean", "USE_FAKE_API", "false")
|
||||
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
|
||||
@@ -226,58 +226,84 @@ class TrmnlDeviceConfigDataStore
|
||||
* - Logs: "Loading device config (legacy): type=..., userApiToken=..."
|
||||
* - Only returns config if `ACCESS_TOKEN_KEY` exists (required field)
|
||||
*
|
||||
* **Domain Migration:**
|
||||
* - Automatically migrates `usetrmnl.com` to `trmnl.com` for TRMNL device types
|
||||
* - See: https://github.com/usetrmnl/trmnl-android/issues/240
|
||||
*
|
||||
* This approach ensures seamless migration from older app versions while
|
||||
* maintaining forward compatibility with newer storage format.
|
||||
*/
|
||||
val deviceConfigFlow: Flow<TrmnlDeviceConfig?> =
|
||||
context.deviceConfigStore.data.map { preferences ->
|
||||
val configJson = preferences[CONFIG_JSON_KEY]
|
||||
if (configJson != null) {
|
||||
try {
|
||||
val config: TrmnlDeviceConfig? = deviceConfigAdapter.fromJson(configJson)
|
||||
Timber.tag(TAG).d(
|
||||
"Loading device config (JSON): type=${config?.type}",
|
||||
)
|
||||
config
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(TAG).e(e, "Failed to parse device config")
|
||||
null
|
||||
}
|
||||
} else {
|
||||
// Legacy migration path - build config from individual preferences
|
||||
val type =
|
||||
preferences[DEVICE_TYPE_KEY]?.let {
|
||||
try {
|
||||
deviceTypeAdapter.fromJson(it)
|
||||
} catch (e: Exception) {
|
||||
TrmnlDeviceType.TRMNL
|
||||
}
|
||||
} ?: TrmnlDeviceType.TRMNL
|
||||
|
||||
val token = preferences[ACCESS_TOKEN_KEY]
|
||||
val url = preferences[API_BASE_URL_KEY] ?: TRMNL_API_SERVER_BASE_URL
|
||||
val refreshRate = preferences[REFRESH_RATE_SEC_KEY] ?: DEFAULT_REFRESH_INTERVAL_SEC
|
||||
val deviceMacId = preferences[DEVICE_MAC_ID_KEY]
|
||||
val isMasterDevice = preferences[IS_MASTER_DEVICE_KEY]?.toBoolean()
|
||||
|
||||
Timber.tag(TAG).d(
|
||||
"Loading device config (legacy): type=$type, deviceApiToken=${token.obfuscated()}",
|
||||
)
|
||||
|
||||
if (token != null) {
|
||||
TrmnlDeviceConfig(
|
||||
type = type,
|
||||
apiBaseUrl = url,
|
||||
apiAccessToken = token,
|
||||
deviceMacId = deviceMacId,
|
||||
refreshRateSecs = refreshRate,
|
||||
isMasterDevice = isMasterDevice,
|
||||
)
|
||||
context.deviceConfigStore.data
|
||||
.map { preferences ->
|
||||
val configJson = preferences[CONFIG_JSON_KEY]
|
||||
if (configJson != null) {
|
||||
try {
|
||||
val config: TrmnlDeviceConfig? = deviceConfigAdapter.fromJson(configJson)
|
||||
Timber.tag(TAG).d(
|
||||
"Loading device config (JSON): type=${config?.type}",
|
||||
)
|
||||
config
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(TAG).e(e, "Failed to parse device config")
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
// Legacy migration path - build config from individual preferences
|
||||
val type =
|
||||
preferences[DEVICE_TYPE_KEY]?.let {
|
||||
try {
|
||||
deviceTypeAdapter.fromJson(it)
|
||||
} catch (e: Exception) {
|
||||
TrmnlDeviceType.TRMNL
|
||||
}
|
||||
} ?: TrmnlDeviceType.TRMNL
|
||||
|
||||
val token = preferences[ACCESS_TOKEN_KEY]
|
||||
val url = preferences[API_BASE_URL_KEY] ?: TRMNL_API_SERVER_BASE_URL
|
||||
val refreshRate = preferences[REFRESH_RATE_SEC_KEY] ?: DEFAULT_REFRESH_INTERVAL_SEC
|
||||
val deviceMacId = preferences[DEVICE_MAC_ID_KEY]
|
||||
val isMasterDevice = preferences[IS_MASTER_DEVICE_KEY]?.toBoolean()
|
||||
|
||||
Timber.tag(TAG).d(
|
||||
"Loading device config (legacy): type=$type, deviceApiToken=${token.obfuscated()}",
|
||||
)
|
||||
|
||||
if (token != null) {
|
||||
TrmnlDeviceConfig(
|
||||
type = type,
|
||||
apiBaseUrl = url,
|
||||
apiAccessToken = token,
|
||||
deviceMacId = deviceMacId,
|
||||
refreshRateSecs = refreshRate,
|
||||
isMasterDevice = isMasterDevice,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}.map { config ->
|
||||
// Migrate usetrmnl.com -> trmnl.com for TRMNL device types
|
||||
// See: https://github.com/usetrmnl/trmnl-android/issues/240
|
||||
if (config != null &&
|
||||
config.type == TrmnlDeviceType.TRMNL &&
|
||||
config.apiBaseUrl.contains("usetrmnl.com", ignoreCase = true)
|
||||
) {
|
||||
val newUrl = config.apiBaseUrl.replace("usetrmnl.com", "trmnl.com", ignoreCase = true)
|
||||
Timber.tag(TAG).i(
|
||||
"Migrating API base URL from ${config.apiBaseUrl} to $newUrl for TRMNL device",
|
||||
)
|
||||
val migratedConfig = config.copy(apiBaseUrl = newUrl)
|
||||
// Save the migrated config back to DataStore synchronously
|
||||
// Using runBlocking is acceptable here as this is a one-time migration
|
||||
runBlocking {
|
||||
saveDeviceConfig(migratedConfig)
|
||||
}
|
||||
migratedConfig
|
||||
} else {
|
||||
config
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the complete device configuration
|
||||
|
||||
Reference in New Issue
Block a user