Recl∆Im — the F∆I product line for Pflicht-basierte
Wirkungsanalyse und Reform-Vorschlags-Pipelines. Replaces the
working title 'Law-Heatmap', which described the demo view
rather than the platform. Heatmap, Norms list, Hub view,
Methodik are now four panes of the same Recl∆Im app.
Renames in this commit:
Flutter package lawheatmap_app -> reclaim_app
macOS bundle id ai.flemming.lawheatmapApp -> ai.flemming.reclaim
macOS PRODUCT_NAME lawheatmap_app -> reclaim_app
Theme tokens LawHeatmapColors/Space/Radius/Typography/Theme
-> Reclaim*
Surface card LawHeatmapCard -> ReclaimCard
Top-level widget LawHeatmapApp -> ReclaimApp
Theme files lawheatmap_theme.dart/lawheatmap_tokens.dart
-> reclaim_theme.dart/reclaim_tokens.dart
Widget file lawheatmap_card.dart -> reclaim_card.dart
Build script build-macos.sh paths and headline string
Docs MACHBARKEITSSTUDIE.md, METHODIK.md, RUN.md,
app/README.md, flow YAML
UI strings 'F∆I Law-Heatmap' / 'Law-Heatmap'
-> 'Recl∆Im' / 'Recl∆Im (F∆I)' for vendor-
prefixed contexts
flutter analyze: clean. flutter test: 2/2 passing.
The local working directory stays fai_lawheatmap/ for now —
the dir is referenced by the .claude project metadata; renaming
it would break session continuity for no real gain. The Forgejo
repo was renamed via API in the same change-set (fai/lawheatmap
-> fai/reclaim), with the legacy slug serving a redirect
courtesy of Forgejo's built-in rename handling.
Signed-off-by: flemming-it <sf@flemming.it>
128 lines
4.6 KiB
Dart
128 lines
4.6 KiB
Dart
import 'package:flutter/material.dart';
|
||
|
||
import '../data/models.dart';
|
||
import '../data/repository.dart';
|
||
import '../theme/reclaim_tokens.dart';
|
||
import '../widgets/demo_banner.dart';
|
||
import '../widgets/heatmap_grid.dart';
|
||
import '../widgets/reclaim_card.dart';
|
||
import '../widgets/tier_badge.dart';
|
||
import 'norm_detail_page.dart';
|
||
|
||
class HeatmapPage extends StatefulWidget {
|
||
const HeatmapPage({super.key, required this.repository});
|
||
|
||
final EvaluationRepository repository;
|
||
|
||
@override
|
||
State<HeatmapPage> createState() => _HeatmapPageState();
|
||
}
|
||
|
||
class _HeatmapPageState extends State<HeatmapPage> {
|
||
late Future<List<Evaluation>> _future;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_future = widget.repository.list();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final t = Theme.of(context).textTheme;
|
||
return Column(
|
||
children: [
|
||
const DemoBanner(),
|
||
Expanded(
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(ReclaimSpace.xl),
|
||
child: FutureBuilder<List<Evaluation>>(
|
||
future: _future,
|
||
builder: (context, snap) {
|
||
if (!snap.hasData) {
|
||
return const Center(
|
||
child: CircularProgressIndicator());
|
||
}
|
||
final items = snap.data!;
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text('Heatmap', style: t.displaySmall),
|
||
const SizedBox(height: ReclaimSpace.xs),
|
||
Text(
|
||
'X: jährliche Bürokratiekosten (€, log10). '
|
||
'Y: Nutzen-Score 0–5. Punktgröße: Betroffenheit. '
|
||
'Klick auf einen Punkt → Detail mit Pflichten + Quellen.',
|
||
style: t.bodyLarge?.copyWith(
|
||
color: ReclaimColors.mute,
|
||
),
|
||
),
|
||
const SizedBox(height: ReclaimSpace.lg),
|
||
Expanded(
|
||
child: ReclaimCard(
|
||
accent: true,
|
||
expand: true,
|
||
child: HeatmapGrid(
|
||
evaluations: items,
|
||
onTap: (e) {
|
||
Navigator.of(context).push(
|
||
MaterialPageRoute(
|
||
builder: (_) => NormDetailPage(
|
||
repository: widget.repository,
|
||
evaluation: e,
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: ReclaimSpace.lg),
|
||
Wrap(
|
||
spacing: ReclaimSpace.md,
|
||
runSpacing: ReclaimSpace.sm,
|
||
children: [
|
||
for (final e in items)
|
||
InkWell(
|
||
onTap: () {
|
||
Navigator.of(context).push(
|
||
MaterialPageRoute(
|
||
builder: (_) => NormDetailPage(
|
||
repository: widget.repository,
|
||
evaluation: e,
|
||
),
|
||
),
|
||
);
|
||
},
|
||
child: ReclaimCard(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: ReclaimSpace.md,
|
||
vertical: ReclaimSpace.sm,
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
TierBadge(
|
||
tier: e.tierLowest, compact: true),
|
||
const SizedBox(
|
||
width: ReclaimSpace.sm),
|
||
Text(
|
||
'${e.norm.jurabk} ${e.norm.paragraph}',
|
||
style: t.labelLarge,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
);
|
||
},
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|