summaryrefslogtreecommitdiff
path: root/lib/jotai.dart
diff options
context:
space:
mode:
authorMica White <botahamec@outlook.com>2026-01-13 22:51:59 -0500
committerMica White <botahamec@outlook.com>2026-01-13 22:51:59 -0500
commit359d8e07ef5cb585fff13031d075d7c949135317 (patch)
tree0b317599d7cbbb9e5f8b4fce8e99559ad45bfd8c /lib/jotai.dart
parentd44654698cc3c65a5a458f4c2cdc3b2d868890f5 (diff)
Complete settings page
Diffstat (limited to 'lib/jotai.dart')
-rw-r--r--lib/jotai.dart150
1 files changed, 150 insertions, 0 deletions
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<T>(
+ String key, [
+ String Function(T) toString = _defaultToString,
+]) {
+ return (T value) => SharedPreferencesAsync().setString(key, toString(value));
+}
+
+Future<T> fromString<T>(
+ String key,
+ List<T> options, [
+ String Function(T) toString = _defaultToString,
+]) {
+ return SharedPreferencesAsync()
+ .getString(key)
+ .then(
+ (value) => options.firstWhere((option) => value == toString(option)),
+ );
+}
+
+Observable<T> observablePreference<T>(
+ String key,
+ T defaultValue,
+ List<T> 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> {
+ T _value;
+ final List<void Function(T)?> _observers = [];
+
+ Observable(
+ this._value, {
+ Future<T>? loadValue,
+ List<void Function(T)?>? 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<T> extends StatefulWidget {
+ final Observable<T> observable;
+ final Widget Function(BuildContext, T, void Function(T)) builder;
+
+ const ObserverBuilder({
+ required this.observable,
+ required this.builder,
+ super.key,
+ });
+
+ @override
+ State<ObserverBuilder<T>> createState() => _ObserverState<T>();
+}
+
+class _ObserverState<T> extends State<ObserverBuilder<T>> {
+ 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,
+ );
+ }
+}