settings_screen.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. import 'package:flutter/material.dart';
  2. import '../utils/colors.dart';
  3. import '../utils/haptic_utils.dart';
  4. import '../utils/settings_manager.dart';
  5. import 'components/animated_background.dart';
  6. import 'google_play_games_widget.dart';
  7. class SettingsScreen extends StatefulWidget {
  8. const SettingsScreen({super.key});
  9. @override
  10. State<SettingsScreen> createState() => _SettingsScreenState();
  11. }
  12. class _SettingsScreenState extends State<SettingsScreen> {
  13. bool _musicEnabled = true;
  14. bool _hapticsEnabled = true;
  15. double _bgmVolume = 0.4;
  16. double _sfxVolume = 0.6;
  17. @override
  18. void initState() {
  19. super.initState();
  20. _loadSettings();
  21. }
  22. Future<void> _loadSettings() async {
  23. setState(() {
  24. _musicEnabled = SettingsManager.isMusicEnabled;
  25. _hapticsEnabled = SettingsManager.isHapticsEnabled;
  26. _bgmVolume = SettingsManager.bgmVolume;
  27. _sfxVolume = SettingsManager.sfxVolume;
  28. });
  29. }
  30. @override
  31. Widget build(BuildContext context) {
  32. return Scaffold(
  33. backgroundColor: ZenColors.appBackground,
  34. body: AnimatedBackground(
  35. child: SafeArea(
  36. child: Column(
  37. children: [
  38. // Header
  39. Padding(
  40. padding: const EdgeInsets.all(16.0),
  41. child: Row(
  42. children: [
  43. IconButton(
  44. onPressed: () => Navigator.of(context).pop(),
  45. icon: const Icon(
  46. Icons.arrow_back,
  47. color: ZenColors.primaryText,
  48. size: 28,
  49. ),
  50. style: IconButton.styleFrom(
  51. backgroundColor: ZenColors.black.withValues(alpha: 0.3),
  52. shape: const CircleBorder(),
  53. ),
  54. ),
  55. const SizedBox(width: 16),
  56. const Text(
  57. 'Settings',
  58. style: TextStyle(
  59. color: ZenColors.primaryText,
  60. fontSize: 28,
  61. fontWeight: FontWeight.bold,
  62. letterSpacing: 1.0,
  63. ),
  64. ),
  65. ],
  66. ),
  67. ),
  68. // Settings List
  69. Expanded(
  70. child: SingleChildScrollView(
  71. padding: const EdgeInsets.symmetric(horizontal: 20.0),
  72. child: Column(
  73. children: [
  74. const SizedBox(height: 20),
  75. // Audio Section
  76. _buildSectionHeader('Audio'),
  77. _buildSettingTile(
  78. icon: Icons.music_note,
  79. title: 'Background Music',
  80. subtitle: 'Enable relaxing ambient sounds',
  81. value: _musicEnabled,
  82. onChanged: _toggleMusic,
  83. ),
  84. _buildVolumeSlider(
  85. icon: Icons.volume_up,
  86. title: 'Music Volume',
  87. value: _bgmVolume,
  88. onChanged: _setBgmVolume,
  89. ),
  90. const SizedBox(height: 10),
  91. _buildVolumeSlider(
  92. icon: Icons.volume_up,
  93. title: 'Sound Effects Volume',
  94. value: _sfxVolume,
  95. onChanged: _setSfxVolume,
  96. ),
  97. const SizedBox(height: 30),
  98. // Feedback Section
  99. _buildSectionHeader('Feedback'),
  100. _buildSettingTile(
  101. icon: Icons.vibration,
  102. title: 'Haptic Feedback',
  103. subtitle: 'Feel gentle vibrations on tap',
  104. value: _hapticsEnabled,
  105. onChanged: _toggleHaptics,
  106. ),
  107. const SizedBox(height: 30),
  108. // Tutorial Section
  109. _buildSectionHeader('Help'),
  110. _buildActionTile(
  111. icon: Icons.help_outline,
  112. title: 'Show Tutorial',
  113. subtitle: 'Learn how to use ZenTap',
  114. onTap: _showTutorial,
  115. ),
  116. const SizedBox(height: 30),
  117. // Google Play Games Section
  118. _buildSectionHeader('Google Play Games'),
  119. const GooglePlayGamesWidget(),
  120. const SizedBox(height: 30),
  121. // About Section
  122. Padding(
  123. padding: const EdgeInsets.only(bottom: 20),
  124. child: Column(
  125. children: [
  126. Text(
  127. 'ZenTap v1.0.1',
  128. style: TextStyle(
  129. color: ZenColors.mutedText,
  130. fontSize: 14,
  131. ),
  132. ),
  133. const SizedBox(height: 8),
  134. Text(
  135. 'A stress relief tapping game',
  136. style: TextStyle(
  137. color: ZenColors.mutedText,
  138. fontSize: 12,
  139. fontStyle: FontStyle.italic,
  140. ),
  141. ),
  142. ],
  143. ),
  144. ),
  145. ],
  146. ),
  147. ),
  148. ),
  149. ],
  150. ),
  151. ),
  152. ),
  153. );
  154. }
  155. Widget _buildSectionHeader(String title) {
  156. return Align(
  157. alignment: Alignment.centerLeft,
  158. child: Text(
  159. title,
  160. style: TextStyle(
  161. color: ZenColors.secondaryText,
  162. fontSize: 16,
  163. fontWeight: FontWeight.w600,
  164. letterSpacing: 0.5,
  165. ),
  166. ),
  167. );
  168. }
  169. Widget _buildSettingTile({
  170. required IconData icon,
  171. required String title,
  172. required String subtitle,
  173. required bool value,
  174. required ValueChanged<bool> onChanged,
  175. }) {
  176. return Container(
  177. margin: const EdgeInsets.only(top: 12),
  178. padding: const EdgeInsets.all(16),
  179. decoration: BoxDecoration(
  180. color: ZenColors.uiElements.withValues(alpha: 0.3),
  181. borderRadius: BorderRadius.circular(12),
  182. border: Border.all(
  183. color: ZenColors.uiElements.withValues(alpha: 0.2),
  184. width: 1,
  185. ),
  186. ),
  187. child: Row(
  188. children: [
  189. Icon(
  190. icon,
  191. color: ZenColors.primaryText,
  192. size: 24,
  193. ),
  194. const SizedBox(width: 16),
  195. Expanded(
  196. child: Column(
  197. crossAxisAlignment: CrossAxisAlignment.start,
  198. children: [
  199. Text(
  200. title,
  201. style: const TextStyle(
  202. color: ZenColors.primaryText,
  203. fontSize: 16,
  204. fontWeight: FontWeight.w500,
  205. ),
  206. ),
  207. const SizedBox(height: 2),
  208. Text(
  209. subtitle,
  210. style: TextStyle(
  211. color: ZenColors.secondaryText,
  212. fontSize: 13,
  213. ),
  214. ),
  215. ],
  216. ),
  217. ),
  218. Switch(
  219. value: value,
  220. onChanged: onChanged,
  221. activeColor: ZenColors.buttonBackground,
  222. activeTrackColor: ZenColors.buttonBackground.withValues(alpha: 0.3),
  223. inactiveThumbColor: ZenColors.mutedText,
  224. inactiveTrackColor: ZenColors.mutedText.withValues(alpha: 0.2),
  225. ),
  226. ],
  227. ),
  228. );
  229. }
  230. Widget _buildActionTile({
  231. required IconData icon,
  232. required String title,
  233. required String subtitle,
  234. required VoidCallback onTap,
  235. }) {
  236. return Container(
  237. margin: const EdgeInsets.only(top: 12),
  238. child: Material(
  239. color: ZenColors.uiElements.withValues(alpha: 0.3),
  240. borderRadius: BorderRadius.circular(12),
  241. child: InkWell(
  242. onTap: onTap,
  243. borderRadius: BorderRadius.circular(12),
  244. child: Container(
  245. padding: const EdgeInsets.all(16),
  246. decoration: BoxDecoration(
  247. borderRadius: BorderRadius.circular(12),
  248. border: Border.all(
  249. color: ZenColors.uiElements.withValues(alpha: 0.2),
  250. width: 1,
  251. ),
  252. ),
  253. child: Row(
  254. children: [
  255. Icon(
  256. icon,
  257. color: ZenColors.primaryText,
  258. size: 24,
  259. ),
  260. const SizedBox(width: 16),
  261. Expanded(
  262. child: Column(
  263. crossAxisAlignment: CrossAxisAlignment.start,
  264. children: [
  265. Text(
  266. title,
  267. style: const TextStyle(
  268. color: ZenColors.primaryText,
  269. fontSize: 16,
  270. fontWeight: FontWeight.w500,
  271. ),
  272. ),
  273. const SizedBox(height: 2),
  274. Text(
  275. subtitle,
  276. style: TextStyle(
  277. color: ZenColors.secondaryText,
  278. fontSize: 13,
  279. ),
  280. ),
  281. ],
  282. ),
  283. ),
  284. Icon(
  285. Icons.arrow_forward_ios,
  286. color: ZenColors.mutedText,
  287. size: 16,
  288. ),
  289. ],
  290. ),
  291. ),
  292. ),
  293. ),
  294. );
  295. }
  296. Future<void> _toggleMusic(bool value) async {
  297. setState(() {
  298. _musicEnabled = value;
  299. });
  300. await SettingsManager.setMusicEnabled(value);
  301. if (_hapticsEnabled) {
  302. await HapticUtils.vibrate(duration: 50);
  303. }
  304. }
  305. Future<void> _setBgmVolume(double value) async {
  306. setState(() {
  307. _bgmVolume = value;
  308. });
  309. await SettingsManager.setBgmVolume(value);
  310. SettingsManager.applyBgmVolume();
  311. }
  312. Future<void> _setSfxVolume(double value) async {
  313. setState(() {
  314. _sfxVolume = value;
  315. });
  316. await SettingsManager.setSfxVolume(value);
  317. }
  318. Future<void> _toggleHaptics(bool value) async {
  319. setState(() {
  320. _hapticsEnabled = value;
  321. });
  322. await SettingsManager.setHapticsEnabled(value);
  323. // Give immediate feedback if enabling haptics
  324. if (value) {
  325. await HapticUtils.vibrate(duration: 100);
  326. }
  327. }
  328. void _showTutorial() {
  329. if (_hapticsEnabled) {
  330. HapticUtils.vibrate(duration: 50);
  331. }
  332. showDialog(
  333. context: context,
  334. builder: (context) => _buildTutorialDialog(),
  335. );
  336. }
  337. Widget _buildTutorialDialog() {
  338. return AlertDialog(
  339. backgroundColor: ZenColors.uiElements,
  340. shape: RoundedRectangleBorder(
  341. borderRadius: BorderRadius.circular(20),
  342. ),
  343. title: const Text(
  344. 'How to Use ZenTap',
  345. style: TextStyle(
  346. color: ZenColors.primaryText,
  347. fontSize: 22,
  348. fontWeight: FontWeight.bold,
  349. ),
  350. ),
  351. content: Column(
  352. mainAxisSize: MainAxisSize.min,
  353. crossAxisAlignment: CrossAxisAlignment.start,
  354. children: [
  355. _buildTutorialStep(
  356. icon: Icons.touch_app,
  357. text: 'Tap anywhere on the screen to pop bubbles',
  358. ),
  359. const SizedBox(height: 16),
  360. _buildTutorialStep(
  361. icon: Icons.stars,
  362. text: 'Earn Relaxation Points in Play mode',
  363. ),
  364. const SizedBox(height: 16),
  365. _buildTutorialStep(
  366. icon: Icons.self_improvement,
  367. text: 'Choose Zen Mode for pure relaxation',
  368. ),
  369. const SizedBox(height: 16),
  370. _buildTutorialStep(
  371. icon: Icons.pause,
  372. text: 'Tap pause anytime to take a break',
  373. ),
  374. ],
  375. ),
  376. actions: [
  377. ElevatedButton(
  378. onPressed: () => Navigator.of(context).pop(),
  379. style: ElevatedButton.styleFrom(
  380. backgroundColor: ZenColors.buttonBackground,
  381. foregroundColor: ZenColors.buttonText,
  382. shape: RoundedRectangleBorder(
  383. borderRadius: BorderRadius.circular(12),
  384. ),
  385. ),
  386. child: const Text('Got it!'),
  387. ),
  388. ],
  389. );
  390. }
  391. Widget _buildVolumeSlider({
  392. required IconData icon,
  393. required String title,
  394. required double value,
  395. required ValueChanged<double> onChanged,
  396. }) {
  397. return Container(
  398. margin: const EdgeInsets.only(top: 8),
  399. padding: const EdgeInsets.all(16),
  400. decoration: BoxDecoration(
  401. color: ZenColors.uiElements.withValues(alpha: 0.3),
  402. borderRadius: BorderRadius.circular(12),
  403. border: Border.all(
  404. color: ZenColors.uiElements.withValues(alpha: 0.2),
  405. width: 1,
  406. ),
  407. ),
  408. child: Row(
  409. children: [
  410. Icon(
  411. icon,
  412. color: ZenColors.primaryText,
  413. size: 24,
  414. ),
  415. const SizedBox(width: 16),
  416. Expanded(
  417. child: Column(
  418. crossAxisAlignment: CrossAxisAlignment.start,
  419. children: [
  420. Text(
  421. title,
  422. style: const TextStyle(
  423. color: ZenColors.primaryText,
  424. fontSize: 16,
  425. fontWeight: FontWeight.w500,
  426. ),
  427. ),
  428. const SizedBox(height: 8),
  429. Slider(
  430. value: value,
  431. onChanged: onChanged,
  432. min: 0.0,
  433. max: 1.0,
  434. divisions: 10,
  435. label: '${(value * 100).round()}%',
  436. activeColor: ZenColors.buttonBackground,
  437. inactiveColor: ZenColors.mutedText.withValues(alpha: 0.2),
  438. ),
  439. ],
  440. ),
  441. ),
  442. ],
  443. ),
  444. );
  445. }
  446. Widget _buildTutorialStep({
  447. required IconData icon,
  448. required String text,
  449. }) {
  450. return Row(
  451. children: [
  452. Icon(
  453. icon,
  454. color: ZenColors.buttonBackground,
  455. size: 20,
  456. ),
  457. const SizedBox(width: 12),
  458. Expanded(
  459. child: Text(
  460. text,
  461. style: TextStyle(
  462. color: ZenColors.secondaryText,
  463. fontSize: 14,
  464. ),
  465. ),
  466. ),
  467. ],
  468. );
  469. }
  470. }