main_menu.dart 14 KB

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