summaryrefslogtreecommitdiff
path: root/lib/jotai.dart
blob: a415e13d17c4941d6ee121067226c25ff10af327 (plain)
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,
    );
  }
}