Initial commit: Grounded Flutter frontend

A to-do app that doesn't believe you — an enforcement layer rather than a
neutral ledger.

Architecture ported from Autoreceptives/Frontend/Receptive: stacked MVVM with
the mandatory 4-file screen pattern, one ParentViewModel owning the loading /
network / error overlays and the handleError decision tree, one AppDataManager
gateway, dio comms carrying the three identity headers, secure storage with
random-suffixed keys, and a single-chokepoint Navigator. Package root and Dart
package name are both Grounded; org is nya.

The enforcement engine, one unit per formula in utils/:

- DebtEngine      w(class) x severity(d) x decay(t), sublinear severity so old
                  misses cannot swamp the score; abandonment 2x with 30-day
                  decay immunity; late complete retains 30%
- StandingEngine  Good -> Warned -> Grounded -> Lockdown, derived not set;
                  Grounded replaces home with the overdue queue
- CapacityEngine  blocks over-scheduling against p50 of historically completed
                  minutes, with a learned per-category estimation multiplier
- IntegrityEngine session integrity, weekly volume, plyometric contact ceiling
                  and enforced recovery gaps
- ExcuseAnalyser  on-device excuse clustering plus the confrontation copy
- GuardrailEngine distress detection and rationed amnesty
- ToneEngine      all enforcement copy, so the tone cap lives in one place

CommitmentEvent is append-only and is the source of truth rather than the
status field, which is what makes honest history and excuse analysis possible.

Goals contain commitments via parentId, and a task can be run from a
full-screen runner that derives elapsed time from wall-clock so screen-off
cannot lose time. Backgrounding pauses the clock and is counted. The runner is
mirrored into an ongoing notification, with alarm-class full-screen intents
reserved for non-negotiables.

Design language, fonts, icon and native splash are in place; Mason bricks are
retargeted to this project and verified end-to-end.

flutter analyze lib/ reports no errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mfu2gQLSFN21YRBcU2NrTt
This commit is contained in:
alvocool
2026-07-27 09:11:17 +03:00
commit 16bff634b5
315 changed files with 19132 additions and 0 deletions

View File

@@ -0,0 +1,374 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import '../../about/external/data/ReportCard.dart';
import '../../about/internal/application/TextType.dart';
import '../../designs/Component.dart';
import '../../designs/Responsive.dart';
import '../../designs/Shell.dart';
import '../../designs/text/Text.dart';
import '../../utils/Colors.dart';
import '../../utils/CommonUtils.dart';
import 'ConnectReportCardScreen.dart';
import 'ReportCardScreen.dart';
import 'ViewReportCardScreen.dart';
class ReportCardScreenState extends State<ReportCardScreen>
implements ConnectReportCardScreen {
ViewReportCardScreen? _model;
ReportCard _report = ReportCard();
@override
Widget build(BuildContext context) {
return ViewModelBuilder<ViewReportCardScreen>.reactive(
viewModelBuilder: () => ViewReportCardScreen(context, this),
onViewModelReady: (viewModel) {
_model = viewModel;
_initiate();
},
builder: (context, viewModel, child) => LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Responsive(
mobile: _mobileView(constraints),
tablet: _mobileView(constraints),
desktop: _mobileView(constraints),
);
},
),
);
}
void _initiate() {
_model?.loadReport();
}
void _onBack() {
Navigator.pop(context);
}
Widget _mobileView(BoxConstraints constraints) {
return Sheet(
eyebrow: "This week",
title: "Report card",
onBack: _onBack,
banner: _gradeBanner(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
if (_report.praise.isNotEmpty) ...[
_praiseCard(),
const SizedBox(height: 24),
],
_assignedAction(),
const SizedBox(height: 28),
sectionBreak("Debt", caption: "across the week"),
_debtTrend(),
const SizedBox(height: 28),
sectionBreak("Completion", caption: "by class"),
..._report.completionByClass.entries.map(_completionRow),
const SizedBox(height: 20),
_worstHour(),
const SizedBox(height: 28),
if (_report.deferralLeaderboard.isNotEmpty) ...[
sectionBreak("Most dodged", caption: "the leaderboard"),
..._report.deferralLeaderboard.take(5).map(_deferralRow),
const SizedBox(height: 28),
],
if (_report.estimationAccuracy.isNotEmpty) ...[
sectionBreak("Your estimates", caption: "against reality"),
..._report.estimationAccuracy.entries.map(_estimationRow),
const SizedBox(height: 28),
],
if (_report.trainingAdherence > 0) ...[
sectionBreak("Training"),
_trainingCard(),
],
],
),
);
}
/// The grade sits in the chrome. Cosmetic, but it is the thing people
/// actually react to.
Widget _gradeBanner() {
return Container(
padding: const EdgeInsets.fromLTRB(16, 14, 16, 14),
decoration: BoxDecoration(
color: colorWhite.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
text("PERIOD", 9, TextType.Bold,
color: colorWhite.withValues(alpha: 0.45),
letterSpacing: 1.2),
const SizedBox(height: 6),
text(
"${formatDate(_report.periodStart)}${formatDate(_report.periodEnd)}",
13,
TextType.Medium,
color: colorWhite,
),
],
),
),
if (_report.grade.isNotEmpty)
Container(
width: 54,
height: 54,
alignment: Alignment.center,
decoration: BoxDecoration(
color: colorWhite,
borderRadius: BorderRadius.circular(16),
),
child: text(_report.grade, 26, TextType.Light,
color: colorPrimaryDark),
),
],
),
);
}
/// Rationed but real. Only rendered when something specific was earned.
Widget _praiseCard() {
return card(
background: colorStandingGoodBg,
borderColor: colorPositive.withValues(alpha: 0.20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(CupertinoIcons.checkmark_seal_fill,
size: 18, color: colorPositive),
const SizedBox(width: 12),
Expanded(
child: text(_report.praise, 14, TextType.Regular,
color: colorPrimaryDark, height: 1.55),
),
],
),
);
}
/// One assigned action for next week. Not five.
Widget _assignedAction() {
return card(
background: colorPrimaryDark,
borderColor: colorPrimaryDark,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
text("NEXT WEEK, ONE THING", 9, TextType.Bold,
color: colorWhite.withValues(alpha: 0.45), letterSpacing: 1.2),
const SizedBox(height: 12),
text(
_report.assignedAction.isEmpty
? "Not enough history yet to assign anything."
: _report.assignedAction,
19,
TextType.Light,
color: colorWhite,
height: 1.4,
),
],
),
);
}
Widget _debtTrend() {
if (_report.debtTrend.isEmpty) {
return card(
child: text("No debt recorded this week.", 13, TextType.Regular,
color: colorGrey2),
);
}
final List<FlSpot> spots = <FlSpot>[];
for (int index = 0; index < _report.debtTrend.length; index++) {
spots.add(FlSpot(index.toDouble(), _report.debtTrend[index]));
}
return card(
padding: const EdgeInsets.fromLTRB(8, 20, 16, 8),
child: SizedBox(
height: 150,
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (value) =>
const FlLine(color: colorChartGrid, strokeWidth: 1),
),
titlesData: FlTitlesData(
topTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
leftTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: true, reservedSize: 32)),
bottomTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
borderData: FlBorderData(show: false),
lineBarsData: <LineChartBarData>[
LineChartBarData(
spots: spots,
isCurved: true,
barWidth: 2.5,
color: colorDebtLine,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(show: true, color: colorDebtFill),
),
],
),
),
),
);
}
Widget _completionRow(MapEntry<String, double> entry) {
return Container(
margin: const EdgeInsets.only(bottom: 14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
text(entry.key, 13, TextType.Medium, color: colorPrimaryDark),
text("${(entry.value * 100).round()}%", 13, TextType.Bold,
color: colorGrey2),
],
),
const SizedBox(height: 8),
meter(
entry.value,
fill: entry.value >= 0.8
? colorPositive
: entry.value >= 0.5
? colorStandingWarned
: colorStandingGrounded,
),
],
),
);
}
/// The recurring window where things go to die.
Widget _worstHour() {
if (_report.worstHour < 0) {
return const SizedBox.shrink();
}
return card(
background: colorStandingWarnedBg,
borderColor: colorStandingWarned.withValues(alpha: 0.20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
text("YOUR WORST HOUR", 9, TextType.Bold,
color: colorStandingWarned, letterSpacing: 1.2),
const SizedBox(height: 10),
text(hourLabel(_report.worstHour), 30, TextType.Light,
color: colorPrimaryDark),
const SizedBox(height: 8),
text(
"This is where things go to die. Stop scheduling anything that matters into it.",
13,
TextType.Regular,
color: colorGrey2,
height: 1.5,
),
],
),
);
}
Widget _deferralRow(DeferralCount item) {
return Container(
margin: const EdgeInsets.only(bottom: 8),
child: card(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13),
child: Row(
children: [
Expanded(
child: text(item.title, 13, TextType.Regular,
color: colorPrimaryDark,
maxLines: 1,
overflow: TextOverflow.ellipsis),
),
pill("${item.count}×", colorStandingGrounded,
colorStandingGroundedBg, textSize: 9),
],
),
),
);
}
/// Your estimates are wrong, and this is by how much.
Widget _estimationRow(MapEntry<String, double> entry) {
return Container(
margin: const EdgeInsets.only(bottom: 8),
child: card(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13),
child: Row(
children: [
Expanded(
child: text(entry.key, 13, TextType.Regular,
color: colorPrimaryDark),
),
text("${entry.value.toStringAsFixed(1)}×", 15, TextType.Bold,
color: entry.value > 1.4 ? colorStandingGrounded : colorGrey2),
],
),
),
);
}
Widget _trainingCard() {
return card(
child: Row(
children: [
Expanded(
child: labelled(
"Adherence",
"${(_report.trainingAdherence * 100).round()}%",
valueSize: 26,
valueType: TextType.Light,
),
),
Expanded(
child: labelled(
"Integrity",
"${(_report.programIntegrity * 100).round()}%",
valueSize: 26,
valueType: TextType.Light,
valueColor: _report.programIntegrity < 0.7
? colorStandingGrounded
: colorPrimaryDark,
),
),
],
),
);
}
@override
void onReportLoaded(ReportCard report) {
setState(() {
_report = report;
});
}
}