diff options
Diffstat (limited to 'lib/settings.dart')
| -rw-r--r-- | lib/settings.dart | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/lib/settings.dart b/lib/settings.dart new file mode 100644 index 0000000..baaf0f4 --- /dev/null +++ b/lib/settings.dart @@ -0,0 +1,263 @@ +import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:speedometer/jotai.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +import 'main.dart'; + +const applicationName = "Simple Speedometer"; +const applicationVersion = "1.0.0"; +const applicationLegalese = r""" +Copyright (C) 2025 by Mica White <botahamec@outlook.com> + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +"""; + +class Option<T> { + final String text; + final T value; + + const Option({required this.text, required this.value}); +} + +class SectionHeader extends StatelessWidget { + final String title; + + const SectionHeader(this.title, {super.key}); + + @override + Widget build(BuildContext context) { + return ListTile( + titleTextStyle: TextStyle(color: Theme.of(context).colorScheme.primary), + title: Text(title), + ); + } +} + +class SelectAlert<T> extends StatefulWidget { + final String title; + final T initialValue; + final List<Option<T>> options; + final void Function(T value) onChanged; + + const SelectAlert({ + required this.title, + required this.initialValue, + required this.options, + required this.onChanged, + super.key, + }); + + @override + State<SelectAlert<T>> createState() => _SelectAlertState<T>(); +} + +class _SelectAlertState<T> extends State<SelectAlert<T>> { + late T _currentValue; + + _SelectAlertState(); + + @override + void initState() { + super.initState(); + _currentValue = widget.initialValue; + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(widget.title), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Divider(), + Flexible( + child: SingleChildScrollView( + child: RadioGroup<T>( + groupValue: _currentValue, + onChanged: (value) { + widget.onChanged(value as T); + setState(() => _currentValue = value); + }, + child: Column( + children: widget.options + .map( + (option) => RadioListTile( + title: Text(option.text), + value: option.value, + ), + ) + .toList(), + ), + ), + ), + ), + Divider(), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Confirm'), + ), + ], + ); + } +} + +class SelectTile<T> extends StatelessWidget { + final String title; + final T value; + final List<Option<T>> options; + final void Function(T value) onChanged; + + const SelectTile({ + required this.title, + required this.value, + required this.options, + required this.onChanged, + super.key, + }); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(title), + subtitle: Text( + options.firstWhere((option) => option.value == value).text, + ), + onTap: () => showDialog( + context: context, + builder: (context) => SelectAlert( + title: title, + initialValue: value, + options: options, + onChanged: onChanged, + ), + ), + ); + } +} + +class SettingsPage extends StatelessWidget { + const SettingsPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Settings')), + body: ListView( + children: [ + SectionHeader('Units'), + ObserverBuilder( + observable: speedUnitsObservable, + builder: (context, speedUnits, setSpeedUnits) => SelectTile( + title: 'Units', + value: speedUnits, + options: [ + Option(text: 'Automatic', value: null), + Option(text: 'Kilometers', value: SpeedUnit.kilometersPerHour), + Option(text: 'Miles', value: SpeedUnit.milesPerHour), + ], + onChanged: (value) => setSpeedUnits(value), + ), + ), + Divider(), + SectionHeader('Appearance'), + ObserverBuilder( + observable: themeModeObservable, + builder: (context, themeMode, setThemeMode) => SelectTile( + title: 'Theme', + value: themeMode, + options: [ + Option(text: 'System', value: ThemeMode.system), + Option(text: 'Light', value: ThemeMode.light), + Option(text: 'Dark', value: ThemeMode.dark), + ], + onChanged: (value) => setThemeMode(value), + ), + ), + ObserverBuilder( + observable: primaryColorObservable, + builder: (context, primaryColor, setPrimaryColor) => SelectTile( + title: 'Primary color', + value: primaryColor, + options: [ + Option(text: 'Red', value: Colors.red), + Option(text: 'Pink', value: Colors.pink), + Option(text: 'Indigo', value: Colors.indigo), + Option(text: 'Purple', value: Colors.purple), + Option(text: 'Deep purple', value: Colors.deepPurple), + Option(text: 'Blue', value: Colors.blue), + Option(text: 'Light blue', value: Colors.lightBlue), + Option(text: 'Cyan', value: Colors.cyan), + Option(text: 'Teal', value: Colors.teal), + Option(text: 'Green', value: Colors.green), + Option(text: 'Light green', value: Colors.lightGreen), + Option(text: 'Lime', value: Colors.lime), + Option(text: 'Yellow', value: Colors.yellow), + Option(text: 'Amber', value: Colors.amber), + Option(text: 'Orange', value: Colors.orange), + Option(text: 'Deep orange', value: Colors.deepOrange), + Option(text: 'Brown', value: Colors.brown), + Option(text: 'Gray', value: Colors.grey), + Option(text: 'Blue gray', value: Colors.blueGrey), + ], + onChanged: (value) => setPrimaryColor(value), + ), + ), + ObserverBuilder( + observable: showMarginOfErrorObservable, + builder: (context, showMargingOfError, setShowMarginOfError) => + SwitchListTile( + title: Text('Show margin of error'), + value: showMargingOfError, + onChanged: (value) => + setShowMarginOfError(!showMargingOfError), + ), + ), + Divider(), + SectionHeader('Performance'), + ObserverBuilder( + observable: locationAccuracyObservable, + builder: (context, locationAccuracy, setLocationAccuracy) => + SelectTile( + title: 'Accuracy', + value: locationAccuracy, + options: [ + Option(text: 'Best', value: LocationAccuracy.best), + Option(text: 'High', value: LocationAccuracy.high), + Option(text: 'Medium', value: LocationAccuracy.medium), + Option(text: 'Low', value: LocationAccuracy.low), + Option(text: 'Lowest', value: LocationAccuracy.lowest), + ], + onChanged: (value) => setLocationAccuracy(value), + ), + ), + Divider(), + SectionHeader('About'), + ListTile( + title: Text('About Simple Speedometer'), + onTap: () => showAdaptiveAboutDialog( + context: context, + applicationName: applicationName, + applicationVersion: applicationVersion, + applicationLegalese: applicationLegalese, + applicationIcon: Icon( + Icons.speed, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ListTile( + title: Text('View source code'), + onTap: () => + launchUrlString('https://www.botahamec.dev/cgit/speedometer'), + ), + ], + ), + ); + } +} |
