settings_screen.dart 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  1. import 'package:flutter/material.dart';
  2. import 'package:url_launcher/url_launcher.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/locale_manager.dart';
  8. import 'components/animated_background.dart';
  9. import 'google_play_games_widget.dart';
  10. class SettingsScreen extends StatefulWidget {
  11. const SettingsScreen({super.key});
  12. @override
  13. State<SettingsScreen> createState() => _SettingsScreenState();
  14. }
  15. class _SettingsScreenState extends State<SettingsScreen> {
  16. bool _musicEnabled = true;
  17. bool _hapticsEnabled = true;
  18. double _bgmVolume = 0.4;
  19. double _sfxVolume = 0.6;
  20. @override
  21. void initState() {
  22. super.initState();
  23. _loadSettings();
  24. }
  25. Future<void> _loadSettings() async {
  26. setState(() {
  27. _musicEnabled = SettingsManager.isMusicEnabled;
  28. _hapticsEnabled = SettingsManager.isHapticsEnabled;
  29. _bgmVolume = SettingsManager.bgmVolume;
  30. _sfxVolume = SettingsManager.sfxVolume;
  31. });
  32. }
  33. @override
  34. Widget build(BuildContext context) {
  35. return Scaffold(
  36. backgroundColor: ZenColors.appBackground,
  37. body: AnimatedBackground(
  38. child: SafeArea(
  39. child: Column(
  40. children: [
  41. // Header
  42. Padding(
  43. padding: const EdgeInsets.all(16.0),
  44. child: Row(
  45. children: [
  46. IconButton(
  47. onPressed: () => Navigator.of(context).pop(),
  48. icon: const Icon(
  49. Icons.arrow_back,
  50. color: ZenColors.primaryText,
  51. size: 28,
  52. ),
  53. style: IconButton.styleFrom(
  54. backgroundColor: ZenColors.black.withValues(alpha: 0.3),
  55. shape: const CircleBorder(),
  56. ),
  57. ),
  58. const SizedBox(width: 16),
  59. Text(
  60. AppLocalizations.of(context)!.settings,
  61. style: TextStyle(
  62. color: ZenColors.primaryText,
  63. fontSize: 28,
  64. fontWeight: FontWeight.bold,
  65. letterSpacing: 1.0,
  66. ),
  67. ),
  68. ],
  69. ),
  70. ),
  71. // Settings List
  72. Expanded(
  73. child: SingleChildScrollView(
  74. padding: const EdgeInsets.symmetric(horizontal: 20.0),
  75. child: Column(
  76. children: [
  77. const SizedBox(height: 20),
  78. // Audio Section
  79. _buildSectionHeader(AppLocalizations.of(context)!.audio),
  80. _buildSettingTile(
  81. icon: Icons.music_note,
  82. title: AppLocalizations.of(context)!.backgroundMusic,
  83. subtitle: AppLocalizations.of(context)!.backgroundMusicDesc,
  84. value: _musicEnabled,
  85. onChanged: _toggleMusic,
  86. ),
  87. _buildVolumeSlider(
  88. icon: Icons.volume_up,
  89. title: AppLocalizations.of(context)!.musicVolume,
  90. value: _bgmVolume,
  91. onChanged: _setBgmVolume,
  92. ),
  93. const SizedBox(height: 10),
  94. _buildVolumeSlider(
  95. icon: Icons.volume_up,
  96. title: AppLocalizations.of(context)!.soundEffectsVolume,
  97. value: _sfxVolume,
  98. onChanged: _setSfxVolume,
  99. ),
  100. const SizedBox(height: 30),
  101. // Feedback Section
  102. _buildSectionHeader(AppLocalizations.of(context)!.feedback),
  103. _buildSettingTile(
  104. icon: Icons.vibration,
  105. title: AppLocalizations.of(context)!.hapticFeedback,
  106. subtitle: AppLocalizations.of(context)!.hapticFeedbackDesc,
  107. value: _hapticsEnabled,
  108. onChanged: _toggleHaptics,
  109. ),
  110. const SizedBox(height: 30),
  111. // Language Section
  112. _buildSectionHeader(AppLocalizations.of(context)!.language),
  113. _buildLanguageTile(),
  114. const SizedBox(height: 30),
  115. // Tutorial Section
  116. _buildSectionHeader(AppLocalizations.of(context)!.help),
  117. _buildActionTile(
  118. icon: Icons.help_outline,
  119. title: AppLocalizations.of(context)!.showTutorial,
  120. subtitle: AppLocalizations.of(context)!.showTutorialDesc,
  121. onTap: _showTutorial,
  122. ),
  123. const SizedBox(height: 30),
  124. // Google Play Games Section
  125. _buildSectionHeader(AppLocalizations.of(context)!.googlePlayGames),
  126. const GooglePlayGamesWidget(),
  127. const SizedBox(height: 30),
  128. // Support Section
  129. _buildSectionHeader(AppLocalizations.of(context)!.supportZenTap),
  130. _buildDonationTile(),
  131. const SizedBox(height: 30),
  132. // About Section
  133. Padding(
  134. padding: const EdgeInsets.only(bottom: 20),
  135. child: Column(
  136. children: [
  137. Text(
  138. 'ZenTap v1.0.2',
  139. style: TextStyle(
  140. color: ZenColors.mutedText,
  141. fontSize: 14,
  142. ),
  143. ),
  144. const SizedBox(height: 8),
  145. Text(
  146. AppLocalizations.of(context)!.stressReliefGame,
  147. style: TextStyle(
  148. color: ZenColors.mutedText,
  149. fontSize: 12,
  150. fontStyle: FontStyle.italic,
  151. ),
  152. ),
  153. ],
  154. ),
  155. ),
  156. ],
  157. ),
  158. ),
  159. ),
  160. ],
  161. ),
  162. ),
  163. ),
  164. );
  165. }
  166. Widget _buildSectionHeader(String title) {
  167. return Align(
  168. alignment: Alignment.centerLeft,
  169. child: Text(
  170. title,
  171. style: TextStyle(
  172. color: ZenColors.secondaryText,
  173. fontSize: 16,
  174. fontWeight: FontWeight.w600,
  175. letterSpacing: 0.5,
  176. ),
  177. ),
  178. );
  179. }
  180. Widget _buildSettingTile({
  181. required IconData icon,
  182. required String title,
  183. required String subtitle,
  184. required bool value,
  185. required ValueChanged<bool> onChanged,
  186. }) {
  187. return Container(
  188. margin: const EdgeInsets.only(top: 12),
  189. padding: const EdgeInsets.all(16),
  190. decoration: BoxDecoration(
  191. color: ZenColors.uiElements.withValues(alpha: 0.3),
  192. borderRadius: BorderRadius.circular(12),
  193. border: Border.all(
  194. color: ZenColors.uiElements.withValues(alpha: 0.2),
  195. width: 1,
  196. ),
  197. ),
  198. child: Row(
  199. children: [
  200. Icon(
  201. icon,
  202. color: ZenColors.primaryText,
  203. size: 24,
  204. ),
  205. const SizedBox(width: 16),
  206. Expanded(
  207. child: Column(
  208. crossAxisAlignment: CrossAxisAlignment.start,
  209. children: [
  210. Text(
  211. title,
  212. style: const TextStyle(
  213. color: ZenColors.primaryText,
  214. fontSize: 16,
  215. fontWeight: FontWeight.w500,
  216. ),
  217. ),
  218. const SizedBox(height: 2),
  219. Text(
  220. subtitle,
  221. style: TextStyle(
  222. color: ZenColors.secondaryText,
  223. fontSize: 13,
  224. ),
  225. ),
  226. ],
  227. ),
  228. ),
  229. Switch(
  230. value: value,
  231. onChanged: onChanged,
  232. activeColor: ZenColors.buttonBackground,
  233. activeTrackColor: ZenColors.buttonBackground.withValues(alpha: 0.3),
  234. inactiveThumbColor: ZenColors.mutedText,
  235. inactiveTrackColor: ZenColors.mutedText.withValues(alpha: 0.2),
  236. ),
  237. ],
  238. ),
  239. );
  240. }
  241. Widget _buildActionTile({
  242. required IconData icon,
  243. required String title,
  244. required String subtitle,
  245. required VoidCallback onTap,
  246. }) {
  247. return Container(
  248. margin: const EdgeInsets.only(top: 12),
  249. child: Material(
  250. color: ZenColors.uiElements.withValues(alpha: 0.3),
  251. borderRadius: BorderRadius.circular(12),
  252. child: InkWell(
  253. onTap: onTap,
  254. borderRadius: BorderRadius.circular(12),
  255. child: Container(
  256. padding: const EdgeInsets.all(16),
  257. decoration: BoxDecoration(
  258. borderRadius: BorderRadius.circular(12),
  259. border: Border.all(
  260. color: ZenColors.uiElements.withValues(alpha: 0.2),
  261. width: 1,
  262. ),
  263. ),
  264. child: Row(
  265. children: [
  266. Icon(
  267. icon,
  268. color: ZenColors.primaryText,
  269. size: 24,
  270. ),
  271. const SizedBox(width: 16),
  272. Expanded(
  273. child: Column(
  274. crossAxisAlignment: CrossAxisAlignment.start,
  275. children: [
  276. Text(
  277. title,
  278. style: const TextStyle(
  279. color: ZenColors.primaryText,
  280. fontSize: 16,
  281. fontWeight: FontWeight.w500,
  282. ),
  283. ),
  284. const SizedBox(height: 2),
  285. Text(
  286. subtitle,
  287. style: TextStyle(
  288. color: ZenColors.secondaryText,
  289. fontSize: 13,
  290. ),
  291. ),
  292. ],
  293. ),
  294. ),
  295. Icon(
  296. Icons.arrow_forward_ios,
  297. color: ZenColors.mutedText,
  298. size: 16,
  299. ),
  300. ],
  301. ),
  302. ),
  303. ),
  304. ),
  305. );
  306. }
  307. Future<void> _toggleMusic(bool value) async {
  308. setState(() {
  309. _musicEnabled = value;
  310. });
  311. await SettingsManager.setMusicEnabled(value);
  312. if (_hapticsEnabled) {
  313. await HapticUtils.vibrate(duration: 50);
  314. }
  315. }
  316. Future<void> _setBgmVolume(double value) async {
  317. setState(() {
  318. _bgmVolume = value;
  319. });
  320. await SettingsManager.setBgmVolume(value);
  321. SettingsManager.applyBgmVolume();
  322. }
  323. Future<void> _setSfxVolume(double value) async {
  324. setState(() {
  325. _sfxVolume = value;
  326. });
  327. await SettingsManager.setSfxVolume(value);
  328. }
  329. Future<void> _toggleHaptics(bool value) async {
  330. setState(() {
  331. _hapticsEnabled = value;
  332. });
  333. await SettingsManager.setHapticsEnabled(value);
  334. // Give immediate feedback if enabling haptics
  335. if (value) {
  336. await HapticUtils.vibrate(duration: 100);
  337. }
  338. }
  339. void _showTutorial() {
  340. if (_hapticsEnabled) {
  341. HapticUtils.vibrate(duration: 50);
  342. }
  343. showDialog(
  344. context: context,
  345. builder: (context) => _buildTutorialDialog(),
  346. );
  347. }
  348. void _showDonationDialog() {
  349. if (_hapticsEnabled) {
  350. HapticUtils.vibrate(duration: 50);
  351. }
  352. showDialog(
  353. context: context,
  354. builder: (context) => _buildDonationDialog(),
  355. );
  356. }
  357. Future<void> _openDonationLink(String url) async {
  358. if (_hapticsEnabled) {
  359. HapticUtils.vibrate(duration: 30);
  360. }
  361. try {
  362. final Uri uri = Uri.parse(url);
  363. if (await canLaunchUrl(uri)) {
  364. await launchUrl(uri, mode: LaunchMode.externalApplication);
  365. Navigator.of(context).pop(); // Close dialog after opening link
  366. } else {
  367. // Show error if URL can't be launched
  368. if (mounted) {
  369. ScaffoldMessenger.of(context).showSnackBar(
  370. SnackBar(
  371. content: Text(AppLocalizations.of(context)!.couldNotOpenDonationLink),
  372. backgroundColor: ZenColors.mutedText,
  373. behavior: SnackBarBehavior.floating,
  374. ),
  375. );
  376. }
  377. }
  378. } catch (e) {
  379. // Show error on exception
  380. if (mounted) {
  381. ScaffoldMessenger.of(context).showSnackBar(
  382. SnackBar(
  383. content: Text(AppLocalizations.of(context)!.errorOpeningDonationLink),
  384. backgroundColor: ZenColors.mutedText,
  385. behavior: SnackBarBehavior.floating,
  386. ),
  387. );
  388. }
  389. }
  390. }
  391. Widget _buildTutorialDialog() {
  392. return AlertDialog(
  393. backgroundColor: ZenColors.uiElements,
  394. shape: RoundedRectangleBorder(
  395. borderRadius: BorderRadius.circular(20),
  396. ),
  397. title: Text(
  398. AppLocalizations.of(context)!.howToUseZenTap,
  399. style: TextStyle(
  400. color: ZenColors.primaryText,
  401. fontSize: 22,
  402. fontWeight: FontWeight.bold,
  403. ),
  404. ),
  405. content: Column(
  406. mainAxisSize: MainAxisSize.min,
  407. crossAxisAlignment: CrossAxisAlignment.start,
  408. children: [
  409. _buildTutorialStep(
  410. icon: Icons.touch_app,
  411. text: AppLocalizations.of(context)!.tapToPopBubbles,
  412. ),
  413. const SizedBox(height: 16),
  414. _buildTutorialStep(
  415. icon: Icons.stars,
  416. text: AppLocalizations.of(context)!.earnRelaxationPoints,
  417. ),
  418. const SizedBox(height: 16),
  419. _buildTutorialStep(
  420. icon: Icons.self_improvement,
  421. text: AppLocalizations.of(context)!.chooseZenMode,
  422. ),
  423. const SizedBox(height: 16),
  424. _buildTutorialStep(
  425. icon: Icons.pause,
  426. text: AppLocalizations.of(context)!.tapPauseAnytime,
  427. ),
  428. ],
  429. ),
  430. actions: [
  431. ElevatedButton(
  432. onPressed: () => Navigator.of(context).pop(),
  433. style: ElevatedButton.styleFrom(
  434. backgroundColor: ZenColors.buttonBackground,
  435. foregroundColor: ZenColors.buttonText,
  436. shape: RoundedRectangleBorder(
  437. borderRadius: BorderRadius.circular(12),
  438. ),
  439. ),
  440. child: Text(AppLocalizations.of(context)!.gotIt),
  441. ),
  442. ],
  443. );
  444. }
  445. Widget _buildVolumeSlider({
  446. required IconData icon,
  447. required String title,
  448. required double value,
  449. required ValueChanged<double> onChanged,
  450. }) {
  451. return Container(
  452. margin: const EdgeInsets.only(top: 8),
  453. padding: const EdgeInsets.all(16),
  454. decoration: BoxDecoration(
  455. color: ZenColors.uiElements.withValues(alpha: 0.3),
  456. borderRadius: BorderRadius.circular(12),
  457. border: Border.all(
  458. color: ZenColors.uiElements.withValues(alpha: 0.2),
  459. width: 1,
  460. ),
  461. ),
  462. child: Row(
  463. children: [
  464. Icon(
  465. icon,
  466. color: ZenColors.primaryText,
  467. size: 24,
  468. ),
  469. const SizedBox(width: 16),
  470. Expanded(
  471. child: Column(
  472. crossAxisAlignment: CrossAxisAlignment.start,
  473. children: [
  474. Text(
  475. title,
  476. style: const TextStyle(
  477. color: ZenColors.primaryText,
  478. fontSize: 16,
  479. fontWeight: FontWeight.w500,
  480. ),
  481. ),
  482. const SizedBox(height: 8),
  483. Slider(
  484. value: value,
  485. onChanged: onChanged,
  486. min: 0.0,
  487. max: 1.0,
  488. divisions: 10,
  489. label: '${(value * 100).round()}%',
  490. activeColor: ZenColors.buttonBackground,
  491. inactiveColor: ZenColors.mutedText.withValues(alpha: 0.2),
  492. ),
  493. ],
  494. ),
  495. ),
  496. ],
  497. ),
  498. );
  499. }
  500. Widget _buildTutorialStep({
  501. required IconData icon,
  502. required String text,
  503. }) {
  504. return Row(
  505. children: [
  506. Icon(
  507. icon,
  508. color: ZenColors.buttonBackground,
  509. size: 20,
  510. ),
  511. const SizedBox(width: 12),
  512. Expanded(
  513. child: Text(
  514. text,
  515. style: TextStyle(
  516. color: ZenColors.secondaryText,
  517. fontSize: 14,
  518. ),
  519. ),
  520. ),
  521. ],
  522. );
  523. }
  524. Widget _buildDonationTile() {
  525. return _buildActionTile(
  526. icon: Icons.favorite,
  527. title: AppLocalizations.of(context)!.supportDevelopment,
  528. subtitle: AppLocalizations.of(context)!.supportDevelopmentDesc,
  529. onTap: _showDonationDialog,
  530. );
  531. }
  532. Widget _buildDonationDialog() {
  533. return AlertDialog(
  534. backgroundColor: ZenColors.uiElements,
  535. shape: RoundedRectangleBorder(
  536. borderRadius: BorderRadius.circular(20),
  537. ),
  538. title: Row(
  539. children: [
  540. Icon(
  541. Icons.favorite,
  542. color: ZenColors.red,
  543. size: 24,
  544. ),
  545. const SizedBox(width: 12),
  546. Text(
  547. AppLocalizations.of(context)!.supportZenTapTitle,
  548. style: TextStyle(
  549. color: ZenColors.primaryText,
  550. fontSize: 22,
  551. fontWeight: FontWeight.bold,
  552. ),
  553. ),
  554. ],
  555. ),
  556. content: Column(
  557. mainAxisSize: MainAxisSize.min,
  558. crossAxisAlignment: CrossAxisAlignment.start,
  559. children: [
  560. Text(
  561. AppLocalizations.of(context)!.supportMessage,
  562. style: TextStyle(
  563. color: ZenColors.secondaryText,
  564. fontSize: 14,
  565. height: 1.4,
  566. ),
  567. ),
  568. const SizedBox(height: 20),
  569. // Ko-fi Button
  570. _buildDonationButton(
  571. icon: Icons.coffee,
  572. title: AppLocalizations.of(context)!.buyMeACoffee,
  573. subtitle: AppLocalizations.of(context)!.kofiOneTime,
  574. color: const Color(0xFF13C3FF),
  575. onTap: () => _openDonationLink('https://ko-fi.com/fsociety_hu'),
  576. ),
  577. const SizedBox(height: 12),
  578. // PayPal Button
  579. _buildDonationButton(
  580. icon: Icons.payment,
  581. title: AppLocalizations.of(context)!.paypalDonation,
  582. subtitle: AppLocalizations.of(context)!.directDonation,
  583. color: const Color(0xFF0070BA),
  584. onTap: () => _openDonationLink('https://paypal.me/fsocietyhu'),
  585. ),
  586. const SizedBox(height: 12),
  587. // GitHub Sponsors Button
  588. _buildDonationButton(
  589. icon: Icons.code,
  590. title: AppLocalizations.of(context)!.githubSponsors,
  591. subtitle: AppLocalizations.of(context)!.monthlySupport,
  592. color: const Color(0xFFEA4AAA),
  593. onTap: () => _openDonationLink('https://github.com/sponsors/fszontagh'),
  594. ),
  595. const SizedBox(height: 16),
  596. Text(
  597. AppLocalizations.of(context)!.thankYouMessage,
  598. style: TextStyle(
  599. color: ZenColors.secondaryText,
  600. fontSize: 12,
  601. fontStyle: FontStyle.italic,
  602. ),
  603. textAlign: TextAlign.center,
  604. ),
  605. ],
  606. ),
  607. actions: [
  608. TextButton(
  609. onPressed: () => Navigator.of(context).pop(),
  610. child: Text(
  611. AppLocalizations.of(context)!.maybeLater,
  612. style: TextStyle(
  613. color: ZenColors.mutedText,
  614. ),
  615. ),
  616. ),
  617. ],
  618. );
  619. }
  620. Widget _buildDonationButton({
  621. required IconData icon,
  622. required String title,
  623. required String subtitle,
  624. required Color color,
  625. required VoidCallback onTap,
  626. }) {
  627. return Material(
  628. color: color.withValues(alpha: 0.1),
  629. borderRadius: BorderRadius.circular(12),
  630. child: InkWell(
  631. onTap: onTap,
  632. borderRadius: BorderRadius.circular(12),
  633. child: Container(
  634. padding: const EdgeInsets.all(12),
  635. decoration: BoxDecoration(
  636. borderRadius: BorderRadius.circular(12),
  637. border: Border.all(
  638. color: color.withValues(alpha: 0.3),
  639. width: 1,
  640. ),
  641. ),
  642. child: Row(
  643. children: [
  644. Icon(
  645. icon,
  646. color: color,
  647. size: 20,
  648. ),
  649. const SizedBox(width: 12),
  650. Expanded(
  651. child: Column(
  652. crossAxisAlignment: CrossAxisAlignment.start,
  653. children: [
  654. Text(
  655. title,
  656. style: TextStyle(
  657. color: ZenColors.primaryText,
  658. fontSize: 14,
  659. fontWeight: FontWeight.w500,
  660. ),
  661. ),
  662. Text(
  663. subtitle,
  664. style: TextStyle(
  665. color: ZenColors.secondaryText,
  666. fontSize: 12,
  667. ),
  668. ),
  669. ],
  670. ),
  671. ),
  672. Icon(
  673. Icons.open_in_new,
  674. color: color,
  675. size: 16,
  676. ),
  677. ],
  678. ),
  679. ),
  680. ),
  681. );
  682. }
  683. Widget _buildLanguageTile() {
  684. final currentLocale = LocaleManager.currentLocale;
  685. final currentLanguageName = LocaleManager.getLocaleDisplayName(currentLocale);
  686. return Container(
  687. margin: const EdgeInsets.only(top: 12),
  688. child: Material(
  689. color: ZenColors.uiElements.withValues(alpha: 0.3),
  690. borderRadius: BorderRadius.circular(12),
  691. child: InkWell(
  692. onTap: _showLanguageDialog,
  693. borderRadius: BorderRadius.circular(12),
  694. child: Container(
  695. padding: const EdgeInsets.all(16),
  696. decoration: BoxDecoration(
  697. borderRadius: BorderRadius.circular(12),
  698. border: Border.all(
  699. color: ZenColors.uiElements.withValues(alpha: 0.2),
  700. width: 1,
  701. ),
  702. ),
  703. child: Row(
  704. children: [
  705. const Icon(
  706. Icons.language,
  707. color: ZenColors.primaryText,
  708. size: 24,
  709. ),
  710. const SizedBox(width: 16),
  711. Expanded(
  712. child: Column(
  713. crossAxisAlignment: CrossAxisAlignment.start,
  714. children: [
  715. Text(
  716. AppLocalizations.of(context)!.language,
  717. style: TextStyle(
  718. color: ZenColors.primaryText,
  719. fontSize: 16,
  720. fontWeight: FontWeight.w500,
  721. ),
  722. ),
  723. const SizedBox(height: 2),
  724. Text(
  725. currentLanguageName,
  726. style: TextStyle(
  727. color: ZenColors.secondaryText,
  728. fontSize: 13,
  729. ),
  730. ),
  731. ],
  732. ),
  733. ),
  734. Icon(
  735. Icons.arrow_forward_ios,
  736. color: ZenColors.mutedText,
  737. size: 16,
  738. ),
  739. ],
  740. ),
  741. ),
  742. ),
  743. ),
  744. );
  745. }
  746. void _showLanguageDialog() {
  747. if (_hapticsEnabled) {
  748. HapticUtils.vibrate(duration: 50);
  749. }
  750. showDialog(
  751. context: context,
  752. builder: (context) => _buildLanguageDialog(),
  753. );
  754. }
  755. Widget _buildLanguageDialog() {
  756. final currentLocale = LocaleManager.currentLocale;
  757. return AlertDialog(
  758. backgroundColor: ZenColors.uiElements,
  759. shape: RoundedRectangleBorder(
  760. borderRadius: BorderRadius.circular(20),
  761. ),
  762. title: Text(
  763. AppLocalizations.of(context)!.selectLanguage,
  764. style: TextStyle(
  765. color: ZenColors.primaryText,
  766. fontSize: 22,
  767. fontWeight: FontWeight.bold,
  768. ),
  769. ),
  770. content: Column(
  771. mainAxisSize: MainAxisSize.min,
  772. children: LocaleManager.supportedLocales.map((locale) {
  773. final isSelected = locale.languageCode == currentLocale.languageCode;
  774. final languageName = LocaleManager.getLocaleDisplayName(locale);
  775. return Container(
  776. margin: const EdgeInsets.only(bottom: 8),
  777. child: Material(
  778. color: isSelected
  779. ? ZenColors.buttonBackground.withValues(alpha: 0.1)
  780. : Colors.transparent,
  781. borderRadius: BorderRadius.circular(12),
  782. child: InkWell(
  783. onTap: () => _selectLanguage(locale),
  784. borderRadius: BorderRadius.circular(12),
  785. child: Container(
  786. padding: const EdgeInsets.all(12),
  787. decoration: BoxDecoration(
  788. borderRadius: BorderRadius.circular(12),
  789. border: Border.all(
  790. color: isSelected
  791. ? ZenColors.buttonBackground.withValues(alpha: 0.3)
  792. : ZenColors.uiElements.withValues(alpha: 0.2),
  793. width: 1,
  794. ),
  795. ),
  796. child: Row(
  797. children: [
  798. Expanded(
  799. child: Text(
  800. languageName,
  801. style: TextStyle(
  802. color: isSelected ? ZenColors.buttonBackground : ZenColors.primaryText,
  803. fontSize: 16,
  804. fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
  805. ),
  806. ),
  807. ),
  808. if (isSelected)
  809. Icon(
  810. Icons.check,
  811. color: ZenColors.buttonBackground,
  812. size: 20,
  813. ),
  814. ],
  815. ),
  816. ),
  817. ),
  818. ),
  819. );
  820. }).toList(),
  821. ),
  822. actions: [
  823. TextButton(
  824. onPressed: () => Navigator.of(context).pop(),
  825. child: Text(
  826. AppLocalizations.of(context)!.cancel,
  827. style: TextStyle(
  828. color: ZenColors.mutedText,
  829. ),
  830. ),
  831. ),
  832. ],
  833. );
  834. }
  835. Future<void> _selectLanguage(Locale locale) async {
  836. if (_hapticsEnabled) {
  837. HapticUtils.vibrate(duration: 30);
  838. }
  839. await LocaleManager.setLocale(locale);
  840. if (mounted) {
  841. Navigator.of(context).pop(); // Close language dialog
  842. setState(() {}); // Refresh the settings screen to show new language
  843. }
  844. }
  845. }