337 lines
12 KiB
Dart
337 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
import 'package:maibu_satabot_v2/core/router/route_paths.dart';
|
||
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart';
|
||
import 'package:maibu_satabot_v2/features/my/presentation/bloc/my_cubit.dart';
|
||
import 'package:maibu_satabot_v2/features/my/presentation/bloc/my_state.dart';
|
||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||
import 'package:get_it/get_it.dart';
|
||
import '../../../auth/presentation/bloc/auth_cubit.dart';
|
||
|
||
class UserInfoCard extends StatefulWidget {
|
||
final String userName;
|
||
final String userId;
|
||
|
||
const UserInfoCard({super.key, required this.userName, required this.userId});
|
||
|
||
@override
|
||
State<UserInfoCard> createState() => _UserInfoCardState();
|
||
}
|
||
|
||
class _UserInfoCardState extends State<UserInfoCard> {
|
||
late TextEditingController _nicknameController;
|
||
final GetIt _getIt = GetIt.I;
|
||
MyCubit? _myCubit; // 可空类型
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
// 优先从 BlocProvider 获取,获取失败则从 GetIt 兜底
|
||
try {
|
||
_myCubit = BlocProvider.of<MyCubit>(context);
|
||
} catch (e) {
|
||
debugPrint('从 BlocProvider 获取 MyCubit 失败:$e,尝试从 GetIt 获取');
|
||
try {
|
||
_myCubit = _getIt<MyCubit>();
|
||
} catch (e) {
|
||
debugPrint('从 GetIt 获取 MyCubit 也失败:$e');
|
||
}
|
||
}
|
||
_nicknameController = TextEditingController(text: widget.userName);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_nicknameController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
// 打开修改昵称的底部抽屉
|
||
void _openEditNicknameBottomSheet() {
|
||
// 修复1:先校验 _myCubit 是否为 null,避免空断言
|
||
final myCubit = _myCubit;
|
||
if (myCubit == null) {
|
||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Cubit 初始化失败,无法修改昵称'), backgroundColor: Colors.red));
|
||
return;
|
||
}
|
||
|
||
showModalBottomSheet(
|
||
context: context,
|
||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
|
||
isScrollControlled: true,
|
||
backgroundColor: Colors.transparent,
|
||
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.8),
|
||
builder: (context) => BlocProvider.value(
|
||
// 使用非空的局部变量,无需空断言
|
||
value: myCubit,
|
||
child: BlocBuilder<MyCubit, MyState>(
|
||
builder: (context, myState) {
|
||
final cubit = context.read<MyCubit>();
|
||
if (myState.errorMessage == '登录状态失效,请重新登录') {
|
||
context.go(RoutePaths.login);
|
||
}
|
||
|
||
return Padding(
|
||
padding: EdgeInsets.only(left: 16, right: 16, top: 20, bottom: MediaQuery.of(context).viewInsets.bottom + 80),
|
||
child: Container(
|
||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)),
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
const Text('修改昵称', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||
IconButton(padding: EdgeInsets.zero, icon: const Icon(Icons.close, size: 20), onPressed: () => Navigator.pop(context)),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
const Text('新昵称', style: TextStyle(fontSize: 12, color: Colors.grey)),
|
||
const SizedBox(height: 8),
|
||
TextField(
|
||
controller: _nicknameController,
|
||
style: const TextStyle(fontSize: 16),
|
||
decoration: InputDecoration(
|
||
border: const OutlineInputBorder(),
|
||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||
),
|
||
autofocus: true,
|
||
enabled: !myState.isLoading,
|
||
),
|
||
const SizedBox(height: 20),
|
||
|
||
// 核心:根据 MyState 显示加载/按钮/错误提示
|
||
if (myState.isLoading)
|
||
const Center(
|
||
child: Column(
|
||
children: [
|
||
CircularProgressIndicator(color: Colors.blue),
|
||
SizedBox(height: 8),
|
||
Text("修改中...", style: TextStyle(color: Colors.grey)),
|
||
],
|
||
),
|
||
)
|
||
else
|
||
Column(
|
||
children: [
|
||
// 修复2:使用 ?. 和 ?? 避免空断言,安全判断错误信息
|
||
if (myState.errorMessage?.isNotEmpty ?? false)
|
||
Padding(
|
||
padding: const EdgeInsets.only(bottom: 12),
|
||
child: Text(myState.errorMessage ?? "修改失败", style: const TextStyle(color: Colors.red, fontSize: 14)),
|
||
),
|
||
|
||
// 确定修改按钮
|
||
SizedBox(
|
||
width: double.infinity,
|
||
child: ElevatedButton(
|
||
onPressed: () => _submitNewNickname(context),
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: Colors.blue,
|
||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||
),
|
||
child: const Text('确定修改', style: TextStyle(fontSize: 16, color: Colors.white)),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// 提交新昵称的业务逻辑
|
||
Future<void> _submitNewNickname(BuildContext context) async {
|
||
final newNickname = _nicknameController.text.trim();
|
||
if (newNickname.isEmpty) {
|
||
Navigator.pop(context);
|
||
return;
|
||
}
|
||
|
||
final myCubit = _myCubit;
|
||
if (myCubit == null) {
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Cubit 初始化失败'), backgroundColor: Colors.red));
|
||
}
|
||
Navigator.pop(context);
|
||
return;
|
||
}
|
||
|
||
// 移除重复的加载弹窗(由 BlocBuilder 显示加载状态)
|
||
await myCubit.updateName(newNickname);
|
||
|
||
// 成功后由 BlocBuilder 监听状态并处理弹窗关闭和提示
|
||
if (context.mounted && myCubit.state.errorMessage!.isEmpty ?? true) {
|
||
setState(() {
|
||
_nicknameController.text = newNickname;
|
||
});
|
||
Navigator.pop(context);
|
||
// 触发 AuthCubit 重新获取用户信息
|
||
}
|
||
}
|
||
|
||
// 扫码绑定设备逻辑(保留)
|
||
// 扫码绑定设备逻辑(修复版)
|
||
Future<void> _handleScanAndBind(BuildContext context) async {
|
||
bool isScanned = false;
|
||
|
||
// 1. 扫码逻辑 (保持不变)
|
||
final String? deviceCode = await Navigator.push<String>(
|
||
context,
|
||
MaterialPageRoute(
|
||
builder: (context) => Scaffold(
|
||
backgroundColor: Colors.transparent,
|
||
appBar: AppBar(
|
||
title: const Text('扫码绑定设备'),
|
||
backgroundColor: Colors.black54,
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.close),
|
||
onPressed: () => Navigator.pop(context),
|
||
),
|
||
),
|
||
body: MobileScanner(
|
||
onDetect: (BarcodeCapture capture) {
|
||
if (isScanned) return;
|
||
final List<Barcode> barcodes = capture.barcodes;
|
||
if (barcodes.isNotEmpty) {
|
||
final String? code = barcodes.first.rawValue;
|
||
if (code != null && code.isNotEmpty) {
|
||
isScanned = true;
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
if (context.mounted && Navigator.canPop(context)) {
|
||
Navigator.pop(context, code);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
},
|
||
),
|
||
),
|
||
),
|
||
);
|
||
|
||
if (deviceCode == null || deviceCode.isEmpty) {
|
||
return;
|
||
}
|
||
|
||
// 2. 确认对话框 (保持不变)
|
||
final bool? confirmBind = await showDialog<bool>(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (context) => AlertDialog(
|
||
title: const Text('确认绑定设备'),
|
||
content: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Text('设备信息:'),
|
||
const SizedBox(height: 8),
|
||
Text('设备编码:$deviceCode'),
|
||
const Text('设备状态:未绑定'),
|
||
const Text('设备类型:智能设备'),
|
||
],
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(context, false),
|
||
child: const Text('取消'),
|
||
),
|
||
ElevatedButton(
|
||
onPressed: () => Navigator.pop(context, true),
|
||
child: const Text('确定绑定'),
|
||
style: ElevatedButton.styleFrom(backgroundColor: Colors.blue),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
|
||
if (confirmBind != true) {
|
||
return;
|
||
}
|
||
|
||
// 3. 显示加载框:保存加载弹窗的 BuildContext
|
||
BuildContext? loadingContext; // 新增:保存加载弹窗的上下文
|
||
showDialog(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (context) {
|
||
loadingContext = context; // 保存这个 context
|
||
return const Center(child: CircularProgressIndicator());
|
||
},
|
||
);
|
||
|
||
try {
|
||
final devicesCubit = GetIt.I<DevicesCubit>();
|
||
await devicesCubit.bindDevice(deviceCode, '');
|
||
|
||
// ✅ 成功:用 loadingContext 关闭弹窗
|
||
if (loadingContext != null && Navigator.canPop(loadingContext!)) {
|
||
Navigator.pop(loadingContext!);
|
||
}
|
||
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('设备绑定成功!'), backgroundColor: Colors.green),
|
||
);
|
||
}
|
||
} catch (e) {
|
||
// ❌ 失败:用 loadingContext 关闭弹窗
|
||
if (loadingContext != null && Navigator.canPop(loadingContext!)) {
|
||
Navigator.pop(loadingContext!);
|
||
}
|
||
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text('绑定失败:${e.toString()}'),
|
||
backgroundColor: Colors.red,
|
||
duration: const Duration(seconds: 4),
|
||
),
|
||
);
|
||
}
|
||
debugPrint('设备绑定失败详情:$e');
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Row(
|
||
children: [
|
||
Container(
|
||
width: 48,
|
||
height: 48,
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(8),
|
||
image: DecorationImage(image: NetworkImage('https://pic4.zhimg.com/v2-c34c61a90095abb8713de9d1dca7ec7b_r.jpg'), fit: BoxFit.cover),
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(_nicknameController.text, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||
Text('用户 id:${widget.userId}', style: const TextStyle(fontSize: 12, color: Colors.grey)),
|
||
],
|
||
),
|
||
),
|
||
IconButton(icon: const Icon(Icons.edit), onPressed: _openEditNicknameBottomSheet),
|
||
const Spacer(),
|
||
IconButton(icon: const Icon(Icons.add), onPressed: () => _handleScanAndBind(context)),
|
||
],
|
||
);
|
||
}
|
||
}
|