main_menu.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. import '../l10n/app_localizations.dart';
  4. import '../utils/colors.dart';
  5. import '../utils/haptic_utils.dart';
  6. import '../utils/settings_manager.dart';
  7. import '../utils/score_manager.dart';
  8. import '../utils/theme_notifier.dart';
  9. import '../game/audio/audio_manager.dart';
  10. import 'game_screen.dart';
  11. import 'settings_screen.dart';
  12. import 'stats_screen.dart';
  13. import 'components/animated_background.dart';
  14. import 'components/tutorial_overlay.dart';
  15. class MainMenu extends StatefulWidget {
  16. const MainMenu({super.key});
  17. @override
  18. State<MainMenu> createState() => _MainMenuState();
  19. }
  20. class _MainMenuState extends State<MainMenu> {
  21. bool _showTutorial = false;
  22. int _todayScore = 0;
  23. @override
  24. void initState() {
  25. super.initState();
  26. _checkTutorial();
  27. _initializeAndLoadScore();
  28. // Start menu music when entering main menu
  29. WidgetsBinding.instance.addPostFrameCallback((_) {
  30. AudioManager().playMenuMusic();
  31. });
  32. }
  33. @override
  34. void didChangeDependencies() {
  35. super.didChangeDependencies();
  36. // Refresh score when orientation changes or when returning from other screens
  37. _loadTodayScore();
  38. }
  39. void _initializeAndLoadScore() async {
  40. // Initialize score manager first
  41. await ScoreManager.initialize();
  42. _loadTodayScore();
  43. }
  44. void _loadTodayScore() {
  45. setState(() {
  46. _todayScore = ScoreManager.todayScore;
  47. });
  48. }
  49. void _checkTutorial() {
  50. WidgetsBinding.instance.addPostFrameCallback((_) {
  51. if (!SettingsManager.isTutorialShown) {
  52. setState(() {
  53. _showTutorial = true;
  54. });
  55. }
  56. });
  57. }
  58. @override
  59. Widget build(BuildContext context) {
  60. return ThemeAwareBuilder(
  61. builder: (context, theme) {
  62. return Scaffold(
  63. backgroundColor: ZenColors.currentAppBackground,
  64. body: OrientationBuilder(
  65. builder: (context, orientation) {
  66. return AnimatedBackground(
  67. child: Stack(
  68. children: [
  69. SafeArea(
  70. child: Padding(
  71. padding: const EdgeInsets.all(20.0),
  72. child:
  73. orientation == Orientation.portrait
  74. ? _buildPortraitLayout()
  75. : _buildLandscapeLayout(),
  76. ),
  77. ),
  78. // Tutorial overlay
  79. if (_showTutorial)
  80. TutorialOverlay(
  81. onComplete: () {
  82. setState(() {
  83. _showTutorial = false;
  84. });
  85. },
  86. ),
  87. ],
  88. ),
  89. );
  90. },
  91. ),
  92. );
  93. },
  94. );
  95. }
  96. Widget _buildPortraitLayout() {
  97. return Column(
  98. children: [
  99. // Header with settings button
  100. Row(
  101. mainAxisAlignment: MainAxisAlignment.end,
  102. children: [
  103. IconButton(
  104. onPressed: _openSettings,
  105. icon: Icon(
  106. Icons.settings,
  107. color: ZenColors.currentPrimaryText,
  108. size: 28,
  109. ),
  110. style: IconButton.styleFrom(
  111. backgroundColor: ZenColors.black.withValues(alpha: 0.3),
  112. shape: const CircleBorder(),
  113. ),
  114. ),
  115. ],
  116. ),
  117. // Main content
  118. Expanded(
  119. child: Column(
  120. mainAxisAlignment: MainAxisAlignment.center,
  121. children: [
  122. _buildGameTitle(),
  123. const SizedBox(height: 40),
  124. _buildTodayScore(),
  125. const SizedBox(height: 40),
  126. _buildMenuButtons(),
  127. const SizedBox(height: 40),
  128. _buildSettingsHint(),
  129. ],
  130. ),
  131. ),
  132. ],
  133. );
  134. }
  135. Widget _buildLandscapeLayout() {
  136. return Row(
  137. children: [
  138. // Left side - Title and score
  139. Expanded(
  140. flex: 2,
  141. child: Column(
  142. mainAxisAlignment: MainAxisAlignment.center,
  143. children: [
  144. _buildGameTitle(),
  145. const SizedBox(height: 20),
  146. _buildTodayScore(),
  147. const SizedBox(height: 20),
  148. _buildSettingsHint(),
  149. ],
  150. ),
  151. ),
  152. // Right side - Menu buttons and settings
  153. Expanded(
  154. flex: 3,
  155. child: Column(
  156. children: [
  157. // Settings button
  158. Row(
  159. mainAxisAlignment: MainAxisAlignment.end,
  160. children: [
  161. IconButton(
  162. onPressed: _openSettings,
  163. icon: Icon(
  164. Icons.settings,
  165. color: ZenColors.currentPrimaryText,
  166. size: 28,
  167. ),
  168. style: IconButton.styleFrom(
  169. backgroundColor: ZenColors.black.withValues(alpha: 0.3),
  170. shape: const CircleBorder(),
  171. ),
  172. ),
  173. ],
  174. ),
  175. // Menu buttons
  176. Expanded(
  177. child: SingleChildScrollView(
  178. child: Column(
  179. mainAxisAlignment: MainAxisAlignment.center,
  180. children: [_buildResponsiveMenuButtons()],
  181. ),
  182. ),
  183. ),
  184. ],
  185. ),
  186. ),
  187. ],
  188. );
  189. }
  190. Widget _buildGameTitle() {
  191. return Column(
  192. children: [
  193. // Game Title
  194. Text(
  195. 'ZenTap',
  196. style: TextStyle(
  197. color: ZenColors.currentPrimaryText,
  198. fontSize: 48,
  199. fontWeight: FontWeight.bold,
  200. letterSpacing: 2.0,
  201. ),
  202. ),
  203. const SizedBox(height: 10),
  204. // Subtitle
  205. Text(
  206. AppLocalizations.of(context)!.appSubtitle,
  207. style: TextStyle(
  208. color: ZenColors.currentSecondaryText,
  209. fontSize: 18,
  210. fontStyle: FontStyle.italic,
  211. ),
  212. ),
  213. ],
  214. );
  215. }
  216. Widget _buildTodayScore() {
  217. return Container(
  218. padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
  219. decoration: BoxDecoration(
  220. color: ZenColors.black.withValues(alpha: 0.3),
  221. borderRadius: BorderRadius.circular(15),
  222. border: Border.all(
  223. color: ZenColors.currentButtonBackground.withValues(alpha: 0.5),
  224. width: 1,
  225. ),
  226. ),
  227. child: Text(
  228. AppLocalizations.of(context)!.todayRelaxationPoints(_todayScore),
  229. style: TextStyle(
  230. color: ZenColors.currentPrimaryText,
  231. fontSize: 18,
  232. fontWeight: FontWeight.w600,
  233. ),
  234. ),
  235. );
  236. }
  237. Widget _buildMenuButtons() {
  238. final l10n = AppLocalizations.of(context)!;
  239. return Column(
  240. children: [
  241. // Play Button
  242. _buildMenuButton(
  243. context,
  244. l10n.play,
  245. l10n.playDescription,
  246. Icons.play_arrow,
  247. () => _navigateToGame(context, false),
  248. ),
  249. const SizedBox(height: 20),
  250. // Zen Mode Button
  251. _buildMenuButton(
  252. context,
  253. l10n.zenMode,
  254. l10n.zenModeDescription,
  255. Icons.self_improvement,
  256. () => _navigateToGame(context, true),
  257. ),
  258. const SizedBox(height: 20),
  259. // Stats Button
  260. _buildMenuButton(
  261. context,
  262. l10n.stats,
  263. l10n.statisticsDescription,
  264. Icons.analytics,
  265. _openStats,
  266. ),
  267. const SizedBox(height: 20),
  268. // Exit Button
  269. _buildExitButton(
  270. context,
  271. l10n.exit,
  272. l10n.exitDescription,
  273. Icons.exit_to_app,
  274. _exitApp,
  275. ),
  276. ],
  277. );
  278. }
  279. Widget _buildSettingsHint() {
  280. return Text(
  281. AppLocalizations.of(context)!.tapToFeelCalm,
  282. style: TextStyle(color: ZenColors.currentMutedText, fontSize: 14),
  283. );
  284. }
  285. Widget _buildResponsiveMenuButtons() {
  286. return OrientationBuilder(
  287. builder: (context, orientation) {
  288. final buttonSpacing = orientation == Orientation.portrait ? 20.0 : 12.0;
  289. final l10n = AppLocalizations.of(context)!;
  290. return Column(
  291. children: [
  292. // Play Button
  293. _buildMenuButton(
  294. context,
  295. l10n.play,
  296. l10n.playDescription,
  297. Icons.play_arrow,
  298. () => _navigateToGame(context, false),
  299. ),
  300. SizedBox(height: buttonSpacing),
  301. // Zen Mode Button
  302. _buildMenuButton(
  303. context,
  304. l10n.zenMode,
  305. l10n.zenModeDescription,
  306. Icons.self_improvement,
  307. () => _navigateToGame(context, true),
  308. ),
  309. SizedBox(height: buttonSpacing),
  310. // Stats Button
  311. _buildMenuButton(
  312. context,
  313. l10n.stats,
  314. l10n.statisticsDescription,
  315. Icons.analytics,
  316. _openStats,
  317. ),
  318. SizedBox(height: buttonSpacing),
  319. // Exit Button
  320. _buildExitButton(
  321. context,
  322. l10n.exit,
  323. l10n.exitDescription,
  324. Icons.exit_to_app,
  325. _exitApp,
  326. ),
  327. ],
  328. );
  329. },
  330. );
  331. }
  332. Widget _buildMenuButton(
  333. BuildContext context,
  334. String title,
  335. String subtitle,
  336. IconData icon,
  337. VoidCallback onPressed,
  338. ) {
  339. return Container(
  340. width: double.infinity,
  341. margin: const EdgeInsets.symmetric(horizontal: 20),
  342. child: ElevatedButton(
  343. onPressed: onPressed,
  344. style: ElevatedButton.styleFrom(
  345. backgroundColor: ZenColors.currentButtonBackground,
  346. foregroundColor: ZenColors.currentButtonText,
  347. padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 30),
  348. shape: RoundedRectangleBorder(
  349. borderRadius: BorderRadius.circular(15),
  350. ),
  351. elevation: 8,
  352. ),
  353. child: Row(
  354. children: [
  355. Icon(icon, size: 32, color: ZenColors.currentButtonText),
  356. const SizedBox(width: 20),
  357. Expanded(
  358. child: Column(
  359. crossAxisAlignment: CrossAxisAlignment.start,
  360. children: [
  361. Text(
  362. title,
  363. style: TextStyle(
  364. fontSize: 22,
  365. fontWeight: FontWeight.bold,
  366. color: ZenColors.currentButtonText,
  367. ),
  368. ),
  369. const SizedBox(height: 4),
  370. Text(
  371. subtitle,
  372. style: TextStyle(
  373. fontSize: 14,
  374. color: ZenColors.currentButtonText.withValues(alpha: 0.8),
  375. ),
  376. ),
  377. ],
  378. ),
  379. ),
  380. Icon(
  381. Icons.arrow_forward_ios,
  382. color: ZenColors.currentButtonText.withValues(alpha: 0.7),
  383. size: 20,
  384. ),
  385. ],
  386. ),
  387. ),
  388. );
  389. }
  390. Widget _buildExitButton(
  391. BuildContext context,
  392. String title,
  393. String subtitle,
  394. IconData icon,
  395. VoidCallback onPressed,
  396. ) {
  397. return Container(
  398. width: double.infinity,
  399. margin: const EdgeInsets.symmetric(horizontal: 20),
  400. child: ElevatedButton(
  401. onPressed: onPressed,
  402. style: ElevatedButton.styleFrom(
  403. backgroundColor: ZenColors.red.withValues(alpha: 0.8),
  404. foregroundColor: ZenColors.white,
  405. padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 30),
  406. shape: RoundedRectangleBorder(
  407. borderRadius: BorderRadius.circular(15),
  408. ),
  409. elevation: 8,
  410. ),
  411. child: Row(
  412. children: [
  413. Icon(icon, size: 32, color: ZenColors.white),
  414. const SizedBox(width: 20),
  415. Expanded(
  416. child: Column(
  417. crossAxisAlignment: CrossAxisAlignment.start,
  418. children: [
  419. Text(
  420. title,
  421. style: const TextStyle(
  422. fontSize: 22,
  423. fontWeight: FontWeight.bold,
  424. color: ZenColors.white,
  425. ),
  426. ),
  427. const SizedBox(height: 4),
  428. Text(
  429. subtitle,
  430. style: TextStyle(
  431. fontSize: 14,
  432. color: ZenColors.white.withValues(alpha: 0.8),
  433. ),
  434. ),
  435. ],
  436. ),
  437. ),
  438. Icon(
  439. Icons.arrow_forward_ios,
  440. color: ZenColors.white.withValues(alpha: 0.7),
  441. size: 20,
  442. ),
  443. ],
  444. ),
  445. ),
  446. );
  447. }
  448. void _exitApp() async {
  449. if (SettingsManager.isHapticsEnabled) {
  450. await HapticUtils.vibrate(duration: 50);
  451. }
  452. // Close the app
  453. SystemNavigator.pop();
  454. }
  455. void _openSettings() async {
  456. if (SettingsManager.isHapticsEnabled) {
  457. await HapticUtils.vibrate(duration: 50);
  458. }
  459. if (!mounted) return;
  460. Navigator.of(
  461. context,
  462. ).push(MaterialPageRoute(builder: (context) => const SettingsScreen()));
  463. }
  464. void _openStats() async {
  465. if (SettingsManager.isHapticsEnabled) {
  466. await HapticUtils.vibrate(duration: 50);
  467. }
  468. if (!mounted) return;
  469. final result = await Navigator.of(
  470. context,
  471. ).push(MaterialPageRoute(builder: (context) => const StatsScreen()));
  472. // Refresh score when returning from stats
  473. if (result == true) {
  474. _loadTodayScore();
  475. }
  476. }
  477. void _navigateToGame(BuildContext ctx, bool isZenMode) async {
  478. if (SettingsManager.isHapticsEnabled) {
  479. await HapticUtils.vibrate(duration: 50);
  480. }
  481. if (!mounted) return;
  482. final result = await Navigator.of(context).push(
  483. MaterialPageRoute(builder: (context) => GameScreen(isZenMode: isZenMode)),
  484. );
  485. // Refresh score when returning from game
  486. if (mounted && result == true) {
  487. _loadTodayScore();
  488. }
  489. }
  490. }