summaryrefslogtreecommitdiff
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
parentd44654698cc3c65a5a458f4c2cdc3b2d868890f5 (diff)
Complete settings page
-rw-r--r--lib/about.dart85
-rw-r--r--lib/home.dart90
-rw-r--r--lib/jotai.dart150
-rwxr-xr-xlib/main.dart28
-rw-r--r--lib/settings.dart263
-rwxr-xr-xmacos/Flutter/GeneratedPluginRegistrant.swift2
-rwxr-xr-xpubspec.lock102
-rwxr-xr-xpubspec.yaml5
8 files changed, 586 insertions, 139 deletions
diff --git a/lib/about.dart b/lib/about.dart
deleted file mode 100644
index 7dbaaf4..0000000
--- a/lib/about.dart
+++ /dev/null
@@ -1,85 +0,0 @@
-import 'package:build_info/build_info.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:package_info_plus/package_info_plus.dart';
-import 'package:url_launcher/url_launcher_string.dart';
-
-class AboutPage extends StatefulWidget {
- const AboutPage({super.key});
-
- @override
- State<AboutPage> createState() => _AboutPageState();
-}
-
-class _AboutPageState extends State<AboutPage> {
- Future<(PackageInfo, String, BuildInfoData?)>? _loadedData;
-
- @override
- void initState() {
- super.initState();
- _loadedData = Future(() async {
- var packageInfo = await PackageInfo.fromPlatform();
- var license = await rootBundle.loadString('LICENSE');
- var buildInfo = await BuildInfo.fromPlatform();
- return (packageInfo, license, buildInfo);
- });
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(title: Text('About')),
- body: Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Icon(
- Icons.speed,
- size: Theme.of(context).textTheme.displayLarge?.fontSize ?? 72,
- ),
- Text(
- 'Simple Speedometer',
- style: Theme.of(context).textTheme.titleLarge,
- ),
- Text('© 2025 Mica White'),
- Text(''),
- TextButton.icon(
- icon: Icon(Icons.code),
- label: Text('View source code'),
- onPressed: () =>
- launchUrlString("https://www.botahamec.dev/cgit/speedometer"),
- ),
- FutureBuilder(
- future: _loadedData,
- builder: (context, snapshot) {
- final appName = snapshot.data?.$1.appName;
- final version = snapshot.data?.$1.version;
- final legalese = snapshot.data?.$2;
- final buildDate = snapshot.data?.$3?.buildDate;
-
- return TextButton.icon(
- icon: Icon(Icons.copyright),
- label: Text('View licenses'),
- onPressed: () => Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => LicensePage(
- applicationIcon: Icon(Icons.speed),
- applicationName: appName,
- applicationVersion: snapshot.hasData
- ? '$version (${buildDate?.year}-${buildDate?.month}-${buildDate?.day})'
- : null,
- applicationLegalese: legalese,
- ),
- ),
- ),
- );
- },
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/lib/home.dart b/lib/home.dart
index 371a52e..f141d0b 100644
--- a/lib/home.dart
+++ b/lib/home.dart
@@ -2,9 +2,11 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
+import 'package:region_settings/region_settings.dart';
+import 'package:speedometer/jotai.dart';
import 'main.dart';
-import 'about.dart';
+import 'settings.dart';
extension on SpeedUnit {
double fromMetersPerSecond(double metersPerSecond) => switch (this) {
@@ -27,15 +29,15 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> {
StreamSubscription<Position>? _positionStream;
+
+ bool _usesMetric = true;
double _speed = 0.0;
double _speedAccuracy = 0.0;
- SpeedUnit _speedUnit = SpeedUnit.milesPerHour;
- LocationSettings _locationSettings = LocationSettings();
- void _initPositionStream() {
+ void _initPositionStream(LocationAccuracy locationAccuracy) {
_positionStream =
Geolocator.getPositionStream(
- locationSettings: _locationSettings,
+ locationSettings: LocationSettings(accuracy: locationAccuracy),
).listen((Position? position) {
if (position != null) {
setState(() {
@@ -61,9 +63,18 @@ class _HomePageState extends State<HomePage> {
LocationPermission.deniedForever,
LocationPermission.denied,
].contains(permission)) {
- _initPositionStream();
+ _initPositionStream(locationAccuracyObservable.value);
}
});
+
+ locationAccuracyObservable.subscribe((locationAccuracy) {
+ _positionStream?.cancel();
+ _initPositionStream(locationAccuracy);
+ });
+
+ RegionSettings.getUsesMetricSystem().then(
+ (usesMetricSystem) => setState(() => _usesMetric = usesMetricSystem),
+ );
}
@override
@@ -77,41 +88,46 @@ class _HomePageState extends State<HomePage> {
return Scaffold(
appBar: AppBar(
actions: [
- MenuAnchor(
- builder: (context, controller, child) => TextButton.icon(
- icon: Icon(Icons.settings),
- label: Text('Settings'),
- onPressed: () =>
- controller.isOpen ? controller.close() : controller.open(),
- ),
- menuChildren: [
- MenuItemButton(child: Text('Settings')),
- MenuItemButton(
- child: Text('About'),
- onPressed: () => Navigator.push(
- context,
- MaterialPageRoute(builder: (context) => AboutPage()),
- ),
- ),
- ],
+ TextButton.icon(
+ icon: Icon(Icons.settings),
+ label: Text('Settings'),
+ onPressed: () => Navigator.of(
+ context,
+ ).push(MaterialPageRoute(builder: (context) => SettingsPage())),
),
],
),
body: Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Flex(direction: Axis.horizontal),
- Text(
- '${_speedUnit.fromMetersPerSecond(_speed).round()} ${_speedUnit.acronym}',
- style: Theme.of(context).textTheme.displayLarge,
- ),
- Text(
- '± ${_speedUnit.fromMetersPerSecond(_speedAccuracy).round()} ${_speedUnit.acronym}',
- style: Theme.of(context).textTheme.displaySmall,
- ),
- ],
+ child: ObserverBuilder(
+ observable: speedUnitsObservable,
+ builder: (context, speedUnitsSetting, _) {
+ final speedUnits =
+ speedUnitsSetting ??
+ (_usesMetric
+ ? SpeedUnit.kilometersPerHour
+ : SpeedUnit.milesPerHour);
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Flex(direction: Axis.horizontal),
+ Text(
+ '${speedUnits.fromMetersPerSecond(_speed).round()} ${speedUnits.acronym}',
+ style: Theme.of(context).textTheme.displayLarge,
+ ),
+ ObserverBuilder(
+ observable: showMarginOfErrorObservable,
+ builder: (context, showMarginOfError, _) =>
+ showMarginOfError == true
+ ? Text(
+ '± ${speedUnits.fromMetersPerSecond(_speedAccuracy).round()} ${speedUnits.acronym}',
+ style: Theme.of(context).textTheme.displaySmall,
+ )
+ : SizedBox(),
+ ),
+ ],
+ );
+ },
),
),
);
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,
+ );
+ }
+}
diff --git a/lib/main.dart b/lib/main.dart
index 1bfbccc..403427c 100755
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'home.dart';
+import 'jotai.dart';
enum SpeedUnit { milesPerHour, kilometersPerHour }
@@ -13,18 +14,25 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return MaterialApp(
- title: 'Speedometer',
- theme: ThemeData(
- colorScheme: ColorScheme.fromSeed(seedColor: Colors.red),
- ),
- darkTheme: ThemeData(
- colorScheme: ColorScheme.fromSeed(
- seedColor: Colors.red,
- brightness: Brightness.dark,
+ return ObserverBuilder(
+ observable: themeModeObservable,
+ builder: (context, themeMode, _) => ObserverBuilder(
+ observable: primaryColorObservable,
+ builder: (context, primaryColor, _) => MaterialApp(
+ title: 'Simple Speedometer',
+ theme: ThemeData(
+ colorScheme: ColorScheme.fromSeed(seedColor: primaryColor),
+ ),
+ darkTheme: ThemeData(
+ colorScheme: ColorScheme.fromSeed(
+ seedColor: primaryColor,
+ brightness: Brightness.dark,
+ ),
+ ),
+ themeMode: themeMode,
+ home: HomePage(),
),
),
- home: HomePage(),
);
}
}
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'),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index d6094b9..aa9ae64 100755
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -8,11 +8,13 @@ import Foundation
import build_info_macos
import geolocator_apple
import package_info_plus
+import shared_preferences_foundation
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
BuildInfoPlugin.register(with: registry.registrar(forPlugin: "BuildInfoPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
+ SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}
diff --git a/pubspec.lock b/pubspec.lock
index e30d5e2..1694d9f 100755
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -165,10 +165,18 @@ packages:
dependency: transitive
description:
name: ffi
- sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
+ sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
url: "https://pub.dev"
source: hosted
- version: "2.1.4"
+ version: "2.1.5"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.1"
fixnum:
dependency: transitive
description:
@@ -377,7 +385,7 @@ packages:
source: hosted
version: "1.17.0"
package_info_plus:
- dependency: "direct main"
+ dependency: transitive
description:
name: package_info_plus
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
@@ -400,6 +408,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
+ path_provider_linux:
+ dependency: transitive
+ description:
+ name: path_provider_linux
+ sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.1"
+ path_provider_platform_interface:
+ dependency: transitive
+ description:
+ name: path_provider_platform_interface
+ sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ path_provider_windows:
+ dependency: transitive
+ description:
+ name: path_provider_windows
+ sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.0"
petitparser:
dependency: transitive
description:
@@ -408,6 +440,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
@@ -432,6 +472,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.0"
+ shared_preferences:
+ dependency: "direct main"
+ description:
+ name: shared_preferences
+ sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.4"
+ shared_preferences_android:
+ dependency: transitive
+ description:
+ name: shared_preferences_android
+ sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.18"
+ shared_preferences_foundation:
+ dependency: transitive
+ description:
+ name: shared_preferences_foundation
+ sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.6"
+ shared_preferences_linux:
+ dependency: transitive
+ description:
+ name: shared_preferences_linux
+ sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ shared_preferences_platform_interface:
+ dependency: transitive
+ description:
+ name: shared_preferences_platform_interface
+ sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ shared_preferences_web:
+ dependency: transitive
+ description:
+ name: shared_preferences_web
+ sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.3"
+ shared_preferences_windows:
+ dependency: transitive
+ description:
+ name: shared_preferences_windows
+ sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
diff --git a/pubspec.yaml b/pubspec.yaml
index 7b4aa5e..4138fb3 100755
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -13,8 +13,8 @@ dependencies:
flutter:
sdk: flutter
geolocator: ^14.0.2
- package_info_plus: ^8.3.1
region_settings: ^1.5.0
+ shared_preferences: ^2.5.4
url_launcher: ^6.3.2
dev_dependencies:
@@ -26,9 +26,6 @@ dev_dependencies:
flutter:
uses-material-design: true
- assets:
- - LICENSE
-
flutter_launcher_icons:
image_path: "assets/speed.png"
android: true