832 lines
36 KiB
Dart
832 lines
36 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
|
||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||
import 'package:maibu_satabot_v2/core/consts/tcp_consts.dart';
|
||
import 'package:maibu_satabot_v2/core/router/route_paths.dart';
|
||
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
|
||
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart';
|
||
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_state.dart';
|
||
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_bloc.dart';
|
||
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_state.dart';
|
||
import 'package:maibu_satabot_v2/features/remote_control/presentation/bloc/remote_control_cubit.dart'; // 🔥 添加 RemoteControlCubit
|
||
import 'package:maibu_satabot_v2/features/remote_control/presentation/widgets/webrtc/webrtc_local_player.dart';
|
||
|
||
class MachineDetailsPage extends StatefulWidget {
|
||
final DeviceEntity? device;
|
||
|
||
const MachineDetailsPage({super.key, this.device});
|
||
|
||
@override
|
||
State<MachineDetailsPage> createState() => _MachineDetailsPageState();
|
||
}
|
||
|
||
class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||
late TextEditingController _nameController;
|
||
late String _deviceName;
|
||
late String _deviceId;
|
||
BuildContext? _dialogContext;
|
||
BuildContext? _editNameSheetContext;
|
||
BuildContext? _loadingDialogContext; // 解绑加载弹窗
|
||
BuildContext? _updateLoadingContext; // 修改名称加载弹窗
|
||
bool _isUpdatingName = false;
|
||
|
||
// 🔥 保存 stream 引用,避免每次 rebuild 都重新获取
|
||
late Stream<DeviceStatusState> _deviceStatusStream;
|
||
late DeviceStatusState _initialState;
|
||
|
||
// 🔥 新增:视频相关状态
|
||
String _videoStreamUrl = '';
|
||
int _currentViewIndex = 0; // 0=前, 1=后, 2=左, 3=右, 4=上
|
||
|
||
// 视角配置
|
||
final List<Map<String, dynamic>> _viewConfigs = [
|
||
{'name': '前视', 'alignment': Alignment.topLeft, 'icon': Icons.arrow_upward},
|
||
{'name': '后视', 'alignment': Alignment.topRight, 'icon': Icons.arrow_downward},
|
||
{'name': '左视', 'alignment': Alignment.bottomLeft, 'icon': Icons.arrow_back},
|
||
{'name': '右视', 'alignment': Alignment.bottomRight, 'icon': Icons.arrow_forward},
|
||
{'name': '俯视', 'alignment': Alignment.center, 'icon': Icons.view_agenda},
|
||
];
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_deviceName = (widget.device?.deviceAlias?.trim().isEmpty ?? true) ? '未知设备' : widget.device!.deviceAlias!;
|
||
_deviceId = widget.device?.deviceName ?? '';
|
||
_nameController = TextEditingController(text: _deviceName);
|
||
|
||
// 🔥 在 initState 中保存 stream 和 initial state
|
||
final bloc = context.read<DeviceStatusBloc>();
|
||
_deviceStatusStream = bloc.stream;
|
||
_initialState = bloc.state;
|
||
debugPrint('📦 [MachineDetails] initState - 初始状态: ${_initialState.runtimeType}');
|
||
|
||
// 🔥 初始化视频 URL
|
||
_initVideoUrl();
|
||
}
|
||
|
||
/// 🔥 初始化视频流 URL
|
||
void _initVideoUrl() {
|
||
final userState = context.read<AppUserCubit>().state;
|
||
debugPrint('🎬 [MachineDetails] 开始初始化视频URL');
|
||
debugPrint('🎬 [MachineDetails] deviceId: $_deviceId');
|
||
debugPrint('🎬 [MachineDetails] user: ${userState.user}');
|
||
debugPrint('🎬 [MachineDetails] token: ${userState.user?.token}');
|
||
|
||
if (_deviceId.isNotEmpty && userState.user != null && userState.user!.token != null) {
|
||
setState(() {
|
||
_videoStreamUrl = "webrtc://${TCPConsts.TCP_IP}/live/livestream/$_deviceId?token=${userState.user!.token}";
|
||
});
|
||
debugPrint('✅ [MachineDetails] 视频URL初始化成功: $_videoStreamUrl');
|
||
} else {
|
||
debugPrint('❌ [MachineDetails] 视频URL初始化失败');
|
||
debugPrint(' - deviceId.isEmpty: ${_deviceId.isEmpty}');
|
||
debugPrint(' - user == null: ${userState.user == null}');
|
||
debugPrint(' - token == null: ${userState.user?.token == null}');
|
||
}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_nameController.dispose();
|
||
// 兜底关闭所有弹窗
|
||
if (_loadingDialogContext != null && Navigator.canPop(_loadingDialogContext!)) {
|
||
Navigator.pop(_loadingDialogContext!);
|
||
}
|
||
if (_updateLoadingContext != null && Navigator.canPop(_updateLoadingContext!)) {
|
||
Navigator.pop(_updateLoadingContext!);
|
||
}
|
||
if (_dialogContext != null && Navigator.canPop(_dialogContext!)) {
|
||
Navigator.pop(_dialogContext!);
|
||
}
|
||
if (_editNameSheetContext != null && Navigator.canPop(_editNameSheetContext!)) {
|
||
Navigator.pop(_editNameSheetContext!);
|
||
}
|
||
super.dispose();
|
||
}
|
||
|
||
/// 弹出修改设备名称底部抽屉(保持不变)
|
||
void _showEditNameSheet() {
|
||
_nameController.text = _deviceName;
|
||
Future<void> bottomSheetFuture = showModalBottomSheet(
|
||
context: context,
|
||
isScrollControlled: true,
|
||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
|
||
builder: (ctx) {
|
||
_editNameSheetContext = ctx;
|
||
return Padding(
|
||
padding: EdgeInsets.only(left: 20, right: 20, top: 20, bottom: MediaQuery.of(ctx).viewInsets.bottom + 20),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Text(AppLocalizations.of(context).translate('machine_details.edit_name_title'), style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600)),
|
||
const SizedBox(height: 16),
|
||
TextField(
|
||
controller: _nameController,
|
||
autofocus: true,
|
||
maxLength: 20,
|
||
decoration: InputDecoration(hintText: AppLocalizations.of(ctx).translate('machine_details.edit_name_hint'), border: const OutlineInputBorder(), counterText: ""),
|
||
),
|
||
const SizedBox(height: 10),
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: OutlinedButton(
|
||
onPressed: () {
|
||
_isUpdatingName = false;
|
||
Navigator.pop(ctx);
|
||
},
|
||
child: Text(AppLocalizations.of(context).translate('common.cancel')),
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: OutlinedButton(
|
||
onPressed: _isUpdatingName
|
||
? null
|
||
: () async {
|
||
final text = _nameController.text.trim();
|
||
if (text.isEmpty) {
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.name_empty_error'))));
|
||
}
|
||
return;
|
||
}
|
||
if (text == _deviceName) {
|
||
Navigator.pop(ctx);
|
||
return;
|
||
}
|
||
setState(() => _isUpdatingName = true);
|
||
await _executeUpdateDeviceName(text);
|
||
},
|
||
child: _isUpdatingName
|
||
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
||
: Text(AppLocalizations.of(context).translate('common.confirm')),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
);
|
||
bottomSheetFuture.whenComplete(() {
|
||
setState(() {
|
||
_isUpdatingName = false;
|
||
_editNameSheetContext = null;
|
||
});
|
||
});
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
if (widget.device == null) {
|
||
return Scaffold(
|
||
backgroundColor: const Color(0xFFF5F5F5),
|
||
appBar: AppBar(
|
||
title: Text(AppLocalizations.of(context).translate('machine_details.title')),
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.arrow_back_ios),
|
||
onPressed: () {
|
||
// 优化1:先刷新列表,再返回(保证刷新逻辑执行)
|
||
|
||
// 核心:返回上一页
|
||
Navigator.pop(context);
|
||
if (context.mounted) {
|
||
final username = context.read<AppUserCubit>().state.user?.username ?? "";
|
||
|
||
// 🔥 修复:检查是否已有 targetDevice
|
||
final remoteControlState = context.read<RemoteControlCubit>().state;
|
||
if (remoteControlState.targetDevice == null) {
|
||
debugPrint('🔄 [MachineDetails] 无 targetDevice,开始加载设备列表');
|
||
context.read<DevicesCubit>().fetchAllDevices(username);
|
||
} else {
|
||
debugPrint('✅ [MachineDetails] 已有 targetDevice: ${remoteControlState.targetDevice!.deviceName},跳过加载');
|
||
}
|
||
}
|
||
},
|
||
),
|
||
),
|
||
body: Center(child: Text(AppLocalizations.of(context).translate('machine_details.device_info_error'))),
|
||
);
|
||
}
|
||
|
||
final currentDevice = widget.device!;
|
||
return BlocListener<DevicesCubit, DevicesState>(
|
||
listener: (context, state) {
|
||
if (state.operationType == DeviceOperationType.unbind) {
|
||
_handleUnbindResult(state);
|
||
}
|
||
if (state.operationType == DeviceOperationType.updateName) {
|
||
_handleUpdateNameResult(state);
|
||
}
|
||
},
|
||
child: Scaffold(
|
||
backgroundColor: const Color(0xFFF5F5F5),
|
||
appBar: AppBar(
|
||
centerTitle: true,
|
||
title: Text(AppLocalizations.of(context).translate('machine_details.title'), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.arrow_back_ios),
|
||
onPressed: () {
|
||
Navigator.pop(context);
|
||
if (context.mounted) {
|
||
final username = context.read<AppUserCubit>().state.user?.username ?? "";
|
||
// 🔥 修复:检查是否已有 targetDevice
|
||
final remoteControlState = context.read<RemoteControlCubit>().state;
|
||
if (remoteControlState.targetDevice == null) {
|
||
debugPrint('🔄 [MachineDetails AppBar] 无 targetDevice,开始加载设备列表');
|
||
context.read<DevicesCubit>().fetchAllDevices(username);
|
||
} else {
|
||
debugPrint('✅ [MachineDetails AppBar] 已有 targetDevice: ${remoteControlState.targetDevice!.deviceName},跳过加载');
|
||
}
|
||
}
|
||
},
|
||
),
|
||
elevation: 1,
|
||
backgroundColor: Colors.white,
|
||
foregroundColor: Colors.black,
|
||
),
|
||
body: SingleChildScrollView(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
children: [
|
||
_buildDeviceStatusCard(currentDevice),
|
||
const SizedBox(height: 16),
|
||
// 🔥 新增:实时状态卡片
|
||
_buildRealTimeStatusCard(),
|
||
const SizedBox(height: 16),
|
||
_buildBasicInfoCard(context, currentDevice),
|
||
const SizedBox(height: 16),
|
||
_buildResourceCenterCard(),
|
||
const SizedBox(height: 24),
|
||
_buildUnbindButton(context),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 🔥 新增:实时状态卡片(显示控制模式、电压、电量)
|
||
Widget _buildRealTimeStatusCard() {
|
||
return StreamBuilder<DeviceStatusState>(
|
||
stream: _deviceStatusStream,
|
||
initialData: _initialState,
|
||
builder: (context, snapshot) {
|
||
final blocState = snapshot.data;
|
||
debugPrint('🔍 [MachineDetails] StreamBuilder 收到状态: ${blocState?.runtimeType}');
|
||
|
||
String controlMode = AppLocalizations.of(context).translate('common.unknown');
|
||
String voltage = '--';
|
||
String battery = '--';
|
||
|
||
if (blocState is DeviceStatusUpdated) {
|
||
debugPrint('✅ [MachineDetails] 有实时数据 - 电压:${blocState.status.voltage}, 电量:${blocState.status.battery}');
|
||
controlMode = blocState.status.controlMode == '3'
|
||
? AppLocalizations.of(context).translate('machine_details.remote_mode')
|
||
: AppLocalizations.of(context).translate('machine_details.local_mode');
|
||
voltage = '${blocState.status.voltage}V';
|
||
battery = '${blocState.status.battery}%';
|
||
} else {
|
||
debugPrint('⚠️ [MachineDetails] 没有实时数据,状态类型: ${blocState?.runtimeType}');
|
||
}
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(12),
|
||
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2))],
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.only(bottom: 12),
|
||
child: Text(AppLocalizations.of(context).translate('machine_details.real_time_status'), style: const TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
||
),
|
||
_buildStatusRow(AppLocalizations.of(context).translate('machine_details.control_mode'), controlMode),
|
||
const Divider(height: 24, color: Color(0xFFF0F0F0)),
|
||
_buildStatusRow(AppLocalizations.of(context).translate('machine_details.battery_voltage'), voltage),
|
||
const Divider(height: 24, color: Color(0xFFF0F0F0)),
|
||
_buildStatusRow(AppLocalizations.of(context).translate('machine_details.remaining_battery'), battery),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildStatusRow(String label, String value) {
|
||
return Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(label, style: const TextStyle(fontSize: 16, color: Color(0xFF333333))),
|
||
Text(value, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF007AFF))),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// 设备状态卡片(🔥 修改为视频展示 + 视角切换)
|
||
Widget _buildDeviceStatusCard(DeviceEntity device) {
|
||
return Container(
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(12),
|
||
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2))],
|
||
),
|
||
child: Column(
|
||
children: [
|
||
// 🔥 视频展示区域
|
||
AspectRatio(
|
||
aspectRatio: 16 / 9,
|
||
child: _videoStreamUrl.isNotEmpty
|
||
? WebRTCLocalPlayer(
|
||
streamUrl: _videoStreamUrl,
|
||
showLeftPip: false, // 不显示悬浮小窗
|
||
showRightPip: false,
|
||
mainViewAlignment: _viewConfigs[_currentViewIndex]['alignment'] as Alignment, // 🔥 根据视角切换画面
|
||
)
|
||
: Container(
|
||
color: Colors.black87,
|
||
child: Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Icon(Icons.videocam_off, size: 64, color: Colors.white54),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
'视频未加载',
|
||
style: TextStyle(color: Colors.white70, fontSize: 16, fontWeight: FontWeight.bold),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
'deviceId: $_deviceId',
|
||
style: TextStyle(color: Colors.white54, fontSize: 12),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
'URL: ${_videoStreamUrl.isEmpty ? "空" : "已设置"}',
|
||
style: TextStyle(color: Colors.white54, fontSize: 12),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
|
||
// 🔥 在线状态标签
|
||
Padding(
|
||
padding: const EdgeInsets.all(12),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: device.isOnline ? const Color(0xFF00C853).withOpacity(0.1) : const Color(0xFF999999).withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Text(
|
||
device.isOnline ? AppLocalizations.of(context).translate('home.device_online') : AppLocalizations.of(context).translate('home.device_offline'),
|
||
style: TextStyle(fontSize: 14, color: device.isOnline ? const Color(0xFF00C853) : const Color(0xFF999999)),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
|
||
// 🔥 视角切换按钮
|
||
Padding(
|
||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||
children: _viewConfigs.asMap().entries.map((entry) {
|
||
final index = entry.key;
|
||
final config = entry.value;
|
||
final isSelected = _currentViewIndex == index;
|
||
|
||
return Expanded(
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||
child: ElevatedButton(
|
||
onPressed: () {
|
||
setState(() {
|
||
_currentViewIndex = index;
|
||
});
|
||
debugPrint('🎬 [MachineDetails] 切换视角: ${config['name']}');
|
||
},
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: isSelected ? const Color(0xFF165DFF) : Colors.grey[200],
|
||
foregroundColor: isSelected ? Colors.white : Colors.grey[700],
|
||
elevation: 0,
|
||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
|
||
minimumSize: const Size(0, 36),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Icon(config['icon'] as IconData, size: 16),
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
config['name'] as String,
|
||
style: const TextStyle(fontSize: 10),
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}).toList(),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 基本信息卡片(保持不变)
|
||
Widget _buildBasicInfoCard(BuildContext context, DeviceEntity device) {
|
||
final deviceId = device.deviceName ?? AppLocalizations.of(context).translate('common.unknown');
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(12),
|
||
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2))],
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.only(bottom: 16),
|
||
child: Text(AppLocalizations.of(context).translate('machine_details.basic_info'), style: const TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
||
),
|
||
_buildInfoRow(
|
||
context: context,
|
||
icon: Icons.info_outline,
|
||
label: AppLocalizations.of(context).translate('machine_details.device_name'),
|
||
trailing: GestureDetector(
|
||
onTap: _isUpdatingName ? null : _showEditNameSheet,
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Text(_deviceName, style: const TextStyle(fontSize: 16)),
|
||
const SizedBox(width: 8),
|
||
const Icon(Icons.edit, size: 16, color: Color(0xFF007AFF)),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
const Divider(height: 32, color: Color(0xFFF0F0F0)),
|
||
_buildInfoRow(
|
||
context: context,
|
||
icon: Icons.shield_outlined,
|
||
label: AppLocalizations.of(context).translate('machine_details.device_id'),
|
||
trailing: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.end,
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
SizedBox(
|
||
width: MediaQuery.of(context).size.width - 180,
|
||
child: GestureDetector(
|
||
onLongPress: () => _copyToClipboard(deviceId, AppLocalizations.of(context).translate('machine_details.copied')),
|
||
child: Text(
|
||
deviceId,
|
||
textAlign: TextAlign.right,
|
||
softWrap: true,
|
||
overflow: TextOverflow.visible,
|
||
style: const TextStyle(fontSize: 14, color: Colors.black87),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
GestureDetector(
|
||
onTap: () => _copyToClipboard(deviceId, AppLocalizations.of(context).translate('machine_details.device_id') + AppLocalizations.of(context).translate('machine_details.copied')),
|
||
child: const Icon(Icons.copy, size: 16, color: Color(0xFF999999)),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 资源中心卡片(保持不变)
|
||
Widget _buildResourceCenterCard() {
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(12),
|
||
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2))],
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.only(bottom: 16),
|
||
child: Text(AppLocalizations.of(context).translate('machine_details.resource_center'), style: const TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
||
),
|
||
_buildResourceItem(AppLocalizations.of(context).translate('machine_details.user_manual'), Icons.description_outlined),
|
||
const Divider(height: 32, color: Color(0xFFF0F0F0)),
|
||
_buildResourceItem(AppLocalizations.of(context).translate('machine_details.product_manual'), Icons.book_outlined),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 通用信息行组件(保持不变)
|
||
Widget _buildInfoRow({required BuildContext context, required IconData icon, required String label, required Widget trailing}) {
|
||
return Row(
|
||
children: [
|
||
Icon(icon, size: 18, color: const Color(0xFF999999)),
|
||
const SizedBox(width: 12),
|
||
Text(label, style: const TextStyle(fontSize: 16)),
|
||
const Spacer(),
|
||
trailing,
|
||
],
|
||
);
|
||
}
|
||
|
||
/// 资源项组件(保持不变)
|
||
Widget _buildResourceItem(String title, IconData icon) {
|
||
return InkWell(
|
||
onTap: () {
|
||
final loc = AppLocalizations.of(context);
|
||
if (title == loc.translate('machine_details.user_manual')) {
|
||
context.push(RoutePaths.usage);
|
||
} else {
|
||
context.push(RoutePaths.productDesc);
|
||
}
|
||
},
|
||
child: Row(
|
||
children: [
|
||
Icon(icon, size: 18, color: const Color(0xFF007AFF)),
|
||
const SizedBox(width: 12),
|
||
Text(title, style: const TextStyle(fontSize: 16)),
|
||
const Spacer(),
|
||
const Icon(Icons.arrow_forward_ios, size: 16, color: Color(0xFF999999)),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 解绑设备按钮(保持不变)
|
||
Widget _buildUnbindButton(BuildContext context) {
|
||
return SizedBox(
|
||
width: double.infinity,
|
||
height: 50,
|
||
child: ElevatedButton(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: Colors.white,
|
||
foregroundColor: const Color(0xFFFF3B30),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
side: const BorderSide(color: Color(0xFFFF3B30), width: 1),
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
textStyle: const TextStyle(height: 1.2),
|
||
),
|
||
onPressed: _isUpdatingName ? null : () => _showUnbindConfirmDialog(),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
crossAxisAlignment: CrossAxisAlignment.center,
|
||
children: [
|
||
const Icon(Icons.unarchive_outlined, size: 18),
|
||
const SizedBox(width: 8),
|
||
Text(AppLocalizations.of(context).translate('machine_details.unbind'), style: const TextStyle(fontSize: 16, height: 1.0), overflow: TextOverflow.visible),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 显示解绑确认弹窗(保持不变)
|
||
void _showUnbindConfirmDialog() {
|
||
showDialog(
|
||
context: context,
|
||
builder: (dialogContext) {
|
||
_dialogContext = dialogContext;
|
||
return AlertDialog(
|
||
title: Text(AppLocalizations.of(dialogContext).translate('machine_details.unbind_confirm_title')),
|
||
content: Text(AppLocalizations.of(dialogContext).translate('machine_details.unbind_confirm_content').replaceAll('%s', _deviceName)),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
actions: [
|
||
TextButton(onPressed: () => Navigator.pop(dialogContext), child: Text(AppLocalizations.of(dialogContext).translate('common.cancel'))),
|
||
TextButton(
|
||
style: TextButton.styleFrom(foregroundColor: const Color(0xFFFF3B30)),
|
||
onPressed: _executeUnbind,
|
||
child: Text(AppLocalizations.of(dialogContext).translate('common.confirm')),
|
||
),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
Future<void> _executeUnbind() async {
|
||
if (_deviceId.isEmpty || _deviceName.isEmpty) {
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.device_info_error')), backgroundColor: Colors.red));
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 关闭确认弹窗
|
||
if (_dialogContext != null && Navigator.canPop(_dialogContext!)) {
|
||
Navigator.pop(_dialogContext!);
|
||
}
|
||
|
||
try {
|
||
// 显示加载弹窗并记录上下文
|
||
if (context.mounted) {
|
||
showDialog(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (ctx) {
|
||
_loadingDialogContext = ctx;
|
||
return AlertDialog(content: Row(children: [const CircularProgressIndicator(strokeWidth: 2), const SizedBox(width: 16), Text(AppLocalizations.of(ctx).translate('machine_details.unbinding_device'))]));
|
||
},
|
||
);
|
||
}
|
||
|
||
// 调用Cubit解绑方法
|
||
await context.read<DevicesCubit>().unbindDevice(_deviceId, _deviceName);
|
||
|
||
// 主动关闭加载弹窗
|
||
if (_loadingDialogContext != null && Navigator.canPop(_loadingDialogContext!)) {
|
||
Navigator.pop(_loadingDialogContext!);
|
||
_loadingDialogContext = null;
|
||
}
|
||
} catch (e) {
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.unbind_exception').replaceAll('%s', e.toString())), backgroundColor: Colors.red));
|
||
// 关闭加载弹窗
|
||
if (_loadingDialogContext != null && Navigator.canPop(_loadingDialogContext!)) {
|
||
Navigator.pop(_loadingDialogContext!);
|
||
_loadingDialogContext = null;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 执行修改设备名称(核心修改:新增兜底逻辑)
|
||
Future<void> _executeUpdateDeviceName(String newDeviceName) async {
|
||
// 基础校验
|
||
if (_deviceId.isEmpty || newDeviceName.isEmpty) {
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.device_info_error')), backgroundColor: Colors.red));
|
||
}
|
||
setState(() => _isUpdatingName = false);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 显示加载弹窗并记录专属上下文
|
||
if (context.mounted) {
|
||
showDialog(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (ctx) {
|
||
_updateLoadingContext = ctx;
|
||
return AlertDialog(content: Row(children: [const CircularProgressIndicator(strokeWidth: 2), const SizedBox(width: 16), Text(AppLocalizations.of(ctx).translate('machine_details.updating_name'))]));
|
||
},
|
||
);
|
||
}
|
||
|
||
//print("调用updateDeviceName,deviceId: $_deviceId, newDeviceName: $newDeviceName");
|
||
// 调用Cubit修改方法
|
||
await context.read<DevicesCubit>().updateDeviceName(_deviceId, newDeviceName);
|
||
|
||
// 主动关闭修改名称加载弹窗
|
||
if (_updateLoadingContext != null && Navigator.canPop(_updateLoadingContext!)) {
|
||
Navigator.pop(_updateLoadingContext!);
|
||
_updateLoadingContext = null;
|
||
}
|
||
// 主动重置更新状态
|
||
setState(() => _isUpdatingName = false);
|
||
|
||
// 兜底:强制将Cubit的isLoading置为false
|
||
if (context.mounted) {
|
||
context.read<DevicesCubit>().emit(context.read<DevicesCubit>().state.copyWith(isLoading: false, operationType: DeviceOperationType.none));
|
||
}
|
||
} catch (e) {
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.update_exception').replaceAll('%s', e.toString())), backgroundColor: Colors.red));
|
||
// 关闭修改名称加载弹窗
|
||
if (_updateLoadingContext != null && Navigator.canPop(_updateLoadingContext!)) {
|
||
Navigator.pop(_updateLoadingContext!);
|
||
_updateLoadingContext = null;
|
||
}
|
||
}
|
||
setState(() => _isUpdatingName = false);
|
||
|
||
// 兜底:强制重置Cubit的loading状态
|
||
if (context.mounted) {
|
||
context.read<DevicesCubit>().emit(context.read<DevicesCubit>().state.copyWith(isLoading: false, operationType: DeviceOperationType.none));
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 处理解绑结果(保持不变)
|
||
void _handleUnbindResult(DevicesState state) {
|
||
// 强制关闭加载弹窗
|
||
if (_loadingDialogContext != null && Navigator.canPop(_loadingDialogContext!)) {
|
||
Navigator.pop(_loadingDialogContext!);
|
||
_loadingDialogContext = null;
|
||
} else if (!state.isLoading && context.mounted && Navigator.canPop(context)) {
|
||
Navigator.pop(context);
|
||
}
|
||
|
||
if (state.errorMessage?.isEmpty == true && !state.isLoading) {
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(
|
||
context,
|
||
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.unbind_success').replaceAll('%s', _deviceName)), backgroundColor: Colors.green, duration: const Duration(seconds: 2)));
|
||
|
||
final username = context.read<AppUserCubit>().state.user?.username ?? "";
|
||
|
||
// 🔥 修复:检查是否已有 targetDevice
|
||
final remoteControlState = context.read<RemoteControlCubit>().state;
|
||
if (remoteControlState.targetDevice == null) {
|
||
debugPrint('🔄 [MachineDetails Unbind] 无 targetDevice,开始加载设备列表');
|
||
context.read<DevicesCubit>().fetchAllDevices(username);
|
||
} else {
|
||
debugPrint('✅ [MachineDetails Unbind] 已有 targetDevice: ${remoteControlState.targetDevice!.deviceName},跳过加载');
|
||
}
|
||
}
|
||
} else if (state.errorMessage?.isNotEmpty == true && !state.isLoading) {
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.unbind_failed').replaceAll('%s', state.errorMessage ?? AppLocalizations.of(context).translate('common.unknown'))), backgroundColor: Colors.red));
|
||
}
|
||
}
|
||
context.go(RoutePaths.home);
|
||
}
|
||
|
||
/// 处理修改名称结果(核心修改:放宽判断条件)
|
||
void _handleUpdateNameResult(DevicesState state) {
|
||
// 强制关闭修改名称加载弹窗(优先用专属上下文)
|
||
if (_updateLoadingContext != null && Navigator.canPop(_updateLoadingContext!)) {
|
||
Navigator.pop(_updateLoadingContext!);
|
||
_updateLoadingContext = null;
|
||
}
|
||
// 兜底:兼容旧逻辑
|
||
else if (context.mounted && Navigator.canPop(context)) {
|
||
// 不管loading状态,只要弹窗存在就关闭
|
||
Navigator.pop(context);
|
||
}
|
||
|
||
// 处理结果:放宽条件,只要无错误信息就认为成功(不管loading)
|
||
// print(
|
||
// "处理修改名称结果,operationType: ${state.operationType}, isLoading: ${state.isLoading}, errorMessage: ${state.errorMessage},state.errorMessage?.isEmpty: ${state.errorMessage?.isEmpty}",
|
||
// );
|
||
|
||
// 核心修改:去掉!state.isLoading的判断
|
||
if (state.errorMessage?.isEmpty == true) {
|
||
setState(() {
|
||
_deviceName = _nameController.text.trim();
|
||
_isUpdatingName = false; // 双重保障重置状态
|
||
});
|
||
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(
|
||
context,
|
||
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.name_updated_success').replaceAll('%s', _deviceName)), backgroundColor: Colors.green, duration: const Duration(seconds: 2)));
|
||
|
||
// 关闭修改名称的底部抽屉(如果还在)
|
||
if (_editNameSheetContext != null && Navigator.canPop(_editNameSheetContext!)) {
|
||
Navigator.pop(_editNameSheetContext!);
|
||
_editNameSheetContext = null;
|
||
}
|
||
|
||
// 强制重置Cubit的loading状态
|
||
context.read<DevicesCubit>().emit(state.copyWith(isLoading: false, operationType: DeviceOperationType.none));
|
||
}
|
||
} else if (state.errorMessage?.isNotEmpty == true) {
|
||
setState(() => _isUpdatingName = false);
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.update_failed').replaceAll('%s', state.errorMessage ?? AppLocalizations.of(context).translate('common.unknown'))), backgroundColor: Colors.red));
|
||
// 强制重置Cubit的loading状态
|
||
context.read<DevicesCubit>().emit(state.copyWith(isLoading: false, operationType: DeviceOperationType.none));
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 复制到剪贴板(保持不变)
|
||
void _copyToClipboard(String text, String tip) {
|
||
Clipboard.setData(ClipboardData(text: text));
|
||
if (context.mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(tip), duration: const Duration(seconds: 1)));
|
||
}
|
||
}
|
||
}
|