Files
feature-tenant/lib/features/main_container/presentation/main_wrapper.dart

283 lines
10 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: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/core/di/injection.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();
final List<GlobalKey> _tabKeys = [];
double? _circleLeft;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateCirclePosition();
});
}
void _updateCirclePosition() {
if (_tabKeys.isEmpty || _currentIndex >= _tabKeys.length) return;
final key = _tabKeys[_currentIndex];
final renderBox = key.currentContext?.findRenderObject() as RenderBox?;
if (renderBox != null) {
setState(() {
_circleLeft = renderBox.localToGlobal(Offset.zero).dx;
});
}
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
void _onTabChanged(int index) {
setState(() {
_currentIndex = index;
});
_pageController.jumpToPage(index);
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateCirclePosition();
});
}
IconData _getIconData(String iconName) {
switch (iconName) {
case 'home_rounded':
return Icons.home_rounded;
case 'grid_view_rounded':
return Icons.grid_view_rounded;
case 'devices_rounded':
return Icons.devices_rounded;
case 'auto_awesome_rounded':
return Icons.auto_awesome_rounded;
case 'warning_amber_rounded':
return Icons.warning_amber_rounded;
case 'assignment_rounded':
return Icons.assignment_rounded;
case 'post_add_rounded':
return Icons.post_add_rounded;
case 'person_rounded':
return Icons.person_rounded;
default:
return Icons.home_rounded;
}
}
double _getIndicatorPosition(int itemCount, int currentIndex) {
final screenWidth = MediaQuery.of(context).size.width;
final itemWidth = screenWidth / itemCount;
return currentIndex * itemWidth;
}
double _getCircularLeftPosition(int itemCount, int currentIndex) {
final screenWidth = MediaQuery.of(context).size.width;
final itemWidth = screenWidth / itemCount;
// 圆形应该在每个Tab项的中心
final tabCenterX = 12 + (currentIndex * itemWidth) + (itemWidth / 2);
final circularLeft = tabCenterX - 22; // 22 = 44/2,让圆形中心对齐Tab中心
// 边界限制
final maxLeft = screenWidth - 12 - 44;
return circularLeft.clamp(12.0, maxLeft);
}
Widget _buildCurrentPage(TabConfigItem tab) {
switch (tab.id) {
case 'home_v2':
return BlocProvider(
create: (context) => 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: (context) => 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) {
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.enabledItems;
if (enabledItems.isEmpty) {
return const Scaffold(
body: Center(
child: Text('请至少启用一个 Tab'),
),
);
}
if (_currentIndex >= enabledItems.length) {
_currentIndex = 0;
}
// 初始化 GlobalKey
if (_tabKeys.length != enabledItems.length) {
_tabKeys.clear();
for (int i = 0; i < enabledItems.length; i++) {
_tabKeys.add(GlobalKey());
}
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateCirclePosition();
});
}
return Scaffold(
extendBody: false,
body: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
onPageChanged: (index) {
setState(() {
_currentIndex = index;
});
},
children: enabledItems.map((tab) => _buildCurrentPage(tab)).toList(),
),
bottomNavigationBar: Container(
decoration: const BoxDecoration(
color: Colors.white,
),
child: SafeArea(
top: false,
child: LayoutBuilder(
builder: (context, constraints) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
padding: const EdgeInsets.symmetric(vertical: 2),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(25),
border: Border.all(
color: const Color(0xFFE5E6EB),
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Stack(
children: [
// 滑动圆形背景
if (_circleLeft != null)
AnimatedPositioned(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
left: _circleLeft! - 8, // 减去外层 margin + 向右偏移
top: 6,
child: Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: const Color(0xFFF2F3F5),
shape: BoxShape.circle,
border: Border.all(
color: const Color(0xFFE5E6EB),
width: 1,
),
),
),
),
// Tab 项
Row(
children: enabledItems.asMap().entries.map((entry) {
final index = entry.key;
final item = entry.value;
final isSelected = _currentIndex == index;
return Expanded(
key: _tabKeys[index],
child: InkWell(
onTap: () => _onTabChanged(index),
child: SizedBox(
height: 56,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
_getIconData(item.icon),
size: 24,
color: isSelected ? const Color(0xFF1D2129) : const Color(0xFF86909C),
),
const SizedBox(height: 4),
Text(
item.name,
style: TextStyle(
fontSize: 11,
color: isSelected ? const Color(0xFF1D2129) : const Color(0xFF86909C),
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
),
),
],
),
),
),
);
}).toList(),
),
],
),
);
},
),
),
),
);
},
);
}
}