Files
feature-next-arch/lib/features/v2/home/presentation/pages/home_v2_page.dart
2026-08-25 11:34:32 +08:00

369 lines
15 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:maibu_satabot_v2/core/di/injection.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/bloc/home_v2_bloc.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/bloc/home_v2_event.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/bloc/home_v2_state.dart';
import 'package:maibu_satabot_v2/features/v2/site/presentation/cubit/site_cubit.dart';
import 'package:maibu_satabot_v2/features/v2/site/presentation/widgets/site_selector_widget.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/power_card.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/stats_grid.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/work_order_card.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/quick_entry_card.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/power_trend_chart.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/plant_overview_card.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/tcp_status_indicator.dart';
import 'package:maibu_satabot_v2/components/device_status_modal.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_bloc.dart';
import '../../../../remote_control/presentation/bloc/remote_control_cubit.dart';
class HomeV2Page extends StatefulWidget {
const HomeV2Page({super.key});
@override
State<HomeV2Page> createState() => _HomeV2PageState();
}
class _HomeV2PageState extends State<HomeV2Page> {
late HomeV2Bloc _bloc;
@override
void initState() {
super.initState();
_bloc = context.read<HomeV2Bloc>();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: context.appColors.pageBackground,
body: BlocConsumer<HomeV2Bloc, HomeV2State>(
listener: (context, state) {
// 🔥 监听错误状态,显示友好提示
if (state is HomeV2Error && state.shouldShowError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
backgroundColor: context.appColors.warning,
),
);
}
},
builder: (context, state) {
// 🔥 错误状态显示重试按钮
if (state is HomeV2Error) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 48,
color: context.appColors.warning,
),
const SizedBox(height: 12),
Text(
state.message,
style: TextStyle(
fontSize: 14,
color: context.appColors.textTertiary,
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => _bloc.add(const HomeV2LoadData()),
child: const Text('重试'),
),
],
),
);
}
if (state is HomeV2Loaded) {
return RefreshIndicator(
onRefresh: () async {
_bloc.add(const HomeV2Refresh());
},
child: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
backgroundColor: context.appColors.cardBackground,
elevation: 0,
automaticallyImplyLeading: false,
flexibleSpace: FlexibleSpaceBar(
background: Container(
padding: const EdgeInsets.fromLTRB(16, 40, 16, 16),
color: context.appColors.cardBackground,
child: Row(
children: [
Expanded(child: SiteSelectorWidget()),
const SizedBox(width: 12),
TcpStatusIndicator(
onTap: () => _showDeviceStatusModal(context),
),
],
),
),
),
),
SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 40),
sliver: SliverList(
delegate: SliverChildListDelegate([
const SizedBox(height: 16),
// 天气和告警
Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.cloud_queue,
size: 16,
color: context.appColors.primary,
),
const SizedBox(width: 6),
Text(
AppLocalizations.of(
context,
).translate('home_v2.weather'),
style: TextStyle(
fontSize: 13,
color: context.appColors.textSecondary,
),
),
],
),
),
),
const SizedBox(width: 8),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: context.appColors.danger.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.warning_amber_rounded,
size: 16,
color: context.appColors.danger,
),
const SizedBox(width: 6),
Expanded(
child: Text(
AppLocalizations.of(
context,
).translate('home_v2.emergency_alarm'),
style: TextStyle(
fontSize: 13,
color: context.appColors.danger,
),
),
),
const SizedBox(width: 4),
Icon(
Icons.arrow_forward_ios,
size: 12,
color: context.appColors.danger,
),
],
),
),
),
],
),
const SizedBox(height: 16),
PowerCard(data: state.homeData),
const SizedBox(height: 10),
StatsGrid(data: state.homeData),
const SizedBox(height: 10),
PlantOverviewCard(
overview: state.homeData.plantOverview,
),
const SizedBox(height: 10),
WorkOrderCard(stats: state.homeData.workOrderStats),
const SizedBox(height: 10),
QuickEntryCard(),
const SizedBox(height: 10),
PowerTrendChart(
data: state.homeData.powerTrendData,
trendType: state.trendType,
onToggleType: (type) =>
_bloc.add(HomeV2ToggleTrendType(type)),
),
const SizedBox(height: 40),
]),
),
),
],
),
);
}
// 🔥 Initial/Loading 状态显示加载指示器
return Center(
child: CircularProgressIndicator(color: context.appColors.primary),
);
},
),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// 首次进入时自动加载数据,但不显示转圈
if (_bloc.state is HomeV2Initial) {
// 延迟 100ms,确保 AppUserCubit 状态已更新
Future.delayed(const Duration(milliseconds: 100), () {
if (mounted) {
_bloc.add(const HomeV2LoadData());
}
});
}
}
void _showPlantSelector() {
if (_bloc.state is! HomeV2Loaded) return;
final currentState = _bloc.state as HomeV2Loaded;
final sites = currentState.sites;
if (sites.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context).translate('home_v2.no_sites'),
),
),
);
return;
}
showModalBottomSheet(
context: context,
backgroundColor: context.appColors.cardBackground,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) {
return BlocBuilder<HomeV2Bloc, HomeV2State>(
bloc: _bloc,
builder: (context, state) {
if (state is! HomeV2Loaded) return Container();
return Container(
padding: const EdgeInsets.all(14),
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.6,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
AppLocalizations.of(
context,
).translate('home_v2.select_site'),
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 14),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: state.sites.length,
itemBuilder: (context, index) {
final site = state.sites[index];
final isSelected = state.selectedSite?.id == site.id;
return ListTile(
title: Text(site.siteName),
subtitle:
site.siteCode != null && site.siteCode!.isNotEmpty
? Text(
AppLocalizations.of(context)
.translate('home_v2.site_code')
.replaceAll('%s', site.siteCode!),
)
: null,
trailing: isSelected
? Icon(
Icons.check,
color: context.appColors.primary,
)
: null,
onTap: () {
// 更新全局选中的场站
sl<SiteCubit>().selectSite(site);
Navigator.pop(context);
// 切换场站后,重新加载首页数据
_bloc.add(const HomeV2LoadData());
},
);
},
),
),
],
),
);
},
);
},
);
}
// 显示设备状态模态框 - 从底部滑出
void _showDeviceStatusModal(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
builder: (BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider.value(value: context.read<DeviceStatusBloc>()),
BlocProvider.value(
value: GetIt.I<RemoteControlCubit>(), // 🔥 注入 RemoteControlCubit
),
],
child: const DeviceStatusModal(),
);
},
);
}
}