Files
flutterApp/lib/features/main_container/presentation/main_wrapper.dart
2026-09-14 12:35:20 +08:00

311 lines
13 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/features/main_container/presentation/cubit/tab_config_cubit.dart';
import 'package:maibu_satabot_v2/features/main_container/domain/tab_config.dart';
import 'package:maibu_satabot_v2/features/home/presentation/pages/home_page.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/pages/home_v2_page.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/bloc/home_v2_bloc.dart';
import 'package:maibu_satabot_v2/features/v2/device_list/presentation/pages/device_status_page.dart';
import 'package:maibu_satabot_v2/features/ai/presentation/pages/ai_page.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/pages/alarm_center_page.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/bloc/alarm_cubit.dart';
import 'package:maibu_satabot_v2/features/my/presentation/pages/my_page.dart';
import 'package:maibu_satabot_v2/features/v2/my/presentation/pages/my_page.dart'
as my_v2;
import 'package:maibu_satabot_v2/features/v2/workorder/presentation/pages/workorder_page.dart';
import 'package:maibu_satabot_v2/features/v2/report/presentation/pages/report_page.dart';
import 'package:maibu_satabot_v2/features/v2/device_list/presentation/float_bar/view/float_bar_widget.dart';
import 'package:maibu_satabot_v2/features/v2/device_list/presentation/float_bar/cubit/float_bar_setting_cubit.dart';
import 'package:maibu_satabot_v2/core/di/injection.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
class MainWrapper extends StatefulWidget {
const MainWrapper({super.key});
@override
State<MainWrapper> createState() => _MainWrapperState();
}
class _MainWrapperState extends State<MainWrapper> {
int _currentIndex = 0;
final PageController _pageController = PageController();
@override
void initState() {
super.initState();
// 🔥 确保 FloatBarSettingService 已初始化
GetIt.I<FloatBarSettingService>();
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
void _onTabChanged(int index) {
setState(() {
_currentIndex = index;
});
_pageController.jumpToPage(index);
}
/// Tab 图标:选中用 filled(实心),未选用 outlined(描边),对比更清晰
IconData _navIcon(String iconName, bool selected) {
switch (iconName) {
case 'home_rounded':
return selected ? Icons.home_rounded : Icons.home_outlined;
case 'grid_view_rounded':
return selected ? Icons.grid_view_rounded : Icons.grid_view_outlined;
case 'devices_rounded':
return selected ? Icons.devices_rounded : Icons.devices_outlined;
case 'auto_awesome_rounded':
return selected
? Icons.auto_awesome_rounded
: Icons.auto_awesome_outlined;
case 'warning_amber_rounded':
return selected
? Icons.warning_amber_rounded
: Icons.warning_amber_outlined;
case 'assignment_rounded':
return selected ? Icons.assignment_rounded : Icons.assignment_outlined;
case 'post_add_rounded':
return selected ? Icons.post_add_rounded : Icons.post_add_outlined;
case 'person_rounded':
return selected ? Icons.person_rounded : Icons.person_outlined;
default:
return selected ? Icons.home_rounded : Icons.home_outlined;
}
}
Widget _buildCurrentPage(TabConfigItem tab) {
switch (tab.id) {
case 'home_v2':
return BlocProvider(
create: (_) => sl<HomeV2Bloc>(),
child: const HomeV2Page(),
);
case 'home':
return const HomePage();
case 'device':
return const DeviceStatusPage();
case 'ai':
return const AiPage();
case 'warning':
return BlocProvider(
create: (_) => sl<AlarmCubit>(),
child: const AlarmCenterPage(),
);
case 'workorder':
return const WorkOrderPage();
case 'report':
return const ReportPage();
case 'my':
return const MyPage();
case 'me':
return const my_v2.MyPage();
default:
return const HomePage();
}
}
@override
Widget build(BuildContext context) {
// 🔥 个人角色(roleKey == 'personal')底部导航强制只显示「设备 + 我的」
final roleKey = context.watch<AppUserCubit>().state.user?.roleKey;
return BlocBuilder<TabConfigCubit, TabConfigState>(
builder: (context, state) {
if (state is! TabConfigLoaded) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
final config = state.config;
final enabledItems = config.navItemsForRole(roleKey);
if (enabledItems.isEmpty) {
return const Scaffold(body: Center(child: Text('请至少启用一个 Tab')));
}
if (_currentIndex >= enabledItems.length) {
_currentIndex = 0;
}
return Scaffold(
extendBody: false,
// 底部导航预留区(SafeArea 那条)与页面同色,消除突兀的底色带
backgroundColor: context.appColors.pageBackground,
body: Stack(
children: [
PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
onPageChanged: (index) {
setState(() {
_currentIndex = index;
});
},
children: enabledItems
.map((tab) => _buildCurrentPage(tab))
.toList(),
),
// 悬浮条 - 可拖动的悬浮按钮
ValueListenableBuilder<bool>(
valueListenable: FloatBarSettingService.settingNotifier,
builder: (context, isEnabled, child) {
return Visibility(
visible: isEnabled,
child: const FloatBarWidget(),
);
},
),
],
),
bottomNavigationBar: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: LayoutBuilder(
builder: (context, constraints) {
final itemCount = enabledItems.length;
// Tab 较少时收缩整体宽度并居中,Tab 多时铺满可用宽度
const double preferredTabWidth = 84;
const double barPaddingH = 6;
final double maxBarWidth = constraints.maxWidth;
final double barWidth =
itemCount * preferredTabWidth < maxBarWidth
? itemCount * preferredTabWidth
: maxBarWidth;
final double tabWidth =
(barWidth - barPaddingH * 2) / itemCount;
// 选中高亮:包裹「图标 + 文字」的紧凑胶囊 chip
const double indicatorH = 46;
final double indicatorW =
tabWidth - 8 < 54 ? tabWidth - 8 : 54.0;
// 用 Align + heightFactor 只水平居中,高度贴合内容;
// 切勿用 Center:它在底部栏有界高度约束下会撑满全屏,把 body 挤没
return Align(
alignment: Alignment.center,
heightFactor: 1.0,
child: Container(
width: barWidth,
padding: const EdgeInsets.symmetric(
horizontal: barPaddingH,
vertical: 2,
),
decoration: BoxDecoration(
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: context.appColors.divider,
width: 1,
),
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: SizedBox(
height: 56,
child: Stack(
children: [
// 蓝色胶囊 chip:纯数学定位,居中包裹图标 + 文字
AnimatedPositioned(
duration: const Duration(milliseconds: 280),
curve: Curves.easeOutCubic,
left: _currentIndex * tabWidth +
(tabWidth - indicatorW) / 2,
top: (56 - indicatorH) / 2,
width: indicatorW,
height: indicatorH,
child: Container(
decoration: BoxDecoration(
color: context.appColors.primary.withOpacity(
0.12,
),
borderRadius: BorderRadius.circular(
indicatorH / 2,
),
),
),
),
// Tab 项
Row(
children:
enabledItems.asMap().entries.map((entry) {
final index = entry.key;
final item = entry.value;
final isSelected = _currentIndex == index;
return Expanded(
child: InkWell(
borderRadius: BorderRadius.circular(
indicatorH / 2,
),
onTap: () => _onTabChanged(index),
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
AnimatedScale(
scale: isSelected ? 1.1 : 1.0,
duration: const Duration(
milliseconds: 200,
),
curve: Curves.easeOutBack,
child: Icon(
_navIcon(item.icon, isSelected),
size: 22,
color: isSelected
? context.appColors.primary
: context
.appColors.textTertiary,
),
),
const SizedBox(height: 2),
AnimatedDefaultTextStyle(
duration: const Duration(
milliseconds: 200,
),
style: TextStyle(
fontSize: 11,
height: 1.0,
color: isSelected
? context.appColors.primary
: context
.appColors.textTertiary,
fontWeight: isSelected
? FontWeight.w600
: FontWeight.w400,
),
child: Text(item.name),
),
],
),
),
);
}).toList(),
),
],
),
),
),
);
},
),
),
),
);
},
);
}
}