diff --git a/airhub_app/lib/core/router/app_router.dart b/airhub_app/lib/core/router/app_router.dart index 44e162c..453d5d4 100644 --- a/airhub_app/lib/core/router/app_router.dart +++ b/airhub_app/lib/core/router/app_router.dart @@ -163,5 +163,34 @@ GoRouter goRouter(Ref ref) { }, ), ], + observers: [_BusinessRouteObserver(ref)], ); } + +/// 监听路由变化,进入业务页时自动保存到 SharedPreferences +class _BusinessRouteObserver extends NavigatorObserver { + final Ref _ref; + _BusinessRouteObserver(this._ref); + + void _saveIfBusiness(Route? route) { + final name = route?.settings.name; + if (name != null && _validBusinessRoutes.contains(name)) { + final productType = _ref.read(currentProductTypeProvider); + debugPrint('[Router] 保存业务页: $name, productType=${productType.name}'); + SharedPreferences.getInstance().then((prefs) { + prefs.setString(_lastRouteKey, name); + prefs.setString(_lastProductTypeKey, productType.name); + }); + } + } + + @override + void didPush(Route route, Route? previousRoute) { + _saveIfBusiness(route); + } + + @override + void didReplace({Route? newRoute, Route? oldRoute}) { + _saveIfBusiness(newRoute); + } +} diff --git a/airhub_app/lib/features/badge/presentation/pages/badge_basic_control_page.dart b/airhub_app/lib/features/badge/presentation/pages/badge_basic_control_page.dart index 7bd9819..3f72c46 100644 --- a/airhub_app/lib/features/badge/presentation/pages/badge_basic_control_page.dart +++ b/airhub_app/lib/features/badge/presentation/pages/badge_basic_control_page.dart @@ -1,14 +1,11 @@ -import 'dart:convert'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; -import 'package:http/http.dart' as http; -import 'package:shared_preferences/shared_preferences.dart'; -import '../../../../core/network/api_config.dart'; +import '../../../../core/network/api_client.dart'; import '../../../../pages/profile/profile_page.dart'; import '../../../../theme/product_theme.dart'; import '../../../../widgets/animated_gradient_background.dart'; @@ -53,25 +50,16 @@ class _BadgeBasicControlPageState extends ConsumerState Future _loadLastImage() async { try { - final prefs = await SharedPreferences.getInstance(); - final token = prefs.getString('access_token'); - final resp = await http.get( - Uri.parse('${ApiConfig.fullBaseUrl}/badge/history/'), - headers: {if (token != null) 'Authorization': 'Bearer $token'}, - ).timeout(const Duration(seconds: 10)); - - if (resp.statusCode == 200) { - final body = jsonDecode(resp.body) as Map; - final data = body['data'] as Map? ?? {}; - final images = (data['images'] as List? ?? []) - .cast>() - .where((img) => - img['generation_status'] == 'completed' && - (img['image_url'] as String?)?.isNotEmpty == true) - .toList(); - if (images.isNotEmpty && mounted) { - setState(() => _lastImageUrl = images.first['image_url'] as String); - } + final apiClient = ref.read(apiClientProvider); + final data = await apiClient.get('/badge/history/'); + final images = ((data as Map)['images'] as List? ?? []) + .cast>() + .where((img) => + img['generation_status'] == 'completed' && + (img['image_url'] as String?)?.isNotEmpty == true) + .toList(); + if (images.isNotEmpty && mounted) { + setState(() => _lastImageUrl = images.first['image_url'] as String); } } catch (_) {} if (mounted) setState(() => _loading = false); diff --git a/airhub_app/lib/features/device/presentation/controllers/device_controller.dart b/airhub_app/lib/features/device/presentation/controllers/device_controller.dart index 4653412..b3dca3d 100644 --- a/airhub_app/lib/features/device/presentation/controllers/device_controller.dart +++ b/airhub_app/lib/features/device/presentation/controllers/device_controller.dart @@ -23,12 +23,11 @@ class DeviceController extends _$DeviceController { Future bindDevice(String sn, {int? spiritId}) async { final repository = ref.read(deviceRepositoryProvider); final result = await repository.bindDevice(sn, spiritId: spiritId); - if (!ref.mounted) return '组件已卸载'; + if (!ref.mounted) return null; // 组件已卸载,绑定请求已发出,视为成功 return result.fold( (failure) => failure.message, (bindingId) { - if (!ref.mounted) return '组件已卸载'; - ref.invalidateSelf(); + if (ref.mounted) ref.invalidateSelf(); return null; }, ); diff --git a/airhub_app/lib/pages/bluetooth_page.dart b/airhub_app/lib/pages/bluetooth_page.dart index af6e049..bbafa24 100644 --- a/airhub_app/lib/pages/bluetooth_page.dart +++ b/airhub_app/lib/pages/bluetooth_page.dart @@ -289,23 +289,18 @@ class _BluetoothPageState extends ConsumerState debugPrint('[Bluetooth] 设备已就绪: $mac → $displayName'); } catch (e) { debugPrint('[Bluetooth] queryByMac 失败($mac): $e'); - // API 查询失败时,用 BLE 名作为 fallback 也显示出来 if (!mounted) return; - final bleDevice = _pendingBleDevices[mac]; - setState(() { - if (!_devices.any((d) => d.macAddress == mac)) { - _devices.add(MockDevice( - sn: '', - name: '${_airhubPrefix}设备', - macAddress: mac, - type: DeviceType.plush, - hasAI: true, - bleDevice: bleDevice, - )); - } - _isSearching = false; - }); + // 查询失败 → 停止扫描,提示用户 + setState(() => _isSearching = false); try { await FlutterBluePlus.stopScan(); } catch (_) {} + _macInfoCache.remove(mac); // 移除占位,允许重新扫描时再查 + showGlassDialog( + context: context, + title: '设备查询失败', + description: '无法验证设备信息,请检查网络后重试。', + confirmText: '确定', + onConfirm: () => Navigator.of(context).pop(), + ); } } @@ -444,6 +439,16 @@ class _BluetoothPageState extends ConsumerState } } catch (e) { debugPrint('[Bluetooth] bindDevice 异常: $e'); + if (!mounted) return; + setState(() => _isConnecting = false); + showGlassDialog( + context: context, + title: '绑定失败', + description: '$e', + confirmText: '确定', + onConfirm: () => Navigator.of(context).pop(), + ); + return; } if (!mounted) return; setState(() => _isConnecting = false); @@ -564,7 +569,13 @@ class _BluetoothPageState extends ConsumerState children: [ // 返回按钮 - CSS: border-radius: 12px, bg: rgba(255,255,255,0.6), no border GestureDetector( - onTap: () => context.pop(), + onTap: () { + if (context.canPop()) { + context.pop(); + } else { + context.go('/'); + } + }, child: Container( width: 40, height: 40, @@ -902,7 +913,13 @@ class _BluetoothPageState extends ConsumerState children: [ // 取消按钮 - HTML: frosted glass with border GestureDetector( - onTap: () => context.pop(), + onTap: () { + if (context.canPop()) { + context.pop(); + } else { + context.go('/'); + } + }, child: ClipRRect( borderRadius: BorderRadius.circular(25), child: Container( diff --git a/airhub_app/lib/pages/wifi_config_page.dart b/airhub_app/lib/pages/wifi_config_page.dart index 3931861..36797fa 100644 --- a/airhub_app/lib/pages/wifi_config_page.dart +++ b/airhub_app/lib/pages/wifi_config_page.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../core/services/ble_provisioning_service.dart'; +import '../features/device/data/datasources/device_remote_data_source.dart'; import '../features/device/presentation/controllers/device_controller.dart'; import '../widgets/animated_gradient_background.dart'; import '../widgets/gradient_button.dart'; @@ -133,6 +134,30 @@ class _WifiConfigPageState extends ConsumerState if (_isBinding) return; setState(() => _isBinding = true); + // 绑定前再次检查设备归属(防止 BLE 扫描时检查遗漏) + final mac = _deviceInfo['mac'] as String? ?? ''; + if (mac.isNotEmpty) { + try { + final dataSource = ref.read(deviceRemoteDataSourceProvider); + final macData = await dataSource.queryByMac(mac); + final bindStatus = macData['bind_status'] as String? ?? 'unbound'; + if (bindStatus == 'bound_by_other') { + if (!mounted) return; + setState(() => _isBinding = false); + showGlassDialog( + context: context, + title: '无法绑定', + description: '该设备已被其他用户绑定,无法使用。如需解绑请联系设备所有者。', + confirmText: '确定', + onConfirm: () => Navigator.of(context).pop(), + ); + return; + } + } catch (e) { + debugPrint('[WiFi Config] 检查设备归属失败: $e'); + } + } + final sn = _deviceInfo['sn'] as String? ?? ''; if (sn.isNotEmpty) { try { @@ -152,6 +177,16 @@ class _WifiConfigPageState extends ConsumerState } } catch (e) { debugPrint('[WiFi Config] bindDevice 异常: $e'); + if (!mounted) return; + setState(() => _isBinding = false); + showGlassDialog( + context: context, + title: '绑定失败', + description: '$e', + confirmText: '确定', + onConfirm: () => Navigator.of(context).pop(), + ); + return; } } if (!mounted) return;