settings_screen.dart 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189
  1. import 'package:flutter/foundation.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:url_launcher/url_launcher.dart';
  4. import '../l10n/app_localizations.dart';
  5. import '../utils/colors.dart';
  6. import '../utils/haptic_utils.dart';
  7. import '../utils/settings_manager.dart';
  8. import '../utils/locale_manager.dart';
  9. import '../utils/theme_manager.dart';
  10. import '../utils/theme_notifier.dart';
  11. import 'components/animated_background.dart';
  12. import 'google_play_games_widget.dart';
  13. import '../game/audio/audio_manager.dart';
  14. class SettingsScreen extends StatefulWidget {
  15. const SettingsScreen({super.key});
  16. @override
  17. State<SettingsScreen> createState() => _SettingsScreenState();
  18. }
  19. class _SettingsScreenState extends State<SettingsScreen> {
  20. bool _musicEnabled = true;
  21. bool _hapticsEnabled = true;
  22. double _bgmVolume = 0.4;
  23. double _sfxVolume = 0.6;
  24. @override
  25. void initState() {
  26. super.initState();
  27. _loadSettings();
  28. }
  29. Future<void> _loadSettings() async {
  30. setState(() {
  31. _musicEnabled = SettingsManager.isMusicEnabled;
  32. _hapticsEnabled = SettingsManager.isHapticsEnabled;
  33. _bgmVolume = SettingsManager.bgmVolume;
  34. _sfxVolume = SettingsManager.sfxVolume;
  35. });
  36. }
  37. @override
  38. Widget build(BuildContext context) {
  39. return ThemeAwareBuilder(
  40. builder: (context, theme) {
  41. return Scaffold(
  42. backgroundColor: ZenColors.currentAppBackground,
  43. body: AnimatedBackground(
  44. child: SafeArea(
  45. child: Column(
  46. children: [
  47. // Header
  48. Padding(
  49. padding: const EdgeInsets.all(16.0),
  50. child: Row(
  51. children: [
  52. IconButton(
  53. onPressed: () => Navigator.of(context).pop(),
  54. icon: Icon(
  55. Icons.arrow_back,
  56. color: ZenColors.currentPrimaryText,
  57. size: 28,
  58. ),
  59. style: IconButton.styleFrom(
  60. backgroundColor: ZenColors.black.withValues(
  61. alpha: 0.3,
  62. ),
  63. shape: const CircleBorder(),
  64. ),
  65. ),
  66. const SizedBox(width: 16),
  67. Text(
  68. AppLocalizations.of(context)!.settings,
  69. style: TextStyle(
  70. color: ZenColors.currentPrimaryText,
  71. fontSize: 28,
  72. fontWeight: FontWeight.bold,
  73. letterSpacing: 1.0,
  74. ),
  75. ),
  76. ],
  77. ),
  78. ),
  79. // Settings List
  80. Expanded(
  81. child: SingleChildScrollView(
  82. padding: const EdgeInsets.symmetric(horizontal: 20.0),
  83. child: Column(
  84. children: [
  85. const SizedBox(height: 20),
  86. // Audio Section
  87. _buildSectionHeader(
  88. AppLocalizations.of(context)!.audio,
  89. ),
  90. _buildSettingTile(
  91. icon: Icons.music_note,
  92. title:
  93. AppLocalizations.of(context)!.backgroundMusic,
  94. subtitle:
  95. AppLocalizations.of(
  96. context,
  97. )!.backgroundMusicDesc,
  98. value: _musicEnabled,
  99. onChanged: _toggleMusic,
  100. ),
  101. _buildVolumeSlider(
  102. icon: Icons.volume_up,
  103. title: AppLocalizations.of(context)!.musicVolume,
  104. value: _bgmVolume,
  105. onChanged: _setBgmVolume,
  106. ),
  107. const SizedBox(height: 10),
  108. _buildVolumeSlider(
  109. icon: Icons.volume_up,
  110. title:
  111. AppLocalizations.of(
  112. context,
  113. )!.soundEffectsVolume,
  114. value: _sfxVolume,
  115. onChanged: _setSfxVolume,
  116. ),
  117. const SizedBox(height: 30),
  118. // Feedback Section
  119. _buildSectionHeader(
  120. AppLocalizations.of(context)!.feedback,
  121. ),
  122. _buildSettingTile(
  123. icon: Icons.vibration,
  124. title: AppLocalizations.of(context)!.hapticFeedback,
  125. subtitle:
  126. AppLocalizations.of(
  127. context,
  128. )!.hapticFeedbackDesc,
  129. value: _hapticsEnabled,
  130. onChanged: _toggleHaptics,
  131. ),
  132. const SizedBox(height: 30),
  133. // Appearance Section
  134. _buildSectionHeader(
  135. AppLocalizations.of(context)!.appearance,
  136. ),
  137. _buildThemeTile(),
  138. const SizedBox(height: 30),
  139. // Language Section
  140. _buildSectionHeader(
  141. AppLocalizations.of(context)!.language,
  142. ),
  143. _buildLanguageTile(),
  144. const SizedBox(height: 30),
  145. // Tutorial Section
  146. _buildSectionHeader(
  147. AppLocalizations.of(context)!.help,
  148. ),
  149. _buildActionTile(
  150. icon: Icons.help_outline,
  151. title: AppLocalizations.of(context)!.showTutorial,
  152. subtitle:
  153. AppLocalizations.of(context)!.showTutorialDesc,
  154. onTap: _showTutorial,
  155. ),
  156. const SizedBox(height: 30),
  157. // Google Play Games Section
  158. _buildSectionHeader(
  159. AppLocalizations.of(context)!.googlePlayGames,
  160. ),
  161. const GooglePlayGamesWidget(),
  162. const SizedBox(height: 30),
  163. // Support Section - Hidden as requested
  164. // _buildSectionHeader(
  165. // AppLocalizations.of(context)!.supportZenTap,
  166. // ),
  167. // _buildDonationTile(),
  168. // const SizedBox(height: 30),
  169. // About Section
  170. Padding(
  171. padding: const EdgeInsets.only(bottom: 20),
  172. child: Column(
  173. children: [
  174. Text(
  175. 'ZenTap v1.0.5',
  176. style: TextStyle(
  177. color: ZenColors.currentMutedText,
  178. fontSize: 14,
  179. ),
  180. ),
  181. const SizedBox(height: 8),
  182. Text(
  183. AppLocalizations.of(
  184. context,
  185. )!.stressReliefGame,
  186. style: TextStyle(
  187. color: ZenColors.currentMutedText,
  188. fontSize: 12,
  189. fontStyle: FontStyle.italic,
  190. ),
  191. ),
  192. ],
  193. ),
  194. ),
  195. ],
  196. ),
  197. ),
  198. ),
  199. ],
  200. ),
  201. ),
  202. ),
  203. );
  204. },
  205. );
  206. }
  207. Widget _buildSectionHeader(String title) {
  208. return Align(
  209. alignment: Alignment.centerLeft,
  210. child: Text(
  211. title,
  212. style: TextStyle(
  213. color: ZenColors.currentSecondaryText,
  214. fontSize: 16,
  215. fontWeight: FontWeight.w600,
  216. letterSpacing: 0.5,
  217. ),
  218. ),
  219. );
  220. }
  221. Widget _buildSettingTile({
  222. required IconData icon,
  223. required String title,
  224. required String subtitle,
  225. required bool value,
  226. required ValueChanged<bool> onChanged,
  227. }) {
  228. return Container(
  229. margin: const EdgeInsets.only(top: 12),
  230. padding: const EdgeInsets.all(16),
  231. decoration: BoxDecoration(
  232. color: ZenColors.currentUiElements.withValues(alpha: 0.3),
  233. borderRadius: BorderRadius.circular(12),
  234. border: Border.all(
  235. color: ZenColors.currentUiElements.withValues(alpha: 0.2),
  236. width: 1,
  237. ),
  238. ),
  239. child: Row(
  240. children: [
  241. Icon(icon, color: ZenColors.currentPrimaryText, size: 24),
  242. const SizedBox(width: 16),
  243. Expanded(
  244. child: Column(
  245. crossAxisAlignment: CrossAxisAlignment.start,
  246. children: [
  247. Text(
  248. title,
  249. style: TextStyle(
  250. color: ZenColors.currentPrimaryText,
  251. fontSize: 16,
  252. fontWeight: FontWeight.w500,
  253. ),
  254. ),
  255. const SizedBox(height: 2),
  256. Text(
  257. subtitle,
  258. style: TextStyle(
  259. color: ZenColors.currentSecondaryText,
  260. fontSize: 13,
  261. ),
  262. ),
  263. ],
  264. ),
  265. ),
  266. Switch(
  267. value: value,
  268. onChanged: onChanged,
  269. activeColor: ZenColors.currentButtonBackground,
  270. activeTrackColor: ZenColors.currentButtonBackground.withValues(
  271. alpha: 0.3,
  272. ),
  273. inactiveThumbColor: ZenColors.currentMutedText,
  274. inactiveTrackColor: ZenColors.currentMutedText.withValues(
  275. alpha: 0.2,
  276. ),
  277. ),
  278. ],
  279. ),
  280. );
  281. }
  282. Widget _buildActionTile({
  283. required IconData icon,
  284. required String title,
  285. required String subtitle,
  286. required VoidCallback onTap,
  287. }) {
  288. return Container(
  289. margin: const EdgeInsets.only(top: 12),
  290. child: Material(
  291. color: ZenColors.currentUiElements.withValues(alpha: 0.3),
  292. borderRadius: BorderRadius.circular(12),
  293. child: InkWell(
  294. onTap: onTap,
  295. borderRadius: BorderRadius.circular(12),
  296. child: Container(
  297. padding: const EdgeInsets.all(16),
  298. decoration: BoxDecoration(
  299. borderRadius: BorderRadius.circular(12),
  300. border: Border.all(
  301. color: ZenColors.currentUiElements.withValues(alpha: 0.2),
  302. width: 1,
  303. ),
  304. ),
  305. child: Row(
  306. children: [
  307. Icon(icon, color: ZenColors.currentPrimaryText, size: 24),
  308. const SizedBox(width: 16),
  309. Expanded(
  310. child: Column(
  311. crossAxisAlignment: CrossAxisAlignment.start,
  312. children: [
  313. Text(
  314. title,
  315. style: TextStyle(
  316. color: ZenColors.currentPrimaryText,
  317. fontSize: 16,
  318. fontWeight: FontWeight.w500,
  319. ),
  320. ),
  321. const SizedBox(height: 2),
  322. Text(
  323. subtitle,
  324. style: TextStyle(
  325. color: ZenColors.currentSecondaryText,
  326. fontSize: 13,
  327. ),
  328. ),
  329. ],
  330. ),
  331. ),
  332. Icon(
  333. Icons.arrow_forward_ios,
  334. color: ZenColors.currentMutedText,
  335. size: 16,
  336. ),
  337. ],
  338. ),
  339. ),
  340. ),
  341. ),
  342. );
  343. }
  344. Widget _buildVolumeSlider({
  345. required IconData icon,
  346. required String title,
  347. required double value,
  348. required ValueChanged<double> onChanged,
  349. }) {
  350. return Container(
  351. margin: const EdgeInsets.only(top: 8),
  352. padding: const EdgeInsets.all(16),
  353. decoration: BoxDecoration(
  354. color: ZenColors.currentUiElements.withValues(alpha: 0.3),
  355. borderRadius: BorderRadius.circular(12),
  356. border: Border.all(
  357. color: ZenColors.currentUiElements.withValues(alpha: 0.2),
  358. width: 1,
  359. ),
  360. ),
  361. child: Row(
  362. children: [
  363. Icon(icon, color: ZenColors.currentPrimaryText, size: 24),
  364. const SizedBox(width: 16),
  365. Expanded(
  366. child: Column(
  367. crossAxisAlignment: CrossAxisAlignment.start,
  368. children: [
  369. Text(
  370. title,
  371. style: TextStyle(
  372. color: ZenColors.currentPrimaryText,
  373. fontSize: 16,
  374. fontWeight: FontWeight.w500,
  375. ),
  376. ),
  377. const SizedBox(height: 8),
  378. Slider(
  379. value: value,
  380. onChanged: onChanged,
  381. min: 0.0,
  382. max: 1.0,
  383. divisions: 10,
  384. label: '${(value * 100).round()}%',
  385. activeColor: ZenColors.currentButtonBackground,
  386. inactiveColor: ZenColors.currentMutedText.withValues(
  387. alpha: 0.2,
  388. ),
  389. ),
  390. ],
  391. ),
  392. ),
  393. ],
  394. ),
  395. );
  396. }
  397. Widget _buildThemeTile() {
  398. var currentTheme = ThemeManager.currentTheme;
  399. // In release builds, if default theme is selected, switch to automatic
  400. if (!kDebugMode && currentTheme == SeasonalTheme.default_) {
  401. currentTheme = SeasonalTheme.automatic;
  402. // Update the setting asynchronously
  403. WidgetsBinding.instance.addPostFrameCallback((_) {
  404. ThemeManager.setTheme(SeasonalTheme.automatic);
  405. });
  406. }
  407. final currentThemeName = _getLocalizedThemeName(currentTheme);
  408. return Container(
  409. margin: const EdgeInsets.only(top: 12),
  410. child: Material(
  411. color: ZenColors.currentUiElements.withValues(alpha: 0.3),
  412. borderRadius: BorderRadius.circular(12),
  413. child: InkWell(
  414. onTap: _showThemeDialog,
  415. borderRadius: BorderRadius.circular(12),
  416. child: Container(
  417. padding: const EdgeInsets.all(16),
  418. decoration: BoxDecoration(
  419. borderRadius: BorderRadius.circular(12),
  420. border: Border.all(
  421. color: ZenColors.currentUiElements.withValues(alpha: 0.2),
  422. width: 1,
  423. ),
  424. ),
  425. child: Row(
  426. children: [
  427. Icon(
  428. ThemeManager.getThemeIcon(currentTheme),
  429. color: ZenColors.currentPrimaryText,
  430. size: 24,
  431. ),
  432. const SizedBox(width: 16),
  433. Expanded(
  434. child: Column(
  435. crossAxisAlignment: CrossAxisAlignment.start,
  436. children: [
  437. Text(
  438. AppLocalizations.of(context)!.colorTheme,
  439. style: TextStyle(
  440. color: ZenColors.currentPrimaryText,
  441. fontSize: 16,
  442. fontWeight: FontWeight.w500,
  443. ),
  444. ),
  445. const SizedBox(height: 2),
  446. Text(
  447. currentThemeName,
  448. style: TextStyle(
  449. color: ZenColors.currentSecondaryText,
  450. fontSize: 13,
  451. ),
  452. ),
  453. ],
  454. ),
  455. ),
  456. Icon(
  457. Icons.arrow_forward_ios,
  458. color: ZenColors.currentMutedText,
  459. size: 16,
  460. ),
  461. ],
  462. ),
  463. ),
  464. ),
  465. ),
  466. );
  467. }
  468. Widget _buildLanguageTile() {
  469. final currentLocale = LocaleManager.currentLocale;
  470. final currentLanguageName = LocaleManager.getLocaleDisplayName(
  471. currentLocale,
  472. );
  473. return Container(
  474. margin: const EdgeInsets.only(top: 12),
  475. child: Material(
  476. color: ZenColors.currentUiElements.withValues(alpha: 0.3),
  477. borderRadius: BorderRadius.circular(12),
  478. child: InkWell(
  479. onTap: _showLanguageDialog,
  480. borderRadius: BorderRadius.circular(12),
  481. child: Container(
  482. padding: const EdgeInsets.all(16),
  483. decoration: BoxDecoration(
  484. borderRadius: BorderRadius.circular(12),
  485. border: Border.all(
  486. color: ZenColors.currentUiElements.withValues(alpha: 0.2),
  487. width: 1,
  488. ),
  489. ),
  490. child: Row(
  491. children: [
  492. Icon(
  493. Icons.language,
  494. color: ZenColors.currentPrimaryText,
  495. size: 24,
  496. ),
  497. const SizedBox(width: 16),
  498. Expanded(
  499. child: Column(
  500. crossAxisAlignment: CrossAxisAlignment.start,
  501. children: [
  502. Text(
  503. AppLocalizations.of(context)!.language,
  504. style: TextStyle(
  505. color: ZenColors.currentPrimaryText,
  506. fontSize: 16,
  507. fontWeight: FontWeight.w500,
  508. ),
  509. ),
  510. const SizedBox(height: 2),
  511. Text(
  512. currentLanguageName,
  513. style: TextStyle(
  514. color: ZenColors.currentSecondaryText,
  515. fontSize: 13,
  516. ),
  517. ),
  518. ],
  519. ),
  520. ),
  521. Icon(
  522. Icons.arrow_forward_ios,
  523. color: ZenColors.currentMutedText,
  524. size: 16,
  525. ),
  526. ],
  527. ),
  528. ),
  529. ),
  530. ),
  531. );
  532. }
  533. Widget _buildDonationTile() {
  534. return _buildActionTile(
  535. icon: Icons.favorite,
  536. title: AppLocalizations.of(context)!.supportDevelopment,
  537. subtitle: AppLocalizations.of(context)!.supportDevelopmentDesc,
  538. onTap: _showDonationDialog,
  539. );
  540. }
  541. // Event Handlers
  542. Future<void> _toggleMusic(bool value) async {
  543. setState(() {
  544. _musicEnabled = value;
  545. });
  546. await SettingsManager.setMusicEnabled(value);
  547. if (_hapticsEnabled) {
  548. await HapticUtils.vibrate(duration: 50);
  549. }
  550. }
  551. Future<void> _setBgmVolume(double value) async {
  552. setState(() {
  553. _bgmVolume = value;
  554. });
  555. await SettingsManager.setBgmVolume(value);
  556. SettingsManager.applyBgmVolume();
  557. }
  558. Future<void> _setSfxVolume(double value) async {
  559. setState(() {
  560. _sfxVolume = value;
  561. });
  562. await SettingsManager.setSfxVolume(value);
  563. SettingsManager.applySfxVolume();
  564. }
  565. Future<void> _toggleHaptics(bool value) async {
  566. setState(() {
  567. _hapticsEnabled = value;
  568. });
  569. await SettingsManager.setHapticsEnabled(value);
  570. // Give immediate feedback if enabling haptics
  571. if (value) {
  572. await HapticUtils.vibrate(duration: 100);
  573. }
  574. }
  575. void _showTutorial() {
  576. if (_hapticsEnabled) {
  577. HapticUtils.vibrate(duration: 50);
  578. }
  579. showDialog(context: context, builder: (context) => _buildTutorialDialog());
  580. }
  581. void _showDonationDialog() {
  582. if (_hapticsEnabled) {
  583. HapticUtils.vibrate(duration: 50);
  584. }
  585. showDialog(context: context, builder: (context) => _buildDonationDialog());
  586. }
  587. void _showLanguageDialog() {
  588. if (_hapticsEnabled) {
  589. HapticUtils.vibrate(duration: 50);
  590. }
  591. showDialog(context: context, builder: (context) => _buildLanguageDialog());
  592. }
  593. void _showThemeDialog() {
  594. if (_hapticsEnabled) {
  595. HapticUtils.vibrate(duration: 50);
  596. }
  597. showDialog(context: context, builder: (context) => _buildThemeDialog());
  598. }
  599. // Dialog Builders
  600. Widget _buildTutorialDialog() {
  601. return AlertDialog(
  602. backgroundColor: ZenColors.currentUiElements,
  603. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
  604. title: Text(
  605. AppLocalizations.of(context)!.howToUseZenTap,
  606. style: TextStyle(
  607. color: ZenColors.currentPrimaryText,
  608. fontSize: 22,
  609. fontWeight: FontWeight.bold,
  610. ),
  611. ),
  612. content: Column(
  613. mainAxisSize: MainAxisSize.min,
  614. crossAxisAlignment: CrossAxisAlignment.start,
  615. children: [
  616. _buildTutorialStep(
  617. icon: Icons.touch_app,
  618. text: AppLocalizations.of(context)!.tapToPopBubbles,
  619. ),
  620. const SizedBox(height: 16),
  621. _buildTutorialStep(
  622. icon: Icons.stars,
  623. text: AppLocalizations.of(context)!.earnRelaxationPoints,
  624. ),
  625. const SizedBox(height: 16),
  626. _buildTutorialStep(
  627. icon: Icons.self_improvement,
  628. text: AppLocalizations.of(context)!.chooseZenMode,
  629. ),
  630. const SizedBox(height: 16),
  631. _buildTutorialStep(
  632. icon: Icons.pause,
  633. text: AppLocalizations.of(context)!.tapPauseAnytime,
  634. ),
  635. ],
  636. ),
  637. actions: [
  638. ElevatedButton(
  639. onPressed: () => Navigator.of(context).pop(),
  640. style: ElevatedButton.styleFrom(
  641. backgroundColor: ZenColors.currentButtonBackground,
  642. foregroundColor: ZenColors.currentButtonText,
  643. shape: RoundedRectangleBorder(
  644. borderRadius: BorderRadius.circular(12),
  645. ),
  646. ),
  647. child: Text(AppLocalizations.of(context)!.gotIt),
  648. ),
  649. ],
  650. );
  651. }
  652. Widget _buildTutorialStep({required IconData icon, required String text}) {
  653. return Row(
  654. children: [
  655. Icon(icon, color: ZenColors.currentButtonBackground, size: 20),
  656. const SizedBox(width: 12),
  657. Expanded(
  658. child: Text(
  659. text,
  660. style: TextStyle(
  661. color: ZenColors.currentSecondaryText,
  662. fontSize: 14,
  663. ),
  664. ),
  665. ),
  666. ],
  667. );
  668. }
  669. Widget _buildLanguageDialog() {
  670. final currentLocale = LocaleManager.currentLocale;
  671. return AlertDialog(
  672. backgroundColor: ZenColors.currentUiElements,
  673. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
  674. title: Text(
  675. AppLocalizations.of(context)!.selectLanguage,
  676. style: TextStyle(
  677. color: ZenColors.currentPrimaryText,
  678. fontSize: 22,
  679. fontWeight: FontWeight.bold,
  680. ),
  681. ),
  682. content: Column(
  683. mainAxisSize: MainAxisSize.min,
  684. children:
  685. LocaleManager.supportedLocales.map((locale) {
  686. final isSelected =
  687. locale.languageCode == currentLocale.languageCode;
  688. final languageName = LocaleManager.getLocaleDisplayName(locale);
  689. return Container(
  690. margin: const EdgeInsets.only(bottom: 8),
  691. child: Material(
  692. color:
  693. isSelected
  694. ? ZenColors.currentButtonBackground.withValues(
  695. alpha: 0.1,
  696. )
  697. : Colors.transparent,
  698. borderRadius: BorderRadius.circular(12),
  699. child: InkWell(
  700. onTap: () => _selectLanguage(locale),
  701. borderRadius: BorderRadius.circular(12),
  702. child: Container(
  703. padding: const EdgeInsets.all(12),
  704. decoration: BoxDecoration(
  705. borderRadius: BorderRadius.circular(12),
  706. border: Border.all(
  707. color:
  708. isSelected
  709. ? ZenColors.currentButtonBackground
  710. .withValues(alpha: 0.3)
  711. : ZenColors.currentUiElements.withValues(
  712. alpha: 0.2,
  713. ),
  714. width: 1,
  715. ),
  716. ),
  717. child: Row(
  718. children: [
  719. Expanded(
  720. child: Text(
  721. languageName,
  722. style: TextStyle(
  723. color:
  724. isSelected
  725. ? ZenColors.currentButtonBackground
  726. : ZenColors.currentPrimaryText,
  727. fontSize: 16,
  728. fontWeight:
  729. isSelected
  730. ? FontWeight.w600
  731. : FontWeight.normal,
  732. ),
  733. ),
  734. ),
  735. if (isSelected)
  736. Icon(
  737. Icons.check,
  738. color: ZenColors.currentButtonBackground,
  739. size: 20,
  740. ),
  741. ],
  742. ),
  743. ),
  744. ),
  745. ),
  746. );
  747. }).toList(),
  748. ),
  749. actions: [
  750. TextButton(
  751. onPressed: () => Navigator.of(context).pop(),
  752. child: Text(
  753. AppLocalizations.of(context)!.cancel,
  754. style: TextStyle(color: ZenColors.currentMutedText),
  755. ),
  756. ),
  757. ],
  758. );
  759. }
  760. Widget _buildThemeDialog() {
  761. final currentTheme = ThemeManager.currentTheme;
  762. return AlertDialog(
  763. backgroundColor: ZenColors.currentUiElements,
  764. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
  765. title: Text(
  766. AppLocalizations.of(context)!.selectTheme,
  767. style: TextStyle(
  768. color: ZenColors.currentPrimaryText,
  769. fontSize: 22,
  770. fontWeight: FontWeight.bold,
  771. ),
  772. ),
  773. content: SizedBox(
  774. width: double.maxFinite,
  775. child: Column(
  776. mainAxisSize: MainAxisSize.min,
  777. children: [
  778. // Use a constraint container with max height for scrolling
  779. ConstrainedBox(
  780. constraints: BoxConstraints(
  781. maxHeight: MediaQuery.of(context).size.height * 0.5,
  782. ),
  783. child: SingleChildScrollView(
  784. child: Column(
  785. mainAxisSize: MainAxisSize.min,
  786. children:
  787. ThemeManager.availableThemes.map((theme) {
  788. final isSelected = theme == currentTheme;
  789. final themeName = _getLocalizedThemeName(theme);
  790. final themeIcon = ThemeManager.getThemeIcon(theme);
  791. return Container(
  792. margin: const EdgeInsets.only(bottom: 8),
  793. child: Material(
  794. color:
  795. isSelected
  796. ? ZenColors.currentButtonBackground
  797. .withValues(alpha: 0.1)
  798. : Colors.transparent,
  799. borderRadius: BorderRadius.circular(12),
  800. child: InkWell(
  801. onTap: () => _selectTheme(theme),
  802. borderRadius: BorderRadius.circular(12),
  803. child: Container(
  804. padding: const EdgeInsets.all(12),
  805. decoration: BoxDecoration(
  806. borderRadius: BorderRadius.circular(12),
  807. border: Border.all(
  808. color:
  809. isSelected
  810. ? ZenColors.currentButtonBackground
  811. .withValues(alpha: 0.3)
  812. : ZenColors.currentUiElements
  813. .withValues(alpha: 0.2),
  814. width: 1,
  815. ),
  816. ),
  817. child: Row(
  818. children: [
  819. Icon(
  820. themeIcon,
  821. color:
  822. isSelected
  823. ? ZenColors
  824. .currentButtonBackground
  825. : ZenColors.currentPrimaryText,
  826. size: 20,
  827. ),
  828. const SizedBox(width: 12),
  829. Expanded(
  830. child: Text(
  831. themeName,
  832. style: TextStyle(
  833. color:
  834. isSelected
  835. ? ZenColors
  836. .currentButtonBackground
  837. : ZenColors
  838. .currentPrimaryText,
  839. fontSize: 16,
  840. fontWeight:
  841. isSelected
  842. ? FontWeight.w600
  843. : FontWeight.normal,
  844. ),
  845. ),
  846. ),
  847. if (isSelected)
  848. Icon(
  849. Icons.check,
  850. color:
  851. ZenColors.currentButtonBackground,
  852. size: 20,
  853. ),
  854. ],
  855. ),
  856. ),
  857. ),
  858. ),
  859. );
  860. }).toList(),
  861. ),
  862. ),
  863. ),
  864. ],
  865. ),
  866. ),
  867. actions: [
  868. TextButton(
  869. onPressed: () => Navigator.of(context).pop(),
  870. child: Text(
  871. AppLocalizations.of(context)!.cancel,
  872. style: TextStyle(color: ZenColors.currentMutedText),
  873. ),
  874. ),
  875. ],
  876. );
  877. }
  878. Widget _buildDonationDialog() {
  879. return AlertDialog(
  880. backgroundColor: ZenColors.currentUiElements,
  881. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
  882. title: Row(
  883. children: [
  884. Icon(Icons.favorite, color: ZenColors.red, size: 24),
  885. const SizedBox(width: 12),
  886. Text(
  887. AppLocalizations.of(context)!.supportZenTapTitle,
  888. style: TextStyle(
  889. color: ZenColors.currentPrimaryText,
  890. fontSize: 22,
  891. fontWeight: FontWeight.bold,
  892. ),
  893. ),
  894. ],
  895. ),
  896. content: Column(
  897. mainAxisSize: MainAxisSize.min,
  898. crossAxisAlignment: CrossAxisAlignment.start,
  899. children: [
  900. Text(
  901. AppLocalizations.of(context)!.supportMessage,
  902. style: TextStyle(
  903. color: ZenColors.currentSecondaryText,
  904. fontSize: 14,
  905. height: 1.4,
  906. ),
  907. ),
  908. const SizedBox(height: 20),
  909. // Ko-fi Button
  910. _buildDonationButton(
  911. icon: Icons.coffee,
  912. title: AppLocalizations.of(context)!.buyMeACoffee,
  913. subtitle: AppLocalizations.of(context)!.kofiOneTime,
  914. color: const Color(0xFF13C3FF),
  915. onTap: () => _openDonationLink('https://ko-fi.com/fsociety_hu'),
  916. ),
  917. const SizedBox(height: 12),
  918. // PayPal Button
  919. _buildDonationButton(
  920. icon: Icons.payment,
  921. title: AppLocalizations.of(context)!.paypalDonation,
  922. subtitle: AppLocalizations.of(context)!.directDonation,
  923. color: const Color(0xFF0070BA),
  924. onTap: () => _openDonationLink('https://paypal.me/fsocietyhu'),
  925. ),
  926. const SizedBox(height: 12),
  927. // GitHub Sponsors Button
  928. _buildDonationButton(
  929. icon: Icons.code,
  930. title: AppLocalizations.of(context)!.githubSponsors,
  931. subtitle: AppLocalizations.of(context)!.monthlySupport,
  932. color: const Color(0xFFEA4AAA),
  933. onTap:
  934. () =>
  935. _openDonationLink('https://github.com/sponsors/fszontagh'),
  936. ),
  937. const SizedBox(height: 16),
  938. Text(
  939. AppLocalizations.of(context)!.thankYouMessage,
  940. style: TextStyle(
  941. color: ZenColors.currentSecondaryText,
  942. fontSize: 12,
  943. fontStyle: FontStyle.italic,
  944. ),
  945. textAlign: TextAlign.center,
  946. ),
  947. ],
  948. ),
  949. actions: [
  950. TextButton(
  951. onPressed: () => Navigator.of(context).pop(),
  952. child: Text(
  953. AppLocalizations.of(context)!.maybeLater,
  954. style: TextStyle(color: ZenColors.currentMutedText),
  955. ),
  956. ),
  957. ],
  958. );
  959. }
  960. Widget _buildDonationButton({
  961. required IconData icon,
  962. required String title,
  963. required String subtitle,
  964. required Color color,
  965. required VoidCallback onTap,
  966. }) {
  967. return Material(
  968. color: color.withValues(alpha: 0.1),
  969. borderRadius: BorderRadius.circular(12),
  970. child: InkWell(
  971. onTap: onTap,
  972. borderRadius: BorderRadius.circular(12),
  973. child: Container(
  974. padding: const EdgeInsets.all(12),
  975. decoration: BoxDecoration(
  976. borderRadius: BorderRadius.circular(12),
  977. border: Border.all(color: color.withValues(alpha: 0.3), width: 1),
  978. ),
  979. child: Row(
  980. children: [
  981. Icon(icon, color: color, size: 20),
  982. const SizedBox(width: 12),
  983. Expanded(
  984. child: Column(
  985. crossAxisAlignment: CrossAxisAlignment.start,
  986. children: [
  987. Text(
  988. title,
  989. style: TextStyle(
  990. color: ZenColors.currentPrimaryText,
  991. fontSize: 14,
  992. fontWeight: FontWeight.w500,
  993. ),
  994. ),
  995. Text(
  996. subtitle,
  997. style: TextStyle(
  998. color: ZenColors.currentSecondaryText,
  999. fontSize: 12,
  1000. ),
  1001. ),
  1002. ],
  1003. ),
  1004. ),
  1005. Icon(Icons.open_in_new, color: color, size: 16),
  1006. ],
  1007. ),
  1008. ),
  1009. ),
  1010. );
  1011. }
  1012. // Helper Methods
  1013. String _getLocalizedThemeName(SeasonalTheme theme) {
  1014. final localizations = AppLocalizations.of(context)!;
  1015. switch (theme) {
  1016. case SeasonalTheme.automatic:
  1017. // Get the auto-detected season (not the effective theme)
  1018. final detectedSeason = ThemeManager.autoDetectedSeason;
  1019. String seasonName;
  1020. // Get the season name based on auto-detected season
  1021. switch (detectedSeason) {
  1022. case SeasonalTheme.spring:
  1023. seasonName = localizations.springTheme;
  1024. break;
  1025. case SeasonalTheme.summer:
  1026. seasonName = localizations.summerTheme;
  1027. break;
  1028. case SeasonalTheme.autumn:
  1029. seasonName = localizations.autumnTheme;
  1030. break;
  1031. case SeasonalTheme.winter:
  1032. seasonName = localizations.winterTheme;
  1033. break;
  1034. default:
  1035. seasonName = localizations.defaultTheme;
  1036. break;
  1037. }
  1038. // Concatenate automatic with detected season
  1039. return '${localizations.automaticTheme} ($seasonName)';
  1040. case SeasonalTheme.default_:
  1041. return localizations.defaultTheme;
  1042. case SeasonalTheme.spring:
  1043. return localizations.springTheme;
  1044. case SeasonalTheme.summer:
  1045. return localizations.summerTheme;
  1046. case SeasonalTheme.autumn:
  1047. return localizations.autumnTheme;
  1048. case SeasonalTheme.winter:
  1049. return localizations.winterTheme;
  1050. }
  1051. }
  1052. Future<void> _selectLanguage(Locale locale) async {
  1053. if (_hapticsEnabled) {
  1054. HapticUtils.vibrate(duration: 30);
  1055. }
  1056. await LocaleManager.setLocale(locale);
  1057. if (mounted) {
  1058. Navigator.of(context).pop(); // Close language dialog
  1059. setState(() {}); // Refresh the settings screen to show new language
  1060. }
  1061. }
  1062. Future<void> _selectTheme(SeasonalTheme theme) async {
  1063. if (_hapticsEnabled) {
  1064. HapticUtils.vibrate(duration: 30);
  1065. }
  1066. await SettingsManager.setSelectedTheme(theme);
  1067. // Switch menu music to match new theme
  1068. AudioManager().playMenuMusic();
  1069. if (mounted) {
  1070. Navigator.of(context).pop(); // Close theme dialog
  1071. setState(() {}); // Refresh the settings screen to show new theme
  1072. }
  1073. }
  1074. Future<void> _openDonationLink(String url) async {
  1075. if (_hapticsEnabled) {
  1076. HapticUtils.vibrate(duration: 30);
  1077. }
  1078. try {
  1079. final Uri uri = Uri.parse(url);
  1080. if (await canLaunchUrl(uri)) {
  1081. await launchUrl(uri, mode: LaunchMode.externalApplication);
  1082. Navigator.of(context).pop(); // Close dialog after opening link
  1083. } else {
  1084. // Show error if URL can't be launched
  1085. if (mounted) {
  1086. ScaffoldMessenger.of(context).showSnackBar(
  1087. SnackBar(
  1088. content: Text(
  1089. AppLocalizations.of(context)!.couldNotOpenDonationLink,
  1090. ),
  1091. backgroundColor: ZenColors.currentMutedText,
  1092. behavior: SnackBarBehavior.floating,
  1093. ),
  1094. );
  1095. }
  1096. }
  1097. } catch (e) {
  1098. // Show error on exception
  1099. if (mounted) {
  1100. ScaffoldMessenger.of(context).showSnackBar(
  1101. SnackBar(
  1102. content: Text(
  1103. AppLocalizations.of(context)!.errorOpeningDonationLink,
  1104. ),
  1105. backgroundColor: ZenColors.currentMutedText,
  1106. behavior: SnackBarBehavior.floating,
  1107. ),
  1108. );
  1109. }
  1110. }
  1111. }
  1112. }