diff options
| author | xengineering <me@xengineering.eu> | 2026-04-06 17:03:46 +0200 |
|---|---|---|
| committer | xengineering <me@xengineering.eu> | 2026-04-06 17:03:46 +0200 |
| commit | 877a39e186699164e68ea969012a4f1ec6840ef8 (patch) | |
| tree | 92932f23c5b1d2798a9e63467b0ad4de70caa39f | |
| parent | 1809a88c679fcd17f29c13ddd47732bb65db96b2 (diff) | |
| parent | fcfb2a17733a38b690f4e034c78bc414be5527ef (diff) | |
| download | sia-app-877a39e186699164e68ea969012a4f1ec6840ef8.tar sia-app-877a39e186699164e68ea969012a4f1ec6840ef8.tar.zst sia-app-877a39e186699164e68ea969012a4f1ec6840ef8.zip | |
Merge persisting server fully qualified domain name
This allows to save the server fully qualified domain name (FQDN). The
user does not have to insert this on every app start.
Saved is the value of the corresponding text field on the last press on
the connect button.
| -rw-r--r-- | analysis_options.yaml | 2 | ||||
| -rw-r--r-- | lib/data.dart | 27 | ||||
| -rw-r--r-- | lib/db.dart | 90 | ||||
| -rw-r--r-- | lib/ui.dart | 11 | ||||
| -rw-r--r-- | pubspec.lock | 148 | ||||
| -rw-r--r-- | pubspec.yaml | 3 |
6 files changed, 276 insertions, 5 deletions
diff --git a/analysis_options.yaml b/analysis_options.yaml index c8ed5db..c7d3757 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -8,3 +8,5 @@ linter: prefer_final_fields: true avoid_dynamic_calls: true unnecessary_null_checks: true + unawaited_futures: true + discarded_futures: true diff --git a/lib/data.dart b/lib/data.dart index 365f5bc..ca350e5 100644 --- a/lib/data.dart +++ b/lib/data.dart @@ -1,7 +1,12 @@ +import 'dart:io'; +import 'dart:async'; + import 'package:flutter/foundation.dart'; import 'package:mqtt_client/mqtt_client.dart'; import 'package:mqtt_client/mqtt_server_client.dart'; +import 'db.dart'; + const int brokerPort = 1883; const String topicPrefix = 'sia'; @@ -22,6 +27,8 @@ enum MachineEvent { } class AppState with ChangeNotifier { + final DB db = DB(); + static const Map<MachineState, Map<MachineEvent, MachineState>> machine = <MachineState, Map<MachineEvent, MachineState>> { MachineState.init: <MachineEvent, MachineState> { MachineEvent.connect: MachineState.disconnected, @@ -46,9 +53,25 @@ class AppState with ChangeNotifier { Map<String, bool> contacts = <String, bool>{}; late MqttServerClient _client; + late Directory supportDir; String fqdn = ''; - AppState(); + AppState() { + unawaited(loadPersistence()); + } + + Future<void> loadPersistence() async { + await db.connect(); + String? dbFqdn = await db.getServerFqdn(); + if (dbFqdn == null) return; + fqdn = dbFqdn; + notifyListeners(); + } + + void setFqdn(String value) { + fqdn = value; + unawaited(db.setServerFqdn(value)); + } void process(MachineEvent event) { MachineState lastState = state; @@ -103,7 +126,7 @@ class AppState with ChangeNotifier { _client.onAutoReconnected = _onAutoReconnected; try { - _client.connect(); + unawaited(_client.connect()); } catch (e) { _client.disconnect(); return; diff --git a/lib/db.dart b/lib/db.dart new file mode 100644 index 0000000..0b2cf7a --- /dev/null +++ b/lib/db.dart @@ -0,0 +1,90 @@ +import 'dart:io'; +import 'dart:async'; + +import 'package:path_provider/path_provider.dart'; +import 'package:sqlite3/sqlite3.dart'; +import 'package:path/path.dart' as p; + +class DB { + final Completer<Database?> _dbCompleter = Completer<Database?>(); + static const List<String> migrations = <String>[ + ''' +PRAGMA user_version = 1; +CREATE TABLE "key_value" ( + "key" TEXT NOT NULL UNIQUE, + "value" TEXT, + PRIMARY KEY("key") +); + ''', + ]; + + Future<void> connect() async { + String path = await _getDbPath(); + Database candidate = sqlite3.open(path); + + migrate(candidate); + + _dbCompleter.complete(candidate); + } + + void dispose() async { + if (_dbCompleter.isCompleted == false) { + return; + } + Database? db = await _dbCompleter.future; + if (db == null) return; + db.close(); + } + + static Future<String> _getDbPath() async { + Directory supportDir = await getApplicationSupportDirectory(); + return p.join(supportDir.path, 'main.sqlite3'); + } + + static void migrate(Database db) { + for (int i=0; i<migrations.length; i++) { + int? userVersion = _getUserVersion(db); + if (userVersion == null) return; + + if (i == userVersion) { + db.execute(migrations[i]); + } + } + } + + static int? _getUserVersion(Database db) { + ResultSet result = db.select('PRAGMA user_version;'); + if (result.length != 1) return null; + return result.first.values.first as int; + } + + Future<String?> getServerFqdn() async { + Database? db = await _dbCompleter.future; + if (db == null) return null; + + ResultSet result = db.select( + 'SELECT value FROM key_value WHERE key = \'server_fqdn\';' + ); + if (result.length != 1) return null; + + return result[0]['value']; + } + + Future<void> setServerFqdn(String value) async { + Database? db = await _dbCompleter.future; + if (db == null) return; + + String? current = await getServerFqdn(); + if (current == null) { + db.execute( + 'INSERT INTO key_value (key, value) VALUES (\'server_fqdn\', ?);', + <Object?>[value], + ); + } else { + db.execute( + 'UPDATE key_value SET value = ? WHERE key = \'server_fqdn\';', + <Object?>[value], + ); + } + } +} diff --git a/lib/ui.dart b/lib/ui.dart index 9603562..3a98be9 100644 --- a/lib/ui.dart +++ b/lib/ui.dart @@ -41,8 +41,17 @@ class _ConnectionPageState extends State<ConnectionPage> { final AppState provider = context.read<AppState>(); controller = TextEditingController(text: provider.fqdn); + provider.addListener(() { + if (controller.text != provider.fqdn) { + controller.text = provider.fqdn; + controller.selection = TextSelection.fromPosition( + TextPosition(offset: controller.text.length), + ); + } + }); + controller.addListener(() { - provider.fqdn = controller.text; + provider.setFqdn(controller.text); }); } diff --git a/pubspec.lock b/pubspec.lock index 26b837f..30521d2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" collection: dependency: transitive description: @@ -105,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -131,6 +147,22 @@ packages: description: flutter source: sdk version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + hooks: + dependency: transitive + description: + name: hooks + sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388 + url: "https://pub.dev" + source: hosted + version: "1.0.2" image: dependency: transitive description: @@ -179,6 +211,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -211,6 +251,14 @@ packages: url: "https://pub.dev" source: hosted version: "10.11.9" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "92b2ca62c8bd2b8d2f267cdfccf9bfbdb7322f778f8f91b3ce5b5cda23a3899f" + url: "https://pub.dev" + source: hosted + version: "0.17.5" nested: dependency: transitive description: @@ -219,14 +267,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - path: + objective_c: dependency: transitive description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" + path: + dependency: "direct main" + description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + 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: @@ -235,6 +339,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" posix: dependency: transitive description: @@ -251,6 +371,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" sky_engine: dependency: transitive description: flutter @@ -264,6 +392,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" + sqlite3: + dependency: "direct main" + description: + name: sqlite3 + sha256: caa693ad15a587a2b4fde093b728131a1827903872171089dedb16f7665d3a91 + url: "https://pub.dev" + source: hosted + version: "3.2.0" stack_trace: dependency: transitive description: @@ -336,6 +472,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" xml: dependency: transitive description: @@ -354,4 +498,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.10.4 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml index ec03450..605d9d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,10 @@ dependencies: flutter: sdk: flutter mqtt_client: ^10.11.3 + path: ^1.9.1 + path_provider: ^2.1.5 provider: ^6.1.5 + sqlite3: ^3.2.0 dev_dependencies: flutter_test: |
