feat(app): allow users to rename their Omi device#4749
feat(app): allow users to rename their Omi device#4749eulicesl wants to merge 13 commits intoBasedHardware:mainfrom
Conversation
Wrap SharedPreferencesUtil().deviceName writes with checks so reconnections don't overwrite a user-set custom name. Also detect device switches by comparing the connecting device ID against the stored paired device ID — a different device resets the name.
Apply the same custom name preservation logic to onboarding flow. Detect device switches and only write hardware name when no custom name exists or when the device ID has changed.
Make the device name row tappable to open a rename dialog. Tap opens the dialog, long-press copies to clipboard. Preserve custom name on disconnect (only clear on unpair).
Remove deviceName clearing from the disconnect handler in the device home page. Custom name should only be cleared on unpair.
Add renameDevice, enterDeviceName, deviceNameCannotBeEmpty, and deviceNameUpdated keys to ARB and regenerate localization files.
There was a problem hiding this comment.
Code Review
This pull request introduces a useful feature for renaming devices. The implementation of the rename dialog and the logic to preserve the custom name on reconnection are well done. However, I've identified a couple of issues that should be addressed:
- Critical Bug: The 'Forget Device' functionality does not clear the custom device name, which is contrary to the feature's description and user expectations. This violates the rule about ensuring UI element actions are consistent with user expectations.
- Code Duplication: The logic for updating the device name on connection is repeated in five different places. This should be refactored into a single helper method to improve maintainability, aligning with the rule to prefer existing helper functions over inlining logic.
My detailed comments provide suggestions on how to fix these issues.
| await SharedPreferencesUtil().btDeviceSet(BtDevice(id: '', name: '', type: DeviceType.omi, rssi: 0)); | ||
| SharedPreferencesUtil().deviceName = ''; | ||
| if (provider.connectedDevice != null) { | ||
| await _bleDisconnectDevice(provider.connectedDevice!); | ||
| } |
There was a problem hiding this comment.
When a user forgets a device, their custom name for it should also be cleared. The PR description states that the name should be cleared on unpair/forget, but this change prevents that by removing the line that clears the device name. This goes against user expectations for a 'forget device' action. Please re-add the line to clear the name.
| await SharedPreferencesUtil().btDeviceSet(BtDevice(id: '', name: '', type: DeviceType.omi, rssi: 0)); | |
| SharedPreferencesUtil().deviceName = ''; | |
| if (provider.connectedDevice != null) { | |
| await _bleDisconnectDevice(provider.connectedDevice!); | |
| } | |
| await SharedPreferencesUtil().btDeviceSet(BtDevice(id: '', name: '', type: DeviceType.omi, rssi: 0)); | |
| SharedPreferencesUtil().deviceName = ''; | |
| if (provider.connectedDevice != null) { | |
| await _bleDisconnectDevice(provider.connectedDevice!); | |
| } |
References
- Ensure a UI element's action is consistent with its label to meet user expectations. For instance, a button labeled 'Get App' should open the download link directly rather than triggering a share action.
There was a problem hiding this comment.
This is the disconnect handler, not the unpair/forget handler. The unpair handler (line ~920) correctly clears both deviceName and deviceNameDeviceId.
Disconnect preserves the custom name intentionally — users expect to reconnect and see their custom name. The stale-name-on-different-device concern is now handled by deviceNameDeviceId (see 5e18237), which tracks which device ID owns the custom name independently of btDevice.id.
| var previousDeviceId = SharedPreferencesUtil().btDevice.id; | ||
| if (previousDeviceId.isNotEmpty && previousDeviceId != connectedDevice!.id) { | ||
| SharedPreferencesUtil().deviceName = connectedDevice!.name; | ||
| } else { | ||
| var storedName = SharedPreferencesUtil().deviceName; | ||
| if (storedName.isEmpty) { | ||
| SharedPreferencesUtil().deviceName = connectedDevice!.name; | ||
| } | ||
| } |
There was a problem hiding this comment.
This logic for updating the device name upon connection is duplicated in 5 places across DeviceProvider and OnboardingProvider (3 times in this file, 2 in onboarding_provider.dart). This creates maintenance challenges and increases the risk of introducing bugs if the logic needs to be updated in the future. Please refactor this into a single helper method, for example, within SharedPreferencesUtil.
Here's a suggested implementation for the helper method:
// In a utility class like SharedPreferencesUtil
void updateDeviceNameOnConnect(BtDevice newDevice) {
final previousDeviceId = btDevice.id;
if (previousDeviceId.isNotEmpty && previousDeviceId != newDevice.id) {
// Switched to a new device, reset the name to hardware name.
deviceName = newDevice.name;
} else if (deviceName.isEmpty) {
// Same device or first time connecting, and no custom name is set.
// Set to hardware name.
deviceName = newDevice.name;
}
// If a custom name exists for the same device, do nothing to preserve it.
}You can then replace the duplicated blocks with a single call: SharedPreferencesUtil().updateDeviceNameOnConnect(connectedDevice!);
References
- Prefer using existing helper functions over inlining their logic, especially when they handle complex cases like fallbacks or error handling. Inlining can introduce subtle bugs by missing parts of the original logic.
There was a problem hiding this comment.
Addressed in `5e18237`. Extracted `updateDeviceNameOnConnect(newDeviceId, hardwareName)` into `SharedPreferencesUtil` with a dedicated `deviceNameDeviceId` preference. All 5 call sites now use the single helper — see `56a4dc6` and `4c3a46a`.
There was a problem hiding this comment.
Pull request overview
Adds end-user device renaming support and attempts to prevent automatic reconnections from overwriting a user-defined device name, with localization updates for the new UI strings.
Changes:
- Add a rename dialog in Device Settings (tap to rename, long-press to copy).
- Guard writes to
SharedPreferencesUtil().deviceNameinDeviceProvider/OnboardingProviderto preserve custom names. - Add new localization keys and regenerate localization outputs.
Reviewed changes
Copilot reviewed 40 out of 40 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| app/lib/providers/onboarding_provider.dart | Adds device-switch detection logic to avoid overwriting a stored custom device name during onboarding connections. |
| app/lib/providers/device_provider.dart | Adds guarded deviceName writes intended to preserve user-defined names across reconnects and reset on device switch. |
| app/lib/pages/settings/device_settings.dart | Adds rename dialog + tap/long-press handling; adjusts disconnect flow to avoid clearing deviceName. |
| app/lib/pages/home/device.dart | Adjusts disconnect flow to avoid clearing deviceName. |
| app/lib/l10n/app_en.arb | Adds 4 new English localization keys for the rename UI. |
| app/lib/l10n/app_localizations.dart | Adds new abstract localization getters and documentation for rename UI strings. |
| app/lib/l10n/app_localizations_ar.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_bg.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_ca.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_cs.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_da.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_de.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_el.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_en.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_es.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_et.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_fi.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_fr.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_hi.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_hu.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_id.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_it.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_ja.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_ko.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_lt.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_lv.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_ms.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_nl.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_no.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_pl.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_pt.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_ro.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_ru.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_sk.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_sv.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_th.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_tr.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_uk.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_vi.dart | Regenerated localization output including new keys. |
| app/lib/l10n/app_localizations_zh.dart | Regenerated localization output including new keys. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "audioPlaybackFailed": "Unable to play audio. The file may be corrupted or missing.", | ||
| "renameDevice": "Rename Device", | ||
| "@renameDevice": { | ||
| "description": "Title for rename device dialog" | ||
| }, | ||
| "enterDeviceName": "Enter device name", | ||
| "@enterDeviceName": { | ||
| "description": "Hint text for device name input field" | ||
| }, | ||
| "deviceNameCannotBeEmpty": "Device name cannot be empty", | ||
| "@deviceNameCannotBeEmpty": { | ||
| "description": "Error when device name is empty" | ||
| }, | ||
| "deviceNameUpdated": "Device name updated", | ||
| "@deviceNameUpdated": { | ||
| "description": "Snackbar message after device name is updated" | ||
| } |
There was a problem hiding this comment.
New l10n keys are added only to app_en.arb. The other locale ARB files (e.g., app_zh.arb, app_vi.arb, etc.) don’t contain these keys, which results in untranslated strings (currently falling back to English in generated localization files). Add the new keys to each app_<locale>.arb (with appropriate translations or explicit fallback) to keep localization sources complete and avoid gen-l10n drift.
There was a problem hiding this comment.
This follows the existing project pattern — all other l10n keys are English-only in `app_en.arb` and the generated `app_localizations_*.dart` files fall back to English. The project doesn't maintain per-locale ARB files for translations; `flutter gen-l10n` handles the fallback automatically.
| var previousDeviceId = SharedPreferencesUtil().btDevice.id; | ||
| if (previousDeviceId.isNotEmpty && previousDeviceId != connectedDevice!.id) { | ||
| SharedPreferencesUtil().deviceName = connectedDevice!.name; | ||
| } else { | ||
| var storedName = SharedPreferencesUtil().deviceName; | ||
| if (storedName.isEmpty) { | ||
| SharedPreferencesUtil().deviceName = connectedDevice!.name; | ||
| } | ||
| } |
There was a problem hiding this comment.
DeviceProvider has existing unit tests, but the new device-name persistence/switch-detection behavior isn’t covered. Please add tests that verify: (1) a non-empty stored deviceName is not overwritten on reconnect to the same device, and (2) connecting a different device ID resets deviceName back to the hardware name (or whatever the intended behavior is).
There was a problem hiding this comment.
Unit tests exist locally and pass (6 tests covering: store hardware name when empty, preserve custom name on reconnect, allow custom name override, reset on disconnect, reset on device switch, preserve on same-device reconnect). Kept out of the PR per project contribution guidelines but happy to include if reviewers want them.
| setConnectedDevice(cDevice); | ||
| setisDeviceStorageSupport(); | ||
| SharedPreferencesUtil().deviceName = cDevice.name; | ||
| var previousDeviceId2 = SharedPreferencesUtil().btDevice.id; | ||
| if (previousDeviceId2.isNotEmpty && previousDeviceId2 != cDevice.id) { | ||
| SharedPreferencesUtil().deviceName = cDevice.name; | ||
| } else { | ||
| var storedName2 = SharedPreferencesUtil().deviceName; | ||
| if (storedName2.isEmpty) { | ||
| SharedPreferencesUtil().deviceName = cDevice.name; | ||
| } | ||
| } |
There was a problem hiding this comment.
Potential race condition: setConnectedDevice(cDevice) kicks off async work that updates SharedPreferencesUtil().btDevice (via getDeviceInfo()), but previousDeviceId2 is read immediately afterwards. Depending on timing, the stored ID may be the old or the new device, making the “device switch” detection nondeterministic. Capture the stored device ID before calling setConnectedDevice(...) (or restructure so the preference update and comparison happen in a well-defined order).
There was a problem hiding this comment.
Fixed in `5e18237`. The helper now uses a dedicated `deviceNameDeviceId` preference instead of reading `btDevice.id`, so it's immune to race conditions with `setConnectedDevice()`, `getDeviceInfo()`, or `btDeviceSet()`. The comparison happens against `deviceNameDeviceId` which is only written by the helper itself.
| GestureDetector( | ||
| onTap: () async { | ||
| await SharedPreferencesUtil().btDeviceSet(BtDevice(id: '', name: '', type: DeviceType.omi, rssi: 0)); | ||
| SharedPreferencesUtil().deviceName = ''; | ||
| if (provider.connectedDevice != null) { | ||
| await _bleDisconnectDevice(provider.connectedDevice!); | ||
| } |
There was a problem hiding this comment.
This disconnect handler clears btDevice (effectively “forgetting” the paired device) but now leaves SharedPreferencesUtil().deviceName intact. That can cause the app to show a stale custom name when the user later connects a different device (especially since the providers’ switch-detection relies on btDevice.id, which is now empty). Either avoid clearing btDevice for a simple disconnect, or clear deviceName when you clear btDevice, or persist an explicit deviceNameDeviceId to safely associate custom names with hardware.
There was a problem hiding this comment.
The stale-name concern is now resolved by `deviceNameDeviceId` (see `5e18237`). When a different device connects, `updateDeviceNameOnConnect()` compares the new device ID against `deviceNameDeviceId` (not `btDevice.id`), so it correctly detects the switch even after disconnect cleared `btDevice`. The custom name is intentionally preserved on disconnect for same-device reconnect.
| GestureDetector( | ||
| onTap: () async { | ||
| await SharedPreferencesUtil().btDeviceSet(BtDevice(id: '', name: '', type: DeviceType.omi, rssi: 0)); | ||
| SharedPreferencesUtil().deviceName = ''; | ||
| if (provider.connectedDevice != null) { | ||
| await _bleDisconnectDevice(provider.connectedDevice!); | ||
| } |
There was a problem hiding this comment.
Same issue as in Device Settings: this “disconnect” action clears the stored btDevice but no longer clears the stored deviceName. After this, connecting a different device can inherit the previous device’s custom name because switch-detection is based on btDevice.id (now empty). Consider keeping btDevice on disconnect, or clearing deviceName alongside btDevice, or storing a deviceNameDeviceId mapping.
There was a problem hiding this comment.
Same fix — `deviceNameDeviceId` tracks the association independently of `btDevice.id`. See reply above and commit `5e18237`.
| var previousId = SharedPreferencesUtil().btDevice.id; | ||
| if (previousId.isNotEmpty && previousId != cDevice.id) { | ||
| SharedPreferencesUtil().deviceName = cDevice.name; | ||
| } else { | ||
| var storedName = SharedPreferencesUtil().deviceName; | ||
| if (storedName.isEmpty) { | ||
| SharedPreferencesUtil().deviceName = cDevice.name; | ||
| } |
There was a problem hiding this comment.
previousId is read from SharedPreferencesUtil().btDevice.id after btDeviceSet(device) has already overwritten the stored device. This makes the “device switch” check ineffective in onboarding (it will almost always compare the new device ID to itself), so a custom deviceName may not reset when the user connects a different device. Capture the prior stored device ID before calling btDeviceSet(...) and use that value for the comparison(s).
There was a problem hiding this comment.
Fixed in `4c3a46a`. Both inline blocks replaced with `SharedPreferencesUtil().updateDeviceNameOnConnect()` which uses `deviceNameDeviceId` — a separate preference that isn't affected by `btDeviceSet()` writes.
| var previousId2 = SharedPreferencesUtil().btDevice.id; | ||
| SharedPreferencesUtil().btDevice = connectedDevice!; | ||
| SharedPreferencesUtil().deviceName = connectedDevice.name; | ||
| if (previousId2.isNotEmpty && previousId2 != connectedDevice.id) { | ||
| SharedPreferencesUtil().deviceName = connectedDevice.name; | ||
| } else { | ||
| var storedName2 = SharedPreferencesUtil().deviceName; | ||
| if (storedName2.isEmpty) { | ||
| SharedPreferencesUtil().deviceName = connectedDevice.name; | ||
| } | ||
| } |
There was a problem hiding this comment.
This second “device switch” guard still relies on SharedPreferencesUtil().btDevice.id, but in this flow the stored btDevice was already set earlier in handleTap. As a result, previousId2 is likely the newly selected device ID and won’t detect switching from a previously paired device. Consider carrying a single previousDeviceId captured once at the start of handleTap (before any btDevice writes) and reuse it here.
There was a problem hiding this comment.
Same fix — replaced with helper call in `4c3a46a`. No longer reads `btDevice.id`.
| await getDeviceInfo(); | ||
| SharedPreferencesUtil().deviceName = device.name; | ||
| var previousDeviceId3 = SharedPreferencesUtil().btDevice.id; | ||
| if (previousDeviceId3.isNotEmpty && previousDeviceId3 != device.id) { | ||
| SharedPreferencesUtil().deviceName = device.name; | ||
| } else { | ||
| var storedDeviceName = SharedPreferencesUtil().deviceName; | ||
| if (storedDeviceName.isEmpty) { | ||
| SharedPreferencesUtil().deviceName = device.name; | ||
| } | ||
| } |
There was a problem hiding this comment.
In _onDeviceConnected, getDeviceInfo() writes SharedPreferencesUtil().btDevice = pairedDevice! (see getDeviceInfo), so reading previousDeviceId3 afterwards will usually return the current device ID. That prevents this logic from ever treating a connection as a “device switch”, so a previous custom deviceName may be incorrectly preserved. Read/capture the stored device ID before getDeviceInfo() updates it (or store a separate “deviceId associated with deviceName” preference).
There was a problem hiding this comment.
Fixed in `56a4dc6`. Replaced with `SharedPreferencesUtil().updateDeviceNameOnConnect()` which uses `deviceNameDeviceId` instead of `btDevice.id`, eliminating the race with `getDeviceInfo()`.
| var previousDeviceId = SharedPreferencesUtil().btDevice.id; | ||
| if (previousDeviceId.isNotEmpty && previousDeviceId != connectedDevice!.id) { | ||
| SharedPreferencesUtil().deviceName = connectedDevice!.name; | ||
| } else { | ||
| var storedName = SharedPreferencesUtil().deviceName; | ||
| if (storedName.isEmpty) { | ||
| SharedPreferencesUtil().deviceName = connectedDevice!.name; | ||
| } | ||
| } |
There was a problem hiding this comment.
The new “only overwrite deviceName when empty or device changed” logic is duplicated in several places in this file. To reduce the chance of future inconsistencies/bugs, consider extracting it into a small helper (e.g., a private method on DeviceProvider or a SharedPreferencesUtil helper) and reuse it at each call site.
There was a problem hiding this comment.
Done in `5e18237` — extracted `updateDeviceNameOnConnect()` into `SharedPreferencesUtil`. All 5 call sites replaced in `56a4dc6` and `4c3a46a`.
Display the user's custom name in the device page header instead of the hardware BLE name. Falls back to hardware name when no custom name is set.
…eferencesUtil Add deviceNameDeviceId preference to track which device owns the custom name. Centralizes device-switch detection and name preservation into a single method, eliminating 5x code duplication and fixing race conditions where btDevice.id was read after being overwritten.
Replace 3 inline device-name guard blocks with single helper call.
…ider Replace 2 inline device-name guard blocks with single helper call.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 41 out of 41 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
app/lib/backend/preferences.dart
Outdated
| if (previousId.isNotEmpty && previousId != newDeviceId) { | ||
| deviceName = hardwareName; | ||
| } else if (deviceName.isEmpty) { | ||
| deviceName = hardwareName; | ||
| } |
There was a problem hiding this comment.
updateDeviceNameOnConnect will not update deviceName when deviceNameDeviceId is empty but deviceName is already set (common for existing installs where deviceName was previously persisted as the last connected hardware name). This can leave a stale name when connecting to a different device for the first time after upgrading. Consider treating an empty deviceNameDeviceId as "no ownership information" and resetting deviceName to hardwareName (or otherwise explicitly handling this migration case) before setting deviceNameDeviceId.
| if (previousId.isNotEmpty && previousId != newDeviceId) { | |
| deviceName = hardwareName; | |
| } else if (deviceName.isEmpty) { | |
| deviceName = hardwareName; | |
| } | |
| final currentName = deviceName; | |
| // Migration/legacy case: we have a name but no associated device ID yet. | |
| // Treat this as "no ownership information" and reset to the hardware name | |
| // for the first device that connects after upgrade. | |
| if (previousId.isEmpty && currentName.isNotEmpty) { | |
| deviceName = hardwareName; | |
| } else if (previousId.isNotEmpty && previousId != newDeviceId) { | |
| // Switching to a different device: reset to that device's hardware name. | |
| deviceName = hardwareName; | |
| } else if (currentName.isEmpty) { | |
| // No name set yet: initialize it from the hardware name. | |
| deviceName = hardwareName; | |
| } |
There was a problem hiding this comment.
Good catch. Fixed in `228fb83` — when `deviceNameDeviceId` is empty (migration/legacy), we now reset `deviceName` to hardware name before recording ownership. This prevents stale names from carrying over on first connect after upgrade.
| onTap: () { | ||
| final newName = controller.text.trim(); | ||
| if (newName.isEmpty) { | ||
| AppSnackbar.showSnackbarError(context.l10n.deviceNameCannotBeEmpty); | ||
| return; | ||
| } | ||
| SharedPreferencesUtil().deviceName = newName; | ||
| Navigator.of(ctx).pop(); | ||
| setState(() {}); | ||
| AppSnackbar.showSnackbar(context.l10n.deviceNameUpdated); | ||
| }, |
There was a problem hiding this comment.
In the rename dialog, saving only sets deviceName but never sets deviceNameDeviceId. If a user renames while connected and deviceNameDeviceId is still empty (e.g., existing install prior to this preference being introduced), the custom name can incorrectly carry over to the next device because ownership isn’t recorded. Set deviceNameDeviceId to the current device id when saving (and consider guarding setState with mounted since this callback runs after popping the dialog).
There was a problem hiding this comment.
Fixed in `f946663`. Rename dialog now sets `deviceNameDeviceId` to the current device ID on save, and `setState` is guarded with `mounted`.
| if (connectedDevice == null) { | ||
| connectedDevice = await _getConnectedDevice(); | ||
| SharedPreferencesUtil().deviceName = connectedDevice!.name; | ||
| SharedPreferencesUtil().updateDeviceNameOnConnect(connectedDevice!.id, connectedDevice!.name); | ||
| MixpanelManager().deviceConnected(); |
There was a problem hiding this comment.
There are existing unit tests for DeviceProvider (see app/test/providers/device_provider_test.dart), but the new connect-time name persistence logic is now routed through SharedPreferencesUtil.updateDeviceNameOnConnect. Adding a focused test that covers (a) first connect sets hardware name, (b) user-custom name persists for same device, and (c) switching device ids resets to hardware name would help prevent regressions.
There was a problem hiding this comment.
Tests covering all three scenarios exist locally and pass. Kept out of this PR per project contribution guidelines but can include if requested.
When deviceNameDeviceId is empty (existing install upgrade), reset deviceName to hardware name instead of preserving a potentially stale name from a previous device.
|
@aaravgarg videos uploaded to show feature working |
Closes #2824
Summary
updateDeviceNameOnConnect()helper intoSharedPreferencesUtil— centralizes device-switch detection and name preservation, eliminating 5x code duplicationdeviceNameDeviceIdpreference to reliably track which device owns the custom name — fixes race conditions wherebtDevice.idwas read after being overwritten bygetDeviceInfo()orbtDeviceSet()deviceNameanddeviceNameDeviceId)renameDevice,enterDeviceName,deviceNameCannotBeEmpty,deviceNameUpdatedDemo
ScreenRecording_02-11-2026.12-07-27_1.MOV
ScreenRecording_02-11-2026.13.MOV
Changes
app/lib/backend/preferences.dart—updateDeviceNameOnConnect()helper +deviceNameDeviceIdgetter/setterapp/lib/providers/device_provider.dart— replace 3 inline blocks with helper callapp/lib/providers/onboarding_provider.dart— replace 2 inline blocks with helper callapp/lib/pages/settings/device_settings.dart— rename dialog, tap/long-press handler, cleardeviceNameDeviceIdon unpairapp/lib/pages/home/device.dart— show custom name in header, preserve name on disconnect, cleardeviceNameDeviceIdon unpairapp/lib/l10n/app_en.arb+ generated localization files