| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725 |
- import 'package:flutter/material.dart';
- import 'package:url_launcher/url_launcher.dart';
- import '../utils/colors.dart';
- import '../utils/haptic_utils.dart';
- import '../utils/settings_manager.dart';
- import 'components/animated_background.dart';
- import 'google_play_games_widget.dart';
- class SettingsScreen extends StatefulWidget {
- const SettingsScreen({super.key});
- @override
- State<SettingsScreen> createState() => _SettingsScreenState();
- }
- class _SettingsScreenState extends State<SettingsScreen> {
- bool _musicEnabled = true;
- bool _hapticsEnabled = true;
- double _bgmVolume = 0.4;
- double _sfxVolume = 0.6;
- @override
- void initState() {
- super.initState();
- _loadSettings();
- }
- Future<void> _loadSettings() async {
- setState(() {
- _musicEnabled = SettingsManager.isMusicEnabled;
- _hapticsEnabled = SettingsManager.isHapticsEnabled;
- _bgmVolume = SettingsManager.bgmVolume;
- _sfxVolume = SettingsManager.sfxVolume;
- });
- }
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- backgroundColor: ZenColors.appBackground,
- body: AnimatedBackground(
- child: SafeArea(
- child: Column(
- children: [
- // Header
- Padding(
- padding: const EdgeInsets.all(16.0),
- child: Row(
- children: [
- IconButton(
- onPressed: () => Navigator.of(context).pop(),
- icon: const Icon(
- Icons.arrow_back,
- color: ZenColors.primaryText,
- size: 28,
- ),
- style: IconButton.styleFrom(
- backgroundColor: ZenColors.black.withValues(alpha: 0.3),
- shape: const CircleBorder(),
- ),
- ),
- const SizedBox(width: 16),
- const Text(
- 'Settings',
- style: TextStyle(
- color: ZenColors.primaryText,
- fontSize: 28,
- fontWeight: FontWeight.bold,
- letterSpacing: 1.0,
- ),
- ),
- ],
- ),
- ),
-
- // Settings List
- Expanded(
- child: SingleChildScrollView(
- padding: const EdgeInsets.symmetric(horizontal: 20.0),
- child: Column(
- children: [
- const SizedBox(height: 20),
-
- // Audio Section
- _buildSectionHeader('Audio'),
- _buildSettingTile(
- icon: Icons.music_note,
- title: 'Background Music',
- subtitle: 'Enable relaxing ambient sounds',
- value: _musicEnabled,
- onChanged: _toggleMusic,
- ),
- _buildVolumeSlider(
- icon: Icons.volume_up,
- title: 'Music Volume',
- value: _bgmVolume,
- onChanged: _setBgmVolume,
- ),
-
- const SizedBox(height: 10),
-
- _buildVolumeSlider(
- icon: Icons.volume_up,
- title: 'Sound Effects Volume',
- value: _sfxVolume,
- onChanged: _setSfxVolume,
- ),
-
- const SizedBox(height: 30),
-
- // Feedback Section
- _buildSectionHeader('Feedback'),
- _buildSettingTile(
- icon: Icons.vibration,
- title: 'Haptic Feedback',
- subtitle: 'Feel gentle vibrations on tap',
- value: _hapticsEnabled,
- onChanged: _toggleHaptics,
- ),
-
- const SizedBox(height: 30),
-
- // Tutorial Section
- _buildSectionHeader('Help'),
- _buildActionTile(
- icon: Icons.help_outline,
- title: 'Show Tutorial',
- subtitle: 'Learn how to use ZenTap',
- onTap: _showTutorial,
- ),
-
- const SizedBox(height: 30),
-
- // Google Play Games Section
- _buildSectionHeader('Google Play Games'),
- const GooglePlayGamesWidget(),
-
- const SizedBox(height: 30),
-
- // Support Section
- _buildSectionHeader('Support ZenTap'),
- _buildDonationTile(),
-
- const SizedBox(height: 30),
-
- // About Section
- Padding(
- padding: const EdgeInsets.only(bottom: 20),
- child: Column(
- children: [
- Text(
- 'ZenTap v1.0.2',
- style: TextStyle(
- color: ZenColors.mutedText,
- fontSize: 14,
- ),
- ),
- const SizedBox(height: 8),
- Text(
- 'A stress relief tapping game',
- style: TextStyle(
- color: ZenColors.mutedText,
- fontSize: 12,
- fontStyle: FontStyle.italic,
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
- Widget _buildSectionHeader(String title) {
- return Align(
- alignment: Alignment.centerLeft,
- child: Text(
- title,
- style: TextStyle(
- color: ZenColors.secondaryText,
- fontSize: 16,
- fontWeight: FontWeight.w600,
- letterSpacing: 0.5,
- ),
- ),
- );
- }
- Widget _buildSettingTile({
- required IconData icon,
- required String title,
- required String subtitle,
- required bool value,
- required ValueChanged<bool> onChanged,
- }) {
- return Container(
- margin: const EdgeInsets.only(top: 12),
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(
- color: ZenColors.uiElements.withValues(alpha: 0.3),
- borderRadius: BorderRadius.circular(12),
- border: Border.all(
- color: ZenColors.uiElements.withValues(alpha: 0.2),
- width: 1,
- ),
- ),
- child: Row(
- children: [
- Icon(
- icon,
- color: ZenColors.primaryText,
- size: 24,
- ),
- const SizedBox(width: 16),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- title,
- style: const TextStyle(
- color: ZenColors.primaryText,
- fontSize: 16,
- fontWeight: FontWeight.w500,
- ),
- ),
- const SizedBox(height: 2),
- Text(
- subtitle,
- style: TextStyle(
- color: ZenColors.secondaryText,
- fontSize: 13,
- ),
- ),
- ],
- ),
- ),
- Switch(
- value: value,
- onChanged: onChanged,
- activeColor: ZenColors.buttonBackground,
- activeTrackColor: ZenColors.buttonBackground.withValues(alpha: 0.3),
- inactiveThumbColor: ZenColors.mutedText,
- inactiveTrackColor: ZenColors.mutedText.withValues(alpha: 0.2),
- ),
- ],
- ),
- );
- }
- Widget _buildActionTile({
- required IconData icon,
- required String title,
- required String subtitle,
- required VoidCallback onTap,
- }) {
- return Container(
- margin: const EdgeInsets.only(top: 12),
- child: Material(
- color: ZenColors.uiElements.withValues(alpha: 0.3),
- borderRadius: BorderRadius.circular(12),
- child: InkWell(
- onTap: onTap,
- borderRadius: BorderRadius.circular(12),
- child: Container(
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(12),
- border: Border.all(
- color: ZenColors.uiElements.withValues(alpha: 0.2),
- width: 1,
- ),
- ),
- child: Row(
- children: [
- Icon(
- icon,
- color: ZenColors.primaryText,
- size: 24,
- ),
- const SizedBox(width: 16),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- title,
- style: const TextStyle(
- color: ZenColors.primaryText,
- fontSize: 16,
- fontWeight: FontWeight.w500,
- ),
- ),
- const SizedBox(height: 2),
- Text(
- subtitle,
- style: TextStyle(
- color: ZenColors.secondaryText,
- fontSize: 13,
- ),
- ),
- ],
- ),
- ),
- Icon(
- Icons.arrow_forward_ios,
- color: ZenColors.mutedText,
- size: 16,
- ),
- ],
- ),
- ),
- ),
- ),
- );
- }
- Future<void> _toggleMusic(bool value) async {
- setState(() {
- _musicEnabled = value;
- });
- await SettingsManager.setMusicEnabled(value);
-
- if (_hapticsEnabled) {
- await HapticUtils.vibrate(duration: 50);
- }
- }
- Future<void> _setBgmVolume(double value) async {
- setState(() {
- _bgmVolume = value;
- });
- await SettingsManager.setBgmVolume(value);
- SettingsManager.applyBgmVolume();
- }
- Future<void> _setSfxVolume(double value) async {
- setState(() {
- _sfxVolume = value;
- });
- await SettingsManager.setSfxVolume(value);
- }
- Future<void> _toggleHaptics(bool value) async {
- setState(() {
- _hapticsEnabled = value;
- });
- await SettingsManager.setHapticsEnabled(value);
-
- // Give immediate feedback if enabling haptics
- if (value) {
- await HapticUtils.vibrate(duration: 100);
- }
- }
- void _showTutorial() {
- if (_hapticsEnabled) {
- HapticUtils.vibrate(duration: 50);
- }
-
- showDialog(
- context: context,
- builder: (context) => _buildTutorialDialog(),
- );
- }
- void _showDonationDialog() {
- if (_hapticsEnabled) {
- HapticUtils.vibrate(duration: 50);
- }
-
- showDialog(
- context: context,
- builder: (context) => _buildDonationDialog(),
- );
- }
- Future<void> _openDonationLink(String url) async {
- if (_hapticsEnabled) {
- HapticUtils.vibrate(duration: 30);
- }
-
- try {
- final Uri uri = Uri.parse(url);
- if (await canLaunchUrl(uri)) {
- await launchUrl(uri, mode: LaunchMode.externalApplication);
- Navigator.of(context).pop(); // Close dialog after opening link
- } else {
- // Show error if URL can't be launched
- if (mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: const Text('Could not open donation link'),
- backgroundColor: ZenColors.mutedText,
- behavior: SnackBarBehavior.floating,
- ),
- );
- }
- }
- } catch (e) {
- // Show error on exception
- if (mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: const Text('Error opening donation link'),
- backgroundColor: ZenColors.mutedText,
- behavior: SnackBarBehavior.floating,
- ),
- );
- }
- }
- }
- Widget _buildTutorialDialog() {
- return AlertDialog(
- backgroundColor: ZenColors.uiElements,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(20),
- ),
- title: const Text(
- 'How to Use ZenTap',
- style: TextStyle(
- color: ZenColors.primaryText,
- fontSize: 22,
- fontWeight: FontWeight.bold,
- ),
- ),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- _buildTutorialStep(
- icon: Icons.touch_app,
- text: 'Tap anywhere on the screen to pop bubbles',
- ),
- const SizedBox(height: 16),
- _buildTutorialStep(
- icon: Icons.stars,
- text: 'Earn Relaxation Points in Play mode',
- ),
- const SizedBox(height: 16),
- _buildTutorialStep(
- icon: Icons.self_improvement,
- text: 'Choose Zen Mode for pure relaxation',
- ),
- const SizedBox(height: 16),
- _buildTutorialStep(
- icon: Icons.pause,
- text: 'Tap pause anytime to take a break',
- ),
- ],
- ),
- actions: [
- ElevatedButton(
- onPressed: () => Navigator.of(context).pop(),
- style: ElevatedButton.styleFrom(
- backgroundColor: ZenColors.buttonBackground,
- foregroundColor: ZenColors.buttonText,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12),
- ),
- ),
- child: const Text('Got it!'),
- ),
- ],
- );
- }
- Widget _buildVolumeSlider({
- required IconData icon,
- required String title,
- required double value,
- required ValueChanged<double> onChanged,
- }) {
- return Container(
- margin: const EdgeInsets.only(top: 8),
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(
- color: ZenColors.uiElements.withValues(alpha: 0.3),
- borderRadius: BorderRadius.circular(12),
- border: Border.all(
- color: ZenColors.uiElements.withValues(alpha: 0.2),
- width: 1,
- ),
- ),
- child: Row(
- children: [
- Icon(
- icon,
- color: ZenColors.primaryText,
- size: 24,
- ),
- const SizedBox(width: 16),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- title,
- style: const TextStyle(
- color: ZenColors.primaryText,
- fontSize: 16,
- fontWeight: FontWeight.w500,
- ),
- ),
- const SizedBox(height: 8),
- Slider(
- value: value,
- onChanged: onChanged,
- min: 0.0,
- max: 1.0,
- divisions: 10,
- label: '${(value * 100).round()}%',
- activeColor: ZenColors.buttonBackground,
- inactiveColor: ZenColors.mutedText.withValues(alpha: 0.2),
- ),
- ],
- ),
- ),
- ],
- ),
- );
- }
- Widget _buildTutorialStep({
- required IconData icon,
- required String text,
- }) {
- return Row(
- children: [
- Icon(
- icon,
- color: ZenColors.buttonBackground,
- size: 20,
- ),
- const SizedBox(width: 12),
- Expanded(
- child: Text(
- text,
- style: TextStyle(
- color: ZenColors.secondaryText,
- fontSize: 14,
- ),
- ),
- ),
- ],
- );
- }
- Widget _buildDonationTile() {
- return _buildActionTile(
- icon: Icons.favorite,
- title: 'Support Development',
- subtitle: 'Help keep ZenTap free and ad-free',
- onTap: _showDonationDialog,
- );
- }
- Widget _buildDonationDialog() {
- return AlertDialog(
- backgroundColor: ZenColors.uiElements,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(20),
- ),
- title: Row(
- children: [
- Icon(
- Icons.favorite,
- color: ZenColors.red,
- size: 24,
- ),
- const SizedBox(width: 12),
- const Text(
- 'Support ZenTap',
- style: TextStyle(
- color: ZenColors.primaryText,
- fontSize: 22,
- fontWeight: FontWeight.bold,
- ),
- ),
- ],
- ),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- 'ZenTap is free and ad-free. If you enjoy using it, consider supporting development:',
- style: TextStyle(
- color: ZenColors.secondaryText,
- fontSize: 14,
- height: 1.4,
- ),
- ),
- const SizedBox(height: 20),
-
- // Ko-fi Button
- _buildDonationButton(
- icon: Icons.coffee,
- title: 'Buy me a coffee',
- subtitle: 'Ko-fi (one-time)',
- color: const Color(0xFF13C3FF),
- onTap: () => _openDonationLink('https://ko-fi.com/fsociety_hu'),
- ),
-
- const SizedBox(height: 12),
-
- // PayPal Button
- _buildDonationButton(
- icon: Icons.payment,
- title: 'PayPal Donation',
- subtitle: 'Direct donation',
- color: const Color(0xFF0070BA),
- onTap: () => _openDonationLink('https://paypal.me/fsocietyhu'),
- ),
-
- const SizedBox(height: 12),
-
- // GitHub Sponsors Button
- _buildDonationButton(
- icon: Icons.code,
- title: 'GitHub Sponsors',
- subtitle: 'Monthly support',
- color: const Color(0xFFEA4AAA),
- onTap: () => _openDonationLink('https://github.com/sponsors/fszontagh'),
- ),
-
- const SizedBox(height: 16),
-
- Text(
- 'Thank you for supporting indie development! 💜',
- style: TextStyle(
- color: ZenColors.secondaryText,
- fontSize: 12,
- fontStyle: FontStyle.italic,
- ),
- textAlign: TextAlign.center,
- ),
- ],
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.of(context).pop(),
- child: Text(
- 'Maybe later',
- style: TextStyle(
- color: ZenColors.mutedText,
- ),
- ),
- ),
- ],
- );
- }
- Widget _buildDonationButton({
- required IconData icon,
- required String title,
- required String subtitle,
- required Color color,
- required VoidCallback onTap,
- }) {
- return Material(
- color: color.withValues(alpha: 0.1),
- borderRadius: BorderRadius.circular(12),
- child: InkWell(
- onTap: onTap,
- borderRadius: BorderRadius.circular(12),
- child: Container(
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(12),
- border: Border.all(
- color: color.withValues(alpha: 0.3),
- width: 1,
- ),
- ),
- child: Row(
- children: [
- Icon(
- icon,
- color: color,
- size: 20,
- ),
- const SizedBox(width: 12),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- title,
- style: TextStyle(
- color: ZenColors.primaryText,
- fontSize: 14,
- fontWeight: FontWeight.w500,
- ),
- ),
- Text(
- subtitle,
- style: TextStyle(
- color: ZenColors.secondaryText,
- fontSize: 12,
- ),
- ),
- ],
- ),
- ),
- Icon(
- Icons.open_in_new,
- color: color,
- size: 16,
- ),
- ],
- ),
- ),
- ),
- );
- }
- }
|