main_menu.dart 14 KB

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