feat: wire chain_client_sdk gRPC client + HubRepository scaffold
Adds a real Ch∆In hub backend to the Flutter client as a sibling-path dependency on chain_client_sdk_dart, alongside the existing MockRepository (which stays the default until the flow.completed-to-Evaluation pipeline lands in Phase 0 week 1). Wiring HubSettings (lib/data/hub_endpoint.dart) — persists host / port / secure / token / useHub flags via SharedPreferences under the Studio-compatible hub.* keys. Distinct from the SDK's chain.HubEndpoint, which is a transport-level value type — HubSettings adapts in that direction. HubRepository (lib/data/hub_repository.dart) — implements EvaluationRepository on top of HubClient. connect() runs a 4-second healthy() probe; on failure returns a repository with isHealthy=false rather than throwing, so the UI can surface "hub-down" without an exception-handling round-trip. list() / byEli() return empty data in Phase 0 because no hub-side flow yet produces Evaluation objects in the lawheatmap-specific shape. main.dart — loads HubSettings on startup; if useHub=false starts with MockRepository (fast offline default), otherwise probes the hub and falls back to MockRepository on probe failure. No mid-session swap to keep the Phase 0 wiring simple; a settings save instructs the user to restart. HubStatusPage — read-only summary card at the top, editable endpoint form, "Verbindung testen" probe button, "Speichern" persist button. Live-flow runbook below describes the chain serve / install / run / approve sequence so a user can wire the real flow themselves once Phase 1 lands. flutter analyze: clean. flutter test: 2/2 passing. Smoke-launch on macOS: app boots, Dart VM service comes up, no runtime errors in mock mode. Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
fa8acd41b8
commit
39733628b6
6 changed files with 509 additions and 31 deletions
83
app/lib/data/hub_endpoint.dart
Normal file
83
app/lib/data/hub_endpoint.dart
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
/// Persisted user settings for the Ch∆In hub connection.
|
||||||
|
///
|
||||||
|
/// Stored under four SharedPreferences keys matching the Studio
|
||||||
|
/// convention (`hub.host`, `hub.port`, `hub.secure`, `hub.token`)
|
||||||
|
/// so a hub once configured stays configured across app launches.
|
||||||
|
/// Distinct from the SDK's `chain.HubEndpoint`, which is a
|
||||||
|
/// transport-level value type — [HubSettings.toEndpoint] adapts
|
||||||
|
/// in that direction.
|
||||||
|
class HubSettings {
|
||||||
|
const HubSettings({
|
||||||
|
required this.host,
|
||||||
|
required this.port,
|
||||||
|
required this.secure,
|
||||||
|
this.authToken,
|
||||||
|
this.useHub = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String host;
|
||||||
|
final int port;
|
||||||
|
final bool secure;
|
||||||
|
|
||||||
|
/// Bearer token; read from `~/.chain/hub-auth-token` by the
|
||||||
|
/// hub-status page in Phase 1. Optional in Phase 0 because the
|
||||||
|
/// default `chain serve` runs without auth.
|
||||||
|
final String? authToken;
|
||||||
|
|
||||||
|
/// User intent: try to connect to the hub on startup. False
|
||||||
|
/// keeps the app in MockRepository mode regardless of host/port.
|
||||||
|
final bool useHub;
|
||||||
|
|
||||||
|
static const defaultHost = '127.0.0.1';
|
||||||
|
static const defaultPort = 50051;
|
||||||
|
|
||||||
|
String get scheme => secure ? 'https' : 'http';
|
||||||
|
String get url => '$scheme://$host:$port';
|
||||||
|
|
||||||
|
static const _kHost = 'hub.host';
|
||||||
|
static const _kPort = 'hub.port';
|
||||||
|
static const _kSecure = 'hub.secure';
|
||||||
|
static const _kToken = 'hub.token';
|
||||||
|
static const _kUseHub = 'hub.use';
|
||||||
|
|
||||||
|
static Future<HubSettings> load() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return HubSettings(
|
||||||
|
host: prefs.getString(_kHost) ?? defaultHost,
|
||||||
|
port: prefs.getInt(_kPort) ?? defaultPort,
|
||||||
|
secure: prefs.getBool(_kSecure) ?? false,
|
||||||
|
authToken: prefs.getString(_kToken),
|
||||||
|
useHub: prefs.getBool(_kUseHub) ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> save() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString(_kHost, host);
|
||||||
|
await prefs.setInt(_kPort, port);
|
||||||
|
await prefs.setBool(_kSecure, secure);
|
||||||
|
await prefs.setBool(_kUseHub, useHub);
|
||||||
|
if (authToken != null) {
|
||||||
|
await prefs.setString(_kToken, authToken!);
|
||||||
|
} else {
|
||||||
|
await prefs.remove(_kToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HubSettings copyWith({
|
||||||
|
String? host,
|
||||||
|
int? port,
|
||||||
|
bool? secure,
|
||||||
|
String? authToken,
|
||||||
|
bool? useHub,
|
||||||
|
}) =>
|
||||||
|
HubSettings(
|
||||||
|
host: host ?? this.host,
|
||||||
|
port: port ?? this.port,
|
||||||
|
secure: secure ?? this.secure,
|
||||||
|
authToken: authToken ?? this.authToken,
|
||||||
|
useHub: useHub ?? this.useHub,
|
||||||
|
);
|
||||||
|
}
|
||||||
74
app/lib/data/hub_repository.dart
Normal file
74
app/lib/data/hub_repository.dart
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import 'package:chain_client_sdk/chain_client_sdk.dart' as chain;
|
||||||
|
|
||||||
|
import 'hub_endpoint.dart';
|
||||||
|
import 'models.dart';
|
||||||
|
import 'repository.dart';
|
||||||
|
|
||||||
|
/// Live `EvaluationRepository` backed by a Ch∆In hub over gRPC.
|
||||||
|
///
|
||||||
|
/// Phase 0 scope: open a HubClient, run a healthy() probe, and
|
||||||
|
/// surface the connection state to the UI. The list() / byEli()
|
||||||
|
/// methods return empty data because no flow on the hub yet
|
||||||
|
/// produces Evaluation objects in the lawheatmap-specific shape —
|
||||||
|
/// that pipeline lands in Phase 0 week 1 once a chain run wraps
|
||||||
|
/// flows/durchstich-gewo-14.yaml output into the Evaluation
|
||||||
|
/// schema.
|
||||||
|
///
|
||||||
|
/// Until then [MockRepository] stays the default. Pick this
|
||||||
|
/// repository explicitly via the Hub-settings page; on first
|
||||||
|
/// healthy() failure the app falls back to MockRepository so the
|
||||||
|
/// UI keeps working.
|
||||||
|
class HubRepository implements EvaluationRepository {
|
||||||
|
HubRepository._(this._client, this.settings, this._healthy);
|
||||||
|
|
||||||
|
final chain.HubClient _client;
|
||||||
|
final HubSettings settings;
|
||||||
|
final bool _healthy;
|
||||||
|
|
||||||
|
/// Try to connect. Returns a repository with `isHealthy=false`
|
||||||
|
/// if the probe times out or the hub is unreachable; the
|
||||||
|
/// caller decides whether to use it or fall back to
|
||||||
|
/// MockRepository.
|
||||||
|
static Future<HubRepository> connect(HubSettings settings) async {
|
||||||
|
final endpoint = chain.HubEndpoint(
|
||||||
|
host: settings.host,
|
||||||
|
port: settings.port,
|
||||||
|
secure: settings.secure,
|
||||||
|
);
|
||||||
|
final client = chain.HubClient(
|
||||||
|
endpoint: endpoint,
|
||||||
|
authToken: settings.authToken,
|
||||||
|
);
|
||||||
|
final ok = await client.healthy().timeout(
|
||||||
|
const Duration(seconds: 4),
|
||||||
|
onTimeout: () => false,
|
||||||
|
);
|
||||||
|
return HubRepository._(client, settings, ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isHealthy => _healthy;
|
||||||
|
|
||||||
|
/// Underlying SDK client — exposed so the hub-status page can
|
||||||
|
/// list capabilities, query the audit log, and verify the
|
||||||
|
/// event-chain without leaking gRPC types to other pages.
|
||||||
|
chain.HubClient get client => _client;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Evaluation>> list() async {
|
||||||
|
// Phase 0 placeholder. The pipeline that turns
|
||||||
|
// flow.completed events into Evaluation objects lives in the
|
||||||
|
// surrounding flow (flows/durchstich-gewo-14.yaml) and lands
|
||||||
|
// in Phase 0 week 1.
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Evaluation?> byEli(String eli) async => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get mode => _healthy ? 'hub-live' : 'hub-down';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hubLabel => '${settings.url}'
|
||||||
|
'${_healthy ? '' : ' — Verbindung gestört'}';
|
||||||
|
}
|
||||||
|
|
@ -1,23 +1,45 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
|
||||||
|
import 'data/hub_endpoint.dart';
|
||||||
|
import 'data/hub_repository.dart';
|
||||||
import 'data/repository.dart';
|
import 'data/repository.dart';
|
||||||
import 'pages/landing_page.dart';
|
import 'pages/landing_page.dart';
|
||||||
import 'theme/lawheatmap_theme.dart';
|
import 'theme/lawheatmap_theme.dart';
|
||||||
|
|
||||||
void main() {
|
void main() async {
|
||||||
runApp(const LawHeatmapApp());
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final settings = await HubSettings.load();
|
||||||
|
final repository = await _pickRepository(settings);
|
||||||
|
runApp(LawHeatmapApp(repository: repository, settings: settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decide which repository to start with.
|
||||||
|
///
|
||||||
|
/// useHub=false → Mock, no probe (fast offline start)
|
||||||
|
/// useHub=true, healthy → HubRepository
|
||||||
|
/// useHub=true, NOT ok → MockRepository (graceful fallback;
|
||||||
|
/// UI banner shows hub-down so the
|
||||||
|
/// user can re-try from settings)
|
||||||
|
Future<EvaluationRepository> _pickRepository(HubSettings settings) async {
|
||||||
|
if (!settings.useHub) return const MockRepository();
|
||||||
|
final hub = await HubRepository.connect(settings);
|
||||||
|
if (hub.isHealthy) return hub;
|
||||||
|
return const MockRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
class LawHeatmapApp extends StatelessWidget {
|
class LawHeatmapApp extends StatelessWidget {
|
||||||
const LawHeatmapApp({super.key});
|
const LawHeatmapApp({
|
||||||
|
super.key,
|
||||||
|
this.repository = const MockRepository(),
|
||||||
|
this.settings,
|
||||||
|
});
|
||||||
|
|
||||||
|
final EvaluationRepository repository;
|
||||||
|
final HubSettings? settings;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Phase 0: in-memory fixtures only. Swap to a HubRepository
|
|
||||||
// (gRPC against `chain serve`) without changing the UI.
|
|
||||||
const EvaluationRepository repository = MockRepository();
|
|
||||||
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'F∆I Law-Heatmap',
|
title: 'F∆I Law-Heatmap',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
|
|
@ -30,7 +52,7 @@ class LawHeatmapApp extends StatelessWidget {
|
||||||
GlobalCupertinoLocalizations.delegate,
|
GlobalCupertinoLocalizations.delegate,
|
||||||
],
|
],
|
||||||
supportedLocales: const [Locale('de'), Locale('en')],
|
supportedLocales: const [Locale('de'), Locale('en')],
|
||||||
home: const LandingPage(repository: repository),
|
home: LandingPage(repository: repository),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,112 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../data/hub_endpoint.dart';
|
||||||
|
import '../data/hub_repository.dart';
|
||||||
import '../data/repository.dart';
|
import '../data/repository.dart';
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/lawheatmap_tokens.dart';
|
||||||
import '../widgets/lawheatmap_card.dart';
|
import '../widgets/lawheatmap_card.dart';
|
||||||
|
|
||||||
class HubStatusPage extends StatelessWidget {
|
/// Hub connection + settings. Read-only summary at the top, then
|
||||||
|
/// an editable form for endpoint host/port/secure/token, then a
|
||||||
|
/// "Verbinden / Test"-button. Changes are persisted via
|
||||||
|
/// HubSettings and take effect on the next app start (Phase 0
|
||||||
|
/// keeps the running repository to avoid mid-session swap
|
||||||
|
/// headaches).
|
||||||
|
class HubStatusPage extends StatefulWidget {
|
||||||
const HubStatusPage({super.key, required this.repository});
|
const HubStatusPage({super.key, required this.repository});
|
||||||
|
|
||||||
final EvaluationRepository repository;
|
final EvaluationRepository repository;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HubStatusPage> createState() => _HubStatusPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HubStatusPageState extends State<HubStatusPage> {
|
||||||
|
HubSettings? _settings;
|
||||||
|
late final TextEditingController _hostCtl;
|
||||||
|
late final TextEditingController _portCtl;
|
||||||
|
late final TextEditingController _tokenCtl;
|
||||||
|
bool _secure = false;
|
||||||
|
bool _useHub = false;
|
||||||
|
String? _probeMessage;
|
||||||
|
bool _probing = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_hostCtl = TextEditingController();
|
||||||
|
_portCtl = TextEditingController();
|
||||||
|
_tokenCtl = TextEditingController();
|
||||||
|
HubSettings.load().then((s) {
|
||||||
|
setState(() {
|
||||||
|
_settings = s;
|
||||||
|
_hostCtl.text = s.host;
|
||||||
|
_portCtl.text = s.port.toString();
|
||||||
|
_tokenCtl.text = s.authToken ?? '';
|
||||||
|
_secure = s.secure;
|
||||||
|
_useHub = s.useHub;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_hostCtl.dispose();
|
||||||
|
_portCtl.dispose();
|
||||||
|
_tokenCtl.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
HubSettings _formSettings() => HubSettings(
|
||||||
|
host: _hostCtl.text.trim().isEmpty
|
||||||
|
? HubSettings.defaultHost
|
||||||
|
: _hostCtl.text.trim(),
|
||||||
|
port: int.tryParse(_portCtl.text.trim()) ??
|
||||||
|
HubSettings.defaultPort,
|
||||||
|
secure: _secure,
|
||||||
|
authToken:
|
||||||
|
_tokenCtl.text.trim().isEmpty ? null : _tokenCtl.text.trim(),
|
||||||
|
useHub: _useHub,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> _save() async {
|
||||||
|
final s = _formSettings();
|
||||||
|
await s.save();
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _settings = s);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Einstellungen gespeichert. '
|
||||||
|
'Beim nächsten Start wird die App den Hub verwenden, '
|
||||||
|
'wenn der Schalter „Hub nutzen" aktiv ist.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _probe() async {
|
||||||
|
setState(() {
|
||||||
|
_probing = true;
|
||||||
|
_probeMessage = null;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
final hub = await HubRepository.connect(_formSettings());
|
||||||
|
setState(() {
|
||||||
|
_probeMessage = hub.isHealthy
|
||||||
|
? 'Verbindung erfolgreich (healthy=true).'
|
||||||
|
: 'Hub nicht erreichbar oder antwortet nicht SERVING.';
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => _probeMessage = 'Fehler: $e');
|
||||||
|
} finally {
|
||||||
|
if (mounted) setState(() => _probing = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Theme.of(context).textTheme;
|
final t = Theme.of(context).textTheme;
|
||||||
|
final repo = widget.repository;
|
||||||
|
final s = _settings;
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(LawHeatmapSpace.xl),
|
padding: const EdgeInsets.all(LawHeatmapSpace.xl),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -20,7 +115,7 @@ class HubStatusPage extends StatelessWidget {
|
||||||
Text('Ch∆In Hub', style: t.displaySmall),
|
Text('Ch∆In Hub', style: t.displaySmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.xs),
|
const SizedBox(height: LawHeatmapSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
'Status der Verbindung zum lokalen Hub und des Audit-Logs.',
|
'Status der Verbindung und Konfiguration des lokalen Hubs.',
|
||||||
style: t.bodyLarge?.copyWith(color: LawHeatmapColors.mute),
|
style: t.bodyLarge?.copyWith(color: LawHeatmapColors.mute),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: LawHeatmapSpace.lg),
|
||||||
|
|
@ -29,10 +124,19 @@ class HubStatusPage extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_row('Modus', repository.mode.toUpperCase(), t),
|
_row('Aktiver Modus', repo.mode.toUpperCase(), t),
|
||||||
_row('Quelle', repository.hubLabel, t),
|
_row('Datenquelle', repo.hubLabel, t),
|
||||||
_row('gRPC-Endpunkt', '— (Phase-0-Stub)', t),
|
_row(
|
||||||
_row('Audit-DB', '~/.chain/chain.db (geplant)', t),
|
'gRPC-Endpunkt',
|
||||||
|
s == null ? '…' : s.url,
|
||||||
|
t,
|
||||||
|
),
|
||||||
|
_row(
|
||||||
|
'Hub-Nutzung beim Start',
|
||||||
|
s == null ? '…' : (s.useHub ? 'ja' : 'nein'),
|
||||||
|
t,
|
||||||
|
),
|
||||||
|
_row('Audit-DB', '~/.chain/chain.db (Hub-seitig)', t),
|
||||||
_row('Hash-Tag', 'CHAIN-EVENT-V3', t),
|
_row('Hash-Tag', 'CHAIN-EVENT-V3', t),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -42,21 +146,120 @@ class HubStatusPage extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Geplante Live-Anbindung', style: t.headlineSmall),
|
Text('Verbindung konfigurieren',
|
||||||
|
style: t.headlineSmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.sm),
|
const SizedBox(height: LawHeatmapSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
'1. `chain serve` startet lokal auf Port 50051.\n'
|
'`chain serve` läuft standardmäßig auf '
|
||||||
'2. Diese App liest `~/.chain/hub-auth-token` und '
|
'127.0.0.1:50051 ohne Auth. '
|
||||||
'verbindet via gRPC.\n'
|
'Bei aktiver Auth den Bearer-Token aus '
|
||||||
'3. Capability `text.akoma_normalize@^0` muss '
|
'~/.chain/hub-auth-token hier eintragen.',
|
||||||
'installiert sein (`chain install text.akoma_normalize`).\n'
|
style: t.labelSmall
|
||||||
'4. Pilot-Flow `flows/durchstich-gewo-14.yaml` läuft '
|
?.copyWith(color: LawHeatmapColors.mute),
|
||||||
'gegen GewO §14.\n'
|
),
|
||||||
'5. Jedes step.completed-Event landet im Audit-Log; '
|
const SizedBox(height: LawHeatmapSpace.lg),
|
||||||
'die Repository-Implementierung tauscht von MockRepository '
|
Row(children: [
|
||||||
'auf HubRepository (gleiches Interface, kein UI-Wechsel).',
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: TextField(
|
||||||
|
controller: _hostCtl,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Host',
|
||||||
|
hintText: '127.0.0.1',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: LawHeatmapSpace.md),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _portCtl,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Port',
|
||||||
|
hintText: '50051',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
const SizedBox(height: LawHeatmapSpace.md),
|
||||||
|
TextField(
|
||||||
|
controller: _tokenCtl,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Auth-Token (optional)',
|
||||||
|
),
|
||||||
|
obscureText: true,
|
||||||
|
),
|
||||||
|
const SizedBox(height: LawHeatmapSpace.md),
|
||||||
|
Row(children: [
|
||||||
|
Switch(
|
||||||
|
value: _secure,
|
||||||
|
onChanged: (v) => setState(() => _secure = v),
|
||||||
|
),
|
||||||
|
const SizedBox(width: LawHeatmapSpace.sm),
|
||||||
|
const Text('TLS (https)'),
|
||||||
|
const SizedBox(width: LawHeatmapSpace.xl),
|
||||||
|
Switch(
|
||||||
|
value: _useHub,
|
||||||
|
onChanged: (v) => setState(() => _useHub = v),
|
||||||
|
),
|
||||||
|
const SizedBox(width: LawHeatmapSpace.sm),
|
||||||
|
const Text('Hub beim Start nutzen'),
|
||||||
|
]),
|
||||||
|
const SizedBox(height: LawHeatmapSpace.lg),
|
||||||
|
Row(children: [
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: _probing ? null : _probe,
|
||||||
|
icon: _probing
|
||||||
|
? const SizedBox(
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.bolt_outlined),
|
||||||
|
label: const Text('Verbindung testen'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: LawHeatmapSpace.md),
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: _save,
|
||||||
|
icon: const Icon(Icons.save_outlined),
|
||||||
|
label: const Text('Speichern'),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
if (_probeMessage != null) ...[
|
||||||
|
const SizedBox(height: LawHeatmapSpace.md),
|
||||||
|
Text(_probeMessage!,
|
||||||
|
style: t.labelLarge?.copyWith(
|
||||||
|
color: LawHeatmapColors.signal,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: LawHeatmapSpace.lg),
|
||||||
|
LawHeatmapCard(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Geplanter Live-Flow', style: t.headlineSmall),
|
||||||
|
const SizedBox(height: LawHeatmapSpace.sm),
|
||||||
|
Text(
|
||||||
|
'1. `chain serve` auf localhost:50051 starten.\n'
|
||||||
|
'2. Module installieren (`chain install`).\n'
|
||||||
|
'3. Flow ausführen:\n'
|
||||||
|
' chain run flows/durchstich-gewo-14.yaml \\\n'
|
||||||
|
' --input norm_url=https://www.gesetze-im-internet.de/gewo/__14.xml \\\n'
|
||||||
|
' --input cohort_id=berlin-kmu\n'
|
||||||
|
'4. `chain admin approvals list` → '
|
||||||
|
'Juristen-Gate freigeben.\n'
|
||||||
|
'5. App neu starten — Hub-Modus aktiv, '
|
||||||
|
'flow.completed-Events werden in '
|
||||||
|
'Evaluation-Objekte übersetzt.',
|
||||||
style: t.bodyLarge?.copyWith(
|
style: t.bodyLarge?.copyWith(
|
||||||
height: 1.6,
|
height: 1.6,
|
||||||
|
fontFamily: LawHeatmapTypography.mono,
|
||||||
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -73,7 +276,7 @@ class HubStatusPage extends StatelessWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 160,
|
width: 200,
|
||||||
child: Text(label,
|
child: Text(label,
|
||||||
style: t.labelLarge
|
style: t.labelLarge
|
||||||
?.copyWith(color: LawHeatmapColors.mute)),
|
?.copyWith(color: LawHeatmapColors.mute)),
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,13 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
chain_client_sdk:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../../fai_chain_client_sdk_dart"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.18.0"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -41,6 +48,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -65,6 +80,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
@ -93,6 +116,62 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
google_cloud:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: google_cloud
|
||||||
|
sha256: b385e20726ef5315d302c5933bfb728103116c5be2d3d17094b01a82da538c1f
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.0"
|
||||||
|
google_identity_services_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: google_identity_services_web
|
||||||
|
sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.3+1"
|
||||||
|
googleapis_auth:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: googleapis_auth
|
||||||
|
sha256: "1417d8846663df5e7b77ca56591c5edd442c66ffc9c01ab036e138a21a148e86"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
grpc:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: grpc
|
||||||
|
sha256: "86be3a7d39ad865b214a7370021ac80e68939238b507730de6d97fc662cb2723"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.0"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
|
http2:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http2
|
||||||
|
sha256: "382d3aefc5bd6dc68c6b892d7664f29b5beb3251611ae946a98d35158a82bbfa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -205,6 +284,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
protobuf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: protobuf
|
||||||
|
sha256: "75ec242d22e950bdcc79ee38dd520ce4ee0bc491d7fadc4ea47694604d22bf06"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -314,6 +401,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.11"
|
version: "0.7.11"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ publish_to: 'none'
|
||||||
version: 0.1.0+1
|
version: 0.1.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.6.0 <4.0.0"
|
sdk: ">=3.11.0 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.24.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -15,10 +15,11 @@ dependencies:
|
||||||
intl: ^0.20.2
|
intl: ^0.20.2
|
||||||
shared_preferences: ^2.3.0
|
shared_preferences: ^2.3.0
|
||||||
|
|
||||||
# Ch∆In wire — same sibling-path pattern as fai_chain_studio.
|
# Ch∆In gRPC wire — sibling-path dep, same pattern as
|
||||||
# Uncomment once the gRPC integration starts (Phase 0, week 1+).
|
# fai_chain_studio. Adapt the chain_client_sdk_dart relative
|
||||||
# chain_client_sdk:
|
# path when this repo is cloned outside the fai/ workspace.
|
||||||
# path: ../../fai_chain_client_sdk_dart
|
chain_client_sdk:
|
||||||
|
path: ../../fai_chain_client_sdk_dart
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue