import 'dart:io'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; import 'package:puzzleweave/audio/jc_audio_controller.dart'; import 'package:puzzleweave/l10n/app_localizations.dart'; import 'package:puzzleweave/settings/settings_controller.dart'; import 'package:puzzleweave/skin/skin.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:launch_review_latest/launch_review_latest.dart'; class SettingScreen extends StatefulWidget { const SettingScreen({super.key}); @override State createState() => _SettingScreenState(); static PageRouteBuilder buildRoute() { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) { return const SettingScreen(); }, transitionsBuilder: (context, animation, secondaryAnimation, child) { return SlideTransition( position: Tween(begin: const Offset(1, 0), end: Offset.zero).animate(animation), child: child, ); }, ); } } class _SettingScreenState extends State { List>? configs; @override void initState() { super.initState(); } @override didChangeDependencies() { super.didChangeDependencies(); final settings = context.watch(); configs = [ [ SettingItem( SettingItemType.toggle, Icons.volume_up_rounded, AppLocalizations.of(context)!.sound, onToggle: (val) { settings.toggleSound(); }, toggleValue: () => settings.sound.value, ), SettingItem( SettingItemType.toggle, Icons.audiotrack_rounded, AppLocalizations.of(context)!.backgroundMusic, onToggle: (val) { settings.toggleMusic(); }, toggleValue: () => settings.music.value, ), SettingItem( SettingItemType.toggle, Icons.vibration_rounded, AppLocalizations.of(context)!.touchFeedback, onToggle: (val) { settings.toggleVibrate(); }, toggleValue: () => settings.vibrate.value, ), ], [ SettingItem(SettingItemType.action, Icons.reviews_rounded, AppLocalizations.of(context)!.giveMeAReview, action: _launchReview), SettingItem(SettingItemType.action, Icons.pest_control_rounded, AppLocalizations.of(context)!.bugReport, action: _bugReportAction), SettingItem(SettingItemType.action, Icons.add_comment_rounded, AppLocalizations.of(context)!.featureRequest, action: _featureRequest), ], [ SettingItem( SettingItemType.action, Icons.info_rounded, AppLocalizations.of(context)!.termOfService, action: () { _linkLaunch('https://longreachai.net/game/terms_of_service.html'); }, ), SettingItem( SettingItemType.action, Icons.description_rounded, AppLocalizations.of(context)!.privacyPolicy, action: () { _linkLaunch('https://longreachai.net/game/privacy_policy.html'); }, ), ], ]; } Future _versionInfo() async { final pkgInfo = await PackageInfo.fromPlatform(); final output = { 'OS Version': '${Platform.operatingSystem} ${Platform.operatingSystemVersion} ', 'App Version': '${pkgInfo.version} ${pkgInfo.buildNumber}', }; return output.keys.map((e) => '$e: ${output[e]}').join("
"); } _bugReportAction() async { final infos = await _versionInfo(); Uri url = Uri(scheme: 'mailto', path: 'whippanyplex@outlook.com', query: 'subject=Bug Report - Jigsort Solitaire &body=
------------------
$infos'); if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { throw Exception('Could not launch $url'); } } _featureRequest() async { Uri url = Uri(scheme: 'mailto', path: 'whippanyplex@outlook.com', query: 'subject=Feature Request - Jigsort Solitaire'); if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { throw Exception('Could not launch $url'); } } _linkLaunch(String link) async { Uri url = Uri.parse(link); if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { throw Exception('Could not launch $url'); } } _launchReview() { LaunchReviewLatest.launch(androidAppId: 'jigsort.solitaire.jigsaw.match.games', iOSAppId: '6499138123'); } @override Widget build(BuildContext context) { final audio = context.read(); return Scaffold( backgroundColor: SkinHelper.colorWhite, appBar: AppBar( backgroundColor: SkinHelper.colorWhite, centerTitle: true, title: Text( AppLocalizations.of(context)!.setting, style: TextStyle(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 24), ), // elevation: 1, leading: IconButton( icon: const Icon(Icons.arrow_back_outlined, color: Colors.black87), onPressed: () { audio.playSfx(SfxType.click); Navigator.pop(context); }, ), ), body: SingleChildScrollView( scrollDirection: Axis.vertical, child: Padding( padding: const EdgeInsets.all(10), child: Column(children: _buildAll(configs!)), ), ), ); } List _buildAll(List> groups) { return groups.map((e) => _buildGroup(e)).toList(); } Widget _buildGroup(List list) { return Padding( padding: const EdgeInsets.only(top: 10), child: Material( elevation: 1, borderRadius: BorderRadius.circular(10), child: Padding( padding: const EdgeInsets.all(10), child: Column(children: list.map((e) => _buildItem(e)).toList()), ), ), ); } Widget _buildItem(SettingItem item) { final audio = context.read(); return Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon(item.icon, color: item.iconColor, size: 24), if (item.type == SettingItemType.toggle) Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), // child: Text(item.text, style: TextStyle(color: item.textColor, fontWeight: FontWeight.bold)), child: TextButton( onPressed: null, style: TextButton.styleFrom(padding: EdgeInsets.zero, alignment: Alignment.centerLeft), child: Text(item.text, style: TextStyle(color: item.textColor, fontSize: 16)), ), ), ), if (item.type == SettingItemType.dropdown) Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), child: TextButton( onPressed: null, style: TextButton.styleFrom(padding: EdgeInsets.zero, alignment: Alignment.centerLeft), child: Text(item.text, style: TextStyle(color: item.textColor, fontSize: 16)), ), ), ), if (item.type == SettingItemType.action) Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), child: SizedBox( width: double.infinity, child: TextButton( onPressed: () { audio.playSfx(SfxType.click); item.action?.call(); }, style: TextButton.styleFrom(padding: EdgeInsets.zero, alignment: Alignment.centerLeft), child: Text(item.text, style: TextStyle(color: item.textColor, fontSize: 16)), ), ), ), ), if (item.type == SettingItemType.toggle) Switch( activeTrackColor: SkinHelper.color5, inactiveTrackColor: Colors.white, inactiveThumbColor: SkinHelper.color4, value: item.toggleValue?.call() ?? false, onChanged: (value) { audio.playSfx(SfxType.click); item.onToggle?.call(value); setState(() {}); }, ), if (item.type == SettingItemType.dropdown) DropdownButton( value: item.dropdownValue?.call() ?? 0, items: (item.dropdownList?.call())!.map((e) => DropdownMenuItem(value: e, child: Text(e.toString()))).toList(), onChanged: (value) { audio.playSfx(SfxType.click); item.onDropdown?.call(value ?? 0); setState(() {}); }, ), ], ); } } enum SettingItemType { toggle, action, dropdown } typedef ToggleCallback = Function(bool val); typedef ToggleValueGetter = bool Function(); typedef DropdownCallback = Function(int val); typedef DropdownValueGetter = int Function(); typedef DropdownListGetter = List Function(); class SettingItem { final SettingItemType type; final IconData icon; final String text; final String? configKey; ToggleCallback? onToggle; ToggleValueGetter? toggleValue; VoidCallback? action; DropdownCallback? onDropdown; DropdownValueGetter? dropdownValue; DropdownListGetter? dropdownList; Color iconColor; Color textColor; SettingItem( this.type, this.icon, this.text, { this.configKey, this.iconColor = const Color.fromARGB(255, 8, 66, 9), this.textColor = Colors.black87, this.action, this.onToggle, this.toggleValue, this.onDropdown, this.dropdownList, this.dropdownValue, }); }