game_screen.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. import 'package:flame/game.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/services.dart';
  4. import 'package:shake/shake.dart';
  5. import '../game/zentap_game.dart';
  6. import '../utils/colors.dart';
  7. import '../utils/settings_manager.dart';
  8. import '../utils/haptic_utils.dart';
  9. import 'components/animated_background.dart';
  10. class GameScreen extends StatefulWidget {
  11. final bool isZenMode;
  12. const GameScreen({
  13. super.key,
  14. required this.isZenMode,
  15. });
  16. @override
  17. State<GameScreen> createState() => _GameScreenState();
  18. }
  19. class _GameScreenState extends State<GameScreen> {
  20. late ZenTapGame game;
  21. bool _isPaused = false;
  22. ShakeDetector? _shakeDetector;
  23. final GlobalKey _backgroundKey = GlobalKey();
  24. @override
  25. void initState() {
  26. super.initState();
  27. game = ZenTapGame();
  28. game.setZenMode(widget.isZenMode);
  29. // Hide system UI for immersive experience
  30. SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
  31. }
  32. @override
  33. void didChangeDependencies() {
  34. super.didChangeDependencies();
  35. // Initialize shake detector only on mobile platforms
  36. _initializeShakeDetector();
  37. }
  38. void _initializeShakeDetector() async {
  39. if (_shakeDetector != null) return; // Already initialized
  40. try {
  41. // Only initialize shake detector on mobile platforms
  42. if (Theme.of(context).platform == TargetPlatform.android ||
  43. Theme.of(context).platform == TargetPlatform.iOS) {
  44. _shakeDetector = ShakeDetector.autoStart(
  45. onPhoneShake: (ShakeEvent event) => _onPhoneShake(),
  46. minimumShakeCount: 1,
  47. shakeSlopTimeMS: 500,
  48. shakeCountResetTime: 3000,
  49. shakeThresholdGravity: 2.7,
  50. );
  51. }
  52. } catch (e) {
  53. // Shake detection not supported on this platform
  54. print('Shake detection not supported: $e');
  55. }
  56. }
  57. @override
  58. void dispose() {
  59. try {
  60. _shakeDetector?.stopListening();
  61. } catch (e) {
  62. // Shake detector might not be initialized on desktop
  63. }
  64. // Restore system UI
  65. SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
  66. super.dispose();
  67. }
  68. void _onPhoneShake() async {
  69. if (!_isPaused) {
  70. // Trigger background shake animation
  71. final backgroundState = _backgroundKey.currentState;
  72. if (backgroundState != null) {
  73. (backgroundState as dynamic).triggerShake();
  74. }
  75. // Trigger game shake effects (spawn bubbles and shake existing ones)
  76. game.handleShake();
  77. // Haptic feedback
  78. if (SettingsManager.isHapticsEnabled) {
  79. await HapticUtils.vibrate(duration: 100);
  80. }
  81. }
  82. }
  83. @override
  84. Widget build(BuildContext context) {
  85. return Scaffold(
  86. backgroundColor: ZenColors.appBackground,
  87. body: OrientationBuilder(
  88. builder: (context, orientation) {
  89. return KeyboardListener(
  90. focusNode: FocusNode()..requestFocus(),
  91. onKeyEvent: (event) {
  92. // Add keyboard shortcut for shake simulation on desktop
  93. if (event.logicalKey == LogicalKeyboardKey.space && event is KeyDownEvent) {
  94. _onPhoneShake();
  95. }
  96. },
  97. child: Stack(
  98. children: [
  99. // Animated Background
  100. AnimatedBackground(
  101. key: _backgroundKey,
  102. isZenMode: widget.isZenMode,
  103. onShake: () {}, // Background handles its own shake animation
  104. child: Container(), // Empty container just for background
  105. ),
  106. // Game Widget with tap detection (transparent background)
  107. GestureDetector(
  108. onTapDown: (details) async {
  109. // Add haptic feedback on tap
  110. if (SettingsManager.isHapticsEnabled) {
  111. await HapticUtils.vibrate(duration: 30);
  112. }
  113. // Convert screen position to game position
  114. final position = Vector2(
  115. details.localPosition.dx,
  116. details.localPosition.dy,
  117. );
  118. game.handleTap(position);
  119. },
  120. child: GameWidget<ZenTapGame>.controlled(
  121. gameFactory: () => game,
  122. ),
  123. ),
  124. // Top UI Overlay - adaptive to orientation
  125. SafeArea(
  126. child: Padding(
  127. padding: const EdgeInsets.all(16.0),
  128. child: orientation == Orientation.portrait
  129. ? _buildPortraitUI()
  130. : _buildLandscapeUI(),
  131. ),
  132. ),
  133. // Pause Overlay
  134. if (_isPaused) _buildPauseOverlay(),
  135. ],
  136. ),
  137. );
  138. },
  139. ),
  140. );
  141. }
  142. Widget _buildPortraitUI() {
  143. return Row(
  144. children: [
  145. // Back Button
  146. IconButton(
  147. onPressed: _showExitDialog,
  148. icon: const Icon(
  149. Icons.arrow_back,
  150. color: ZenColors.primaryText,
  151. size: 28,
  152. ),
  153. style: IconButton.styleFrom(
  154. backgroundColor: ZenColors.black.withValues(alpha: 0.3),
  155. shape: const CircleBorder(),
  156. ),
  157. ),
  158. const Spacer(),
  159. // Mode Indicator
  160. Container(
  161. padding: const EdgeInsets.symmetric(
  162. horizontal: 16,
  163. vertical: 8,
  164. ),
  165. decoration: BoxDecoration(
  166. color: ZenColors.black.withValues(alpha: 0.3),
  167. borderRadius: BorderRadius.circular(20),
  168. ),
  169. child: Text(
  170. widget.isZenMode ? 'ZEN MODE' : 'PLAY MODE',
  171. style: TextStyle(
  172. color: ZenColors.primaryText,
  173. fontSize: 14,
  174. fontWeight: FontWeight.w600,
  175. letterSpacing: 1.0,
  176. ),
  177. ),
  178. ),
  179. const Spacer(),
  180. // Pause Button
  181. IconButton(
  182. onPressed: _togglePause,
  183. icon: Icon(
  184. _isPaused ? Icons.play_arrow : Icons.pause,
  185. color: ZenColors.primaryText,
  186. size: 28,
  187. ),
  188. style: IconButton.styleFrom(
  189. backgroundColor: ZenColors.black.withValues(alpha: 0.3),
  190. shape: const CircleBorder(),
  191. ),
  192. ),
  193. ],
  194. );
  195. }
  196. Widget _buildLandscapeUI() {
  197. return Row(
  198. children: [
  199. // Back Button
  200. IconButton(
  201. onPressed: _showExitDialog,
  202. icon: const Icon(
  203. Icons.arrow_back,
  204. color: ZenColors.primaryText,
  205. size: 24,
  206. ),
  207. style: IconButton.styleFrom(
  208. backgroundColor: ZenColors.black.withValues(alpha: 0.3),
  209. shape: const CircleBorder(),
  210. ),
  211. ),
  212. const SizedBox(width: 16),
  213. // Mode Indicator (smaller in landscape)
  214. Container(
  215. padding: const EdgeInsets.symmetric(
  216. horizontal: 12,
  217. vertical: 6,
  218. ),
  219. decoration: BoxDecoration(
  220. color: ZenColors.black.withValues(alpha: 0.3),
  221. borderRadius: BorderRadius.circular(16),
  222. ),
  223. child: Text(
  224. widget.isZenMode ? 'ZEN' : 'PLAY',
  225. style: TextStyle(
  226. color: ZenColors.primaryText,
  227. fontSize: 12,
  228. fontWeight: FontWeight.w600,
  229. letterSpacing: 1.0,
  230. ),
  231. ),
  232. ),
  233. const Spacer(),
  234. // Pause Button
  235. IconButton(
  236. onPressed: _togglePause,
  237. icon: Icon(
  238. _isPaused ? Icons.play_arrow : Icons.pause,
  239. color: ZenColors.primaryText,
  240. size: 24,
  241. ),
  242. style: IconButton.styleFrom(
  243. backgroundColor: ZenColors.black.withValues(alpha: 0.3),
  244. shape: const CircleBorder(),
  245. ),
  246. ),
  247. ],
  248. );
  249. }
  250. Widget _buildPauseOverlay() {
  251. return Container(
  252. color: ZenColors.black.withValues(alpha: 0.8),
  253. child: Center(
  254. child: Container(
  255. margin: const EdgeInsets.all(40),
  256. padding: const EdgeInsets.all(30),
  257. decoration: BoxDecoration(
  258. color: ZenColors.uiElements,
  259. borderRadius: BorderRadius.circular(20),
  260. ),
  261. child: Column(
  262. mainAxisSize: MainAxisSize.min,
  263. children: [
  264. const Text(
  265. 'Paused',
  266. style: TextStyle(
  267. color: ZenColors.primaryText,
  268. fontSize: 32,
  269. fontWeight: FontWeight.bold,
  270. ),
  271. ),
  272. const SizedBox(height: 20),
  273. Text(
  274. 'Take a moment to breathe',
  275. style: TextStyle(
  276. color: ZenColors.secondaryText,
  277. fontSize: 16,
  278. ),
  279. ),
  280. const SizedBox(height: 30),
  281. // Resume Button
  282. ElevatedButton(
  283. onPressed: _togglePause,
  284. style: ElevatedButton.styleFrom(
  285. backgroundColor: ZenColors.buttonBackground,
  286. foregroundColor: ZenColors.buttonText,
  287. padding: const EdgeInsets.symmetric(
  288. horizontal: 40,
  289. vertical: 15,
  290. ),
  291. shape: RoundedRectangleBorder(
  292. borderRadius: BorderRadius.circular(12),
  293. ),
  294. ),
  295. child: const Text(
  296. 'Resume',
  297. style: TextStyle(
  298. fontSize: 18,
  299. fontWeight: FontWeight.w600,
  300. ),
  301. ),
  302. ),
  303. ],
  304. ),
  305. ),
  306. ),
  307. );
  308. }
  309. void _togglePause() async {
  310. if (SettingsManager.isHapticsEnabled) {
  311. await HapticUtils.vibrate(duration: 50);
  312. }
  313. setState(() {
  314. _isPaused = !_isPaused;
  315. if (_isPaused) {
  316. game.pauseGame();
  317. } else {
  318. game.resumeGame();
  319. }
  320. });
  321. }
  322. void _showExitDialog() async {
  323. if (!mounted) return;
  324. if (SettingsManager.isHapticsEnabled) {
  325. await HapticUtils.vibrate(duration: 50);
  326. }
  327. showDialog(
  328. context: context,
  329. builder: (BuildContext context) {
  330. return AlertDialog(
  331. backgroundColor: ZenColors.uiElements,
  332. title: const Text(
  333. 'Leave Game?',
  334. style: TextStyle(color: ZenColors.primaryText),
  335. ),
  336. content: Text(
  337. 'Are you sure you want to return to the main menu?',
  338. style: TextStyle(color: ZenColors.secondaryText),
  339. ),
  340. actions: [
  341. TextButton(
  342. onPressed: () => Navigator.of(context).pop(),
  343. child: Text(
  344. 'Cancel',
  345. style: TextStyle(color: ZenColors.links),
  346. ),
  347. ),
  348. ElevatedButton(
  349. onPressed: () {
  350. Navigator.of(context).pop(); // Close dialog
  351. Navigator.of(context).pop(true); // Return to main menu with result
  352. },
  353. style: ElevatedButton.styleFrom(
  354. backgroundColor: ZenColors.red,
  355. foregroundColor: ZenColors.white,
  356. ),
  357. child: const Text('Leave'),
  358. ),
  359. ],
  360. );
  361. },
  362. );
  363. }
  364. }