initial commit

This commit is contained in:
2026-07-27 14:42:23 +02:00
commit 833c68a189
257 changed files with 17947 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'connection.freezed.dart';
part 'connection.g.dart';
/// A saved NodeMaster API endpoint. Client-side only — has no server-side
/// counterpart. `baseUrl` excludes the `/v1` suffix (added by
/// [NodeMasterApiClient]) and any trailing slash, e.g.
/// `https://edge-01.local:8080`.
@freezed
sealed class Connection with _$Connection {
const factory Connection({
required String id,
required String name,
required String baseUrl,
}) = _Connection;
factory Connection.fromJson(Map<String, dynamic> json) => _$ConnectionFromJson(json);
}
@@ -0,0 +1,277 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'connection.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Connection {
String get id; String get name; String get baseUrl;
/// Create a copy of Connection
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ConnectionCopyWith<Connection> get copyWith => _$ConnectionCopyWithImpl<Connection>(this as Connection, _$identity);
/// Serializes this Connection to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Connection&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.baseUrl, baseUrl) || other.baseUrl == baseUrl));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,name,baseUrl);
@override
String toString() {
return 'Connection(id: $id, name: $name, baseUrl: $baseUrl)';
}
}
/// @nodoc
abstract mixin class $ConnectionCopyWith<$Res> {
factory $ConnectionCopyWith(Connection value, $Res Function(Connection) _then) = _$ConnectionCopyWithImpl;
@useResult
$Res call({
String id, String name, String baseUrl
});
}
/// @nodoc
class _$ConnectionCopyWithImpl<$Res>
implements $ConnectionCopyWith<$Res> {
_$ConnectionCopyWithImpl(this._self, this._then);
final Connection _self;
final $Res Function(Connection) _then;
/// Create a copy of Connection
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? baseUrl = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,baseUrl: null == baseUrl ? _self.baseUrl : baseUrl // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [Connection].
extension ConnectionPatterns on Connection {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Connection value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Connection() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Connection value) $default,){
final _that = this;
switch (_that) {
case _Connection():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Connection value)? $default,){
final _that = this;
switch (_that) {
case _Connection() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String baseUrl)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Connection() when $default != null:
return $default(_that.id,_that.name,_that.baseUrl);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String baseUrl) $default,) {final _that = this;
switch (_that) {
case _Connection():
return $default(_that.id,_that.name,_that.baseUrl);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String baseUrl)? $default,) {final _that = this;
switch (_that) {
case _Connection() when $default != null:
return $default(_that.id,_that.name,_that.baseUrl);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _Connection implements Connection {
const _Connection({required this.id, required this.name, required this.baseUrl});
factory _Connection.fromJson(Map<String, dynamic> json) => _$ConnectionFromJson(json);
@override final String id;
@override final String name;
@override final String baseUrl;
/// Create a copy of Connection
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ConnectionCopyWith<_Connection> get copyWith => __$ConnectionCopyWithImpl<_Connection>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ConnectionToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Connection&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.baseUrl, baseUrl) || other.baseUrl == baseUrl));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,name,baseUrl);
@override
String toString() {
return 'Connection(id: $id, name: $name, baseUrl: $baseUrl)';
}
}
/// @nodoc
abstract mixin class _$ConnectionCopyWith<$Res> implements $ConnectionCopyWith<$Res> {
factory _$ConnectionCopyWith(_Connection value, $Res Function(_Connection) _then) = __$ConnectionCopyWithImpl;
@override @useResult
$Res call({
String id, String name, String baseUrl
});
}
/// @nodoc
class __$ConnectionCopyWithImpl<$Res>
implements _$ConnectionCopyWith<$Res> {
__$ConnectionCopyWithImpl(this._self, this._then);
final _Connection _self;
final $Res Function(_Connection) _then;
/// Create a copy of Connection
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? baseUrl = null,}) {
return _then(_Connection(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,baseUrl: null == baseUrl ? _self.baseUrl : baseUrl // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on
+20
View File
@@ -0,0 +1,20 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'connection.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_Connection _$ConnectionFromJson(Map<String, dynamic> json) => _Connection(
id: json['id'] as String,
name: json['name'] as String,
baseUrl: json['baseUrl'] as String,
);
Map<String, dynamic> _$ConnectionToJson(_Connection instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'baseUrl': instance.baseUrl,
};
@@ -0,0 +1,75 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../utils/shared_preferences_provider.dart';
import 'connection.dart';
import 'connection_storage.dart';
part 'connection_providers.g.dart';
@Riverpod(keepAlive: true)
ConnectionStorage connectionStorage(Ref ref) {
return ConnectionStorage(ref.watch(sharedPreferencesProvider));
}
/// The full list of saved connections. Mutations persist immediately.
@Riverpod(keepAlive: true)
class SavedConnections extends _$SavedConnections {
@override
List<Connection> build() => ref.watch(connectionStorageProvider).readConnections();
Future<void> add(Connection connection) async {
state = [...state, connection];
await ref.read(connectionStorageProvider).writeConnections(state);
}
Future<void> update(Connection connection) async {
state = [
for (final c in state)
if (c.id == connection.id) connection else c,
];
await ref.read(connectionStorageProvider).writeConnections(state);
}
Future<void> remove(String id) async {
state = state.where((c) => c.id != id).toList(growable: false);
await ref.read(connectionStorageProvider).writeConnections(state);
final activeId = ref.read(activeConnectionIdProvider);
if (activeId == id) {
await ref.read(activeConnectionIdProvider.notifier).setActive(
state.isEmpty ? null : state.first.id,
);
}
}
}
/// The id of the currently active connection, or null if none is selected
/// (e.g. first launch with nothing configured yet).
@Riverpod(keepAlive: true)
class ActiveConnectionId extends _$ActiveConnectionId {
@override
String? build() {
final stored = ref.watch(connectionStorageProvider).readActiveConnectionId();
final connections = ref.watch(savedConnectionsProvider);
if (stored != null && connections.any((c) => c.id == stored)) return stored;
return connections.isEmpty ? null : connections.first.id;
}
Future<void> setActive(String? id) async {
state = id;
await ref.read(connectionStorageProvider).writeActiveConnectionId(id);
}
}
/// The currently active [Connection], or null if none is configured.
/// Everything in `core/network` derives from this — switching it rebuilds
/// the API client and, transitively, every provider that watches it.
@riverpod
Connection? activeConnection(Ref ref) {
final id = ref.watch(activeConnectionIdProvider);
if (id == null) return null;
final connections = ref.watch(savedConnectionsProvider);
for (final c in connections) {
if (c.id == id) return c;
}
return null;
}
@@ -0,0 +1,232 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'connection_providers.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(connectionStorage)
const connectionStorageProvider = ConnectionStorageProvider._();
final class ConnectionStorageProvider
extends
$FunctionalProvider<
ConnectionStorage,
ConnectionStorage,
ConnectionStorage
>
with $Provider<ConnectionStorage> {
const ConnectionStorageProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'connectionStorageProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$connectionStorageHash();
@$internal
@override
$ProviderElement<ConnectionStorage> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
ConnectionStorage create(Ref ref) {
return connectionStorage(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(ConnectionStorage value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<ConnectionStorage>(value),
);
}
}
String _$connectionStorageHash() => r'0faf909e3978260785f2f8ce4bc41f101e27ad66';
/// The full list of saved connections. Mutations persist immediately.
@ProviderFor(SavedConnections)
const savedConnectionsProvider = SavedConnectionsProvider._();
/// The full list of saved connections. Mutations persist immediately.
final class SavedConnectionsProvider
extends $NotifierProvider<SavedConnections, List<Connection>> {
/// The full list of saved connections. Mutations persist immediately.
const SavedConnectionsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'savedConnectionsProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$savedConnectionsHash();
@$internal
@override
SavedConnections create() => SavedConnections();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(List<Connection> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<List<Connection>>(value),
);
}
}
String _$savedConnectionsHash() => r'2595280b9ef1f5835ecce70b791a901a51aaf8e8';
/// The full list of saved connections. Mutations persist immediately.
abstract class _$SavedConnections extends $Notifier<List<Connection>> {
List<Connection> build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<List<Connection>, List<Connection>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<List<Connection>, List<Connection>>,
List<Connection>,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
/// The id of the currently active connection, or null if none is selected
/// (e.g. first launch with nothing configured yet).
@ProviderFor(ActiveConnectionId)
const activeConnectionIdProvider = ActiveConnectionIdProvider._();
/// The id of the currently active connection, or null if none is selected
/// (e.g. first launch with nothing configured yet).
final class ActiveConnectionIdProvider
extends $NotifierProvider<ActiveConnectionId, String?> {
/// The id of the currently active connection, or null if none is selected
/// (e.g. first launch with nothing configured yet).
const ActiveConnectionIdProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'activeConnectionIdProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$activeConnectionIdHash();
@$internal
@override
ActiveConnectionId create() => ActiveConnectionId();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(String? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<String?>(value),
);
}
}
String _$activeConnectionIdHash() =>
r'ac8b7d48bc04e6f9245b23c83a15e11a539e1fc8';
/// The id of the currently active connection, or null if none is selected
/// (e.g. first launch with nothing configured yet).
abstract class _$ActiveConnectionId extends $Notifier<String?> {
String? build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<String?, String?>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<String?, String?>,
String?,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
/// The currently active [Connection], or null if none is configured.
/// Everything in `core/network` derives from this — switching it rebuilds
/// the API client and, transitively, every provider that watches it.
@ProviderFor(activeConnection)
const activeConnectionProvider = ActiveConnectionProvider._();
/// The currently active [Connection], or null if none is configured.
/// Everything in `core/network` derives from this — switching it rebuilds
/// the API client and, transitively, every provider that watches it.
final class ActiveConnectionProvider
extends $FunctionalProvider<Connection?, Connection?, Connection?>
with $Provider<Connection?> {
/// The currently active [Connection], or null if none is configured.
/// Everything in `core/network` derives from this — switching it rebuilds
/// the API client and, transitively, every provider that watches it.
const ActiveConnectionProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'activeConnectionProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$activeConnectionHash();
@$internal
@override
$ProviderElement<Connection?> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
Connection? create(Ref ref) {
return activeConnection(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Connection? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Connection?>(value),
);
}
}
String _$activeConnectionHash() => r'544cf6f795b35636cbfc497e6379a84b1e8f5b88';
@@ -0,0 +1,41 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'connection.dart';
const _connectionsKey = 'connections';
const _activeConnectionIdKey = 'active_connection_id';
/// Reads/writes the saved connection list and active-connection id to
/// [SharedPreferences] as a plain JSON-encoded array. Not a secret store —
/// the API has no auth, so there's nothing sensitive in a saved base URL.
class ConnectionStorage {
const ConnectionStorage(this._prefs);
final SharedPreferences _prefs;
List<Connection> readConnections() {
final raw = _prefs.getString(_connectionsKey);
if (raw == null || raw.isEmpty) return const [];
final decoded = jsonDecode(raw) as List<dynamic>;
return decoded
.map((e) => Connection.fromJson(e as Map<String, dynamic>))
.toList(growable: false);
}
Future<void> writeConnections(List<Connection> connections) async {
final encoded = jsonEncode(connections.map((c) => c.toJson()).toList());
await _prefs.setString(_connectionsKey, encoded);
}
String? readActiveConnectionId() => _prefs.getString(_activeConnectionIdKey);
Future<void> writeActiveConnectionId(String? id) async {
if (id == null) {
await _prefs.remove(_activeConnectionIdKey);
} else {
await _prefs.setString(_activeConnectionIdKey, id);
}
}
}