fix: auto repair bugs #83
This commit is contained in:
parent
ce249058f2
commit
67ec658bad
@ -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<dynamic>? 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<dynamic> route, Route<dynamic>? previousRoute) {
|
||||||
|
_saveIfBusiness(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
|
||||||
|
_saveIfBusiness(newRoute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:go_router/go_router.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 '../../../../pages/profile/profile_page.dart';
|
||||||
import '../../../../theme/product_theme.dart';
|
import '../../../../theme/product_theme.dart';
|
||||||
import '../../../../widgets/animated_gradient_background.dart';
|
import '../../../../widgets/animated_gradient_background.dart';
|
||||||
@ -53,25 +50,16 @@ class _BadgeBasicControlPageState extends ConsumerState<BadgeBasicControlPage>
|
|||||||
|
|
||||||
Future<void> _loadLastImage() async {
|
Future<void> _loadLastImage() async {
|
||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final apiClient = ref.read(apiClientProvider);
|
||||||
final token = prefs.getString('access_token');
|
final data = await apiClient.get('/badge/history/');
|
||||||
final resp = await http.get(
|
final images = ((data as Map<String, dynamic>)['images'] as List<dynamic>? ?? [])
|
||||||
Uri.parse('${ApiConfig.fullBaseUrl}/badge/history/'),
|
.cast<Map<String, dynamic>>()
|
||||||
headers: {if (token != null) 'Authorization': 'Bearer $token'},
|
.where((img) =>
|
||||||
).timeout(const Duration(seconds: 10));
|
img['generation_status'] == 'completed' &&
|
||||||
|
(img['image_url'] as String?)?.isNotEmpty == true)
|
||||||
if (resp.statusCode == 200) {
|
.toList();
|
||||||
final body = jsonDecode(resp.body) as Map<String, dynamic>;
|
if (images.isNotEmpty && mounted) {
|
||||||
final data = body['data'] as Map<String, dynamic>? ?? {};
|
setState(() => _lastImageUrl = images.first['image_url'] as String);
|
||||||
final images = (data['images'] as List<dynamic>? ?? [])
|
|
||||||
.cast<Map<String, dynamic>>()
|
|
||||||
.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 (_) {}
|
} catch (_) {}
|
||||||
if (mounted) setState(() => _loading = false);
|
if (mounted) setState(() => _loading = false);
|
||||||
|
|||||||
@ -23,12 +23,11 @@ class DeviceController extends _$DeviceController {
|
|||||||
Future<String?> bindDevice(String sn, {int? spiritId}) async {
|
Future<String?> bindDevice(String sn, {int? spiritId}) async {
|
||||||
final repository = ref.read(deviceRepositoryProvider);
|
final repository = ref.read(deviceRepositoryProvider);
|
||||||
final result = await repository.bindDevice(sn, spiritId: spiritId);
|
final result = await repository.bindDevice(sn, spiritId: spiritId);
|
||||||
if (!ref.mounted) return '组件已卸载';
|
if (!ref.mounted) return null; // 组件已卸载,绑定请求已发出,视为成功
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(failure) => failure.message,
|
(failure) => failure.message,
|
||||||
(bindingId) {
|
(bindingId) {
|
||||||
if (!ref.mounted) return '组件已卸载';
|
if (ref.mounted) ref.invalidateSelf();
|
||||||
ref.invalidateSelf();
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -289,23 +289,18 @@ class _BluetoothPageState extends ConsumerState<BluetoothPage>
|
|||||||
debugPrint('[Bluetooth] 设备已就绪: $mac → $displayName');
|
debugPrint('[Bluetooth] 设备已就绪: $mac → $displayName');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('[Bluetooth] queryByMac 失败($mac): $e');
|
debugPrint('[Bluetooth] queryByMac 失败($mac): $e');
|
||||||
// API 查询失败时,用 BLE 名作为 fallback 也显示出来
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final bleDevice = _pendingBleDevices[mac];
|
// 查询失败 → 停止扫描,提示用户
|
||||||
setState(() {
|
setState(() => _isSearching = false);
|
||||||
if (!_devices.any((d) => d.macAddress == mac)) {
|
|
||||||
_devices.add(MockDevice(
|
|
||||||
sn: '',
|
|
||||||
name: '${_airhubPrefix}设备',
|
|
||||||
macAddress: mac,
|
|
||||||
type: DeviceType.plush,
|
|
||||||
hasAI: true,
|
|
||||||
bleDevice: bleDevice,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_isSearching = false;
|
|
||||||
});
|
|
||||||
try { await FlutterBluePlus.stopScan(); } catch (_) {}
|
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<BluetoothPage>
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('[Bluetooth] bindDevice 异常: $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;
|
if (!mounted) return;
|
||||||
setState(() => _isConnecting = false);
|
setState(() => _isConnecting = false);
|
||||||
@ -564,7 +569,13 @@ class _BluetoothPageState extends ConsumerState<BluetoothPage>
|
|||||||
children: [
|
children: [
|
||||||
// 返回按钮 - CSS: border-radius: 12px, bg: rgba(255,255,255,0.6), no border
|
// 返回按钮 - CSS: border-radius: 12px, bg: rgba(255,255,255,0.6), no border
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => context.pop(),
|
onTap: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
context.pop();
|
||||||
|
} else {
|
||||||
|
context.go('/');
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
@ -902,7 +913,13 @@ class _BluetoothPageState extends ConsumerState<BluetoothPage>
|
|||||||
children: [
|
children: [
|
||||||
// 取消按钮 - HTML: frosted glass with border
|
// 取消按钮 - HTML: frosted glass with border
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => context.pop(),
|
onTap: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
context.pop();
|
||||||
|
} else {
|
||||||
|
context.go('/');
|
||||||
|
}
|
||||||
|
},
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(25),
|
borderRadius: BorderRadius.circular(25),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import '../core/services/ble_provisioning_service.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 '../features/device/presentation/controllers/device_controller.dart';
|
||||||
import '../widgets/animated_gradient_background.dart';
|
import '../widgets/animated_gradient_background.dart';
|
||||||
import '../widgets/gradient_button.dart';
|
import '../widgets/gradient_button.dart';
|
||||||
@ -133,6 +134,30 @@ class _WifiConfigPageState extends ConsumerState<WifiConfigPage>
|
|||||||
if (_isBinding) return;
|
if (_isBinding) return;
|
||||||
setState(() => _isBinding = true);
|
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? ?? '';
|
final sn = _deviceInfo['sn'] as String? ?? '';
|
||||||
if (sn.isNotEmpty) {
|
if (sn.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
@ -152,6 +177,16 @@ class _WifiConfigPageState extends ConsumerState<WifiConfigPage>
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('[WiFi Config] bindDevice 异常: $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;
|
if (!mounted) return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user