mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Surface persistence and fetch errors instead of swallowing them
PrefUtil.setValue now returns the underlying SharedPreferences result and throws on unsupported types; settings save reports failures rather than always showing success. Device detail and event history report the real Dio error message and skip cancellations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
f7f255e20f
commit
e70676a193
@@ -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<DeviceDetail> {
|
||||
});
|
||||
} 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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<DeviceEventHistory> {
|
||||
});
|
||||
} 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<DeviceEventHistory> {
|
||||
}
|
||||
|
||||
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')),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<bool> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,13 +67,23 @@ class _SettingsState extends State<Settings> {
|
||||
}
|
||||
}
|
||||
|
||||
void _save() {
|
||||
Future<void> _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<AppState>().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<AppState>().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
|
||||
|
||||
@@ -10,15 +10,18 @@ class PrefUtil {
|
||||
return preferences;
|
||||
}
|
||||
|
||||
static void setValue(String key, Object value) {
|
||||
static Future<bool> 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}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user