main_menu.dart 14 KB

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