From b0958b8564c9933ec2ced07bce2f987efec13d20 Mon Sep 17 00:00:00 2001 From: Hossain Khan Date: Thu, 5 Feb 2026 19:54:43 -0500 Subject: [PATCH] refactor: remove deprecated user token and device ID code Complete removal of deprecated fields and methods: - Remove userApiToken and deviceId from TrmnlDeviceConfig - Remove deprecated DataStore methods (saveUserApiToken, getUserApiToken, saveDeviceId, getDeviceId) - Remove userApiToken from AppSettingsScreen state and UI - Remove USER_API_TOKEN_KEY and DEVICE_ID_KEY constants - Add context comments for battery percentage reporting This simplifies the codebase by fully removing unused code paths rather than keeping them as deprecated. --- .../data/TrmnlDeviceConfigDataStore.kt | 100 +------- .../android/data/TrmnlDisplayRepository.kt | 4 + .../trmnl/android/model/TrmnlDeviceConfig.kt | 39 --- .../android/ui/settings/AppSettingsScreen.kt | 239 ------------------ 4 files changed, 7 insertions(+), 375 deletions(-) diff --git a/app/src/main/java/ink/trmnl/android/data/TrmnlDeviceConfigDataStore.kt b/app/src/main/java/ink/trmnl/android/data/TrmnlDeviceConfigDataStore.kt index cb11382..bc575b7 100644 --- a/app/src/main/java/ink/trmnl/android/data/TrmnlDeviceConfigDataStore.kt +++ b/app/src/main/java/ink/trmnl/android/data/TrmnlDeviceConfigDataStore.kt @@ -99,8 +99,6 @@ class TrmnlDeviceConfigDataStore private val CONFIG_JSON_KEY = stringPreferencesKey("config_json") private val DEVICE_MAC_ID_KEY = stringPreferencesKey("device_mac_id") private val IS_MASTER_DEVICE_KEY = stringPreferencesKey("is_master_device") - private val USER_API_TOKEN_KEY = stringPreferencesKey("user_api_token") - private val DEVICE_ID_KEY = stringPreferencesKey("device_id") private val DEVICE_MODEL_PREFERENCES_KEY = stringPreferencesKey("device_model_preferences") } @@ -238,7 +236,7 @@ class TrmnlDeviceConfigDataStore try { val config: TrmnlDeviceConfig? = deviceConfigAdapter.fromJson(configJson) Timber.tag(TAG).d( - "Loading device config (JSON): type=${config?.type}, userApiToken=${config?.userApiToken.obfuscated()}", + "Loading device config (JSON): type=${config?.type}", ) config } catch (e: Exception) { @@ -261,11 +259,9 @@ class TrmnlDeviceConfigDataStore 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() - val userApiToken = preferences[USER_API_TOKEN_KEY] - val deviceId = preferences[DEVICE_ID_KEY]?.toIntOrNull() Timber.tag(TAG).d( - "Loading device config (legacy): type=$type, deviceApiToken=${token.obfuscated()}, deviceId=$deviceId", + "Loading device config (legacy): type=$type, deviceApiToken=${token.obfuscated()}", ) if (token != null) { @@ -276,8 +272,6 @@ class TrmnlDeviceConfigDataStore deviceMacId = deviceMacId, refreshRateSecs = refreshRate, isMasterDevice = isMasterDevice, - userApiToken = userApiToken, - deviceId = deviceId, ) } else { null @@ -311,7 +305,7 @@ class TrmnlDeviceConfigDataStore suspend fun saveDeviceConfig(config: TrmnlDeviceConfig) { try { Timber.tag(TAG).d( - "Saving device config: type=${config.type}, userApiToken=${config.userApiToken.obfuscated()}", + "Saving device config: type=${config.type}", ) val configJson = deviceConfigAdapter.toJson(config) context.deviceConfigStore.edit { preferences -> @@ -333,16 +327,6 @@ class TrmnlDeviceConfigDataStore config.isMasterDevice?.let { isMaster -> preferences[IS_MASTER_DEVICE_KEY] = isMaster.toString() } ?: preferences.remove(IS_MASTER_DEVICE_KEY) - - // Save userApiToken if available - config.userApiToken?.let { userToken -> - preferences[USER_API_TOKEN_KEY] = userToken - } ?: preferences.remove(USER_API_TOKEN_KEY) - - // Save deviceId if available - config.deviceId?.let { deviceId -> - preferences[DEVICE_ID_KEY] = deviceId.toString() - } ?: preferences.remove(DEVICE_ID_KEY) } Timber.tag(TAG).d("Device config saved successfully") } catch (e: Exception) { @@ -359,84 +343,6 @@ class TrmnlDeviceConfigDataStore } } - /** - * Saves the user-level API token (Account API key) - * - * **DEPRECATED:** User API token is no longer needed for battery reporting. - * Battery percentage is now sent via the Percent-Charged header in /api/display call. - */ - @Deprecated( - message = "No longer needed for battery reporting. Battery is now sent via Percent-Charged header.", - level = DeprecationLevel.WARNING, - ) - suspend fun saveUserApiToken(token: String) { - Timber.tag(TAG).d("Saving user API token: ${token.obfuscated()}") - context.deviceConfigStore.edit { preferences -> - preferences[USER_API_TOKEN_KEY] = token - } - Timber.tag(TAG).d("User API token saved successfully") - } - - /** - * Gets the user-level API token - * - * **DEPRECATED:** User API token is no longer needed for battery reporting. - * Battery percentage is now sent via the Percent-Charged header in /api/display call. - */ - @Deprecated( - message = "No longer needed for battery reporting. Battery is now sent via Percent-Charged header.", - level = DeprecationLevel.WARNING, - ) - suspend fun getUserApiToken(): String? { - val token = - context.deviceConfigStore.data - .map { preferences -> preferences[USER_API_TOKEN_KEY] } - .first() - Timber.tag(TAG).d("Retrieved user API token: ${token.obfuscated()}") - return token - } - - /** - * Saves the device ID (TRMNL device ID from /api/devices/me). - * - * **DEPRECATED:** Device ID is no longer needed for battery reporting. - * Battery percentage is now sent via the Percent-Charged header in /api/display call. - * - * **Note:** This is only applicable for BYOD device types. - */ - @Deprecated( - message = "No longer needed for battery reporting. Battery is now sent via Percent-Charged header.", - level = DeprecationLevel.WARNING, - ) - suspend fun saveDeviceId(deviceId: Int) { - Timber.tag(TAG).d("Saving device ID: $deviceId") - context.deviceConfigStore.edit { preferences -> - preferences[DEVICE_ID_KEY] = deviceId.toString() - } - Timber.tag(TAG).d("Device ID saved successfully") - } - - /** - * Gets the device ID. - * - * **DEPRECATED:** Device ID is no longer needed for battery reporting. - * Battery percentage is now sent via the Percent-Charged header in /api/display call. - * - * **Note:** This is only applicable for BYOD device types. - */ - @Deprecated( - message = "No longer needed for battery reporting. Battery is now sent via Percent-Charged header.", - level = DeprecationLevel.WARNING, - ) - suspend fun getDeviceId(): Int? { - val deviceId = - context.deviceConfigStore.data - .map { preferences -> preferences[DEVICE_ID_KEY]?.toIntOrNull() } - .first() - Timber.tag(TAG).d("Retrieved device ID: $deviceId") - return deviceId - } - /** * Saves the access token */ diff --git a/app/src/main/java/ink/trmnl/android/data/TrmnlDisplayRepository.kt b/app/src/main/java/ink/trmnl/android/data/TrmnlDisplayRepository.kt index c5c5995..db6600d 100644 --- a/app/src/main/java/ink/trmnl/android/data/TrmnlDisplayRepository.kt +++ b/app/src/main/java/ink/trmnl/android/data/TrmnlDisplayRepository.kt @@ -82,6 +82,10 @@ class TrmnlDisplayRepository percentCharged = if (trmnlDeviceConfig.type == TrmnlDeviceType.BYOD) { // Send battery percentage if available for BYOD devices only + // See following for context: + // https://github.com/usetrmnl/trmnl-android/issues/252 + // https://github.com/usetrmnl/trmnl-android/issues/239 + // https://discord.com/channels/1281055965508141100/1466030731770855434/1469103763846463620 androidDeviceInfoProvider.getBatteryLevel()?.toDouble() } else { null diff --git a/app/src/main/java/ink/trmnl/android/model/TrmnlDeviceConfig.kt b/app/src/main/java/ink/trmnl/android/model/TrmnlDeviceConfig.kt index 5bdba97..33926a1 100644 --- a/app/src/main/java/ink/trmnl/android/model/TrmnlDeviceConfig.kt +++ b/app/src/main/java/ink/trmnl/android/model/TrmnlDeviceConfig.kt @@ -33,43 +33,4 @@ data class TrmnlDeviceConfig constructor( * See https://github.com/usetrmnl/trmnl-android/issues/190 */ val isMasterDevice: Boolean? = null, - /** - * User-level API token (Account API key) for user-level endpoints. - * - * **DEPRECATED:** This field is no longer needed for battery reporting. - * Battery percentage is now sent via the Percent-Charged header in /api/display call, - * which only requires device-level authentication ([apiAccessToken]). - * - * This field is kept for backward compatibility and may be removed in a future version. - * - * Required for BYOD devices to access user-level API endpoints like /api/me and /api/devices. - * - * This is separate from [apiAccessToken] which is the device-level API key. - */ - @Deprecated( - message = "No longer needed for battery reporting. Battery is now sent via Percent-Charged header.", - level = DeprecationLevel.WARNING, - ) - val userApiToken: String? = null, - /** - * TRMNL device ID extracted from /api/devices/me endpoint. - * - * **DEPRECATED:** This field is no longer needed for battery reporting. - * Battery percentage is now sent via the Percent-Charged header in /api/display call, - * which doesn't require device ID or user-level authentication. - * - * This field is kept for backward compatibility and may be removed in a future version. - * - * Used for making user-level API calls to /api/devices/{id}. - * - * This ID is fetched during BYOD device validation and is required for - * reporting battery status and other device-specific updates. - * - * **Note:** This field is only applicable for BYOD device types. - */ - @Deprecated( - message = "No longer needed for battery reporting. Battery is now sent via Percent-Charged header.", - level = DeprecationLevel.WARNING, - ) - val deviceId: Int? = null, ) diff --git a/app/src/main/java/ink/trmnl/android/ui/settings/AppSettingsScreen.kt b/app/src/main/java/ink/trmnl/android/ui/settings/AppSettingsScreen.kt index dacfec9..1775ee2 100644 --- a/app/src/main/java/ink/trmnl/android/ui/settings/AppSettingsScreen.kt +++ b/app/src/main/java/ink/trmnl/android/ui/settings/AppSettingsScreen.kt @@ -143,7 +143,6 @@ data class AppSettingsScreen( val accessToken: String, val deviceMacId: String, val isByodMasterDevice: Boolean, - val userApiToken: String, val usesFakeApiData: Boolean, val isLoading: Boolean = false, val validationResult: ValidationResult? = null, @@ -197,18 +196,6 @@ data class AppSettingsScreen( val token: String, ) : Event() - /** - * Event triggered when the user API token is changed. - */ - data class UserApiTokenChanged( - val token: String, - ) : Event() - - /** - * Event triggered to validate the current user API token. - */ - data object ValidateUserToken : Event() - /** * Event triggered to validate the current access token. */ @@ -289,7 +276,6 @@ class AppSettingsPresenter var accessToken by remember { mutableStateOf("") } var deviceMacId by remember { mutableStateOf("") } var isByodMasterDevice by remember { mutableStateOf(true) } - var userApiToken by remember { mutableStateOf("") } var isLoading by remember { mutableStateOf(false) } var validationResult by remember { mutableStateOf(null) } var isDeviceSetupLoading by remember { mutableStateOf(false) } @@ -352,7 +338,6 @@ class AppSettingsPresenter // Load BYOD-specific settings if (it.type == TrmnlDeviceType.BYOD) { isByodMasterDevice = it.isMasterDevice ?: true - userApiToken = it.userApiToken ?: "" } } } @@ -363,7 +348,6 @@ class AppSettingsPresenter accessToken = accessToken, deviceMacId = deviceMacId, isByodMasterDevice = isByodMasterDevice, - userApiToken = userApiToken, usesFakeApiData = usesFakeApiData, isLoading = isLoading, validationResult = validationResult, @@ -380,73 +364,6 @@ class AppSettingsPresenter deviceSetupMessage = null } - is AppSettingsScreen.Event.UserApiTokenChanged -> { - userApiToken = event.token - // Clear previous validation when user token changes - if (validationResult is ValidationResult.UserTokenSuccess || - validationResult is ValidationResult.InvalidUserToken - ) { - validationResult = null - } - } - - AppSettingsScreen.Event.ValidateUserToken -> { - // DEPRECATED: User API token validation is no longer needed - // Battery reporting now uses Percent-Charged header instead of user-level API - Timber.d("User token validation skipped - no longer needed for battery reporting") - /* DISABLED - User token no longer needed for battery reporting - scope.launch { - focusManager.clearFocus() - isLoading = true - - // Clear previous user token validation - if (validationResult is ValidationResult.UserTokenSuccess || - validationResult is ValidationResult.InvalidUserToken - ) { - validationResult = null - } - - // Validate user API token by calling /api/me - val result = - displayRepository.validateUserApiToken( - apiBaseUrl = serverBaseUrl.forDevice(deviceType), - userApiToken = userApiToken, - ) - - validationResult = - when { - result.isSuccess -> { - val user = result.getOrNull() - if (user != null) { - // Token is valid - user will save it via "Save and Continue" - ValidationResult.UserTokenSuccess( - userName = user.name, - userEmail = user.email, - ) - } else { - Timber.e( - "validateUserApiToken succeeded but returned null user. " + - "apiBaseUrl=%s, deviceType=%s", - serverBaseUrl.forDevice(deviceType), - deviceType, - ) - ValidationResult.InvalidUserToken( - "Unexpected error: API returned success but no user data was received", - ) - } - } - else -> { - ValidationResult.InvalidUserToken( - result.exceptionOrNull()?.message ?: "Invalid user API token", - ) - } - } - - isLoading = false - } - */ - } - AppSettingsScreen.Event.ValidateToken -> { scope.launch { focusManager.clearFocus() @@ -518,28 +435,6 @@ class AppSettingsPresenter response.imageUrl, response.refreshIntervalSeconds ?: DEFAULT_REFRESH_INTERVAL_SEC, ) - - // DEPRECATED: Device ID fetching no longer needed - // Battery reporting now uses Percent-Charged header instead of user-level API - - /* DISABLED - Device ID no longer needed for battery reporting - // For BYOD devices, also fetch and save the device ID - if (deviceType == TrmnlDeviceType.BYOD) { - val deviceIdResult = displayRepository.getDeviceIdFromApi(deviceConfig) - if (deviceIdResult.isSuccess) { - val deviceId = deviceIdResult.getOrNull() - if (deviceId != null) { - deviceConfigStore.saveDeviceId(deviceId) - Timber.d("Device ID saved successfully for BYOD device: $deviceId") - } - } else { - Timber.w( - "Failed to fetch device ID for BYOD device. Error: %s", - deviceIdResult.exceptionOrNull(), - ) - } - } - */ } else { // No error but also no image URL val errorMessage = response.error ?: "" @@ -562,14 +457,6 @@ class AppSettingsPresenter TrmnlDeviceType.TRMNL -> false } - // For BYOD devices, retrieve the device ID that was fetched during validation - val deviceId = - if (deviceType == TrmnlDeviceType.BYOD) { - deviceConfigStore.getDeviceId() - } else { - null - } - deviceConfigStore.saveDeviceConfig( TrmnlDeviceConfig( type = deviceType, @@ -579,13 +466,6 @@ class AppSettingsPresenter // Normalize the MAC address to standard format if provided in different format deviceMacId = normalizeMacAddress(deviceMacId)?.ifBlank { null }, isMasterDevice = isMaster, - // Save user API token if provided (for BYOD). - // Note: user token validation is optional and may be skipped by the user. - // We still persist the token here; any invalid or expired token will be - // detected and surfaced via downstream API error handling. - userApiToken = userApiToken.ifBlank { null }, - // Include device ID for BYOD devices (fetched during validation) - deviceId = deviceId, ), ) trmnlWorkScheduler.updateRefreshInterval(result.refreshRateSecs) @@ -823,116 +703,6 @@ fun AppSettingsContent( deviceIdError = (state.validationResult as? ValidationResult.InvalidDeviceMacId)?.message, ) - // - // DEPRECATED: User API Token field is no longer needed - // - // Battery reporting now uses the Percent-Charged header in /api/display call, - // which only requires device-level authentication (Access-Token). - // User-level authentication is no longer needed for BYOD device battery reporting. - // - // This UI has been disabled but kept in code for reference. - // - - /* DISABLED - User API token no longer needed for battery reporting - // User API Token field (only for BYOD) - AnimatedVisibility( - visible = state.deviceType == TrmnlDeviceType.BYOD, - enter = expandVertically() + fadeIn(), - exit = shrinkVertically() + fadeOut(), - ) { - Column { - Spacer(modifier = Modifier.height(16.dp)) - - var userTokenVisible by remember { mutableStateOf(false) } - - OutlinedTextField( - value = state.userApiToken, - onValueChange = { state.eventSink(AppSettingsScreen.Event.UserApiTokenChanged(it)) }, - label = { Text("User API Token (Account Key)") }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - visualTransformation = if (userTokenVisible) VisualTransformation.None else PasswordVisualTransformation(), - keyboardOptions = - KeyboardOptions( - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Done, - ), - keyboardActions = - KeyboardActions( - onDone = { - state.eventSink(AppSettingsScreen.Event.ValidateUserToken) - }, - ), - supportingText = { - Text( - "Optional: This token is needed for device management features like battery reporting. Get this from your TRMNL user account settings.", - ) - }, - trailingIcon = { - IconButton(onClick = { userTokenVisible = !userTokenVisible }) { - Icon( - painter = - painterResource( - if (userTokenVisible) R.drawable.visibility_off_24dp else R.drawable.visibility_24dp, - ), - contentDescription = if (userTokenVisible) "Hide user token" else "Show user token", - ) - } - }, - isError = state.validationResult is ValidationResult.InvalidUserToken, - ) - - Spacer(modifier = Modifier.height(8.dp)) - - // Determine button state based on validation result - val isValidationSuccess = state.validationResult is ValidationResult.UserTokenSuccess - val isValidationError = state.validationResult is ValidationResult.InvalidUserToken - - Button( - onClick = { state.eventSink(AppSettingsScreen.Event.ValidateUserToken) }, - enabled = state.userApiToken.isNotBlank() && !state.isLoading, - modifier = Modifier.fillMaxWidth(), - colors = - when { - isValidationSuccess -> - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - ) - isValidationError -> - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.onErrorContainer, - ) - else -> ButtonDefaults.buttonColors() - }, - ) { - Text("Validate User Token") - when { - isValidationSuccess -> { - Spacer(modifier = Modifier.width(8.dp)) - Icon( - painter = painterResource(R.drawable.check_circle_24dp), - contentDescription = "Validation successful", - modifier = Modifier.size(20.dp), - ) - } - isValidationError -> { - Spacer(modifier = Modifier.width(8.dp)) - Icon( - painter = painterResource(R.drawable.error_24dp), - contentDescription = "Validation failed", - modifier = Modifier.size(20.dp), - ) - } - } - } - } - } - */ - - Spacer(modifier = Modifier.height(16.dp)) - // Password field with toggle visibility button OutlinedTextField( value = state.accessToken, @@ -1556,7 +1326,6 @@ private fun PreviewAppSettingsContentInitial() { accessToken = "", deviceMacId = "aa:bb:cc:dd:ee:ff", isByodMasterDevice = true, - userApiToken = "", usesFakeApiData = true, isLoading = false, validationResult = null, @@ -1579,7 +1348,6 @@ private fun PreviewAppSettingsContentLoading() { accessToken = "some-token", deviceMacId = "aa:bb:cc:dd:ee:ff", isByodMasterDevice = true, - userApiToken = "", usesFakeApiData = false, isLoading = true, validationResult = null, @@ -1602,7 +1370,6 @@ private fun PreviewAppSettingsContentSuccess() { accessToken = "valid-token-123", deviceMacId = "aa:bb:cc:dd:ee:ff", isByodMasterDevice = true, - userApiToken = "", usesFakeApiData = false, isLoading = false, validationResult = @@ -1629,7 +1396,6 @@ private fun PreviewAppSettingsContentFailure() { accessToken = "invalid-token", deviceMacId = "aa:bb:cc:dd:ee:ff", isByodMasterDevice = true, - userApiToken = "", usesFakeApiData = false, isLoading = false, validationResult = @@ -1659,7 +1425,6 @@ private fun PreviewAppSettingsContentWithWork() { accessToken = "valid-token-123", deviceMacId = "aa:bb:cc:dd:ee:ff", isByodMasterDevice = true, - userApiToken = "", usesFakeApiData = false, isLoading = false, validationResult = null, // Can also be Success state @@ -1692,7 +1457,6 @@ private fun PreviewWorkScheduleStatusCardScheduled() { accessToken = "some-token", deviceMacId = "AA:BB:CC:DD:EE:FF", isByodMasterDevice = true, - userApiToken = "", usesFakeApiData = false, nextRefreshJobInfo = NextImageRefreshDisplayInfo( @@ -1719,7 +1483,6 @@ private fun PreviewWorkScheduleStatusCardNoWork() { accessToken = "some-token", deviceMacId = "aa:bb:cc:dd:ee:ff", isByodMasterDevice = true, - userApiToken = "", usesFakeApiData = false, nextRefreshJobInfo = null, eventSink = {}, @@ -1748,7 +1511,6 @@ private fun PreviewAppSettingsContentByod() { accessToken = "byod-access-token-here", deviceMacId = "", isByodMasterDevice = false, - userApiToken = "user_test123", usesFakeApiData = false, isLoading = false, validationResult = null, @@ -1772,7 +1534,6 @@ private fun PreviewAppSettingsContentByos() { accessToken = "byos-access-token-here", deviceMacId = "AA:BB:CC:DD:EE:FF", isByodMasterDevice = true, - userApiToken = "", usesFakeApiData = false, isLoading = false, validationResult = null,