app_shell.dart 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import 'package:flutter/material.dart';
  2. import 'package:go_router/go_router.dart';
  3. import 'package:asr_client/theme/app_colors.dart';
  4. class AppShell extends StatelessWidget {
  5. const AppShell({super.key, required this.navigationShell});
  6. final StatefulNavigationShell navigationShell;
  7. int get _currentIndex => navigationShell.currentIndex;
  8. void _onTap(BuildContext context, int index) {
  9. if (index == 2) {
  10. // FAB — navigate to recording page
  11. GoRouter.of(context).go('/recording');
  12. return;
  13. }
  14. // Adjust index: tab 0=tasks, 1=records, 2=FAB(skip), 3=reports→2, 4=profile→3
  15. final adjustedIndex = index > 2 ? index - 1 : index;
  16. navigationShell.goBranch(
  17. adjustedIndex,
  18. initialLocation: adjustedIndex == navigationShell.currentIndex,
  19. );
  20. }
  21. @override
  22. Widget build(BuildContext context) {
  23. return Scaffold(
  24. body: navigationShell,
  25. bottomNavigationBar: _BottomNavBar(
  26. currentIndex: _currentIndex,
  27. onTap: (index) => _onTap(context, index),
  28. ),
  29. );
  30. }
  31. }
  32. class _BottomNavBar extends StatelessWidget {
  33. const _BottomNavBar({required this.currentIndex, required this.onTap});
  34. final int currentIndex;
  35. final ValueChanged<int> onTap;
  36. @override
  37. Widget build(BuildContext context) {
  38. return Container(
  39. decoration: const BoxDecoration(
  40. color: AppColors.card,
  41. border: Border(top: BorderSide(color: AppColors.line)),
  42. ),
  43. padding: const EdgeInsets.only(bottom: 6),
  44. child: SizedBox(
  45. height: 64,
  46. child: Row(
  47. children: [
  48. _TabItem(
  49. icon: Icons.task_alt,
  50. label: '任务',
  51. isSelected: currentIndex == 0,
  52. onTap: () => onTap(0),
  53. ),
  54. _TabItem(
  55. icon: Icons.folder_open,
  56. label: '记录',
  57. isSelected: currentIndex == 1,
  58. onTap: () => onTap(1),
  59. ),
  60. _FabButton(onTap: () => onTap(2)),
  61. _TabItem(
  62. icon: Icons.bar_chart,
  63. label: '报告',
  64. isSelected: currentIndex == 3,
  65. onTap: () => onTap(3),
  66. ),
  67. _TabItem(
  68. icon: Icons.person,
  69. label: '我的',
  70. isSelected: currentIndex == 4,
  71. onTap: () => onTap(4),
  72. ),
  73. ],
  74. ),
  75. ),
  76. );
  77. }
  78. }
  79. class _TabItem extends StatelessWidget {
  80. const _TabItem({
  81. required this.icon,
  82. required this.label,
  83. required this.isSelected,
  84. required this.onTap,
  85. });
  86. final IconData icon;
  87. final String label;
  88. final bool isSelected;
  89. final VoidCallback onTap;
  90. @override
  91. Widget build(BuildContext context) {
  92. return Expanded(
  93. child: InkWell(
  94. onTap: onTap,
  95. child: Column(
  96. mainAxisAlignment: MainAxisAlignment.center,
  97. children: [
  98. Icon(
  99. icon,
  100. size: 20,
  101. color: isSelected ? AppColors.app : AppColors.ink3,
  102. ),
  103. const SizedBox(height: 3),
  104. Text(
  105. label,
  106. style: TextStyle(
  107. fontSize: 10.5,
  108. fontWeight: FontWeight.w600,
  109. color: isSelected ? AppColors.app : AppColors.ink3,
  110. ),
  111. ),
  112. ],
  113. ),
  114. ),
  115. );
  116. }
  117. }
  118. class _FabButton extends StatelessWidget {
  119. const _FabButton({required this.onTap});
  120. final VoidCallback onTap;
  121. @override
  122. Widget build(BuildContext context) {
  123. return Expanded(
  124. child: GestureDetector(
  125. onTap: onTap,
  126. child: Center(
  127. child: Transform.translate(
  128. offset: const Offset(0, -16),
  129. child: Container(
  130. width: 60,
  131. height: 60,
  132. decoration: BoxDecoration(
  133. shape: BoxShape.circle,
  134. gradient: const LinearGradient(
  135. colors: [AppColors.app, AppColors.appDark],
  136. begin: Alignment.topLeft,
  137. end: Alignment.bottomRight,
  138. ),
  139. border: Border.all(color: AppColors.card, width: 3),
  140. boxShadow: [
  141. BoxShadow(
  142. color: AppColors.app.withValues(alpha: 0.6),
  143. blurRadius: 18,
  144. offset: const Offset(0, 8),
  145. ),
  146. ],
  147. ),
  148. child: const Icon(Icons.add, color: Colors.white, size: 32),
  149. ),
  150. ),
  151. ),
  152. ),
  153. );
  154. }
  155. }