diff --git a/frontend/lib/devices/device_detail.dart b/frontend/lib/devices/device_detail.dart index 7dbfe19..2f91a71 100644 --- a/frontend/lib/devices/device_detail.dart +++ b/frontend/lib/devices/device_detail.dart @@ -1,3 +1,4 @@ +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -42,8 +43,9 @@ class _DeviceDetailState extends State { }); } catch (e) { if (!mounted) return; + if (e is DioException && e.type == DioExceptionType.cancel) return; setState(() { - _error = e.toString(); + _error = dioErrorToUserMessage(e); _isLoading = false; }); } @@ -140,7 +142,10 @@ class _DeviceHeader extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SelectableText(device.ipv4Address, style: theme.textTheme.headlineSmall), + SelectableText( + device.ipv4Address, + style: theme.textTheme.headlineSmall, + ), const SizedBox(height: 4), if (device.isRegistered) StatusBadge(label: 'Registered', color: BadgeColor.success) @@ -166,7 +171,10 @@ class _DeviceInfoCard extends StatelessWidget { Widget build(BuildContext context) { final formatter = FriendlyDateFormatter(); final rows = <(String, String)>[ - ('Name', device.name == null || device.name!.isEmpty ? '—' : device.name!), + ( + 'Name', + device.name == null || device.name!.isEmpty ? '—' : device.name!, + ), ('MAC Address', device.macAddress), ('IP Address', device.ipv4Address), ('Vendor', device.vendor.isEmpty ? '—' : device.vendor), @@ -251,7 +259,9 @@ class _InfoRow extends StatelessWidget { ), ), ), - Expanded(child: SelectableText(value, style: theme.textTheme.bodyMedium)), + Expanded( + child: SelectableText(value, style: theme.textTheme.bodyMedium), + ), ], ), ); diff --git a/frontend/lib/devices/device_event_history.dart b/frontend/lib/devices/device_event_history.dart index dd3dc1f..5295f4d 100644 --- a/frontend/lib/devices/device_event_history.dart +++ b/frontend/lib/devices/device_event_history.dart @@ -1,3 +1,4 @@ +import 'package:dio/dio.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -98,8 +99,9 @@ class _DeviceEventHistoryState extends State { }); } catch (e) { if (!mounted) return; + if (e is DioException && e.type == DioExceptionType.cancel) return; setState(() { - _error = e.toString(); + _error = dioErrorToUserMessage(e); _isLoading = false; }); } @@ -115,9 +117,9 @@ class _DeviceEventHistoryState extends State { } if (_error != null) { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 16), - child: Center(child: Text('Failed to load event history')), + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Center(child: Text('Failed to load event history: $_error')), ); } diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 6ae80e5..7c30757 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -44,10 +44,11 @@ class AppState extends ChangeNotifier { String get themeKey => _themeKey; ThemeData get theme => _themes[_themeKey]!; - void setTheme(String key) { - if (!_themes.containsKey(key) || key == _themeKey) return; + Future setTheme(String key) async { + if (!_themes.containsKey(key)) return false; + if (key == _themeKey) return true; _themeKey = key; - PrefUtil.setValue('theme', key); notifyListeners(); + return PrefUtil.setValue('theme', key); } } diff --git a/frontend/lib/settings/settings.dart b/frontend/lib/settings/settings.dart index ae85215..fd77f2b 100644 --- a/frontend/lib/settings/settings.dart +++ b/frontend/lib/settings/settings.dart @@ -67,13 +67,23 @@ class _SettingsState extends State { } } - void _save() { + Future _save() async { if (!_formKey.currentState!.validate()) return; - PrefUtil.setValue('base_url', _baseUrlController.text); - PrefUtil.setValue('api_key', XOR().xorEncode(_apiKeyController.text)); - BackendAPI.instance.reconfigureFromPrefs(); - context.read().setTheme(_selectedTheme); - UISnackbars.showSuccess(context, 'Settings saved successfully'); + final urlOk = await PrefUtil.setValue('base_url', _baseUrlController.text); + if (!mounted) return; + final keyOk = await PrefUtil.setValue( + 'api_key', + XOR().xorEncode(_apiKeyController.text), + ); + if (!mounted) return; + final themeOk = await context.read().setTheme(_selectedTheme); + if (!mounted) return; + if (urlOk && keyOk && themeOk) { + BackendAPI.instance.reconfigureFromPrefs(); + UISnackbars.showSuccess(context, 'Settings saved successfully'); + } else { + UISnackbars.showError(context, 'Failed to save settings'); + } } @override diff --git a/frontend/lib/utils/pref_utils.dart b/frontend/lib/utils/pref_utils.dart index 88895c8..5bd65e8 100644 --- a/frontend/lib/utils/pref_utils.dart +++ b/frontend/lib/utils/pref_utils.dart @@ -10,15 +10,18 @@ class PrefUtil { return preferences; } - static void setValue(String key, Object value) { + static Future setValue(String key, Object value) { switch (value) { case String s: - preferences.setString(key, s); + return preferences.setString(key, s); case bool b: - preferences.setBool(key, b); + return preferences.setBool(key, b); case int i: - preferences.setInt(key, i); + return preferences.setInt(key, i); default: + throw ArgumentError( + 'Unsupported pref value type: ${value.runtimeType}', + ); } }