From 359d8e07ef5cb585fff13031d075d7c949135317 Mon Sep 17 00:00:00 2001 From: Mica White Date: Tue, 13 Jan 2026 22:51:59 -0500 Subject: Complete settings page --- lib/jotai.dart | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 lib/jotai.dart (limited to 'lib/jotai.dart') diff --git a/lib/jotai.dart b/lib/jotai.dart new file mode 100644 index 0000000..a415e13 --- /dev/null +++ b/lib/jotai.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:speedometer/main.dart'; + +String _defaultToString(dynamic value) => value.toString(); + +void Function(T) storageObserver( + String key, [ + String Function(T) toString = _defaultToString, +]) { + return (T value) => SharedPreferencesAsync().setString(key, toString(value)); +} + +Future fromString( + String key, + List options, [ + String Function(T) toString = _defaultToString, +]) { + return SharedPreferencesAsync() + .getString(key) + .then( + (value) => options.firstWhere((option) => value == toString(option)), + ); +} + +Observable observablePreference( + String key, + T defaultValue, + List options, [ + String Function(T) toString = _defaultToString, +]) => Observable( + defaultValue, + loadValue: fromString(key, options, toString), + observers: [storageObserver(key, toString)], +); + +final speedUnitsObservable = observablePreference('speedUnits', null, [ + ...SpeedUnit.values, + null, +]); +final themeModeObservable = observablePreference( + 'themeMode', + ThemeMode.system, + ThemeMode.values, +); +final primaryColorObservable = observablePreference( + "primaryColor", + Colors.red, + [...Colors.primaries, Colors.grey], + (color) => color.toARGB32().toString(), +); +final showMarginOfErrorObservable = observablePreference( + "showMarginOfError", + true, + [true, false], +); +final locationAccuracyObservable = observablePreference( + "locationAccuracy", + LocationAccuracy.best, + LocationAccuracy.values, +); + +class Observable { + T _value; + final List _observers = []; + + Observable( + this._value, { + Future? loadValue, + List? observers, + }) { + if (observers != null) { + _observers.addAll(observers); + } + + if (loadValue != null) { + loadValue.then((value) => this.value = value); + } + } + + T get value => _value; + set value(T value) { + if (value == _value) { + return; + } + + _value = value; + + for (var observer in _observers) { + if (observer != null) { + observer(_value); + } + } + } + + int subscribe(void Function(T) onChange) { + final id = _observers.length; + _observers.add(onChange); + return id; + } + + void unsubscribe(int subscriberId) { + _observers[subscriberId] = null; + } +} + +class ObserverBuilder extends StatefulWidget { + final Observable observable; + final Widget Function(BuildContext, T, void Function(T)) builder; + + const ObserverBuilder({ + required this.observable, + required this.builder, + super.key, + }); + + @override + State> createState() => _ObserverState(); +} + +class _ObserverState extends State> { + late T _value; + late int _subscriberId; + + @override + void initState() { + super.initState(); + _subscriberId = widget.observable.subscribe( + (value) => setState(() => _value = value), + ); + _value = widget.observable.value; + } + + @override + void dispose() { + super.dispose(); + widget.observable.unsubscribe(_subscriberId); + } + + @override + Widget build(BuildContext context) { + return widget.builder( + context, + _value, + (value) => widget.observable.value = value, + ); + } +} -- cgit v1.2.3