summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxengineering <me@xengineering.eu>2026-04-06 17:03:46 +0200
committerxengineering <me@xengineering.eu>2026-04-06 17:03:46 +0200
commit877a39e186699164e68ea969012a4f1ec6840ef8 (patch)
tree92932f23c5b1d2798a9e63467b0ad4de70caa39f
parent1809a88c679fcd17f29c13ddd47732bb65db96b2 (diff)
parentfcfb2a17733a38b690f4e034c78bc414be5527ef (diff)
downloadsia-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.yaml2
-rw-r--r--lib/data.dart27
-rw-r--r--lib/db.dart90
-rw-r--r--lib/ui.dart11
-rw-r--r--pubspec.lock148
-rw-r--r--pubspec.yaml3
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: