Compare commits
2 commits
90b7500aaf
...
116310b65b
| Author | SHA1 | Date | |
|---|---|---|---|
| 116310b65b | |||
| e08cedcd3f |
36 changed files with 628 additions and 259 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
# F∆I Law-Heatmap — Machbarkeitsstudie & Umsetzungsplan
|
# Recl∆Im (F∆I) — Machbarkeitsstudie & Umsetzungsplan
|
||||||
|
|
||||||
> Arbeitstitel des Systems: **„Gesetzes-Heatmap" / „Regulierungs-Wirkungsanalyse auf Ch∆In-Basis"**
|
> Arbeitstitel des Systems: **„Gesetzes-Heatmap" / „Regulierungs-Wirkungsanalyse auf Ch∆In-Basis"**
|
||||||
> Auftraggeber-Kontext: MIT AG Digitalisierung & KI-Strategien (Vorsitz Dr. S. Flemming),
|
> Auftraggeber-Kontext: MIT AG Digitalisierung & KI-Strategien (Vorsitz Dr. S. Flemming),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Methodik-Papier — Pflicht-basierte Wirkungsanalyse von Rechtsnormen
|
# Methodik-Papier — Pflicht-basierte Wirkungsanalyse von Rechtsnormen
|
||||||
|
|
||||||
> Standalone-Auszug aus der Machbarkeitsstudie F∆I Law-Heatmap,
|
> Standalone-Auszug aus der Machbarkeitsstudie Recl∆Im (F∆I),
|
||||||
> destilliert für AG-Vorlage und Beirats-Konsultation.
|
> destilliert für AG-Vorlage und Beirats-Konsultation.
|
||||||
>
|
>
|
||||||
> Version v0.1.0 · 2026-06 · Diskussionsgrundlage, AG-intern.
|
> Version v0.1.0 · 2026-06 · Diskussionsgrundlage, AG-intern.
|
||||||
|
|
|
||||||
4
RUN.md
4
RUN.md
|
|
@ -1,4 +1,4 @@
|
||||||
# F∆I Law-Heatmap — Run-Guide (Phase 0)
|
# Recl∆Im (F∆I) — Run-Guide (Phase 0)
|
||||||
|
|
||||||
> Stand v0.1.0 — Demo-Modus, ohne externe Daten, ohne Hub-Verbindung.
|
> Stand v0.1.0 — Demo-Modus, ohne externe Daten, ohne Hub-Verbindung.
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ eigene Crate-Verzeichnisse, ABI `chain:platform`, alle Apache 2.0:
|
||||||
cd app
|
cd app
|
||||||
./setup.sh # Fonts + flutter pub get
|
./setup.sh # Fonts + flutter pub get
|
||||||
./build-macos.sh # Robust-Build (strippt macOS-xattrs vor CodeSign)
|
./build-macos.sh # Robust-Build (strippt macOS-xattrs vor CodeSign)
|
||||||
open build/macos/Build/Products/Debug/lawheatmap_app.app
|
open build/macos/Build/Products/Debug/reclaim_app.app
|
||||||
```
|
```
|
||||||
|
|
||||||
Direkter `flutter run -d macos` funktioniert auch, kann aber bei
|
Direkter `flutter run -d macos` funktioniert auch, kann aber bei
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# lawheatmap_app
|
# reclaim_app
|
||||||
|
|
||||||
F∆I Law-Heatmap client
|
Recl∆Im (F∆I) client
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Robust macOS build for the F∆I Law-Heatmap client.
|
# Robust macOS build for the Recl∆Im (F∆I) client.
|
||||||
#
|
#
|
||||||
# macOS xattrs (especially `com.apple.provenance` on files that
|
# macOS xattrs (especially `com.apple.provenance` on files that
|
||||||
# touched iCloud or were downloaded by curl) make Xcode's
|
# touched iCloud or were downloaded by curl) make Xcode's
|
||||||
|
|
@ -30,7 +30,7 @@ flutter build macos --debug \
|
||||||
echo " flutter exited $rc — continuing to xattr+resign step in case the binary linked"
|
echo " flutter exited $rc — continuing to xattr+resign step in case the binary linked"
|
||||||
}
|
}
|
||||||
|
|
||||||
APP=build/macos/Build/Products/Debug/lawheatmap_app.app
|
APP=build/macos/Build/Products/Debug/reclaim_app.app
|
||||||
if [ ! -d "$APP" ]; then
|
if [ ! -d "$APP" ]; then
|
||||||
echo "ERROR: $APP does not exist — flutter build failed before linking"
|
echo "ERROR: $APP does not exist — flutter build failed before linking"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
@ -45,4 +45,4 @@ echo "[4/4] ad-hoc resign"
|
||||||
echo
|
echo
|
||||||
echo "done. Launch with:"
|
echo "done. Launch with:"
|
||||||
echo " open $APP"
|
echo " open $APP"
|
||||||
echo " # or directly: $APP/Contents/MacOS/lawheatmap_app"
|
echo " # or directly: $APP/Contents/MacOS/reclaim_app"
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,26 @@ class Fixtures {
|
||||||
standDate: DateTime.utc(2025, 12, 1),
|
standDate: DateTime.utc(2025, 12, 1),
|
||||||
sourceUrl: 'https://www.gesetze-im-internet.de/gewo/__14.html',
|
sourceUrl: 'https://www.gesetze-im-internet.de/gewo/__14.html',
|
||||||
sourceSha256: 'demo-${'a' * 60}',
|
sourceSha256: 'demo-${'a' * 60}',
|
||||||
|
paragraphs: const [
|
||||||
|
NormParagraph(
|
||||||
|
number: '(1)',
|
||||||
|
text: 'Wer den selbständigen Betrieb eines stehenden Gewerbes, '
|
||||||
|
'einer Zweigniederlassung oder einer unselbständigen '
|
||||||
|
'Zweigstelle anfängt, muss dies der zuständigen Behörde '
|
||||||
|
'gleichzeitig anzeigen. Das Gleiche gilt, wenn '
|
||||||
|
'1. der Betrieb verlegt wird, '
|
||||||
|
'2. der Gegenstand des Gewerbes gewechselt oder '
|
||||||
|
'auf Waren oder Leistungen ausgedehnt wird, die bei '
|
||||||
|
'Gewerbebetrieben der angemeldeten Art nicht geschäftsüblich '
|
||||||
|
'sind, oder '
|
||||||
|
'3. der Betrieb aufgegeben wird.',
|
||||||
|
),
|
||||||
|
NormParagraph(
|
||||||
|
number: '(2)',
|
||||||
|
text: 'Die Anzeige hat unverzüglich zu erfolgen. Sie ist nach '
|
||||||
|
'amtlich vorgeschriebenem Vordruck zu erstatten.',
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
skmEurPerYear: 2_800_000,
|
skmEurPerYear: 2_800_000,
|
||||||
benefitScore: 2.5,
|
benefitScore: 2.5,
|
||||||
|
|
@ -78,6 +98,30 @@ class Fixtures {
|
||||||
sourceUrl:
|
sourceUrl:
|
||||||
'https://www.gesetze-im-internet.de/kassensichv/index.html',
|
'https://www.gesetze-im-internet.de/kassensichv/index.html',
|
||||||
sourceSha256: 'demo-${'b' * 60}',
|
sourceSha256: 'demo-${'b' * 60}',
|
||||||
|
paragraphs: const [
|
||||||
|
NormParagraph(
|
||||||
|
number: '§ 1',
|
||||||
|
text: 'Diese Verordnung gilt für elektronische '
|
||||||
|
'Aufzeichnungssysteme im Sinne des § 146a Absatz 1 '
|
||||||
|
'Satz 1 der Abgabenordnung, mit denen aufzeichnungspflichtige '
|
||||||
|
'Geschäftsvorfälle oder andere Vorgänge erfasst werden.',
|
||||||
|
),
|
||||||
|
NormParagraph(
|
||||||
|
number: '§ 2',
|
||||||
|
text: 'Das elektronische Aufzeichnungssystem muss jeden '
|
||||||
|
'einzelnen aufzeichnungspflichtigen Vorgang vollständig '
|
||||||
|
'aufzeichnen. Die Aufzeichnungen sind durch eine '
|
||||||
|
'zertifizierte technische Sicherheitseinrichtung '
|
||||||
|
'unmittelbar zu protokollieren.',
|
||||||
|
),
|
||||||
|
NormParagraph(
|
||||||
|
number: '§ 3',
|
||||||
|
text: 'Den Steuerpflichtigen treffen Mitteilungspflichten '
|
||||||
|
'gegenüber dem zuständigen Finanzamt; der Ausfall einer '
|
||||||
|
'zertifizierten technischen Sicherheitseinrichtung ist '
|
||||||
|
'unverzüglich anzuzeigen.',
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
skmEurPerYear: 25_000_000,
|
skmEurPerYear: 25_000_000,
|
||||||
benefitScore: 3.0,
|
benefitScore: 3.0,
|
||||||
|
|
@ -136,6 +180,48 @@ class Fixtures {
|
||||||
standDate: DateTime.utc(2025, 10, 1),
|
standDate: DateTime.utc(2025, 10, 1),
|
||||||
sourceUrl: 'https://eur-lex.europa.eu/eli/reg/2016/679',
|
sourceUrl: 'https://eur-lex.europa.eu/eli/reg/2016/679',
|
||||||
sourceSha256: 'demo-${'c' * 60}',
|
sourceSha256: 'demo-${'c' * 60}',
|
||||||
|
paragraphs: const [
|
||||||
|
NormParagraph(
|
||||||
|
number: '(1)',
|
||||||
|
text: 'Jeder Verantwortliche und gegebenenfalls sein Vertreter '
|
||||||
|
'führen ein Verzeichnis aller Verarbeitungstätigkeiten, '
|
||||||
|
'die ihrer Zuständigkeit unterliegen. Dieses Verzeichnis '
|
||||||
|
'enthält sämtliche folgenden Angaben: a) den Namen und '
|
||||||
|
'die Kontaktdaten des Verantwortlichen … b) die Zwecke '
|
||||||
|
'der Verarbeitung; c) eine Beschreibung der Kategorien '
|
||||||
|
'betroffener Personen und der Kategorien personenbezogener '
|
||||||
|
'Daten; d) die Kategorien von Empfängern …',
|
||||||
|
),
|
||||||
|
NormParagraph(
|
||||||
|
number: '(2)',
|
||||||
|
text: 'Jeder Auftragsverarbeiter und gegebenenfalls sein '
|
||||||
|
'Vertreter führen ein Verzeichnis zu allen Kategorien von '
|
||||||
|
'im Auftrag eines Verantwortlichen durchgeführten '
|
||||||
|
'Verarbeitungstätigkeiten.',
|
||||||
|
),
|
||||||
|
NormParagraph(
|
||||||
|
number: '(3)',
|
||||||
|
text: 'Das in den Absätzen 1 und 2 genannte Verzeichnis ist '
|
||||||
|
'schriftlich zu führen, was auch in einem elektronischen '
|
||||||
|
'Format erfolgen kann.',
|
||||||
|
),
|
||||||
|
NormParagraph(
|
||||||
|
number: '(4)',
|
||||||
|
text: 'Der Verantwortliche oder der Auftragsverarbeiter sowie '
|
||||||
|
'gegebenenfalls deren Vertreter stellen das Verzeichnis '
|
||||||
|
'der Aufsichtsbehörde auf Anfrage zur Verfügung.',
|
||||||
|
),
|
||||||
|
NormParagraph(
|
||||||
|
number: '(5)',
|
||||||
|
text: 'Die in den Absätzen 1 und 2 genannten Pflichten gelten '
|
||||||
|
'nicht für Unternehmen oder Einrichtungen, die weniger als '
|
||||||
|
'250 Mitarbeiter beschäftigen, es sei denn die von ihnen '
|
||||||
|
'vorgenommene Verarbeitung birgt ein Risiko für die Rechte '
|
||||||
|
'und Freiheiten der betroffenen Personen, die Verarbeitung '
|
||||||
|
'erfolgt nicht nur gelegentlich oder umfasst besondere '
|
||||||
|
'Datenkategorien gemäß Artikel 9 Absatz 1 …',
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
skmEurPerYear: 450_000_000,
|
skmEurPerYear: 450_000_000,
|
||||||
benefitScore: 3.8,
|
benefitScore: 3.8,
|
||||||
|
|
@ -185,6 +271,31 @@ class Fixtures {
|
||||||
sourceUrl:
|
sourceUrl:
|
||||||
'https://gesetze.berlin.de/bsbe/document/jlr-BauOBE2005V25P60',
|
'https://gesetze.berlin.de/bsbe/document/jlr-BauOBE2005V25P60',
|
||||||
sourceSha256: 'demo-${'d' * 60}',
|
sourceSha256: 'demo-${'d' * 60}',
|
||||||
|
paragraphs: const [
|
||||||
|
NormParagraph(
|
||||||
|
number: '§ 60 (1)',
|
||||||
|
text: 'Für die Errichtung, Änderung und Nutzungsänderung '
|
||||||
|
'baulicher Anlagen sowie anderer Anlagen und '
|
||||||
|
'Einrichtungen im Sinne des § 1 Absatz 1 Satz 2 ist die '
|
||||||
|
'Baugenehmigung erforderlich, soweit in den §§ 61 bis 65 '
|
||||||
|
'sowie 76 und 77 nichts anderes bestimmt ist.',
|
||||||
|
),
|
||||||
|
NormParagraph(
|
||||||
|
number: '§ 60 (2)',
|
||||||
|
text: 'Bauliche Anlagen und andere Anlagen, an die in dieser '
|
||||||
|
'Bauordnung oder in Vorschriften aufgrund dieser '
|
||||||
|
'Bauordnung Anforderungen gestellt werden, dürfen nur so '
|
||||||
|
'errichtet, geändert und in der Nutzung geändert werden, '
|
||||||
|
'dass sie diesen Anforderungen entsprechen.',
|
||||||
|
),
|
||||||
|
NormParagraph(
|
||||||
|
number: '§ 63',
|
||||||
|
text: 'Der Bauantrag ist schriftlich bei der Bauaufsichtsbehörde '
|
||||||
|
'unter Beifügung der für die Beurteilung des Vorhabens und '
|
||||||
|
'die Bearbeitung des Bauantrags erforderlichen Bauvorlagen '
|
||||||
|
'einzureichen.',
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
skmEurPerYear: 72_000_000,
|
skmEurPerYear: 72_000_000,
|
||||||
benefitScore: 3.5,
|
benefitScore: 3.5,
|
||||||
|
|
@ -233,6 +344,30 @@ class Fixtures {
|
||||||
standDate: DateTime.utc(2025, 11, 1),
|
standDate: DateTime.utc(2025, 11, 1),
|
||||||
sourceUrl: 'https://www.gesetze-im-internet.de/milog/__17.html',
|
sourceUrl: 'https://www.gesetze-im-internet.de/milog/__17.html',
|
||||||
sourceSha256: 'demo-${'e' * 60}',
|
sourceSha256: 'demo-${'e' * 60}',
|
||||||
|
paragraphs: const [
|
||||||
|
NormParagraph(
|
||||||
|
number: '(1)',
|
||||||
|
text: 'Ein Arbeitgeber, der Arbeitnehmerinnen und Arbeitnehmer '
|
||||||
|
'nach § 8 Absatz 1 des Vierten Buches Sozialgesetzbuch '
|
||||||
|
'geringfügig beschäftigt oder in den in § 2a des '
|
||||||
|
'Schwarzarbeitsbekämpfungsgesetzes genannten '
|
||||||
|
'Wirtschaftsbereichen oder Wirtschaftszweigen Personen '
|
||||||
|
'beschäftigt, ist verpflichtet, Beginn, Ende und Dauer der '
|
||||||
|
'täglichen Arbeitszeit dieser Arbeitnehmerinnen und '
|
||||||
|
'Arbeitnehmer spätestens bis zum Ablauf des siebten auf '
|
||||||
|
'den Tag der Arbeitsleistung folgenden Kalendertages '
|
||||||
|
'aufzuzeichnen und diese Aufzeichnungen mindestens zwei '
|
||||||
|
'Jahre beginnend ab dem für die Aufzeichnung maßgeblichen '
|
||||||
|
'Zeitpunkt aufzubewahren.',
|
||||||
|
),
|
||||||
|
NormParagraph(
|
||||||
|
number: '(2)',
|
||||||
|
text: 'Arbeitgeber im Sinne des Absatzes 1 haben die für die '
|
||||||
|
'Kontrolle der Einhaltung der Verpflichtungen nach § 20 '
|
||||||
|
'in Verbindung mit § 2 Absatz 1 erforderlichen Unterlagen '
|
||||||
|
'im Inland in deutscher Sprache bereitzuhalten.',
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
skmEurPerYear: 270_000_000,
|
skmEurPerYear: 270_000_000,
|
||||||
benefitScore: 3.2,
|
benefitScore: 3.2,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Domain model for the F∆I Law-Heatmap client.
|
// Domain model for the F∆I Recl∆Im client.
|
||||||
//
|
//
|
||||||
// These types are the lingua franca between the (eventually
|
// These types are the lingua franca between the (eventually
|
||||||
// gRPC-fed) repository and the UI. Names mirror the Studie's
|
// gRPC-fed) repository and the UI. Names mirror the Studie's
|
||||||
|
|
@ -75,6 +75,16 @@ class Duty {
|
||||||
final String? consequence;
|
final String? consequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One Absatz/paragraph of the actual normative text.
|
||||||
|
class NormParagraph {
|
||||||
|
const NormParagraph({required this.number, required this.text});
|
||||||
|
|
||||||
|
/// Source numbering as it appears, e.g. "(1)", "(2a)". Empty
|
||||||
|
/// when the source has no Absatz numbering.
|
||||||
|
final String number;
|
||||||
|
final String text;
|
||||||
|
}
|
||||||
|
|
||||||
/// One norm version — a versioned legal text identified by its
|
/// One norm version — a versioned legal text identified by its
|
||||||
/// (synthetic for now) ELI/CELEX.
|
/// (synthetic for now) ELI/CELEX.
|
||||||
class Norm {
|
class Norm {
|
||||||
|
|
@ -86,6 +96,7 @@ class Norm {
|
||||||
required this.standDate,
|
required this.standDate,
|
||||||
required this.sourceUrl,
|
required this.sourceUrl,
|
||||||
required this.sourceSha256,
|
required this.sourceSha256,
|
||||||
|
this.paragraphs = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
final String eli;
|
final String eli;
|
||||||
|
|
@ -95,6 +106,12 @@ class Norm {
|
||||||
final DateTime standDate;
|
final DateTime standDate;
|
||||||
final String sourceUrl;
|
final String sourceUrl;
|
||||||
final String sourceSha256;
|
final String sourceSha256;
|
||||||
|
|
||||||
|
/// The actual paragraphs of the norm text, normalised by
|
||||||
|
/// `text.akoma_normalize` in live mode. In demo mode the
|
||||||
|
/// fixture quotes the public source verbatim under §5 UrhG
|
||||||
|
/// (amtliche Werke).
|
||||||
|
final List<NormParagraph> paragraphs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A heatmap point: one norm with its computed scores.
|
/// A heatmap point: one norm with its computed scores.
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ import 'data/hub_endpoint.dart';
|
||||||
import 'data/hub_repository.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/reclaim_theme.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
final settings = await HubSettings.load();
|
final settings = await HubSettings.load();
|
||||||
final repository = await _pickRepository(settings);
|
final repository = await _pickRepository(settings);
|
||||||
runApp(LawHeatmapApp(repository: repository, settings: settings));
|
runApp(ReclaimApp(repository: repository, settings: settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decide which repository to start with.
|
/// Decide which repository to start with.
|
||||||
|
|
@ -28,8 +28,8 @@ Future<EvaluationRepository> _pickRepository(HubSettings settings) async {
|
||||||
return const MockRepository();
|
return const MockRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
class LawHeatmapApp extends StatelessWidget {
|
class ReclaimApp extends StatelessWidget {
|
||||||
const LawHeatmapApp({
|
const ReclaimApp({
|
||||||
super.key,
|
super.key,
|
||||||
this.repository = const MockRepository(),
|
this.repository = const MockRepository(),
|
||||||
this.settings,
|
this.settings,
|
||||||
|
|
@ -41,10 +41,10 @@ class LawHeatmapApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'F∆I Law-Heatmap',
|
title: 'Recl∆Im',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: LawHeatmapTheme.light(),
|
theme: ReclaimTheme.light(),
|
||||||
darkTheme: LawHeatmapTheme.dark(),
|
darkTheme: ReclaimTheme.dark(),
|
||||||
themeMode: ThemeMode.dark,
|
themeMode: ThemeMode.dark,
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: const [
|
||||||
GlobalMaterialLocalizations.delegate,
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import '../widgets/lawheatmap_card.dart';
|
import '../widgets/reclaim_card.dart';
|
||||||
|
|
||||||
class AboutPage extends StatelessWidget {
|
class AboutPage extends StatelessWidget {
|
||||||
const AboutPage({super.key});
|
const AboutPage({super.key});
|
||||||
|
|
@ -10,25 +10,25 @@ class AboutPage extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Theme.of(context).textTheme;
|
final t = Theme.of(context).textTheme;
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(LawHeatmapSpace.xl),
|
padding: const EdgeInsets.all(ReclaimSpace.xl),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Methodik & Quellen', style: t.displaySmall),
|
Text('Methodik & Quellen', style: t.displaySmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.xs),
|
const SizedBox(height: ReclaimSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
'Wie das System bewertet, woran es sich messen lässt.',
|
'Wie das System bewertet, woran es sich messen lässt.',
|
||||||
style: t.bodyLarge?.copyWith(color: LawHeatmapColors.mute),
|
style: t.bodyLarge?.copyWith(color: ReclaimColors.mute),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
LawHeatmapCard(
|
ReclaimCard(
|
||||||
accent: true,
|
accent: true,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Drei nicht verhandelbare Sätze',
|
Text('Drei nicht verhandelbare Sätze',
|
||||||
style: t.headlineSmall),
|
style: t.headlineSmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.md),
|
const SizedBox(height: ReclaimSpace.md),
|
||||||
Text(
|
Text(
|
||||||
'1. Wir bewerten Pflichten, nicht Gesetze.\n'
|
'1. Wir bewerten Pflichten, nicht Gesetze.\n'
|
||||||
'2. Jede Zahl trägt die Tier-Stufe ihres schwächsten '
|
'2. Jede Zahl trägt die Tier-Stufe ihres schwächsten '
|
||||||
|
|
@ -40,13 +40,13 @@ class AboutPage extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
LawHeatmapCard(
|
ReclaimCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Methodische Vorbilder', style: t.headlineSmall),
|
Text('Methodische Vorbilder', style: t.headlineSmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
_bullet(t,
|
_bullet(t,
|
||||||
'Standardkostenmodell (SKM) — Nationaler Normenkontrollrat. P × F × T × h.'),
|
'Standardkostenmodell (SKM) — Nationaler Normenkontrollrat. P × F × T × h.'),
|
||||||
_bullet(t,
|
_bullet(t,
|
||||||
|
|
@ -62,13 +62,13 @@ class AboutPage extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
LawHeatmapCard(
|
ReclaimCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('4-Tier-Evidenz-Modell', style: t.headlineSmall),
|
Text('4-Tier-Evidenz-Modell', style: t.headlineSmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
_bullet(t,
|
_bullet(t,
|
||||||
'T1 — amtlich/peer-reviewed: NKR, DESTATIS, ifo, ZEW, BMWK, SVR, IfM Bonn.'),
|
'T1 — amtlich/peer-reviewed: NKR, DESTATIS, ifo, ZEW, BMWK, SVR, IfM Bonn.'),
|
||||||
_bullet(t,
|
_bullet(t,
|
||||||
|
|
@ -80,13 +80,13 @@ class AboutPage extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
LawHeatmapCard(
|
ReclaimCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Plattform', style: t.headlineSmall),
|
Text('Plattform', style: t.headlineSmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
'Ch∆In v0.16.0 — Vendor: F∆I = Flemming.AI. '
|
'Ch∆In v0.16.0 — Vendor: F∆I = Flemming.AI. '
|
||||||
'Hub. Module. Flow. Deterministisch, WASM-sandboxed, '
|
'Hub. Module. Flow. Deterministisch, WASM-sandboxed, '
|
||||||
|
|
@ -104,7 +104,7 @@ class AboutPage extends StatelessWidget {
|
||||||
|
|
||||||
Widget _bullet(TextTheme t, String text) {
|
Widget _bullet(TextTheme t, String text) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: LawHeatmapSpace.sm),
|
padding: const EdgeInsets.only(bottom: ReclaimSpace.sm),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -114,7 +114,7 @@ class AboutPage extends StatelessWidget {
|
||||||
width: 5,
|
width: 5,
|
||||||
height: 5,
|
height: 5,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: LawHeatmapColors.signal,
|
color: ReclaimColors.signal,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../data/models.dart';
|
import '../data/models.dart';
|
||||||
import '../data/repository.dart';
|
import '../data/repository.dart';
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import '../widgets/demo_banner.dart';
|
import '../widgets/demo_banner.dart';
|
||||||
import '../widgets/heatmap_grid.dart';
|
import '../widgets/heatmap_grid.dart';
|
||||||
import '../widgets/lawheatmap_card.dart';
|
import '../widgets/reclaim_card.dart';
|
||||||
import '../widgets/tier_badge.dart';
|
import '../widgets/tier_badge.dart';
|
||||||
import 'norm_detail_page.dart';
|
import 'norm_detail_page.dart';
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
||||||
const DemoBanner(),
|
const DemoBanner(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(LawHeatmapSpace.xl),
|
padding: const EdgeInsets.all(ReclaimSpace.xl),
|
||||||
child: FutureBuilder<List<Evaluation>>(
|
child: FutureBuilder<List<Evaluation>>(
|
||||||
future: _future,
|
future: _future,
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
|
|
@ -48,18 +48,18 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Heatmap', style: t.displaySmall),
|
Text('Heatmap', style: t.displaySmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.xs),
|
const SizedBox(height: ReclaimSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
'X: jährliche Bürokratiekosten (€, log10). '
|
'X: jährliche Bürokratiekosten (€, log10). '
|
||||||
'Y: Nutzen-Score 0–5. Punktgröße: Betroffenheit. '
|
'Y: Nutzen-Score 0–5. Punktgröße: Betroffenheit. '
|
||||||
'Klick auf einen Punkt → Detail mit Pflichten + Quellen.',
|
'Klick auf einen Punkt → Detail mit Pflichten + Quellen.',
|
||||||
style: t.bodyLarge?.copyWith(
|
style: t.bodyLarge?.copyWith(
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: LawHeatmapCard(
|
child: ReclaimCard(
|
||||||
accent: true,
|
accent: true,
|
||||||
expand: true,
|
expand: true,
|
||||||
child: HeatmapGrid(
|
child: HeatmapGrid(
|
||||||
|
|
@ -77,10 +77,10 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: LawHeatmapSpace.md,
|
spacing: ReclaimSpace.md,
|
||||||
runSpacing: LawHeatmapSpace.sm,
|
runSpacing: ReclaimSpace.sm,
|
||||||
children: [
|
children: [
|
||||||
for (final e in items)
|
for (final e in items)
|
||||||
InkWell(
|
InkWell(
|
||||||
|
|
@ -94,10 +94,10 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: LawHeatmapCard(
|
child: ReclaimCard(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: LawHeatmapSpace.md,
|
horizontal: ReclaimSpace.md,
|
||||||
vertical: LawHeatmapSpace.sm,
|
vertical: ReclaimSpace.sm,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
@ -105,7 +105,7 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
||||||
TierBadge(
|
TierBadge(
|
||||||
tier: e.tierLowest, compact: true),
|
tier: e.tierLowest, compact: true),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: LawHeatmapSpace.sm),
|
width: ReclaimSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
'${e.norm.jurabk} ${e.norm.paragraph}',
|
'${e.norm.jurabk} ${e.norm.paragraph}',
|
||||||
style: t.labelLarge,
|
style: t.labelLarge,
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import 'package:flutter/material.dart';
|
||||||
import '../data/hub_endpoint.dart';
|
import '../data/hub_endpoint.dart';
|
||||||
import '../data/hub_repository.dart';
|
import '../data/hub_repository.dart';
|
||||||
import '../data/repository.dart';
|
import '../data/repository.dart';
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import '../widgets/hub_capabilities_card.dart';
|
import '../widgets/hub_capabilities_card.dart';
|
||||||
import '../widgets/lawheatmap_card.dart';
|
import '../widgets/reclaim_card.dart';
|
||||||
|
|
||||||
/// Hub connection + settings. Read-only summary at the top, then
|
/// Hub connection + settings. Read-only summary at the top, then
|
||||||
/// an editable form for endpoint host/port/secure/token, then a
|
/// an editable form for endpoint host/port/secure/token, then a
|
||||||
|
|
@ -109,18 +109,18 @@ class _HubStatusPageState extends State<HubStatusPage> {
|
||||||
final repo = widget.repository;
|
final repo = widget.repository;
|
||||||
final s = _settings;
|
final s = _settings;
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(LawHeatmapSpace.xl),
|
padding: const EdgeInsets.all(ReclaimSpace.xl),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Ch∆In Hub', style: t.displaySmall),
|
Text('Ch∆In Hub', style: t.displaySmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.xs),
|
const SizedBox(height: ReclaimSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
'Status der Verbindung und Konfiguration des lokalen Hubs.',
|
'Status der Verbindung und Konfiguration des lokalen Hubs.',
|
||||||
style: t.bodyLarge?.copyWith(color: LawHeatmapColors.mute),
|
style: t.bodyLarge?.copyWith(color: ReclaimColors.mute),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
LawHeatmapCard(
|
ReclaimCard(
|
||||||
accent: true,
|
accent: true,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -142,23 +142,23 @@ class _HubStatusPageState extends State<HubStatusPage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
LawHeatmapCard(
|
ReclaimCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Verbindung konfigurieren',
|
Text('Verbindung konfigurieren',
|
||||||
style: t.headlineSmall),
|
style: t.headlineSmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
'`chain serve` läuft standardmäßig auf '
|
'`chain serve` läuft standardmäßig auf '
|
||||||
'127.0.0.1:50051 ohne Auth. '
|
'127.0.0.1:50051 ohne Auth. '
|
||||||
'Bei aktiver Auth den Bearer-Token aus '
|
'Bei aktiver Auth den Bearer-Token aus '
|
||||||
'~/.chain/hub-auth-token hier eintragen.',
|
'~/.chain/hub-auth-token hier eintragen.',
|
||||||
style: t.labelSmall
|
style: t.labelSmall
|
||||||
?.copyWith(color: LawHeatmapColors.mute),
|
?.copyWith(color: ReclaimColors.mute),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
|
|
@ -170,7 +170,7 @@ class _HubStatusPageState extends State<HubStatusPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: LawHeatmapSpace.md),
|
const SizedBox(width: ReclaimSpace.md),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _portCtl,
|
controller: _portCtl,
|
||||||
|
|
@ -182,7 +182,7 @@ class _HubStatusPageState extends State<HubStatusPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: LawHeatmapSpace.md),
|
const SizedBox(height: ReclaimSpace.md),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _tokenCtl,
|
controller: _tokenCtl,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
|
|
@ -190,23 +190,23 @@ class _HubStatusPageState extends State<HubStatusPage> {
|
||||||
),
|
),
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.md),
|
const SizedBox(height: ReclaimSpace.md),
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Switch(
|
Switch(
|
||||||
value: _secure,
|
value: _secure,
|
||||||
onChanged: (v) => setState(() => _secure = v),
|
onChanged: (v) => setState(() => _secure = v),
|
||||||
),
|
),
|
||||||
const SizedBox(width: LawHeatmapSpace.sm),
|
const SizedBox(width: ReclaimSpace.sm),
|
||||||
const Text('TLS (https)'),
|
const Text('TLS (https)'),
|
||||||
const SizedBox(width: LawHeatmapSpace.xl),
|
const SizedBox(width: ReclaimSpace.xl),
|
||||||
Switch(
|
Switch(
|
||||||
value: _useHub,
|
value: _useHub,
|
||||||
onChanged: (v) => setState(() => _useHub = v),
|
onChanged: (v) => setState(() => _useHub = v),
|
||||||
),
|
),
|
||||||
const SizedBox(width: LawHeatmapSpace.sm),
|
const SizedBox(width: ReclaimSpace.sm),
|
||||||
const Text('Hub beim Start nutzen'),
|
const Text('Hub beim Start nutzen'),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
Row(children: [
|
Row(children: [
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: _probing ? null : _probe,
|
onPressed: _probing ? null : _probe,
|
||||||
|
|
@ -221,7 +221,7 @@ class _HubStatusPageState extends State<HubStatusPage> {
|
||||||
: const Icon(Icons.bolt_outlined),
|
: const Icon(Icons.bolt_outlined),
|
||||||
label: const Text('Verbindung testen'),
|
label: const Text('Verbindung testen'),
|
||||||
),
|
),
|
||||||
const SizedBox(width: LawHeatmapSpace.md),
|
const SizedBox(width: ReclaimSpace.md),
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: _save,
|
onPressed: _save,
|
||||||
icon: const Icon(Icons.save_outlined),
|
icon: const Icon(Icons.save_outlined),
|
||||||
|
|
@ -229,24 +229,24 @@ class _HubStatusPageState extends State<HubStatusPage> {
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
if (_probeMessage != null) ...[
|
if (_probeMessage != null) ...[
|
||||||
const SizedBox(height: LawHeatmapSpace.md),
|
const SizedBox(height: ReclaimSpace.md),
|
||||||
Text(_probeMessage!,
|
Text(_probeMessage!,
|
||||||
style: t.labelLarge?.copyWith(
|
style: t.labelLarge?.copyWith(
|
||||||
color: LawHeatmapColors.signal,
|
color: ReclaimColors.signal,
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
HubCapabilitiesCard(repository: repo),
|
HubCapabilitiesCard(repository: repo),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
LawHeatmapCard(
|
ReclaimCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Geplanter Live-Flow', style: t.headlineSmall),
|
Text('Geplanter Live-Flow', style: t.headlineSmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
'1. `chain serve` auf localhost:50051 starten.\n'
|
'1. `chain serve` auf localhost:50051 starten.\n'
|
||||||
'2. Module installieren (`chain install`).\n'
|
'2. Module installieren (`chain install`).\n'
|
||||||
|
|
@ -261,7 +261,7 @@ class _HubStatusPageState extends State<HubStatusPage> {
|
||||||
'Evaluation-Objekte übersetzt.',
|
'Evaluation-Objekte übersetzt.',
|
||||||
style: t.bodyLarge?.copyWith(
|
style: t.bodyLarge?.copyWith(
|
||||||
height: 1.6,
|
height: 1.6,
|
||||||
fontFamily: LawHeatmapTypography.mono,
|
fontFamily: ReclaimTypography.mono,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -282,12 +282,12 @@ class _HubStatusPageState extends State<HubStatusPage> {
|
||||||
width: 200,
|
width: 200,
|
||||||
child: Text(label,
|
child: Text(label,
|
||||||
style: t.labelLarge
|
style: t.labelLarge
|
||||||
?.copyWith(color: LawHeatmapColors.mute)),
|
?.copyWith(color: ReclaimColors.mute)),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(value,
|
child: Text(value,
|
||||||
style: t.bodyLarge
|
style: t.bodyLarge
|
||||||
?.copyWith(fontFamily: LawHeatmapTypography.mono)),
|
?.copyWith(fontFamily: ReclaimTypography.mono)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../data/repository.dart';
|
import '../data/repository.dart';
|
||||||
import '../theme/lawheatmap_theme.dart';
|
import '../theme/reclaim_theme.dart';
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import '../widgets/delta_mark.dart';
|
import '../widgets/delta_mark.dart';
|
||||||
import 'shell_page.dart';
|
import 'shell_page.dart';
|
||||||
|
|
||||||
|
|
@ -23,28 +23,28 @@ class LandingPage extends StatelessWidget {
|
||||||
constraints: const BoxConstraints(maxWidth: 720),
|
constraints: const BoxConstraints(maxWidth: 720),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: LawHeatmapSpace.xxl,
|
horizontal: ReclaimSpace.xxl,
|
||||||
vertical: LawHeatmapSpace.xxxl,
|
vertical: ReclaimSpace.xxxl,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const DeltaMark(size: 96),
|
const DeltaMark(size: 96),
|
||||||
const SizedBox(height: LawHeatmapSpace.xxl),
|
const SizedBox(height: ReclaimSpace.xxl),
|
||||||
Text('F∆I Law-Heatmap', style: t.displaySmall),
|
Text('Recl∆Im', style: t.displaySmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
'Pflicht-basierte Wirkungsanalyse von Rechtsnormen — '
|
'Pflicht-basierte Wirkungsanalyse von Rechtsnormen — '
|
||||||
'auf Ch∆In, mit Quellen-Audit by construction.',
|
'auf Ch∆In, mit Quellen-Audit by construction.',
|
||||||
style: t.bodyLarge
|
style: t.bodyLarge
|
||||||
?.copyWith(color: LawHeatmapColors.mute),
|
?.copyWith(color: ReclaimColors.mute),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.xxl),
|
const SizedBox(height: ReclaimSpace.xxl),
|
||||||
Wrap(
|
Wrap(
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
spacing: LawHeatmapSpace.md,
|
spacing: ReclaimSpace.md,
|
||||||
runSpacing: LawHeatmapSpace.sm,
|
runSpacing: ReclaimSpace.sm,
|
||||||
children: [
|
children: [
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|
@ -61,18 +61,18 @@ class LandingPage extends StatelessWidget {
|
||||||
Text(
|
Text(
|
||||||
'Demo: 5 Pilot-Normen, ohne Hub-Verbindung',
|
'Demo: 5 Pilot-Normen, ohne Hub-Verbindung',
|
||||||
style: t.labelSmall?.copyWith(
|
style: t.labelSmall?.copyWith(
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.xl),
|
const SizedBox(height: ReclaimSpace.xl),
|
||||||
Text(
|
Text(
|
||||||
'v0.1.0 · Phase 0 · Mock-Modus · '
|
'v0.1.0 · Phase 0 · Mock-Modus · '
|
||||||
'Live-Hub-Anbindung folgt in Woche 1',
|
'Live-Hub-Anbindung folgt in Woche 1',
|
||||||
style: LawHeatmapTheme.mono(
|
style: ReclaimTheme.mono(
|
||||||
t.labelSmall ?? const TextStyle(),
|
t.labelSmall ?? const TextStyle(),
|
||||||
).copyWith(color: LawHeatmapColors.mute),
|
).copyWith(color: ReclaimColors.mute),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../data/models.dart';
|
import '../data/models.dart';
|
||||||
import '../data/repository.dart';
|
import '../data/repository.dart';
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import '../widgets/demo_banner.dart';
|
import '../widgets/demo_banner.dart';
|
||||||
import '../widgets/evidence_sidebar.dart';
|
import '../widgets/evidence_sidebar.dart';
|
||||||
import '../widgets/lawheatmap_card.dart';
|
import '../widgets/norm_text_card.dart';
|
||||||
|
import '../widgets/reclaim_card.dart';
|
||||||
import '../widgets/tier_badge.dart';
|
import '../widgets/tier_badge.dart';
|
||||||
|
|
||||||
class NormDetailPage extends StatelessWidget {
|
class NormDetailPage extends StatelessWidget {
|
||||||
|
|
@ -31,7 +32,7 @@ class NormDetailPage extends StatelessWidget {
|
||||||
const DemoBanner(),
|
const DemoBanner(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(LawHeatmapSpace.xl),
|
padding: const EdgeInsets.all(ReclaimSpace.xl),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -55,8 +56,8 @@ class NormDetailPage extends StatelessWidget {
|
||||||
e.norm.eli,
|
e.norm.eli,
|
||||||
style: t.bodyLarge?.copyWith(
|
style: t.bodyLarge?.copyWith(
|
||||||
fontFamily:
|
fontFamily:
|
||||||
LawHeatmapTypography.mono,
|
ReclaimTypography.mono,
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -65,34 +66,36 @@ class NormDetailPage extends StatelessWidget {
|
||||||
TierBadge(tier: e.tierLowest),
|
TierBadge(tier: e.tierLowest),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
_MetricsRow(e: e),
|
_MetricsRow(e: e),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
LawHeatmapCard(
|
NormTextCard(norm: e.norm),
|
||||||
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
|
ReclaimCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Pflichten',
|
Text('Pflichten',
|
||||||
style: t.headlineSmall),
|
style: t.headlineSmall),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: LawHeatmapSpace.sm),
|
height: ReclaimSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
'Atomare deontische Aussagen, extrahiert '
|
'Atomare deontische Aussagen, extrahiert '
|
||||||
'aus dem Norm-Text (Phase 1: regex + LLM-'
|
'aus dem Norm-Text (Phase 1: regex + LLM-'
|
||||||
'Vorschlag, Juristen-Review obligatorisch).',
|
'Vorschlag, Juristen-Review obligatorisch).',
|
||||||
style: t.labelSmall?.copyWith(
|
style: t.labelSmall?.copyWith(
|
||||||
color: LawHeatmapColors.mute),
|
color: ReclaimColors.mute),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: LawHeatmapSpace.md),
|
height: ReclaimSpace.md),
|
||||||
for (final d in e.duties)
|
for (final d in e.duties)
|
||||||
_DutyTile(duty: d),
|
_DutyTile(duty: d),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (e.notes != null) ...[
|
if (e.notes != null) ...[
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
LawHeatmapCard(
|
ReclaimCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment:
|
||||||
CrossAxisAlignment.start,
|
CrossAxisAlignment.start,
|
||||||
|
|
@ -100,7 +103,7 @@ class NormDetailPage extends StatelessWidget {
|
||||||
Text('Methodik-Notiz',
|
Text('Methodik-Notiz',
|
||||||
style: t.headlineSmall),
|
style: t.headlineSmall),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: LawHeatmapSpace.sm),
|
height: ReclaimSpace.sm),
|
||||||
Text(e.notes!, style: t.bodyLarge),
|
Text(e.notes!, style: t.bodyLarge),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -109,7 +112,7 @@ class NormDetailPage extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: LawHeatmapSpace.xl),
|
const SizedBox(width: ReclaimSpace.xl),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 340,
|
width: 340,
|
||||||
child: EvidenceSidebar(evaluation: e),
|
child: EvidenceSidebar(evaluation: e),
|
||||||
|
|
@ -137,19 +140,19 @@ class _MetricsRow extends StatelessWidget {
|
||||||
value: _eur(e.skmEurPerYear),
|
value: _eur(e.skmEurPerYear),
|
||||||
sub: 'SKM P×F×T×h',
|
sub: 'SKM P×F×T×h',
|
||||||
),
|
),
|
||||||
const SizedBox(width: LawHeatmapSpace.md),
|
const SizedBox(width: ReclaimSpace.md),
|
||||||
_MetricBox(
|
_MetricBox(
|
||||||
label: 'Nutzen',
|
label: 'Nutzen',
|
||||||
value: '${e.benefitScore.toStringAsFixed(1)} / 5',
|
value: '${e.benefitScore.toStringAsFixed(1)} / 5',
|
||||||
sub: '7-dim. Score',
|
sub: '7-dim. Score',
|
||||||
),
|
),
|
||||||
const SizedBox(width: LawHeatmapSpace.md),
|
const SizedBox(width: ReclaimSpace.md),
|
||||||
_MetricBox(
|
_MetricBox(
|
||||||
label: 'Betroffene',
|
label: 'Betroffene',
|
||||||
value: _kmu(e.affectedCount),
|
value: _kmu(e.affectedCount),
|
||||||
sub: 'DESTATIS',
|
sub: 'DESTATIS',
|
||||||
),
|
),
|
||||||
const SizedBox(width: LawHeatmapSpace.md),
|
const SizedBox(width: ReclaimSpace.md),
|
||||||
_MetricBox(
|
_MetricBox(
|
||||||
label: 'Frust',
|
label: 'Frust',
|
||||||
value: '${e.frustScore.toStringAsFixed(1)} / 10',
|
value: '${e.frustScore.toStringAsFixed(1)} / 10',
|
||||||
|
|
@ -187,19 +190,19 @@ class _MetricBox extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Theme.of(context).textTheme;
|
final t = Theme.of(context).textTheme;
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: LawHeatmapCard(
|
child: ReclaimCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(label,
|
Text(label,
|
||||||
style: t.labelSmall
|
style: t.labelSmall
|
||||||
?.copyWith(color: LawHeatmapColors.mute)),
|
?.copyWith(color: ReclaimColors.mute)),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(value, style: t.displaySmall),
|
Text(value, style: t.displaySmall),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(sub,
|
Text(sub,
|
||||||
style: t.labelSmall
|
style: t.labelSmall
|
||||||
?.copyWith(color: LawHeatmapColors.mute)),
|
?.copyWith(color: ReclaimColors.mute)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -215,16 +218,16 @@ class _DutyTile extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Theme.of(context).textTheme;
|
final t = Theme.of(context).textTheme;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: LawHeatmapSpace.md),
|
padding: const EdgeInsets.only(bottom: ReclaimSpace.md),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(LawHeatmapSpace.md),
|
padding: const EdgeInsets.all(ReclaimSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onSurface
|
.onSurface
|
||||||
.withValues(alpha: 0.12)),
|
.withValues(alpha: 0.12)),
|
||||||
borderRadius: const BorderRadius.all(LawHeatmapRadius.sm),
|
borderRadius: const BorderRadius.all(ReclaimRadius.sm),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -234,26 +237,26 @@ class _DutyTile extends StatelessWidget {
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 6, vertical: 2),
|
horizontal: 6, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: LawHeatmapColors.signal.withValues(alpha: 0.18),
|
color: ReclaimColors.signal.withValues(alpha: 0.18),
|
||||||
borderRadius:
|
borderRadius:
|
||||||
const BorderRadius.all(LawHeatmapRadius.sm),
|
const BorderRadius.all(ReclaimRadius.sm),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
duty.modality,
|
duty.modality,
|
||||||
style: t.labelSmall?.copyWith(
|
style: t.labelSmall?.copyWith(
|
||||||
color: LawHeatmapColors.signal,
|
color: ReclaimColors.signal,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: LawHeatmapSpace.sm),
|
const SizedBox(width: ReclaimSpace.sm),
|
||||||
Text(duty.sourceNorm,
|
Text(duty.sourceNorm,
|
||||||
style: t.labelSmall?.copyWith(
|
style: t.labelSmall?.copyWith(
|
||||||
fontFamily: LawHeatmapTypography.mono,
|
fontFamily: ReclaimTypography.mono,
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
)),
|
)),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: LawHeatmapSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
Text(duty.action, style: t.bodyLarge),
|
Text(duty.action, style: t.bodyLarge),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -261,7 +264,7 @@ class _DutyTile extends StatelessWidget {
|
||||||
'Frequenz: ${duty.frequency}'
|
'Frequenz: ${duty.frequency}'
|
||||||
'${duty.authority != null ? " · Behörde: ${duty.authority}" : ""}'
|
'${duty.authority != null ? " · Behörde: ${duty.authority}" : ""}'
|
||||||
'${duty.consequence != null ? " · Sanktion: ${duty.consequence}" : ""}',
|
'${duty.consequence != null ? " · Sanktion: ${duty.consequence}" : ""}',
|
||||||
style: t.labelSmall?.copyWith(color: LawHeatmapColors.mute),
|
style: t.labelSmall?.copyWith(color: ReclaimColors.mute),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../data/models.dart';
|
import '../data/models.dart';
|
||||||
import '../data/repository.dart';
|
import '../data/repository.dart';
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import '../widgets/demo_banner.dart';
|
import '../widgets/demo_banner.dart';
|
||||||
import '../widgets/lawheatmap_card.dart';
|
import '../widgets/reclaim_card.dart';
|
||||||
import '../widgets/tier_badge.dart';
|
import '../widgets/tier_badge.dart';
|
||||||
import 'norm_detail_page.dart';
|
import 'norm_detail_page.dart';
|
||||||
|
|
||||||
|
|
@ -21,20 +21,20 @@ class NormsListPage extends StatelessWidget {
|
||||||
const DemoBanner(),
|
const DemoBanner(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(LawHeatmapSpace.xl),
|
padding: const EdgeInsets.all(ReclaimSpace.xl),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Normen-Pilotkorpus', style: t.displaySmall),
|
Text('Normen-Pilotkorpus', style: t.displaySmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.xs),
|
const SizedBox(height: ReclaimSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
'5 Normen für den vertikalen Durchstich. '
|
'5 Normen für den vertikalen Durchstich. '
|
||||||
'Korpus erweiterbar in Phase 1.',
|
'Korpus erweiterbar in Phase 1.',
|
||||||
style: t.bodyLarge?.copyWith(
|
style: t.bodyLarge?.copyWith(
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
FutureBuilder<List<Evaluation>>(
|
FutureBuilder<List<Evaluation>>(
|
||||||
future: repository.list(),
|
future: repository.list(),
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
|
|
@ -47,7 +47,7 @@ class NormsListPage extends StatelessWidget {
|
||||||
for (final e in snap.data!)
|
for (final e in snap.data!)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
bottom: LawHeatmapSpace.md),
|
bottom: ReclaimSpace.md),
|
||||||
child: _NormRow(
|
child: _NormRow(
|
||||||
evaluation: e,
|
evaluation: e,
|
||||||
repository: repository,
|
repository: repository,
|
||||||
|
|
@ -86,7 +86,7 @@ class _NormRow extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: LawHeatmapCard(
|
child: ReclaimCard(
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -103,14 +103,14 @@ class _NormRow extends StatelessWidget {
|
||||||
Text(
|
Text(
|
||||||
evaluation.norm.eli,
|
evaluation.norm.eli,
|
||||||
style: t.labelSmall?.copyWith(
|
style: t.labelSmall?.copyWith(
|
||||||
fontFamily: LawHeatmapTypography.mono,
|
fontFamily: ReclaimTypography.mono,
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: LawHeatmapSpace.lg),
|
const SizedBox(width: ReclaimSpace.lg),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _MetricColumn(
|
child: _MetricColumn(
|
||||||
label: 'Schaden / Jahr',
|
label: 'Schaden / Jahr',
|
||||||
|
|
@ -130,7 +130,7 @@ class _NormRow extends StatelessWidget {
|
||||||
value: _kmu(evaluation.affectedCount),
|
value: _kmu(evaluation.affectedCount),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: LawHeatmapSpace.md),
|
const SizedBox(width: ReclaimSpace.md),
|
||||||
TierBadge(tier: evaluation.tierLowest),
|
TierBadge(tier: evaluation.tierLowest),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -166,7 +166,7 @@ class _MetricColumn extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
label,
|
label,
|
||||||
style: t.labelSmall?.copyWith(color: LawHeatmapColors.mute),
|
style: t.labelSmall?.copyWith(color: ReclaimColors.mute),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(value, style: t.titleMedium),
|
Text(value, style: t.titleMedium),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../data/repository.dart';
|
import '../data/repository.dart';
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import '../widgets/delta_mark.dart';
|
import '../widgets/delta_mark.dart';
|
||||||
import 'about_page.dart';
|
import 'about_page.dart';
|
||||||
import 'heatmap_page.dart';
|
import 'heatmap_page.dart';
|
||||||
|
|
@ -42,18 +42,18 @@ class _ShellPageState extends State<ShellPage> {
|
||||||
onDestinationSelected: (i) => setState(() => _index = i),
|
onDestinationSelected: (i) => setState(() => _index = i),
|
||||||
leading: Padding(
|
leading: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: LawHeatmapSpace.lg,
|
vertical: ReclaimSpace.lg,
|
||||||
horizontal: LawHeatmapSpace.md,
|
horizontal: ReclaimSpace.md,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const DeltaMark(size: 36),
|
const DeltaMark(size: 36),
|
||||||
const SizedBox(height: LawHeatmapSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
'F∆I',
|
'F∆I',
|
||||||
style: t.labelLarge?.copyWith(
|
style: t.labelLarge?.copyWith(
|
||||||
letterSpacing: 2,
|
letterSpacing: 2,
|
||||||
color: LawHeatmapColors.signal,
|
color: ReclaimColors.signal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,35 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'lawheatmap_tokens.dart';
|
import 'reclaim_tokens.dart';
|
||||||
|
|
||||||
/// ThemeData assembly for the F∆I Law-Heatmap client.
|
/// ThemeData assembly for the F∆I Recl∆Im client.
|
||||||
///
|
///
|
||||||
/// Dark mode is the default (consistent with fai_web's
|
/// Dark mode is the default (consistent with fai_web's
|
||||||
/// `color-scheme: dark`). Light mode falls back to the paper
|
/// `color-scheme: dark`). Light mode falls back to the paper
|
||||||
/// palette for printing/screenshot work.
|
/// palette for printing/screenshot work.
|
||||||
class LawHeatmapTheme {
|
class ReclaimTheme {
|
||||||
LawHeatmapTheme._();
|
ReclaimTheme._();
|
||||||
|
|
||||||
static ThemeData dark() => _build(Brightness.dark);
|
static ThemeData dark() => _build(Brightness.dark);
|
||||||
static ThemeData light() => _build(Brightness.light);
|
static ThemeData light() => _build(Brightness.light);
|
||||||
|
|
||||||
static ThemeData _build(Brightness brightness) {
|
static ThemeData _build(Brightness brightness) {
|
||||||
final isDark = brightness == Brightness.dark;
|
final isDark = brightness == Brightness.dark;
|
||||||
final canvas = isDark ? LawHeatmapColors.ink : LawHeatmapColors.paper;
|
final canvas = isDark ? ReclaimColors.ink : ReclaimColors.paper;
|
||||||
final surface =
|
final surface =
|
||||||
isDark ? LawHeatmapColors.inkRaised : LawHeatmapColors.paperRaised;
|
isDark ? ReclaimColors.inkRaised : ReclaimColors.paperRaised;
|
||||||
final onSurface = isDark
|
final onSurface = isDark
|
||||||
? const Color(0xFFE9E7E2)
|
? const Color(0xFFE9E7E2)
|
||||||
: const Color(0xFF1A1B1E);
|
: const Color(0xFF1A1B1E);
|
||||||
|
|
||||||
final colorScheme = ColorScheme(
|
final colorScheme = ColorScheme(
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
primary: LawHeatmapColors.signal,
|
primary: ReclaimColors.signal,
|
||||||
onPrimary: LawHeatmapColors.ink,
|
onPrimary: ReclaimColors.ink,
|
||||||
secondary: LawHeatmapColors.mute,
|
secondary: ReclaimColors.mute,
|
||||||
onSecondary: onSurface,
|
onSecondary: onSurface,
|
||||||
error: const Color(0xFFD8723A),
|
error: const Color(0xFFD8723A),
|
||||||
onError: LawHeatmapColors.ink,
|
onError: ReclaimColors.ink,
|
||||||
surface: surface,
|
surface: surface,
|
||||||
onSurface: onSurface,
|
onSurface: onSurface,
|
||||||
);
|
);
|
||||||
|
|
@ -42,33 +42,33 @@ class LawHeatmapTheme {
|
||||||
colorScheme: colorScheme,
|
colorScheme: colorScheme,
|
||||||
scaffoldBackgroundColor: canvas,
|
scaffoldBackgroundColor: canvas,
|
||||||
canvasColor: canvas,
|
canvasColor: canvas,
|
||||||
fontFamily: LawHeatmapTypography.body,
|
fontFamily: ReclaimTypography.body,
|
||||||
textTheme: textTheme,
|
textTheme: textTheme,
|
||||||
cardTheme: CardThemeData(
|
cardTheme: CardThemeData(
|
||||||
color: surface,
|
color: surface,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(LawHeatmapRadius.md),
|
borderRadius: BorderRadius.all(ReclaimRadius.md),
|
||||||
side: BorderSide(color: Color(0x24FFFFFF)),
|
side: BorderSide(color: Color(0x24FFFFFF)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
dividerTheme: DividerThemeData(
|
dividerTheme: DividerThemeData(
|
||||||
color: onSurface.withValues(alpha: LawHeatmapColors.hairlineDarkOpacity),
|
color: onSurface.withValues(alpha: ReclaimColors.hairlineDarkOpacity),
|
||||||
thickness: 1,
|
thickness: 1,
|
||||||
space: 1,
|
space: 1,
|
||||||
),
|
),
|
||||||
filledButtonTheme: FilledButtonThemeData(
|
filledButtonTheme: FilledButtonThemeData(
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
backgroundColor: LawHeatmapColors.signal,
|
backgroundColor: ReclaimColors.signal,
|
||||||
foregroundColor: LawHeatmapColors.ink,
|
foregroundColor: ReclaimColors.ink,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(LawHeatmapRadius.sm),
|
borderRadius: BorderRadius.all(ReclaimRadius.sm),
|
||||||
),
|
),
|
||||||
textStyle: textTheme.labelLarge,
|
textStyle: textTheme.labelLarge,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: LawHeatmapSpace.lg,
|
horizontal: ReclaimSpace.lg,
|
||||||
vertical: LawHeatmapSpace.md,
|
vertical: ReclaimSpace.md,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -76,8 +76,8 @@ class LawHeatmapTheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
static TextTheme _textTheme(Color onSurface) {
|
static TextTheme _textTheme(Color onSurface) {
|
||||||
const display = LawHeatmapTypography.display;
|
const display = ReclaimTypography.display;
|
||||||
const body = LawHeatmapTypography.body;
|
const body = ReclaimTypography.body;
|
||||||
return TextTheme(
|
return TextTheme(
|
||||||
displaySmall: TextStyle(
|
displaySmall: TextStyle(
|
||||||
fontFamily: display,
|
fontFamily: display,
|
||||||
|
|
@ -123,5 +123,5 @@ class LawHeatmapTheme {
|
||||||
/// Convenience: JetBrains-Mono variant of an existing style for
|
/// Convenience: JetBrains-Mono variant of an existing style for
|
||||||
/// technical content (ELI/CELEX, SHA-256, audit-event IDs).
|
/// technical content (ELI/CELEX, SHA-256, audit-event IDs).
|
||||||
static TextStyle mono(TextStyle base) =>
|
static TextStyle mono(TextStyle base) =>
|
||||||
base.copyWith(fontFamily: LawHeatmapTypography.mono);
|
base.copyWith(fontFamily: ReclaimTypography.mono);
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// Visual tokens for the F∆I Law-Heatmap client, sourced from the
|
/// Visual tokens for the F∆I Recl∆Im client, sourced from the
|
||||||
/// fai_web brand palette (see fai_web/src/styles/global.css:14-20).
|
/// fai_web brand palette (see fai_web/src/styles/global.css:14-20).
|
||||||
///
|
///
|
||||||
/// Unlike `fai_chain_studio`'s `ChainColors` (Tailwind sky-400 —
|
/// Unlike `fai_chain_studio`'s `ChainColors` (Tailwind sky-400 —
|
||||||
/// dev-tool look), this palette matches the public-facing brand:
|
/// dev-tool look), this palette matches the public-facing brand:
|
||||||
/// ink/paper with a petrol "signal" accent that doubles as the eye
|
/// ink/paper with a petrol "signal" accent that doubles as the eye
|
||||||
/// of the `∆` mark.
|
/// of the `∆` mark.
|
||||||
class LawHeatmapColors {
|
class ReclaimColors {
|
||||||
LawHeatmapColors._();
|
ReclaimColors._();
|
||||||
|
|
||||||
// Core palette (fai_web/src/styles/global.css)
|
// Core palette (fai_web/src/styles/global.css)
|
||||||
static const ink = Color(0xFF08090A);
|
static const ink = Color(0xFF08090A);
|
||||||
|
|
@ -40,8 +40,8 @@ class LawHeatmapColors {
|
||||||
|
|
||||||
/// Spacing scale — 4/8/12/16/24/32/48 multiples, matching
|
/// Spacing scale — 4/8/12/16/24/32/48 multiples, matching
|
||||||
/// `ChainSpace` from `fai_chain_studio/lib/theme/tokens.dart`.
|
/// `ChainSpace` from `fai_chain_studio/lib/theme/tokens.dart`.
|
||||||
class LawHeatmapSpace {
|
class ReclaimSpace {
|
||||||
LawHeatmapSpace._();
|
ReclaimSpace._();
|
||||||
|
|
||||||
static const xs = 4.0;
|
static const xs = 4.0;
|
||||||
static const sm = 8.0;
|
static const sm = 8.0;
|
||||||
|
|
@ -52,15 +52,15 @@ class LawHeatmapSpace {
|
||||||
static const xxxl = 48.0;
|
static const xxxl = 48.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LawHeatmapRadius {
|
class ReclaimRadius {
|
||||||
LawHeatmapRadius._();
|
ReclaimRadius._();
|
||||||
|
|
||||||
static const sm = Radius.circular(6);
|
static const sm = Radius.circular(6);
|
||||||
static const md = Radius.circular(10);
|
static const md = Radius.circular(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
class LawHeatmapTypography {
|
class ReclaimTypography {
|
||||||
LawHeatmapTypography._();
|
ReclaimTypography._();
|
||||||
|
|
||||||
static const display = 'SpaceGroteskVariable';
|
static const display = 'SpaceGroteskVariable';
|
||||||
static const body = 'InterVariable';
|
static const body = 'InterVariable';
|
||||||
|
|
@ -2,7 +2,7 @@ import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
|
|
||||||
/// The F∆I / Ch∆In brand mark: an upward triangle (the `∆`) with
|
/// The F∆I / Ch∆In brand mark: an upward triangle (the `∆`) with
|
||||||
/// a petrol "eye" at the centre. Geometry mirrors fai_web's
|
/// a petrol "eye" at the centre. Geometry mirrors fai_web's
|
||||||
|
|
@ -89,7 +89,7 @@ class _DeltaPainter extends CustomPainter {
|
||||||
// Eye — petrol fill, centred on the triangle's centroid.
|
// Eye — petrol fill, centred on the triangle's centroid.
|
||||||
final eyeCentre = Offset(w / 2, inset + (h - 2 * inset) * 2 / 3);
|
final eyeCentre = Offset(w / 2, inset + (h - 2 * inset) * 2 / 3);
|
||||||
final eyeR = w * 0.06 * pulse;
|
final eyeR = w * 0.06 * pulse;
|
||||||
final eye = Paint()..color = LawHeatmapColors.signal;
|
final eye = Paint()..color = ReclaimColors.signal;
|
||||||
canvas.drawCircle(eyeCentre, eyeR, eye);
|
canvas.drawCircle(eyeCentre, eyeR, eye);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
|
|
||||||
/// Permanent banner shown above all data screens in mock mode.
|
/// Permanent banner shown above all data screens in mock mode.
|
||||||
/// Carries the political-defensibility caveat: this is demo data,
|
/// Carries the political-defensibility caveat: this is demo data,
|
||||||
|
|
@ -14,29 +14,29 @@ class DemoBanner extends StatelessWidget {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: LawHeatmapSpace.lg,
|
horizontal: ReclaimSpace.lg,
|
||||||
vertical: LawHeatmapSpace.sm,
|
vertical: ReclaimSpace.sm,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: LawHeatmapColors.tierT4.withValues(alpha: 0.12),
|
color: ReclaimColors.tierT4.withValues(alpha: 0.12),
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
color: LawHeatmapColors.tierT4.withValues(alpha: 0.55),
|
color: ReclaimColors.tierT4.withValues(alpha: 0.55),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.science_outlined,
|
const Icon(Icons.science_outlined,
|
||||||
size: 18, color: LawHeatmapColors.tierT4),
|
size: 18, color: ReclaimColors.tierT4),
|
||||||
const SizedBox(width: LawHeatmapSpace.sm),
|
const SizedBox(width: ReclaimSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Demo-Daten — Phase 0, ohne Verbands-Erhebung. '
|
'Demo-Daten — Phase 0, ohne Verbands-Erhebung. '
|
||||||
'Keine Juristen-Approval, keine Beirats-Validierung. '
|
'Keine Juristen-Approval, keine Beirats-Validierung. '
|
||||||
'Alle Werte tragen Tier T4, bis T1/T2-Pipeline läuft.',
|
'Alle Werte tragen Tier T4, bis T1/T2-Pipeline läuft.',
|
||||||
style: t.labelSmall?.copyWith(
|
style: t.labelSmall?.copyWith(
|
||||||
color: LawHeatmapColors.tierT4,
|
color: ReclaimColors.tierT4,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../data/models.dart';
|
import '../data/models.dart';
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import 'lawheatmap_card.dart';
|
import 'reclaim_card.dart';
|
||||||
import 'source_chip.dart';
|
import 'source_chip.dart';
|
||||||
|
|
||||||
/// Sidebar listing the citable sources behind an evaluation.
|
/// Sidebar listing the citable sources behind an evaluation.
|
||||||
|
|
@ -16,39 +16,39 @@ class EvidenceSidebar extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Theme.of(context).textTheme;
|
final t = Theme.of(context).textTheme;
|
||||||
return LawHeatmapCard(
|
return ReclaimCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Quellen', style: t.headlineSmall),
|
Text('Quellen', style: t.headlineSmall),
|
||||||
const SizedBox(height: LawHeatmapSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
'Jede Zahl trägt die Tier-Stufe ihres schwächsten '
|
'Jede Zahl trägt die Tier-Stufe ihres schwächsten '
|
||||||
'Bestandteils (Regel der niedrigsten Stufe, §6.7).',
|
'Bestandteils (Regel der niedrigsten Stufe, §6.7).',
|
||||||
style: t.labelSmall?.copyWith(color: LawHeatmapColors.mute),
|
style: t.labelSmall?.copyWith(color: ReclaimColors.mute),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
for (final s in evaluation.sources) SourceChip(source: s),
|
for (final s in evaluation.sources) SourceChip(source: s),
|
||||||
const Divider(height: LawHeatmapSpace.xl),
|
const Divider(height: ReclaimSpace.xl),
|
||||||
Text('Audit-Event', style: t.labelLarge),
|
Text('Audit-Event', style: t.labelLarge),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
evaluation.auditEventId,
|
evaluation.auditEventId,
|
||||||
style: t.labelSmall?.copyWith(
|
style: t.labelSmall?.copyWith(
|
||||||
fontFamily: LawHeatmapTypography.mono,
|
fontFamily: ReclaimTypography.mono,
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: LawHeatmapSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
'Quell-SHA-256:',
|
'Quell-SHA-256:',
|
||||||
style: t.labelSmall?.copyWith(color: LawHeatmapColors.mute),
|
style: t.labelSmall?.copyWith(color: ReclaimColors.mute),
|
||||||
),
|
),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
evaluation.norm.sourceSha256,
|
evaluation.norm.sourceSha256,
|
||||||
style: t.labelSmall?.copyWith(
|
style: t.labelSmall?.copyWith(
|
||||||
fontFamily: LawHeatmapTypography.mono,
|
fontFamily: ReclaimTypography.mono,
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import 'dart:math' as math;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../data/models.dart';
|
import '../data/models.dart';
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
|
|
||||||
/// 2D scatter heatmap — x: Schaden (€/Jahr, log-scaled),
|
/// 2D scatter heatmap — x: Schaden (€/Jahr, log-scaled),
|
||||||
/// y: Nutzen (0–5), point area: Betroffenheit (sqrt-scaled).
|
/// y: Nutzen (0–5), point area: Betroffenheit (sqrt-scaled).
|
||||||
|
|
@ -121,7 +121,7 @@ class _HeatmapPainter extends CustomPainter {
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
final layout = _Layout(size);
|
final layout = _Layout(size);
|
||||||
final onSurface = theme.colorScheme.onSurface;
|
final onSurface = theme.colorScheme.onSurface;
|
||||||
const mute = LawHeatmapColors.mute;
|
const mute = ReclaimColors.mute;
|
||||||
|
|
||||||
// Plot frame.
|
// Plot frame.
|
||||||
final frame = Paint()
|
final frame = Paint()
|
||||||
|
|
@ -163,7 +163,7 @@ class _HeatmapPainter extends CustomPainter {
|
||||||
const axisStyle = TextStyle(
|
const axisStyle = TextStyle(
|
||||||
color: mute,
|
color: mute,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontFamily: LawHeatmapTypography.body,
|
fontFamily: ReclaimTypography.body,
|
||||||
);
|
);
|
||||||
_drawText(
|
_drawText(
|
||||||
canvas,
|
canvas,
|
||||||
|
|
@ -205,8 +205,8 @@ class _HeatmapPainter extends CustomPainter {
|
||||||
final r = layout.radius(e);
|
final r = layout.radius(e);
|
||||||
final hot = e.skmEurPerYear > 50000000;
|
final hot = e.skmEurPerYear > 50000000;
|
||||||
final color = Color.lerp(
|
final color = Color.lerp(
|
||||||
LawHeatmapColors.benefitSaturated,
|
ReclaimColors.benefitSaturated,
|
||||||
LawHeatmapColors.harmWarm,
|
ReclaimColors.harmWarm,
|
||||||
hot ? 0.5 : 0.0,
|
hot ? 0.5 : 0.0,
|
||||||
)!;
|
)!;
|
||||||
final body = Paint()..color = color.withValues(alpha: 0.7);
|
final body = Paint()..color = color.withValues(alpha: 0.7);
|
||||||
|
|
@ -224,7 +224,7 @@ class _HeatmapPainter extends CustomPainter {
|
||||||
TextStyle(
|
TextStyle(
|
||||||
color: onSurface,
|
color: onSurface,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontFamily: LawHeatmapTypography.body,
|
fontFamily: ReclaimTypography.body,
|
||||||
fontWeight: i == hoverIndex ? FontWeight.w600 : FontWeight.w400,
|
fontWeight: i == hoverIndex ? FontWeight.w600 : FontWeight.w400,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -240,8 +240,8 @@ class _HeatmapPainter extends CustomPainter {
|
||||||
..color = theme.colorScheme.surface.withValues(alpha: 0.92);
|
..color = theme.colorScheme.surface.withValues(alpha: 0.92);
|
||||||
final boxBorder = Paint()
|
final boxBorder = Paint()
|
||||||
..style = PaintingStyle.stroke
|
..style = PaintingStyle.stroke
|
||||||
..color = LawHeatmapColors.signal.withValues(alpha: 0.6);
|
..color = ReclaimColors.signal.withValues(alpha: 0.6);
|
||||||
final rrect = RRect.fromRectAndRadius(box, LawHeatmapRadius.sm);
|
final rrect = RRect.fromRectAndRadius(box, ReclaimRadius.sm);
|
||||||
canvas.drawRRect(rrect, boxPaint);
|
canvas.drawRRect(rrect, boxPaint);
|
||||||
canvas.drawRRect(rrect, boxBorder);
|
canvas.drawRRect(rrect, boxBorder);
|
||||||
_drawText(
|
_drawText(
|
||||||
|
|
@ -252,7 +252,7 @@ class _HeatmapPainter extends CustomPainter {
|
||||||
color: onSurface,
|
color: onSurface,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
fontFamily: LawHeatmapTypography.body,
|
fontFamily: ReclaimTypography.body,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_drawText(
|
_drawText(
|
||||||
|
|
@ -262,7 +262,7 @@ class _HeatmapPainter extends CustomPainter {
|
||||||
const TextStyle(
|
const TextStyle(
|
||||||
color: mute,
|
color: mute,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontFamily: LawHeatmapTypography.body,
|
fontFamily: ReclaimTypography.body,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_drawText(
|
_drawText(
|
||||||
|
|
@ -272,7 +272,7 @@ class _HeatmapPainter extends CustomPainter {
|
||||||
const TextStyle(
|
const TextStyle(
|
||||||
color: mute,
|
color: mute,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontFamily: LawHeatmapTypography.body,
|
fontFamily: ReclaimTypography.body,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../data/hub_repository.dart';
|
import '../data/hub_repository.dart';
|
||||||
import '../data/repository.dart';
|
import '../data/repository.dart';
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import 'lawheatmap_card.dart';
|
import 'reclaim_card.dart';
|
||||||
|
|
||||||
/// Lists the capabilities the connected hub has installed.
|
/// Lists the capabilities the connected hub has installed.
|
||||||
/// Empty / explanatory state when the active repository is not
|
/// Empty / explanatory state when the active repository is not
|
||||||
|
|
@ -39,7 +39,7 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
|
||||||
final repo = widget.repository;
|
final repo = widget.repository;
|
||||||
final liveHub = repo is HubRepository && repo.isHealthy;
|
final liveHub = repo is HubRepository && repo.isHealthy;
|
||||||
|
|
||||||
return LawHeatmapCard(
|
return ReclaimCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -53,13 +53,13 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
|
||||||
icon: const Icon(Icons.refresh),
|
icon: const Icon(Icons.refresh),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: LawHeatmapSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
if (!liveHub)
|
if (!liveHub)
|
||||||
Text(
|
Text(
|
||||||
'Sichtbar sobald die App im Hub-Modus startet und '
|
'Sichtbar sobald die App im Hub-Modus startet und '
|
||||||
'der Hub erreichbar ist (siehe Verbindung oben).',
|
'der Hub erreichbar ist (siehe Verbindung oben).',
|
||||||
style: t.labelSmall?.copyWith(
|
style: t.labelSmall?.copyWith(
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
|
@ -68,14 +68,14 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
if (snap.connectionState == ConnectionState.waiting) {
|
if (snap.connectionState == ConnectionState.waiting) {
|
||||||
return const Padding(
|
return const Padding(
|
||||||
padding: EdgeInsets.all(LawHeatmapSpace.md),
|
padding: EdgeInsets.all(ReclaimSpace.md),
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (snap.hasError) {
|
if (snap.hasError) {
|
||||||
return Text('Fehler: ${snap.error}',
|
return Text('Fehler: ${snap.error}',
|
||||||
style: t.labelLarge
|
style: t.labelLarge
|
||||||
?.copyWith(color: LawHeatmapColors.harmWarm));
|
?.copyWith(color: ReclaimColors.harmWarm));
|
||||||
}
|
}
|
||||||
final items = snap.data ?? const [];
|
final items = snap.data ?? const [];
|
||||||
if (items.isEmpty) {
|
if (items.isEmpty) {
|
||||||
|
|
@ -83,7 +83,7 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
|
||||||
'Hub hat noch keine Capabilities installiert. '
|
'Hub hat noch keine Capabilities installiert. '
|
||||||
'Mit `chain install <name>` hinzufügen.',
|
'Mit `chain install <name>` hinzufügen.',
|
||||||
style: t.bodyLarge?.copyWith(
|
style: t.bodyLarge?.copyWith(
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +101,7 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
|
||||||
child: Text(
|
child: Text(
|
||||||
'${c.capability}@${c.version}',
|
'${c.capability}@${c.version}',
|
||||||
style: t.bodyLarge?.copyWith(
|
style: t.bodyLarge?.copyWith(
|
||||||
fontFamily: LawHeatmapTypography.mono,
|
fontFamily: ReclaimTypography.mono,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -110,7 +110,7 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
|
||||||
'${c.moduleName} ${c.moduleVersion}'
|
'${c.moduleName} ${c.moduleVersion}'
|
||||||
'${c.kind.isEmpty ? "" : " · ${c.kind}"}',
|
'${c.kind.isEmpty ? "" : " · ${c.kind}"}',
|
||||||
style: t.labelSmall?.copyWith(
|
style: t.labelSmall?.copyWith(
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
143
app/lib/widgets/norm_text_card.dart
Normal file
143
app/lib/widgets/norm_text_card.dart
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
import '../data/models.dart';
|
||||||
|
import '../theme/reclaim_tokens.dart';
|
||||||
|
import 'reclaim_card.dart';
|
||||||
|
|
||||||
|
/// Renders the actual paragraphs of a norm right next to the
|
||||||
|
/// evaluation. Solves the "I want to read the law in place"
|
||||||
|
/// requirement so a reviewer never has to context-switch to a
|
||||||
|
/// browser tab just to confirm what the score is about.
|
||||||
|
///
|
||||||
|
/// Two side-buttons at the top:
|
||||||
|
/// - "Im Browser öffnen" — pop the canonical source URL
|
||||||
|
/// - "URL kopieren" — clipboard, useful for citations
|
||||||
|
///
|
||||||
|
/// On macOS the browser-open uses the system `open` command —
|
||||||
|
/// no extra package, no permission prompt. iOS/Linux/Windows
|
||||||
|
/// branches would need `url_launcher` later; Phase 0 ships
|
||||||
|
/// macOS first.
|
||||||
|
class NormTextCard extends StatelessWidget {
|
||||||
|
const NormTextCard({super.key, required this.norm});
|
||||||
|
|
||||||
|
final Norm norm;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final t = Theme.of(context).textTheme;
|
||||||
|
final paragraphs = norm.paragraphs;
|
||||||
|
return ReclaimCard(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Gesetzestext', style: t.headlineSmall),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'${norm.jurabk} ${norm.paragraph} — Stand '
|
||||||
|
'${_isoDate(norm.standDate)}',
|
||||||
|
style: t.labelSmall?.copyWith(
|
||||||
|
color: ReclaimColors.mute,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
tooltip: 'URL kopieren',
|
||||||
|
onPressed: () =>
|
||||||
|
Clipboard.setData(ClipboardData(text: norm.sourceUrl)),
|
||||||
|
icon: const Icon(Icons.link),
|
||||||
|
),
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: () => _openInBrowser(context, norm.sourceUrl),
|
||||||
|
icon: const Icon(Icons.open_in_new),
|
||||||
|
label: const Text('Im Browser öffnen'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
|
if (paragraphs.isEmpty)
|
||||||
|
Text(
|
||||||
|
'Für diese Norm ist noch kein normalisierter Text '
|
||||||
|
'eingebunden. Wenn Ch∆In mit `text.akoma_normalize@^0` '
|
||||||
|
'gegen die Quell-URL läuft, erscheinen die Absätze hier.',
|
||||||
|
style: t.bodyLarge?.copyWith(color: ReclaimColors.mute),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
for (final p in paragraphs)
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.only(bottom: ReclaimSpace.md),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 64,
|
||||||
|
child: Text(
|
||||||
|
p.number,
|
||||||
|
style: t.labelLarge?.copyWith(
|
||||||
|
color: ReclaimColors.signal,
|
||||||
|
fontFamily: ReclaimTypography.mono,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SelectableText(
|
||||||
|
p.text,
|
||||||
|
style: t.bodyLarge?.copyWith(height: 1.55),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
|
SelectableText(
|
||||||
|
norm.sourceUrl,
|
||||||
|
style: t.labelSmall?.copyWith(
|
||||||
|
color: ReclaimColors.mute,
|
||||||
|
fontFamily: ReclaimTypography.mono,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _isoDate(DateTime d) =>
|
||||||
|
'${d.year.toString().padLeft(4, "0")}-'
|
||||||
|
'${d.month.toString().padLeft(2, "0")}-'
|
||||||
|
'${d.day.toString().padLeft(2, "0")}';
|
||||||
|
|
||||||
|
Future<void> _openInBrowser(BuildContext context, String url) async {
|
||||||
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
|
final uri = Uri.tryParse(url);
|
||||||
|
if (uri == null) {
|
||||||
|
messenger.showSnackBar(
|
||||||
|
SnackBar(content: Text('Ungültige URL: $url')),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final ok =
|
||||||
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||||
|
if (!ok) {
|
||||||
|
messenger.showSnackBar(
|
||||||
|
const SnackBar(content: Text('Browser-Aufruf abgelehnt.')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
messenger.showSnackBar(
|
||||||
|
SnackBar(content: Text('Konnte Browser nicht öffnen: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
|
|
||||||
/// Surface container — visual equivalent of fai_chain_studio's
|
/// Surface container — visual equivalent of fai_chain_studio's
|
||||||
/// `ChainCard`, retuned to the fai_web palette.
|
/// `ChainCard`, retuned to the fai_web palette.
|
||||||
class LawHeatmapCard extends StatelessWidget {
|
class ReclaimCard extends StatelessWidget {
|
||||||
const LawHeatmapCard({
|
const ReclaimCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.child,
|
required this.child,
|
||||||
this.padding =
|
this.padding =
|
||||||
const EdgeInsets.all(LawHeatmapSpace.lg),
|
const EdgeInsets.all(ReclaimSpace.lg),
|
||||||
this.accent = false,
|
this.accent = false,
|
||||||
this.expand = false,
|
this.expand = false,
|
||||||
});
|
});
|
||||||
|
|
@ -39,9 +39,9 @@ class LawHeatmapCard extends StatelessWidget {
|
||||||
Container(
|
Container(
|
||||||
height: 2,
|
height: 2,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: LawHeatmapColors.signal,
|
color: ReclaimColors.signal,
|
||||||
borderRadius: BorderRadius.vertical(
|
borderRadius: BorderRadius.vertical(
|
||||||
top: LawHeatmapRadius.md,
|
top: ReclaimRadius.md,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../data/models.dart';
|
import '../data/models.dart';
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import 'tier_badge.dart';
|
import 'tier_badge.dart';
|
||||||
|
|
||||||
/// A citable source line: tier badge + label + (planned) link.
|
/// A citable source line: tier badge + label + (planned) link.
|
||||||
|
|
@ -16,7 +16,7 @@ class SourceChip extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Theme.of(context).textTheme;
|
final t = Theme.of(context).textTheme;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: LawHeatmapSpace.sm),
|
padding: const EdgeInsets.only(bottom: ReclaimSpace.sm),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -24,7 +24,7 @@ class SourceChip extends StatelessWidget {
|
||||||
padding: const EdgeInsets.only(top: 2),
|
padding: const EdgeInsets.only(top: 2),
|
||||||
child: TierBadge(tier: source.tier, compact: true),
|
child: TierBadge(tier: source.tier, compact: true),
|
||||||
),
|
),
|
||||||
const SizedBox(width: LawHeatmapSpace.sm),
|
const SizedBox(width: ReclaimSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -33,14 +33,14 @@ class SourceChip extends StatelessWidget {
|
||||||
Text(
|
Text(
|
||||||
source.url,
|
source.url,
|
||||||
style: t.labelSmall?.copyWith(
|
style: t.labelSmall?.copyWith(
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (source.note != null)
|
if (source.note != null)
|
||||||
Text(
|
Text(
|
||||||
source.note!,
|
source.note!,
|
||||||
style: t.labelSmall?.copyWith(
|
style: t.labelSmall?.copyWith(
|
||||||
color: LawHeatmapColors.mute,
|
color: ReclaimColors.mute,
|
||||||
fontStyle: FontStyle.italic,
|
fontStyle: FontStyle.italic,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../data/models.dart';
|
import '../data/models.dart';
|
||||||
import '../theme/lawheatmap_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
|
|
||||||
/// Tier badge — surfaces the lowest evidence tier in a composed
|
/// Tier badge — surfaces the lowest evidence tier in a composed
|
||||||
/// value. Every figure in the app carries one of these next to it.
|
/// value. Every figure in the app carries one of these next to it.
|
||||||
|
|
@ -15,22 +15,22 @@ class TierBadge extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Theme.of(context).textTheme;
|
final t = Theme.of(context).textTheme;
|
||||||
final color = switch (tier) {
|
final color = switch (tier) {
|
||||||
EvidenceTier.t1 => LawHeatmapColors.tierT1,
|
EvidenceTier.t1 => ReclaimColors.tierT1,
|
||||||
EvidenceTier.t2 => LawHeatmapColors.tierT2,
|
EvidenceTier.t2 => ReclaimColors.tierT2,
|
||||||
EvidenceTier.t3 => LawHeatmapColors.tierT3,
|
EvidenceTier.t3 => ReclaimColors.tierT3,
|
||||||
EvidenceTier.t4 => LawHeatmapColors.tierT4,
|
EvidenceTier.t4 => ReclaimColors.tierT4,
|
||||||
};
|
};
|
||||||
return Tooltip(
|
return Tooltip(
|
||||||
message: '${tier.short} — ${tier.description}',
|
message: '${tier.short} — ${tier.description}',
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: compact ? 6 : LawHeatmapSpace.sm,
|
horizontal: compact ? 6 : ReclaimSpace.sm,
|
||||||
vertical: compact ? 2 : 4,
|
vertical: compact ? 2 : 4,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color.withValues(alpha: 0.18),
|
color: color.withValues(alpha: 0.18),
|
||||||
border: Border.all(color: color.withValues(alpha: 0.55)),
|
border: Border.all(color: color.withValues(alpha: 0.55)),
|
||||||
borderRadius: const BorderRadius.all(LawHeatmapRadius.sm),
|
borderRadius: const BorderRadius.all(ReclaimRadius.sm),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
|
import url_launcher_macos
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||||
33CC10ED2044A3C60003C045 /* lawheatmap_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "lawheatmap_app.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
33CC10ED2044A3C60003C045 /* reclaim_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "reclaim_app.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||||
|
|
@ -134,7 +134,7 @@
|
||||||
33CC10EE2044A3C60003C045 /* Products */ = {
|
33CC10EE2044A3C60003C045 /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
33CC10ED2044A3C60003C045 /* lawheatmap_app.app */,
|
33CC10ED2044A3C60003C045 /* reclaim_app.app */,
|
||||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
|
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
|
|
@ -224,7 +224,7 @@
|
||||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||||
);
|
);
|
||||||
productName = Runner;
|
productName = Runner;
|
||||||
productReference = 33CC10ED2044A3C60003C045 /* lawheatmap_app.app */;
|
productReference = 33CC10ED2044A3C60003C045 /* reclaim_app.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
|
|
@ -395,10 +395,10 @@
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.lawheatmapApp.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.reclaim.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/lawheatmap_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/lawheatmap_app";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/reclaim_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/reclaim_app";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -409,10 +409,10 @@
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.lawheatmapApp.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.reclaim.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/lawheatmap_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/lawheatmap_app";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/reclaim_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/reclaim_app";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|
@ -423,10 +423,10 @@
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.lawheatmapApp.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.reclaim.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/lawheatmap_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/lawheatmap_app";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/reclaim_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/reclaim_app";
|
||||||
};
|
};
|
||||||
name = Profile;
|
name = Profile;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
BuildableName = "lawheatmap_app.app"
|
BuildableName = "reclaim_app.app"
|
||||||
BlueprintName = "Runner"
|
BlueprintName = "Runner"
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
BuildableName = "lawheatmap_app.app"
|
BuildableName = "reclaim_app.app"
|
||||||
BlueprintName = "Runner"
|
BlueprintName = "Runner"
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
BuildableName = "lawheatmap_app.app"
|
BuildableName = "reclaim_app.app"
|
||||||
BlueprintName = "Runner"
|
BlueprintName = "Runner"
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
|
|
@ -84,7 +84,7 @@
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
BuildableName = "lawheatmap_app.app"
|
BuildableName = "reclaim_app.app"
|
||||||
BlueprintName = "Runner"
|
BlueprintName = "Runner"
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
|
|
@ -101,7 +101,7 @@
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
BuildableName = "lawheatmap_app.app"
|
BuildableName = "reclaim_app.app"
|
||||||
BlueprintName = "Runner"
|
BlueprintName = "Runner"
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
// 'flutter create' template.
|
// 'flutter create' template.
|
||||||
|
|
||||||
// The application's name. By default this is also the title of the Flutter window.
|
// The application's name. By default this is also the title of the Flutter window.
|
||||||
PRODUCT_NAME = lawheatmap_app
|
PRODUCT_NAME = reclaim_app
|
||||||
|
|
||||||
// The application's bundle identifier
|
// The application's bundle identifier
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.lawheatmapApp
|
PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.reclaim
|
||||||
|
|
||||||
// The copyright displayed in application information
|
// The copyright displayed in application information
|
||||||
PRODUCT_COPYRIGHT = Copyright © 2026 ai.flemming. All rights reserved.
|
PRODUCT_COPYRIGHT = Copyright © 2026 ai.flemming. All rights reserved.
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,7 @@
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.network.server</key>
|
<key>com.apple.security.network.server</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,7 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -409,6 +409,70 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
url_launcher:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: url_launcher
|
||||||
|
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.2"
|
||||||
|
url_launcher_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_android
|
||||||
|
sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.32"
|
||||||
|
url_launcher_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_ios
|
||||||
|
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.4.1"
|
||||||
|
url_launcher_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_linux
|
||||||
|
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
url_launcher_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_macos
|
||||||
|
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.5"
|
||||||
|
url_launcher_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_platform_interface
|
||||||
|
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
url_launcher_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_web
|
||||||
|
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.3"
|
||||||
|
url_launcher_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_windows
|
||||||
|
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.5"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
name: lawheatmap_app
|
name: reclaim_app
|
||||||
description: F∆I Law-Heatmap — Flutter client for the Ch∆In-based law-bureaucracy heatmap.
|
description: Recl∆Im — Flutter client for the Ch∆In-based duty-level reform-mapping platform (F∆I).
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.1.0+1
|
version: 0.1.0+1
|
||||||
|
|
||||||
|
|
@ -14,6 +14,7 @@ dependencies:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
intl: ^0.20.2
|
intl: ^0.20.2
|
||||||
shared_preferences: ^2.3.0
|
shared_preferences: ^2.3.0
|
||||||
|
url_launcher: ^6.3.0
|
||||||
|
|
||||||
# Ch∆In gRPC wire — sibling-path dep, same pattern as
|
# Ch∆In gRPC wire — sibling-path dep, same pattern as
|
||||||
# fai_chain_studio. Adapt the chain_client_sdk_dart relative
|
# fai_chain_studio. Adapt the chain_client_sdk_dart relative
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:lawheatmap_app/main.dart';
|
import 'package:reclaim_app/main.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// Generous surface so NavigationRail + Heatmap fit without
|
// Generous surface so NavigationRail + Heatmap fit without
|
||||||
|
|
@ -18,9 +18,9 @@ void main() {
|
||||||
testWidgets('Landing renders brand mark + CTA',
|
testWidgets('Landing renders brand mark + CTA',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
await tester.binding.setSurfaceSize(surfaceSize);
|
await tester.binding.setSurfaceSize(surfaceSize);
|
||||||
await tester.pumpWidget(const LawHeatmapApp());
|
await tester.pumpWidget(const ReclaimApp());
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(find.text('F∆I Law-Heatmap'), findsOneWidget);
|
expect(find.text('Recl∆Im'), findsOneWidget);
|
||||||
expect(
|
expect(
|
||||||
find.widgetWithText(FilledButton, 'App öffnen (Demo-Modus)'),
|
find.widgetWithText(FilledButton, 'App öffnen (Demo-Modus)'),
|
||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
|
|
@ -31,7 +31,7 @@ void main() {
|
||||||
testWidgets('Tapping CTA reaches the shell and shows the heatmap',
|
testWidgets('Tapping CTA reaches the shell and shows the heatmap',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
await tester.binding.setSurfaceSize(surfaceSize);
|
await tester.binding.setSurfaceSize(surfaceSize);
|
||||||
await tester.pumpWidget(const LawHeatmapApp());
|
await tester.pumpWidget(const ReclaimApp());
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.tap(
|
await tester.tap(
|
||||||
find.widgetWithText(FilledButton, 'App öffnen (Demo-Modus)'),
|
find.widgetWithText(FilledButton, 'App öffnen (Demo-Modus)'),
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ steps:
|
||||||
- id: review
|
- id: review
|
||||||
use: system.approval@^0
|
use: system.approval@^0
|
||||||
with:
|
with:
|
||||||
title: "F∆I Law-Heatmap — Review ${{ normalize.norm.eli }}"
|
title: "Recl∆Im (F∆I) — Review ${{ normalize.norm.eli }}"
|
||||||
payload:
|
payload:
|
||||||
norm: ${{ normalize.norm }}
|
norm: ${{ normalize.norm }}
|
||||||
skm: ${{ skm.report }}
|
skm: ${{ skm.report }}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue