232 lines
7.3 KiB
Dart
232 lines
7.3 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
|
||
import 'package:maibu_satabot_v2/core/di/injection.dart';
|
||
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_task_entity.dart';
|
||
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_task_cubit.dart';
|
||
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_task_state.dart';
|
||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||
|
||
import '../theme/pv_colors.dart';
|
||
|
||
/// 「当前任务」弹窗:展示路径规划模块正在执行的任务。
|
||
///
|
||
/// 入口为遥控 HUD 顶栏右上第一项「任务」。数据源与路径规划作业面板同源——
|
||
/// 全局单例 [DeviceTaskCubit] 的 currentTask(用 BlocBuilder 实时订阅,
|
||
/// 任务状态变化自动刷新);接口没给的字段统一显示「无」。
|
||
class PvTaskDialog {
|
||
const PvTaskDialog._();
|
||
|
||
static Future<void> show(BuildContext context) {
|
||
return showDialog<void>(
|
||
context: context,
|
||
builder: (_) => BlocProvider.value(
|
||
// 共享全局实例:弹窗只订阅状态,不负责 cubit 生命周期
|
||
value: sl<DeviceTaskCubit>(),
|
||
child: const _PvTaskDialogContent(),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _PvTaskDialogContent extends StatelessWidget {
|
||
const _PvTaskDialogContent();
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return BlocBuilder<DeviceTaskCubit, DeviceTaskState>(
|
||
builder: (context, state) {
|
||
final task = state.currentTask;
|
||
return Dialog(
|
||
backgroundColor: Colors.transparent,
|
||
insetPadding: const EdgeInsets.symmetric(horizontal: 60),
|
||
child: Container(
|
||
padding: const EdgeInsets.fromLTRB(16, 8, 12, 14),
|
||
decoration: PvColors.panel(radius: 14),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// 标题行 + 关闭
|
||
Row(
|
||
children: [
|
||
const Icon(
|
||
Icons.assignment_outlined,
|
||
color: PvColors.accentBright,
|
||
size: 20,
|
||
),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
AppLocalizations.of(
|
||
context,
|
||
).translate('pv_control.task_current'),
|
||
style: PvType.cardTitle,
|
||
),
|
||
const Spacer(),
|
||
IconButton(
|
||
icon: const Icon(
|
||
Icons.close,
|
||
color: PvColors.textSecondary,
|
||
size: 20,
|
||
),
|
||
visualDensity: VisualDensity.compact,
|
||
padding: EdgeInsets.zero,
|
||
constraints: const BoxConstraints(
|
||
minWidth: 32,
|
||
minHeight: 32,
|
||
),
|
||
onPressed: () => Navigator.of(context).pop(),
|
||
),
|
||
],
|
||
),
|
||
if (task == null)
|
||
const _EmptyTask()
|
||
else
|
||
_TaskFields(task: task),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 无执行任务:路径规划侧 currentTask 为空(未下发/已完成/被清除)。
|
||
class _EmptyTask extends StatelessWidget {
|
||
const _EmptyTask();
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 18),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const Icon(
|
||
Icons.info_outline,
|
||
color: PvColors.textTertiary,
|
||
size: 18,
|
||
),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
AppLocalizations.of(
|
||
context,
|
||
).translate('pv_control.task_none_running'),
|
||
style: PvType.body,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 任务数据项:与路径规划作业面板展示的字段对齐
|
||
/// (任务编号 / 设备 / 状态,另补路线、地块、创建时间),空字段显示「无」。
|
||
class _TaskFields extends StatelessWidget {
|
||
const _TaskFields({required this.task});
|
||
|
||
final DeviceTaskEntity task;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final tr = AppLocalizations.of(context);
|
||
final (statusColor, statusText) = _statusOf(context, task);
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_row(context, tr.translate('pv_control.task_field_no'), '#${task.id}'),
|
||
_row(
|
||
context,
|
||
tr.translate('pv_control.task_field_device'),
|
||
task.deviceId,
|
||
),
|
||
_row(
|
||
context,
|
||
tr.translate('pv_control.task_field_status'),
|
||
statusText,
|
||
valueColor: statusColor,
|
||
),
|
||
_row(
|
||
context,
|
||
tr.translate('pv_control.task_field_route'),
|
||
task.routeId?.toString(),
|
||
),
|
||
_row(
|
||
context,
|
||
tr.translate('pv_control.task_field_site'),
|
||
task.siteId?.toString(),
|
||
),
|
||
_row(
|
||
context,
|
||
tr.translate('pv_control.task_field_create_time'),
|
||
task.createTime,
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// 状态映射与路径规划页一致:NEW 蓝 / EXECUTING 绿 / PAUSE 橙,
|
||
/// 其余用接口给的翻译文案;颜色换成本模块语义色保持风格统一。
|
||
(Color, String) _statusOf(BuildContext context, DeviceTaskEntity t) {
|
||
final tr = AppLocalizations.of(context);
|
||
final translate = t.taskStatusTranslate.isNotEmpty
|
||
? t.taskStatusTranslate
|
||
: t.taskStatus;
|
||
switch (t.taskStatus) {
|
||
case 'NEW':
|
||
return (
|
||
PvColors.info,
|
||
translate.isEmpty
|
||
? tr.translate('pv_control.task_none_value')
|
||
: tr.translate('pv_control.task_new'),
|
||
);
|
||
case 'EXECUTING':
|
||
return (PvColors.online, tr.translate('pv_control.task_executing'));
|
||
case 'PAUSE':
|
||
return (PvColors.warning, tr.translate('pv_control.task_paused'));
|
||
default:
|
||
return (
|
||
PvColors.textSecondary,
|
||
translate.isEmpty
|
||
? tr.translate('pv_control.task_none_value')
|
||
: translate,
|
||
);
|
||
}
|
||
}
|
||
|
||
Widget _row(
|
||
BuildContext context,
|
||
String label,
|
||
String? value, {
|
||
Color? valueColor,
|
||
}) {
|
||
final text = (value == null || value.isEmpty)
|
||
? AppLocalizations.of(context).translate('pv_control.task_none_value')
|
||
: value;
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
SizedBox(
|
||
width: 76,
|
||
child: Text(label, style: PvType.label),
|
||
),
|
||
Expanded(
|
||
child: Text(
|
||
text,
|
||
style: PvType.body.copyWith(
|
||
color: valueColor ?? PvColors.textPrimary,
|
||
fontWeight: valueColor != null
|
||
? FontWeight.w700
|
||
: FontWeight.w400,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|