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:
129
frontend/lib/Grounded/about/external/data/Commitment.dart
vendored
Normal file
129
frontend/lib/Grounded/about/external/data/Commitment.dart
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
import '../../internal/application/CommitmentClass.dart';
|
||||
import '../../internal/application/CommitmentStatus.dart';
|
||||
import '../../internal/application/CommitmentType.dart';
|
||||
import '../../internal/application/EnergyCost.dart';
|
||||
import '../../internal/application/ProofType.dart';
|
||||
|
||||
/// Something the user said they would do. A due *window*, not a due date —
|
||||
/// "Mon 06:00-08:00" beats "Monday", because a window can actually close.
|
||||
class Commitment {
|
||||
String? id;
|
||||
|
||||
CommitmentType type;
|
||||
|
||||
CommitmentClass commitmentClass;
|
||||
|
||||
String title;
|
||||
|
||||
String category;
|
||||
|
||||
DateTime? dueStart;
|
||||
|
||||
DateTime? dueEnd;
|
||||
|
||||
int estMinutes;
|
||||
|
||||
EnergyCost energy;
|
||||
|
||||
ProofType proofType;
|
||||
|
||||
/// Minimum foreground minutes when proofType is Timer.
|
||||
int proofTimerMinutes;
|
||||
|
||||
String rrule;
|
||||
|
||||
String? parentId;
|
||||
|
||||
CommitmentStatus status;
|
||||
|
||||
int deferralCount;
|
||||
|
||||
Commitment({
|
||||
this.id,
|
||||
this.type = CommitmentType.TASK,
|
||||
this.commitmentClass = CommitmentClass.Standard,
|
||||
this.title = "",
|
||||
this.category = "",
|
||||
this.dueStart,
|
||||
this.dueEnd,
|
||||
this.estMinutes = 0,
|
||||
this.energy = EnergyCost.Medium,
|
||||
this.proofType = ProofType.Honour,
|
||||
this.proofTimerMinutes = 0,
|
||||
this.rrule = "",
|
||||
this.parentId,
|
||||
this.status = CommitmentStatus.Open,
|
||||
this.deferralCount = 0,
|
||||
});
|
||||
|
||||
factory Commitment.fromJson(Map<String, dynamic> json) {
|
||||
return Commitment(
|
||||
id: json['id'],
|
||||
type: getCommitmentType(json['type']),
|
||||
commitmentClass: getCommitmentClass(json['commitmentClass']),
|
||||
title: json['title'] ?? "",
|
||||
category: json['category'] ?? "",
|
||||
dueStart: DateTime.tryParse(json['dueStart'] ?? ""),
|
||||
dueEnd: DateTime.tryParse(json['dueEnd'] ?? ""),
|
||||
estMinutes: json['estMinutes'] ?? 0,
|
||||
energy: getEnergyCost(json['energy']),
|
||||
proofType: getProofType(json['proofType']),
|
||||
proofTimerMinutes: json['proofTimerMinutes'] ?? 0,
|
||||
rrule: json['rrule'] ?? "",
|
||||
parentId: json['parentId'],
|
||||
status: getCommitmentStatus(json['status']),
|
||||
deferralCount: json['deferralCount'] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['type'] = type.name;
|
||||
data['commitmentClass'] = commitmentClass.name;
|
||||
data['title'] = title;
|
||||
data['category'] = category;
|
||||
data['dueStart'] = dueStart?.toIso8601String();
|
||||
data['dueEnd'] = dueEnd?.toIso8601String();
|
||||
data['estMinutes'] = estMinutes;
|
||||
data['energy'] = energy.name;
|
||||
data['proofType'] = proofType.name;
|
||||
data['proofTimerMinutes'] = proofTimerMinutes;
|
||||
data['rrule'] = rrule;
|
||||
data['parentId'] = parentId;
|
||||
data['status'] = status.name;
|
||||
data['deferralCount'] = deferralCount;
|
||||
return data;
|
||||
}
|
||||
|
||||
/// The window has closed. A commitment goes Overdue at close — it never
|
||||
/// silently rolls over to today.
|
||||
bool get windowClosed {
|
||||
if (dueEnd == null) {
|
||||
return false;
|
||||
}
|
||||
return DateTime.now().isAfter(dueEnd!);
|
||||
}
|
||||
|
||||
/// Whole days past the close of the window; 0 while still open.
|
||||
int get daysOverdue {
|
||||
if (dueEnd == null || !windowClosed) {
|
||||
return 0;
|
||||
}
|
||||
return DateTime.now().difference(dueEnd!).inDays;
|
||||
}
|
||||
|
||||
/// Whether completing right now would count as a late complete rather than a
|
||||
/// clean one. Late is recorded distinctly and never reduces debt to zero.
|
||||
bool get wouldBeLate {
|
||||
return windowClosed;
|
||||
}
|
||||
|
||||
/// Non-negotiables are never deferrable, at any count.
|
||||
bool deferrableUnder(int maxDeferrals) {
|
||||
if (commitmentClass == CommitmentClass.NonNegotiable) {
|
||||
return false;
|
||||
}
|
||||
return deferralCount < maxDeferrals;
|
||||
}
|
||||
}
|
||||
53
frontend/lib/Grounded/about/external/data/CommitmentEvent.dart
vendored
Normal file
53
frontend/lib/Grounded/about/external/data/CommitmentEvent.dart
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
import '../../internal/application/EventType.dart';
|
||||
|
||||
/// Append-only history. This log — not the status field — is the source of
|
||||
/// truth, and it is what makes excuse analysis and honest history possible.
|
||||
class CommitmentEvent {
|
||||
String? id;
|
||||
|
||||
String commitmentId;
|
||||
|
||||
EventType event;
|
||||
|
||||
DateTime? at;
|
||||
|
||||
String excuseText;
|
||||
|
||||
String? excuseClusterId;
|
||||
|
||||
String? proofRef;
|
||||
|
||||
CommitmentEvent({
|
||||
this.id,
|
||||
this.commitmentId = "",
|
||||
this.event = EventType.CREATED,
|
||||
this.at,
|
||||
this.excuseText = "",
|
||||
this.excuseClusterId,
|
||||
this.proofRef,
|
||||
});
|
||||
|
||||
factory CommitmentEvent.fromJson(Map<String, dynamic> json) {
|
||||
return CommitmentEvent(
|
||||
id: json['id'],
|
||||
commitmentId: json['commitmentId'] ?? "",
|
||||
event: getEventType(json['event']),
|
||||
at: DateTime.tryParse(json['at'] ?? ""),
|
||||
excuseText: json['excuseText'] ?? "",
|
||||
excuseClusterId: json['excuseClusterId'],
|
||||
proofRef: json['proofRef'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['commitmentId'] = commitmentId;
|
||||
data['event'] = event.name;
|
||||
data['at'] = at?.toIso8601String();
|
||||
data['excuseText'] = excuseText;
|
||||
data['excuseClusterId'] = excuseClusterId;
|
||||
data['proofRef'] = proofRef;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
46
frontend/lib/Grounded/about/external/data/DebtEntry.dart
vendored
Normal file
46
frontend/lib/Grounded/about/external/data/DebtEntry.dart
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
/// One line of the debt ledger. [decayedValue] is what the entry is worth
|
||||
/// today, after recency decay has been applied.
|
||||
class DebtEntry {
|
||||
String? id;
|
||||
|
||||
double delta;
|
||||
|
||||
String reason;
|
||||
|
||||
String? commitmentId;
|
||||
|
||||
DateTime? at;
|
||||
|
||||
double decayedValue;
|
||||
|
||||
DebtEntry({
|
||||
this.id,
|
||||
this.delta = 0,
|
||||
this.reason = "",
|
||||
this.commitmentId,
|
||||
this.at,
|
||||
this.decayedValue = 0,
|
||||
});
|
||||
|
||||
factory DebtEntry.fromJson(Map<String, dynamic> json) {
|
||||
return DebtEntry(
|
||||
id: json['id'],
|
||||
delta: (json['delta'] ?? 0).toDouble(),
|
||||
reason: json['reason'] ?? "",
|
||||
commitmentId: json['commitmentId'],
|
||||
at: DateTime.tryParse(json['at'] ?? ""),
|
||||
decayedValue: (json['decayedValue'] ?? 0).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['delta'] = delta;
|
||||
data['reason'] = reason;
|
||||
data['commitmentId'] = commitmentId;
|
||||
data['at'] = at?.toIso8601String();
|
||||
data['decayedValue'] = decayedValue;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
72
frontend/lib/Grounded/about/external/data/ExcuseCluster.dart
vendored
Normal file
72
frontend/lib/Grounded/about/external/data/ExcuseCluster.dart
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
/// A recurring excuse plus the pattern the app confronts you with, e.g.
|
||||
/// "Too tired has appeared 14 times this month, 11 of them on gym days,
|
||||
/// 9 of them after 7pm. Consider moving gym to morning."
|
||||
class ExcuseCluster {
|
||||
String? id;
|
||||
|
||||
String label;
|
||||
|
||||
int occurrences;
|
||||
|
||||
/// Weekday histogram (1 = Monday) — where this excuse concentrates.
|
||||
Map<int, int> byWeekday;
|
||||
|
||||
/// Hour-of-day histogram — when it concentrates.
|
||||
Map<int, int> byHour;
|
||||
|
||||
/// The category this excuse most often attaches to.
|
||||
String dominantCategory;
|
||||
|
||||
/// The confrontation copy rendered to the user.
|
||||
String insight;
|
||||
|
||||
ExcuseCluster({
|
||||
this.id,
|
||||
this.label = "",
|
||||
this.occurrences = 0,
|
||||
Map<int, int>? byWeekday,
|
||||
Map<int, int>? byHour,
|
||||
this.dominantCategory = "",
|
||||
this.insight = "",
|
||||
}) : byWeekday = byWeekday ?? <int, int>{},
|
||||
byHour = byHour ?? <int, int>{};
|
||||
|
||||
factory ExcuseCluster.fromJson(Map<String, dynamic> json) {
|
||||
final Map<int, int> weekdays = <int, int>{};
|
||||
if (json['byWeekday'] != null) {
|
||||
(json['byWeekday'] as Map<String, dynamic>).forEach((key, value) {
|
||||
weekdays[int.tryParse(key) ?? 1] = value ?? 0;
|
||||
});
|
||||
}
|
||||
|
||||
final Map<int, int> hours = <int, int>{};
|
||||
if (json['byHour'] != null) {
|
||||
(json['byHour'] as Map<String, dynamic>).forEach((key, value) {
|
||||
hours[int.tryParse(key) ?? 0] = value ?? 0;
|
||||
});
|
||||
}
|
||||
|
||||
return ExcuseCluster(
|
||||
id: json['id'],
|
||||
label: json['label'] ?? "",
|
||||
occurrences: json['occurrences'] ?? 0,
|
||||
byWeekday: weekdays,
|
||||
byHour: hours,
|
||||
dominantCategory: json['dominantCategory'] ?? "",
|
||||
insight: json['insight'] ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['label'] = label;
|
||||
data['occurrences'] = occurrences;
|
||||
data['byWeekday'] =
|
||||
byWeekday.map((key, value) => MapEntry(key.toString(), value));
|
||||
data['byHour'] = byHour.map((key, value) => MapEntry(key.toString(), value));
|
||||
data['dominantCategory'] = dominantCategory;
|
||||
data['insight'] = insight;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
91
frontend/lib/Grounded/about/external/data/ExercisePrescription.dart
vendored
Normal file
91
frontend/lib/Grounded/about/external/data/ExercisePrescription.dart
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
import '../../internal/application/ProgressionRule.dart';
|
||||
|
||||
class ExercisePrescription {
|
||||
String? id;
|
||||
|
||||
String sessionTemplateId;
|
||||
|
||||
String exerciseId;
|
||||
|
||||
String exerciseName;
|
||||
|
||||
/// Primary muscle group, for weekly volume tracking.
|
||||
String muscleGroup;
|
||||
|
||||
int sets;
|
||||
|
||||
int targetReps;
|
||||
|
||||
/// For timed holds and intervals; 0 when the work is rep-based.
|
||||
int targetTimeSeconds;
|
||||
|
||||
int restSeconds;
|
||||
|
||||
/// Eccentric-pause-concentric-pause, e.g. "3010".
|
||||
String tempo;
|
||||
|
||||
ProgressionRule progressionRule;
|
||||
|
||||
/// Ground contacts per set, for the plyometric weekly ceiling.
|
||||
int contactsPerSet;
|
||||
|
||||
/// Whether this is a hard exercise for integrity scoring. Derived from
|
||||
/// historical RPE rather than set statically.
|
||||
bool hard;
|
||||
|
||||
ExercisePrescription({
|
||||
this.id,
|
||||
this.sessionTemplateId = "",
|
||||
this.exerciseId = "",
|
||||
this.exerciseName = "",
|
||||
this.muscleGroup = "",
|
||||
this.sets = 0,
|
||||
this.targetReps = 0,
|
||||
this.targetTimeSeconds = 0,
|
||||
this.restSeconds = 0,
|
||||
this.tempo = "",
|
||||
this.progressionRule = ProgressionRule.Reps,
|
||||
this.contactsPerSet = 0,
|
||||
this.hard = false,
|
||||
});
|
||||
|
||||
factory ExercisePrescription.fromJson(Map<String, dynamic> json) {
|
||||
return ExercisePrescription(
|
||||
id: json['id'],
|
||||
sessionTemplateId: json['sessionTemplateId'] ?? "",
|
||||
exerciseId: json['exerciseId'] ?? "",
|
||||
exerciseName: json['exerciseName'] ?? "",
|
||||
muscleGroup: json['muscleGroup'] ?? "",
|
||||
sets: json['sets'] ?? 0,
|
||||
targetReps: json['targetReps'] ?? 0,
|
||||
targetTimeSeconds: json['targetTimeSeconds'] ?? 0,
|
||||
restSeconds: json['restSeconds'] ?? 0,
|
||||
tempo: json['tempo'] ?? "",
|
||||
progressionRule: getProgressionRule(json['progressionRule']),
|
||||
contactsPerSet: json['contactsPerSet'] ?? 0,
|
||||
hard: json['hard'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['sessionTemplateId'] = sessionTemplateId;
|
||||
data['exerciseId'] = exerciseId;
|
||||
data['exerciseName'] = exerciseName;
|
||||
data['muscleGroup'] = muscleGroup;
|
||||
data['sets'] = sets;
|
||||
data['targetReps'] = targetReps;
|
||||
data['targetTimeSeconds'] = targetTimeSeconds;
|
||||
data['restSeconds'] = restSeconds;
|
||||
data['tempo'] = tempo;
|
||||
data['progressionRule'] = progressionRule.name;
|
||||
data['contactsPerSet'] = contactsPerSet;
|
||||
data['hard'] = hard;
|
||||
return data;
|
||||
}
|
||||
|
||||
int get plannedContacts {
|
||||
return contactsPerSet * sets;
|
||||
}
|
||||
}
|
||||
110
frontend/lib/Grounded/about/external/data/Goal.dart
vendored
Normal file
110
frontend/lib/Grounded/about/external/data/Goal.dart
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
import '../../internal/application/CommitmentClass.dart';
|
||||
|
||||
/// The container a set of commitments belongs to — "Workout", "Thesis",
|
||||
/// "Get the flat sorted". A goal owns its tasks through
|
||||
/// [Commitment.parentId]; it never carries debt itself, because debt belongs
|
||||
/// to the specific thing you said you would do, not the ambition behind it.
|
||||
class Goal {
|
||||
String? id;
|
||||
|
||||
String title;
|
||||
|
||||
String description;
|
||||
|
||||
String category;
|
||||
|
||||
/// The default class inherited by tasks created inside this goal.
|
||||
CommitmentClass defaultClass;
|
||||
|
||||
DateTime? startDate;
|
||||
|
||||
/// Optional deadline for the goal as a whole.
|
||||
DateTime? targetDate;
|
||||
|
||||
/// Colour accent, stored as a hex string so the goal reads consistently
|
||||
/// wherever it appears.
|
||||
String colourHex;
|
||||
|
||||
bool archived;
|
||||
|
||||
// ── Derived, supplied by the server ─────────────────────────────────────
|
||||
|
||||
int totalTasks;
|
||||
|
||||
int completedTasks;
|
||||
|
||||
int overdueTasks;
|
||||
|
||||
/// Debt accrued across every task under this goal.
|
||||
double debtContribution;
|
||||
|
||||
Goal({
|
||||
this.id,
|
||||
this.title = "",
|
||||
this.description = "",
|
||||
this.category = "",
|
||||
this.defaultClass = CommitmentClass.Standard,
|
||||
this.startDate,
|
||||
this.targetDate,
|
||||
this.colourHex = "",
|
||||
this.archived = false,
|
||||
this.totalTasks = 0,
|
||||
this.completedTasks = 0,
|
||||
this.overdueTasks = 0,
|
||||
this.debtContribution = 0,
|
||||
});
|
||||
|
||||
factory Goal.fromJson(Map<String, dynamic> json) {
|
||||
return Goal(
|
||||
id: json['id'],
|
||||
title: json['title'] ?? "",
|
||||
description: json['description'] ?? "",
|
||||
category: json['category'] ?? "",
|
||||
defaultClass: getCommitmentClass(json['defaultClass']),
|
||||
startDate: DateTime.tryParse(json['startDate'] ?? ""),
|
||||
targetDate: DateTime.tryParse(json['targetDate'] ?? ""),
|
||||
colourHex: json['colourHex'] ?? "",
|
||||
archived: json['archived'] ?? false,
|
||||
totalTasks: json['totalTasks'] ?? 0,
|
||||
completedTasks: json['completedTasks'] ?? 0,
|
||||
overdueTasks: json['overdueTasks'] ?? 0,
|
||||
debtContribution: (json['debtContribution'] ?? 0).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['title'] = title;
|
||||
data['description'] = description;
|
||||
data['category'] = category;
|
||||
data['defaultClass'] = defaultClass.name;
|
||||
data['startDate'] = startDate?.toIso8601String();
|
||||
data['targetDate'] = targetDate?.toIso8601String();
|
||||
data['colourHex'] = colourHex;
|
||||
data['archived'] = archived;
|
||||
return data;
|
||||
}
|
||||
|
||||
/// 0..1 across the goal's tasks.
|
||||
double get progress {
|
||||
if (totalTasks == 0) {
|
||||
return 0;
|
||||
}
|
||||
return completedTasks / totalTasks;
|
||||
}
|
||||
|
||||
/// A goal is in trouble when a meaningful share of its tasks are past their
|
||||
/// windows, not merely because one slipped.
|
||||
bool get slipping {
|
||||
if (totalTasks == 0) {
|
||||
return false;
|
||||
}
|
||||
return overdueTasks / totalTasks >= 0.34;
|
||||
}
|
||||
|
||||
int get remainingTasks {
|
||||
final int remaining = totalTasks - completedTasks;
|
||||
return remaining > 0 ? remaining : 0;
|
||||
}
|
||||
}
|
||||
45
frontend/lib/Grounded/about/external/data/GroundedError.dart
vendored
Normal file
45
frontend/lib/Grounded/about/external/data/GroundedError.dart
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'Severity.dart';
|
||||
|
||||
class GroundedError {
|
||||
double code;
|
||||
String message;
|
||||
String helper;
|
||||
String title;
|
||||
String severity;
|
||||
|
||||
GroundedError(
|
||||
{required this.code,
|
||||
required this.message,
|
||||
required this.helper,
|
||||
required this.title,
|
||||
required this.severity});
|
||||
|
||||
factory GroundedError.fromJson(Map<String, dynamic> json) {
|
||||
return GroundedError(
|
||||
code: json['code'],
|
||||
message: json['message'],
|
||||
helper: json['helper'],
|
||||
title: json['title'],
|
||||
severity: json['severity'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['code'] = code;
|
||||
data['message'] = message;
|
||||
data['helper'] = helper;
|
||||
data['title'] = title;
|
||||
data['severity'] = severity;
|
||||
return data;
|
||||
}
|
||||
|
||||
Severity getSeverityEnum(String severityString) {
|
||||
for (Severity severity in Severity.values) {
|
||||
if (severityString == severity.name) {
|
||||
return severity;
|
||||
}
|
||||
}
|
||||
return Severity.error;
|
||||
}
|
||||
}
|
||||
73
frontend/lib/Grounded/about/external/data/Habit.dart
vendored
Normal file
73
frontend/lib/Grounded/about/external/data/Habit.dart
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
/// Frequency-based rather than instance-based: a single miss costs nothing,
|
||||
/// falling below the target in the rolling window is what accrues debt.
|
||||
class Habit {
|
||||
String? id;
|
||||
|
||||
String title;
|
||||
|
||||
String category;
|
||||
|
||||
/// Target completions per rolling window, e.g. 5.
|
||||
int targetPerWindow;
|
||||
|
||||
/// Rolling window length in days, e.g. 7.
|
||||
int windowDays;
|
||||
|
||||
/// Completions inside the current window.
|
||||
int completionsInWindow;
|
||||
|
||||
/// Marked keystone once the data says its completion predicts day quality —
|
||||
/// the app works this out after ~60 days rather than taking your word.
|
||||
bool keystone;
|
||||
|
||||
/// 0..1 correlation with overall day quality; -1 until there is enough data.
|
||||
double predictiveStrength;
|
||||
|
||||
Habit({
|
||||
this.id,
|
||||
this.title = "",
|
||||
this.category = "",
|
||||
this.targetPerWindow = 0,
|
||||
this.windowDays = 7,
|
||||
this.completionsInWindow = 0,
|
||||
this.keystone = false,
|
||||
this.predictiveStrength = -1,
|
||||
});
|
||||
|
||||
factory Habit.fromJson(Map<String, dynamic> json) {
|
||||
return Habit(
|
||||
id: json['id'],
|
||||
title: json['title'] ?? "",
|
||||
category: json['category'] ?? "",
|
||||
targetPerWindow: json['targetPerWindow'] ?? 0,
|
||||
windowDays: json['windowDays'] ?? 7,
|
||||
completionsInWindow: json['completionsInWindow'] ?? 0,
|
||||
keystone: json['keystone'] ?? false,
|
||||
predictiveStrength: (json['predictiveStrength'] ?? -1).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['title'] = title;
|
||||
data['category'] = category;
|
||||
data['targetPerWindow'] = targetPerWindow;
|
||||
data['windowDays'] = windowDays;
|
||||
data['completionsInWindow'] = completionsInWindow;
|
||||
data['keystone'] = keystone;
|
||||
data['predictiveStrength'] = predictiveStrength;
|
||||
return data;
|
||||
}
|
||||
|
||||
/// Behind target for the window — the only condition under which a habit
|
||||
/// accrues debt.
|
||||
bool get behindTarget {
|
||||
return completionsInWindow < targetPerWindow;
|
||||
}
|
||||
|
||||
int get shortfall {
|
||||
final int gap = targetPerWindow - completionsInWindow;
|
||||
return gap > 0 ? gap : 0;
|
||||
}
|
||||
}
|
||||
118
frontend/lib/Grounded/about/external/data/LiveSession.dart
vendored
Normal file
118
frontend/lib/Grounded/about/external/data/LiveSession.dart
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
/// A task actively being run. This is what the full-screen runner and the
|
||||
/// ongoing notification are both driven from.
|
||||
class LiveSession {
|
||||
String commitmentId;
|
||||
|
||||
String title;
|
||||
|
||||
String goalTitle;
|
||||
|
||||
/// Wall-clock start of the run.
|
||||
DateTime startedAt;
|
||||
|
||||
/// Seconds of *foreground* work accumulated. Backgrounding the app stops
|
||||
/// this accruing — that is the whole point of Timer proof.
|
||||
int accumulatedSeconds;
|
||||
|
||||
/// When the current active stretch began; null while paused.
|
||||
DateTime? resumedAt;
|
||||
|
||||
/// Seconds required before the run counts as proof.
|
||||
int requiredSeconds;
|
||||
|
||||
/// How many times the user left the app mid-run. Surfaced afterwards rather
|
||||
/// than hidden, because leaving repeatedly is the behaviour worth seeing.
|
||||
int backgroundedCount;
|
||||
|
||||
LiveSession({
|
||||
this.commitmentId = "",
|
||||
this.title = "",
|
||||
this.goalTitle = "",
|
||||
required this.startedAt,
|
||||
this.accumulatedSeconds = 0,
|
||||
this.resumedAt,
|
||||
this.requiredSeconds = 0,
|
||||
this.backgroundedCount = 0,
|
||||
});
|
||||
|
||||
factory LiveSession.fromJson(Map<String, dynamic> json) {
|
||||
return LiveSession(
|
||||
commitmentId: json['commitmentId'] ?? "",
|
||||
title: json['title'] ?? "",
|
||||
goalTitle: json['goalTitle'] ?? "",
|
||||
startedAt: DateTime.tryParse(json['startedAt'] ?? "") ?? DateTime.now(),
|
||||
accumulatedSeconds: json['accumulatedSeconds'] ?? 0,
|
||||
resumedAt: DateTime.tryParse(json['resumedAt'] ?? ""),
|
||||
requiredSeconds: json['requiredSeconds'] ?? 0,
|
||||
backgroundedCount: json['backgroundedCount'] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['commitmentId'] = commitmentId;
|
||||
data['title'] = title;
|
||||
data['goalTitle'] = goalTitle;
|
||||
data['startedAt'] = startedAt.toIso8601String();
|
||||
data['accumulatedSeconds'] = accumulatedSeconds;
|
||||
data['resumedAt'] = resumedAt?.toIso8601String();
|
||||
data['requiredSeconds'] = requiredSeconds;
|
||||
data['backgroundedCount'] = backgroundedCount;
|
||||
return data;
|
||||
}
|
||||
|
||||
bool get running {
|
||||
return resumedAt != null;
|
||||
}
|
||||
|
||||
/// Foreground seconds as of now, including the stretch in progress. This is
|
||||
/// derived from wall-clock rather than counted by the ticker, so the value
|
||||
/// stays correct across a screen-off period where timers are throttled.
|
||||
int elapsedSeconds({DateTime? now}) {
|
||||
if (resumedAt == null) {
|
||||
return accumulatedSeconds;
|
||||
}
|
||||
final DateTime moment = now ?? DateTime.now();
|
||||
return accumulatedSeconds + moment.difference(resumedAt!).inSeconds;
|
||||
}
|
||||
|
||||
int remainingSeconds({DateTime? now}) {
|
||||
final int remaining = requiredSeconds - elapsedSeconds(now: now);
|
||||
return remaining > 0 ? remaining : 0;
|
||||
}
|
||||
|
||||
/// 0..1 toward the requirement.
|
||||
double progress({DateTime? now}) {
|
||||
if (requiredSeconds <= 0) {
|
||||
return 0;
|
||||
}
|
||||
final double value = elapsedSeconds(now: now) / requiredSeconds;
|
||||
return value > 1 ? 1 : value;
|
||||
}
|
||||
|
||||
/// The requirement has been met — completion is now allowed.
|
||||
bool satisfied({DateTime? now}) {
|
||||
if (requiredSeconds <= 0) {
|
||||
return true;
|
||||
}
|
||||
return elapsedSeconds(now: now) >= requiredSeconds;
|
||||
}
|
||||
|
||||
/// Pause and bank the stretch that just ended.
|
||||
void pause({DateTime? now}) {
|
||||
if (resumedAt == null) {
|
||||
return;
|
||||
}
|
||||
final DateTime moment = now ?? DateTime.now();
|
||||
accumulatedSeconds =
|
||||
accumulatedSeconds + moment.difference(resumedAt!).inSeconds;
|
||||
resumedAt = null;
|
||||
}
|
||||
|
||||
void resume({DateTime? now}) {
|
||||
if (resumedAt != null) {
|
||||
return;
|
||||
}
|
||||
resumedAt = now ?? DateTime.now();
|
||||
}
|
||||
}
|
||||
66
frontend/lib/Grounded/about/external/data/Program.dart
vendored
Normal file
66
frontend/lib/Grounded/about/external/data/Program.dart
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/// A training program. Deload weeks are scheduled and enforced — training
|
||||
/// through a deload logs as non-compliance, same as skipping.
|
||||
class Program {
|
||||
String? id;
|
||||
|
||||
String name;
|
||||
|
||||
int weeks;
|
||||
|
||||
int sessionsPerWeek;
|
||||
|
||||
/// 1-based week indices that are deloads.
|
||||
List<int> deloadWeeks;
|
||||
|
||||
/// Weekly ceiling on plyometric ground contacts. Plyo is the one modality
|
||||
/// where the app stops you rather than pushes you.
|
||||
int weeklyContactCeiling;
|
||||
|
||||
/// Mandatory hours between high-intensity lower-body sessions.
|
||||
int lowerBodyRecoveryHours;
|
||||
|
||||
bool active;
|
||||
|
||||
Program({
|
||||
this.id,
|
||||
this.name = "",
|
||||
this.weeks = 0,
|
||||
this.sessionsPerWeek = 0,
|
||||
List<int>? deloadWeeks,
|
||||
this.weeklyContactCeiling = 0,
|
||||
this.lowerBodyRecoveryHours = 48,
|
||||
this.active = false,
|
||||
}) : deloadWeeks = deloadWeeks ?? <int>[];
|
||||
|
||||
factory Program.fromJson(Map<String, dynamic> json) {
|
||||
return Program(
|
||||
id: json['id'],
|
||||
name: json['name'] ?? "",
|
||||
weeks: json['weeks'] ?? 0,
|
||||
sessionsPerWeek: json['sessionsPerWeek'] ?? 0,
|
||||
deloadWeeks: json['deloadWeeks'] == null
|
||||
? <int>[]
|
||||
: (json['deloadWeeks'] as List).map((item) => item as int).toList(),
|
||||
weeklyContactCeiling: json['weeklyContactCeiling'] ?? 0,
|
||||
lowerBodyRecoveryHours: json['lowerBodyRecoveryHours'] ?? 48,
|
||||
active: json['active'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['name'] = name;
|
||||
data['weeks'] = weeks;
|
||||
data['sessionsPerWeek'] = sessionsPerWeek;
|
||||
data['deloadWeeks'] = deloadWeeks;
|
||||
data['weeklyContactCeiling'] = weeklyContactCeiling;
|
||||
data['lowerBodyRecoveryHours'] = lowerBodyRecoveryHours;
|
||||
data['active'] = active;
|
||||
return data;
|
||||
}
|
||||
|
||||
bool isDeloadWeek(int week) {
|
||||
return deloadWeeks.contains(week);
|
||||
}
|
||||
}
|
||||
192
frontend/lib/Grounded/about/external/data/ReportCard.dart
vendored
Normal file
192
frontend/lib/Grounded/about/external/data/ReportCard.dart
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
import 'ExcuseCluster.dart';
|
||||
|
||||
/// The weekly parent-teacher conference. One assigned action for next week —
|
||||
/// not five.
|
||||
class ReportCard {
|
||||
String? id;
|
||||
|
||||
DateTime? periodStart;
|
||||
|
||||
DateTime? periodEnd;
|
||||
|
||||
/// Completion rate 0..1, keyed by commitment class name.
|
||||
Map<String, double> completionByClass;
|
||||
|
||||
/// Completion rate 0..1, keyed by category.
|
||||
Map<String, double> completionByCategory;
|
||||
|
||||
/// Completion rate 0..1, keyed by weekday (1 = Monday).
|
||||
Map<int, double> completionByWeekday;
|
||||
|
||||
/// The recurring window where things go to die, as an hour of the day.
|
||||
int worstHour;
|
||||
|
||||
/// Debt score sampled per day across the period.
|
||||
List<double> debtTrend;
|
||||
|
||||
/// The commitments dodged most, highest first.
|
||||
List<DeferralCount> deferralLeaderboard;
|
||||
|
||||
List<ExcuseCluster> excuseTaxonomy;
|
||||
|
||||
/// Estimation multiplier per category, surfaced here rather than hidden.
|
||||
Map<String, double> estimationAccuracy;
|
||||
|
||||
/// Training adherence 0..1 across the period.
|
||||
double trainingAdherence;
|
||||
|
||||
double programIntegrity;
|
||||
|
||||
/// The single assigned action for next week.
|
||||
String assignedAction;
|
||||
|
||||
/// Cosmetic but effective.
|
||||
String grade;
|
||||
|
||||
/// Rationed, specific praise — empty when nothing was genuinely earned.
|
||||
String praise;
|
||||
|
||||
ReportCard({
|
||||
this.id,
|
||||
this.periodStart,
|
||||
this.periodEnd,
|
||||
Map<String, double>? completionByClass,
|
||||
Map<String, double>? completionByCategory,
|
||||
Map<int, double>? completionByWeekday,
|
||||
this.worstHour = -1,
|
||||
List<double>? debtTrend,
|
||||
List<DeferralCount>? deferralLeaderboard,
|
||||
List<ExcuseCluster>? excuseTaxonomy,
|
||||
Map<String, double>? estimationAccuracy,
|
||||
this.trainingAdherence = 0,
|
||||
this.programIntegrity = 0,
|
||||
this.assignedAction = "",
|
||||
this.grade = "",
|
||||
this.praise = "",
|
||||
}) : completionByClass = completionByClass ?? <String, double>{},
|
||||
completionByCategory = completionByCategory ?? <String, double>{},
|
||||
completionByWeekday = completionByWeekday ?? <int, double>{},
|
||||
debtTrend = debtTrend ?? <double>[],
|
||||
deferralLeaderboard = deferralLeaderboard ?? <DeferralCount>[],
|
||||
excuseTaxonomy = excuseTaxonomy ?? <ExcuseCluster>[],
|
||||
estimationAccuracy = estimationAccuracy ?? <String, double>{};
|
||||
|
||||
factory ReportCard.fromJson(Map<String, dynamic> json) {
|
||||
final Map<String, double> byClass = <String, double>{};
|
||||
if (json['completionByClass'] != null) {
|
||||
(json['completionByClass'] as Map<String, dynamic>).forEach((key, value) {
|
||||
byClass[key] = (value ?? 0).toDouble();
|
||||
});
|
||||
}
|
||||
|
||||
final Map<String, double> byCategory = <String, double>{};
|
||||
if (json['completionByCategory'] != null) {
|
||||
(json['completionByCategory'] as Map<String, dynamic>)
|
||||
.forEach((key, value) {
|
||||
byCategory[key] = (value ?? 0).toDouble();
|
||||
});
|
||||
}
|
||||
|
||||
final Map<int, double> byWeekday = <int, double>{};
|
||||
if (json['completionByWeekday'] != null) {
|
||||
(json['completionByWeekday'] as Map<String, dynamic>)
|
||||
.forEach((key, value) {
|
||||
byWeekday[int.tryParse(key) ?? 1] = (value ?? 0).toDouble();
|
||||
});
|
||||
}
|
||||
|
||||
final Map<String, double> estimation = <String, double>{};
|
||||
if (json['estimationAccuracy'] != null) {
|
||||
(json['estimationAccuracy'] as Map<String, dynamic>)
|
||||
.forEach((key, value) {
|
||||
estimation[key] = (value ?? 1).toDouble();
|
||||
});
|
||||
}
|
||||
|
||||
return ReportCard(
|
||||
id: json['id'],
|
||||
periodStart: DateTime.tryParse(json['periodStart'] ?? ""),
|
||||
periodEnd: DateTime.tryParse(json['periodEnd'] ?? ""),
|
||||
completionByClass: byClass,
|
||||
completionByCategory: byCategory,
|
||||
completionByWeekday: byWeekday,
|
||||
worstHour: json['worstHour'] ?? -1,
|
||||
debtTrend: json['debtTrend'] == null
|
||||
? <double>[]
|
||||
: (json['debtTrend'] as List)
|
||||
.map((item) => (item ?? 0).toDouble() as double)
|
||||
.toList(),
|
||||
deferralLeaderboard: json['deferralLeaderboard'] == null
|
||||
? <DeferralCount>[]
|
||||
: (json['deferralLeaderboard'] as List)
|
||||
.map((item) => DeferralCount.fromJson(item))
|
||||
.toList(),
|
||||
excuseTaxonomy: json['excuseTaxonomy'] == null
|
||||
? <ExcuseCluster>[]
|
||||
: (json['excuseTaxonomy'] as List)
|
||||
.map((item) => ExcuseCluster.fromJson(item))
|
||||
.toList(),
|
||||
estimationAccuracy: estimation,
|
||||
trainingAdherence: (json['trainingAdherence'] ?? 0).toDouble(),
|
||||
programIntegrity: (json['programIntegrity'] ?? 0).toDouble(),
|
||||
assignedAction: json['assignedAction'] ?? "",
|
||||
grade: json['grade'] ?? "",
|
||||
praise: json['praise'] ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['periodStart'] = periodStart?.toIso8601String();
|
||||
data['periodEnd'] = periodEnd?.toIso8601String();
|
||||
data['completionByClass'] = completionByClass;
|
||||
data['completionByCategory'] = completionByCategory;
|
||||
data['completionByWeekday'] =
|
||||
completionByWeekday.map((key, value) => MapEntry(key.toString(), value));
|
||||
data['worstHour'] = worstHour;
|
||||
data['debtTrend'] = debtTrend;
|
||||
data['deferralLeaderboard'] =
|
||||
deferralLeaderboard.map((item) => item.toJson()).toList();
|
||||
data['excuseTaxonomy'] =
|
||||
excuseTaxonomy.map((item) => item.toJson()).toList();
|
||||
data['estimationAccuracy'] = estimationAccuracy;
|
||||
data['trainingAdherence'] = trainingAdherence;
|
||||
data['programIntegrity'] = programIntegrity;
|
||||
data['assignedAction'] = assignedAction;
|
||||
data['grade'] = grade;
|
||||
data['praise'] = praise;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/// One row of the deferral leaderboard — the tasks dodged most.
|
||||
class DeferralCount {
|
||||
String commitmentId;
|
||||
|
||||
String title;
|
||||
|
||||
int count;
|
||||
|
||||
DeferralCount({
|
||||
this.commitmentId = "",
|
||||
this.title = "",
|
||||
this.count = 0,
|
||||
});
|
||||
|
||||
factory DeferralCount.fromJson(Map<String, dynamic> json) {
|
||||
return DeferralCount(
|
||||
commitmentId: json['commitmentId'] ?? "",
|
||||
title: json['title'] ?? "",
|
||||
count: json['count'] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['commitmentId'] = commitmentId;
|
||||
data['title'] = title;
|
||||
data['count'] = count;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
1
frontend/lib/Grounded/about/external/data/ResponseState.dart
vendored
Normal file
1
frontend/lib/Grounded/about/external/data/ResponseState.dart
vendored
Normal file
@@ -0,0 +1 @@
|
||||
enum ResponseState { Success, Failure, Pending }
|
||||
97
frontend/lib/Grounded/about/external/data/RoutineChain.dart
vendored
Normal file
97
frontend/lib/Grounded/about/external/data/RoutineChain.dart
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
/// An ordered sequence where the chain, not the step, is the unit of
|
||||
/// completion. Breaking mid-way logs partial.
|
||||
class RoutineChain {
|
||||
String? id;
|
||||
|
||||
String title;
|
||||
|
||||
List<RoutineStep> steps;
|
||||
|
||||
/// Index of the step reached when the chain last broke; -1 when clean.
|
||||
int lastBreakIndex;
|
||||
|
||||
RoutineChain({
|
||||
this.id,
|
||||
this.title = "",
|
||||
List<RoutineStep>? steps,
|
||||
this.lastBreakIndex = -1,
|
||||
}) : steps = steps ?? <RoutineStep>[];
|
||||
|
||||
factory RoutineChain.fromJson(Map<String, dynamic> json) {
|
||||
return RoutineChain(
|
||||
id: json['id'],
|
||||
title: json['title'] ?? "",
|
||||
steps: json['steps'] == null
|
||||
? <RoutineStep>[]
|
||||
: (json['steps'] as List)
|
||||
.map((item) => RoutineStep.fromJson(item))
|
||||
.toList(),
|
||||
lastBreakIndex: json['lastBreakIndex'] ?? -1,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['title'] = title;
|
||||
data['steps'] = steps.map((item) => item.toJson()).toList();
|
||||
data['lastBreakIndex'] = lastBreakIndex;
|
||||
return data;
|
||||
}
|
||||
|
||||
int get completedSteps {
|
||||
return steps.where((step) => step.completed).length;
|
||||
}
|
||||
|
||||
/// 0..1 — a partially run chain is recorded as partial, not as done.
|
||||
double get partialCompletion {
|
||||
if (steps.isEmpty) {
|
||||
return 0;
|
||||
}
|
||||
return completedSteps / steps.length;
|
||||
}
|
||||
|
||||
bool get complete {
|
||||
return steps.isNotEmpty && completedSteps == steps.length;
|
||||
}
|
||||
}
|
||||
|
||||
class RoutineStep {
|
||||
String? id;
|
||||
|
||||
String title;
|
||||
|
||||
int order;
|
||||
|
||||
int timerSeconds;
|
||||
|
||||
bool completed;
|
||||
|
||||
RoutineStep({
|
||||
this.id,
|
||||
this.title = "",
|
||||
this.order = 0,
|
||||
this.timerSeconds = 0,
|
||||
this.completed = false,
|
||||
});
|
||||
|
||||
factory RoutineStep.fromJson(Map<String, dynamic> json) {
|
||||
return RoutineStep(
|
||||
id: json['id'],
|
||||
title: json['title'] ?? "",
|
||||
order: json['order'] ?? 0,
|
||||
timerSeconds: json['timerSeconds'] ?? 0,
|
||||
completed: json['completed'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['title'] = title;
|
||||
data['order'] = order;
|
||||
data['timerSeconds'] = timerSeconds;
|
||||
data['completed'] = completed;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
105
frontend/lib/Grounded/about/external/data/SessionLog.dart
vendored
Normal file
105
frontend/lib/Grounded/about/external/data/SessionLog.dart
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'SetLog.dart';
|
||||
|
||||
class SessionLog {
|
||||
String? id;
|
||||
|
||||
String templateId;
|
||||
|
||||
String templateName;
|
||||
|
||||
DateTime? startedAt;
|
||||
|
||||
DateTime? endedAt;
|
||||
|
||||
/// Session RPE 1..10.
|
||||
double sessionRpe;
|
||||
|
||||
/// Readiness check-in at session start.
|
||||
int sleepScore;
|
||||
|
||||
int sorenessScore;
|
||||
|
||||
int motivationScore;
|
||||
|
||||
/// Did you do the session, or a watered-down version of it?
|
||||
double integrityScore;
|
||||
|
||||
bool duringDeload;
|
||||
|
||||
List<SetLog> sets;
|
||||
|
||||
SessionLog({
|
||||
this.id,
|
||||
this.templateId = "",
|
||||
this.templateName = "",
|
||||
this.startedAt,
|
||||
this.endedAt,
|
||||
this.sessionRpe = 0,
|
||||
this.sleepScore = 0,
|
||||
this.sorenessScore = 0,
|
||||
this.motivationScore = 0,
|
||||
this.integrityScore = 0,
|
||||
this.duringDeload = false,
|
||||
List<SetLog>? sets,
|
||||
}) : sets = sets ?? <SetLog>[];
|
||||
|
||||
factory SessionLog.fromJson(Map<String, dynamic> json) {
|
||||
return SessionLog(
|
||||
id: json['id'],
|
||||
templateId: json['templateId'] ?? "",
|
||||
templateName: json['templateName'] ?? "",
|
||||
startedAt: DateTime.tryParse(json['startedAt'] ?? ""),
|
||||
endedAt: DateTime.tryParse(json['endedAt'] ?? ""),
|
||||
sessionRpe: (json['sessionRpe'] ?? 0).toDouble(),
|
||||
sleepScore: json['sleepScore'] ?? 0,
|
||||
sorenessScore: json['sorenessScore'] ?? 0,
|
||||
motivationScore: json['motivationScore'] ?? 0,
|
||||
integrityScore: (json['integrityScore'] ?? 0).toDouble(),
|
||||
duringDeload: json['duringDeload'] ?? false,
|
||||
sets: json['sets'] == null
|
||||
? <SetLog>[]
|
||||
: (json['sets'] as List).map((item) => SetLog.fromJson(item)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['templateId'] = templateId;
|
||||
data['templateName'] = templateName;
|
||||
data['startedAt'] = startedAt?.toIso8601String();
|
||||
data['endedAt'] = endedAt?.toIso8601String();
|
||||
data['sessionRpe'] = sessionRpe;
|
||||
data['sleepScore'] = sleepScore;
|
||||
data['sorenessScore'] = sorenessScore;
|
||||
data['motivationScore'] = motivationScore;
|
||||
data['integrityScore'] = integrityScore;
|
||||
data['duringDeload'] = duringDeload;
|
||||
data['sets'] = sets.map((item) => item.toJson()).toList();
|
||||
return data;
|
||||
}
|
||||
|
||||
int get durationMinutes {
|
||||
if (startedAt == null || endedAt == null) {
|
||||
return 0;
|
||||
}
|
||||
return endedAt!.difference(startedAt!).inMinutes;
|
||||
}
|
||||
|
||||
/// Weekly tonnage contribution for loaded work.
|
||||
double get tonnage {
|
||||
double total = 0;
|
||||
for (SetLog entry in sets) {
|
||||
total = total + (entry.loadKg * entry.reps);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
int get contacts {
|
||||
int total = 0;
|
||||
for (SetLog entry in sets) {
|
||||
total = total + entry.contacts;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
53
frontend/lib/Grounded/about/external/data/SessionTemplate.dart
vendored
Normal file
53
frontend/lib/Grounded/about/external/data/SessionTemplate.dart
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'ExercisePrescription.dart';
|
||||
|
||||
class SessionTemplate {
|
||||
String? id;
|
||||
|
||||
String programId;
|
||||
|
||||
/// 0-based day within the training week.
|
||||
int dayIndex;
|
||||
|
||||
String name;
|
||||
|
||||
/// True when this session loads the lower body hard enough to require the
|
||||
/// program recovery gap before the next one.
|
||||
bool highIntensityLowerBody;
|
||||
|
||||
List<ExercisePrescription> prescriptions;
|
||||
|
||||
SessionTemplate({
|
||||
this.id,
|
||||
this.programId = "",
|
||||
this.dayIndex = 0,
|
||||
this.name = "",
|
||||
this.highIntensityLowerBody = false,
|
||||
List<ExercisePrescription>? prescriptions,
|
||||
}) : prescriptions = prescriptions ?? <ExercisePrescription>[];
|
||||
|
||||
factory SessionTemplate.fromJson(Map<String, dynamic> json) {
|
||||
return SessionTemplate(
|
||||
id: json['id'],
|
||||
programId: json['programId'] ?? "",
|
||||
dayIndex: json['dayIndex'] ?? 0,
|
||||
name: json['name'] ?? "",
|
||||
highIntensityLowerBody: json['highIntensityLowerBody'] ?? false,
|
||||
prescriptions: json['prescriptions'] == null
|
||||
? <ExercisePrescription>[]
|
||||
: (json['prescriptions'] as List)
|
||||
.map((item) => ExercisePrescription.fromJson(item))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['programId'] = programId;
|
||||
data['dayIndex'] = dayIndex;
|
||||
data['name'] = name;
|
||||
data['highIntensityLowerBody'] = highIntensityLowerBody;
|
||||
data['prescriptions'] = prescriptions.map((item) => item.toJson()).toList();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
76
frontend/lib/Grounded/about/external/data/SetLog.dart
vendored
Normal file
76
frontend/lib/Grounded/about/external/data/SetLog.dart
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
class SetLog {
|
||||
String? id;
|
||||
|
||||
String sessionLogId;
|
||||
|
||||
String exerciseId;
|
||||
|
||||
String exerciseName;
|
||||
|
||||
String muscleGroup;
|
||||
|
||||
int setNo;
|
||||
|
||||
int reps;
|
||||
|
||||
double loadKg;
|
||||
|
||||
int timeSeconds;
|
||||
|
||||
double rpe;
|
||||
|
||||
/// False when the set was improvised rather than prescribed — this is what
|
||||
/// separates doing the session from doing something adjacent to it.
|
||||
bool isPrescribed;
|
||||
|
||||
int contacts;
|
||||
|
||||
SetLog({
|
||||
this.id,
|
||||
this.sessionLogId = "",
|
||||
this.exerciseId = "",
|
||||
this.exerciseName = "",
|
||||
this.muscleGroup = "",
|
||||
this.setNo = 0,
|
||||
this.reps = 0,
|
||||
this.loadKg = 0,
|
||||
this.timeSeconds = 0,
|
||||
this.rpe = 0,
|
||||
this.isPrescribed = true,
|
||||
this.contacts = 0,
|
||||
});
|
||||
|
||||
factory SetLog.fromJson(Map<String, dynamic> json) {
|
||||
return SetLog(
|
||||
id: json['id'],
|
||||
sessionLogId: json['sessionLogId'] ?? "",
|
||||
exerciseId: json['exerciseId'] ?? "",
|
||||
exerciseName: json['exerciseName'] ?? "",
|
||||
muscleGroup: json['muscleGroup'] ?? "",
|
||||
setNo: json['setNo'] ?? 0,
|
||||
reps: json['reps'] ?? 0,
|
||||
loadKg: (json['loadKg'] ?? 0).toDouble(),
|
||||
timeSeconds: json['timeSeconds'] ?? 0,
|
||||
rpe: (json['rpe'] ?? 0).toDouble(),
|
||||
isPrescribed: json['isPrescribed'] ?? true,
|
||||
contacts: json['contacts'] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['sessionLogId'] = sessionLogId;
|
||||
data['exerciseId'] = exerciseId;
|
||||
data['exerciseName'] = exerciseName;
|
||||
data['muscleGroup'] = muscleGroup;
|
||||
data['setNo'] = setNo;
|
||||
data['reps'] = reps;
|
||||
data['loadKg'] = loadKg;
|
||||
data['timeSeconds'] = timeSeconds;
|
||||
data['rpe'] = rpe;
|
||||
data['isPrescribed'] = isPrescribed;
|
||||
data['contacts'] = contacts;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
1
frontend/lib/Grounded/about/external/data/Severity.dart
vendored
Normal file
1
frontend/lib/Grounded/about/external/data/Severity.dart
vendored
Normal file
@@ -0,0 +1 @@
|
||||
enum Severity { warning, alert, message, error }
|
||||
41
frontend/lib/Grounded/about/external/data/StandingChange.dart
vendored
Normal file
41
frontend/lib/Grounded/about/external/data/StandingChange.dart
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
import '../../internal/application/Standing.dart';
|
||||
|
||||
class StandingChange {
|
||||
String? id;
|
||||
|
||||
Standing from;
|
||||
|
||||
Standing to;
|
||||
|
||||
DateTime? at;
|
||||
|
||||
String trigger;
|
||||
|
||||
StandingChange({
|
||||
this.id,
|
||||
this.from = Standing.Good,
|
||||
this.to = Standing.Good,
|
||||
this.at,
|
||||
this.trigger = "",
|
||||
});
|
||||
|
||||
factory StandingChange.fromJson(Map<String, dynamic> json) {
|
||||
return StandingChange(
|
||||
id: json['id'],
|
||||
from: getStanding(json['from']),
|
||||
to: getStanding(json['to']),
|
||||
at: DateTime.tryParse(json['at'] ?? ""),
|
||||
trigger: json['trigger'] ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['from'] = from.name;
|
||||
data['to'] = to.name;
|
||||
data['at'] = at?.toIso8601String();
|
||||
data['trigger'] = trigger;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
40
frontend/lib/Grounded/about/external/data/SystemResponse.dart
vendored
Normal file
40
frontend/lib/Grounded/about/external/data/SystemResponse.dart
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'ResponseState.dart';
|
||||
|
||||
class SystemResponse {
|
||||
String key;
|
||||
|
||||
String value;
|
||||
|
||||
String description;
|
||||
|
||||
ResponseState state;
|
||||
|
||||
SystemResponse(this.key, this.value, this.description, this.state);
|
||||
|
||||
factory SystemResponse.fromJsonMap(Map<String, dynamic> json) {
|
||||
return SystemResponse(
|
||||
json['key'] ?? "",
|
||||
json['value'] ?? "",
|
||||
json['description'] ?? "",
|
||||
_state(json['state']),
|
||||
);
|
||||
}
|
||||
|
||||
static ResponseState _state(String? name) {
|
||||
for (ResponseState value in ResponseState.values) {
|
||||
if (value.name == name) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return ResponseState.Success;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['key'] = key;
|
||||
data['value'] = value;
|
||||
data['description'] = description;
|
||||
data['state'] = state.name;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
21
frontend/lib/Grounded/about/external/data/pages/request/CommitmentsRequest.dart
vendored
Normal file
21
frontend/lib/Grounded/about/external/data/pages/request/CommitmentsRequest.dart
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'PageAndSort.dart';
|
||||
|
||||
class CommitmentsRequest {
|
||||
PageAndSort? query;
|
||||
|
||||
/// ISO day the plan is being requested for; empty means today.
|
||||
String day;
|
||||
|
||||
/// Filter by status name; empty means all.
|
||||
String status;
|
||||
|
||||
CommitmentsRequest({this.query, this.day = "", this.status = ""});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['query'] = query?.toJson();
|
||||
data['day'] = day;
|
||||
data['status'] = status;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
14
frontend/lib/Grounded/about/external/data/pages/request/HistoryRequest.dart
vendored
Normal file
14
frontend/lib/Grounded/about/external/data/pages/request/HistoryRequest.dart
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'PageAndSort.dart';
|
||||
|
||||
/// Every list endpoint takes this shape. Never inline flat sort/page fields.
|
||||
class HistoryRequest {
|
||||
PageAndSort? query;
|
||||
|
||||
HistoryRequest({this.query});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['query'] = query?.toJson();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
17
frontend/lib/Grounded/about/external/data/pages/request/PageAndSort.dart
vendored
Normal file
17
frontend/lib/Grounded/about/external/data/pages/request/PageAndSort.dart
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'Pageable.dart';
|
||||
import 'Sort.dart';
|
||||
|
||||
class PageAndSort {
|
||||
Sort? sort;
|
||||
|
||||
Pageable? page;
|
||||
|
||||
PageAndSort({this.sort, this.page});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['sort'] = sort?.toJson();
|
||||
data['page'] = page?.toJson();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
20
frontend/lib/Grounded/about/external/data/pages/request/Pageable.dart
vendored
Normal file
20
frontend/lib/Grounded/about/external/data/pages/request/Pageable.dart
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
class Pageable {
|
||||
int offset;
|
||||
|
||||
int pageNumber;
|
||||
|
||||
int pageSize;
|
||||
|
||||
int paged;
|
||||
|
||||
Pageable(this.offset, this.pageNumber, this.pageSize, this.paged);
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['offset'] = offset;
|
||||
data['pageNumber'] = pageNumber;
|
||||
data['pageSize'] = pageSize;
|
||||
data['paged'] = paged;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
14
frontend/lib/Grounded/about/external/data/pages/request/Sort.dart
vendored
Normal file
14
frontend/lib/Grounded/about/external/data/pages/request/Sort.dart
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
class Sort {
|
||||
String direction;
|
||||
|
||||
String field;
|
||||
|
||||
Sort(this.direction, this.field);
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['direction'] = direction;
|
||||
data['field'] = field;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
47
frontend/lib/Grounded/about/external/data/pages/response/CommitmentEventPage.dart
vendored
Normal file
47
frontend/lib/Grounded/about/external/data/pages/response/CommitmentEventPage.dart
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import '../../CommitmentEvent.dart';
|
||||
|
||||
class CommitmentEventPage {
|
||||
int number;
|
||||
|
||||
int size;
|
||||
|
||||
int totalElements;
|
||||
|
||||
int totalPages;
|
||||
|
||||
int numberOfElements;
|
||||
|
||||
bool first;
|
||||
|
||||
bool last;
|
||||
|
||||
List<CommitmentEvent> content;
|
||||
|
||||
CommitmentEventPage({
|
||||
this.number = 0,
|
||||
this.size = 0,
|
||||
this.totalElements = 0,
|
||||
this.totalPages = 0,
|
||||
this.numberOfElements = 0,
|
||||
this.first = true,
|
||||
this.last = true,
|
||||
List<CommitmentEvent>? content,
|
||||
}) : content = content ?? <CommitmentEvent>[];
|
||||
|
||||
factory CommitmentEventPage.fromJson(Map<String, dynamic> json) {
|
||||
return CommitmentEventPage(
|
||||
number: json['number'] ?? 0,
|
||||
size: json['size'] ?? 0,
|
||||
totalElements: json['totalElements'] ?? 0,
|
||||
totalPages: json['totalPages'] ?? 0,
|
||||
numberOfElements: json['numberOfElements'] ?? 0,
|
||||
first: json['first'] ?? true,
|
||||
last: json['last'] ?? true,
|
||||
content: json['content'] == null
|
||||
? <CommitmentEvent>[]
|
||||
: (json['content'] as List)
|
||||
.map((item) => CommitmentEvent.fromJson(item))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
47
frontend/lib/Grounded/about/external/data/pages/response/CommitmentPage.dart
vendored
Normal file
47
frontend/lib/Grounded/about/external/data/pages/response/CommitmentPage.dart
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import '../../Commitment.dart';
|
||||
|
||||
class CommitmentPage {
|
||||
int number;
|
||||
|
||||
int size;
|
||||
|
||||
int totalElements;
|
||||
|
||||
int totalPages;
|
||||
|
||||
int numberOfElements;
|
||||
|
||||
bool first;
|
||||
|
||||
bool last;
|
||||
|
||||
List<Commitment> content;
|
||||
|
||||
CommitmentPage({
|
||||
this.number = 0,
|
||||
this.size = 0,
|
||||
this.totalElements = 0,
|
||||
this.totalPages = 0,
|
||||
this.numberOfElements = 0,
|
||||
this.first = true,
|
||||
this.last = true,
|
||||
List<Commitment>? content,
|
||||
}) : content = content ?? <Commitment>[];
|
||||
|
||||
factory CommitmentPage.fromJson(Map<String, dynamic> json) {
|
||||
return CommitmentPage(
|
||||
number: json['number'] ?? 0,
|
||||
size: json['size'] ?? 0,
|
||||
totalElements: json['totalElements'] ?? 0,
|
||||
totalPages: json['totalPages'] ?? 0,
|
||||
numberOfElements: json['numberOfElements'] ?? 0,
|
||||
first: json['first'] ?? true,
|
||||
last: json['last'] ?? true,
|
||||
content: json['content'] == null
|
||||
? <Commitment>[]
|
||||
: (json['content'] as List)
|
||||
.map((item) => Commitment.fromJson(item))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
47
frontend/lib/Grounded/about/external/data/pages/response/SessionLogPage.dart
vendored
Normal file
47
frontend/lib/Grounded/about/external/data/pages/response/SessionLogPage.dart
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import '../../SessionLog.dart';
|
||||
|
||||
class SessionLogPage {
|
||||
int number;
|
||||
|
||||
int size;
|
||||
|
||||
int totalElements;
|
||||
|
||||
int totalPages;
|
||||
|
||||
int numberOfElements;
|
||||
|
||||
bool first;
|
||||
|
||||
bool last;
|
||||
|
||||
List<SessionLog> content;
|
||||
|
||||
SessionLogPage({
|
||||
this.number = 0,
|
||||
this.size = 0,
|
||||
this.totalElements = 0,
|
||||
this.totalPages = 0,
|
||||
this.numberOfElements = 0,
|
||||
this.first = true,
|
||||
this.last = true,
|
||||
List<SessionLog>? content,
|
||||
}) : content = content ?? <SessionLog>[];
|
||||
|
||||
factory SessionLogPage.fromJson(Map<String, dynamic> json) {
|
||||
return SessionLogPage(
|
||||
number: json['number'] ?? 0,
|
||||
size: json['size'] ?? 0,
|
||||
totalElements: json['totalElements'] ?? 0,
|
||||
totalPages: json['totalPages'] ?? 0,
|
||||
numberOfElements: json['numberOfElements'] ?? 0,
|
||||
first: json['first'] ?? true,
|
||||
last: json['last'] ?? true,
|
||||
content: json['content'] == null
|
||||
? <SessionLog>[]
|
||||
: (json['content'] as List)
|
||||
.map((item) => SessionLog.fromJson(item))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
16
frontend/lib/Grounded/about/external/initial/AbandonRequest.dart
vendored
Normal file
16
frontend/lib/Grounded/about/external/initial/AbandonRequest.dart
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/// Abandoning costs the most debt of all, so it is always explicit and always
|
||||
/// carries a reason.
|
||||
class AbandonRequest {
|
||||
String commitmentId;
|
||||
|
||||
String reason;
|
||||
|
||||
AbandonRequest({this.commitmentId = "", this.reason = ""});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['commitmentId'] = commitmentId;
|
||||
data['reason'] = reason;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
13
frontend/lib/Grounded/about/external/initial/AmnestyRequest.dart
vendored
Normal file
13
frontend/lib/Grounded/about/external/initial/AmnestyRequest.dart
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/// Spend one of the rationed monthly tokens to wipe an item debt, no questions
|
||||
/// asked.
|
||||
class AmnestyRequest {
|
||||
String commitmentId;
|
||||
|
||||
AmnestyRequest({this.commitmentId = ""});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['commitmentId'] = commitmentId;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
58
frontend/lib/Grounded/about/external/initial/CommitmentRequest.dart
vendored
Normal file
58
frontend/lib/Grounded/about/external/initial/CommitmentRequest.dart
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/// Create or update a commitment.
|
||||
class CommitmentRequest {
|
||||
String? id;
|
||||
|
||||
String type;
|
||||
|
||||
String commitmentClass;
|
||||
|
||||
String title;
|
||||
|
||||
String category;
|
||||
|
||||
String dueStart;
|
||||
|
||||
String dueEnd;
|
||||
|
||||
int estMinutes;
|
||||
|
||||
String energy;
|
||||
|
||||
String proofType;
|
||||
|
||||
int proofTimerMinutes;
|
||||
|
||||
String rrule;
|
||||
|
||||
CommitmentRequest({
|
||||
this.id,
|
||||
this.type = "TASK",
|
||||
this.commitmentClass = "Standard",
|
||||
this.title = "",
|
||||
this.category = "",
|
||||
this.dueStart = "",
|
||||
this.dueEnd = "",
|
||||
this.estMinutes = 0,
|
||||
this.energy = "Medium",
|
||||
this.proofType = "Honour",
|
||||
this.proofTimerMinutes = 0,
|
||||
this.rrule = "",
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['type'] = type;
|
||||
data['commitmentClass'] = commitmentClass;
|
||||
data['title'] = title;
|
||||
data['category'] = category;
|
||||
data['dueStart'] = dueStart;
|
||||
data['dueEnd'] = dueEnd;
|
||||
data['estMinutes'] = estMinutes;
|
||||
data['energy'] = energy;
|
||||
data['proofType'] = proofType;
|
||||
data['proofTimerMinutes'] = proofTimerMinutes;
|
||||
data['rrule'] = rrule;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
38
frontend/lib/Grounded/about/external/initial/CompletionRequest.dart
vendored
Normal file
38
frontend/lib/Grounded/about/external/initial/CompletionRequest.dart
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
/// Completion always carries its proof. The server decides Complete vs Late
|
||||
/// Complete from the window, never the client.
|
||||
class CompletionRequest {
|
||||
String commitmentId;
|
||||
|
||||
String proofType;
|
||||
|
||||
/// Reference to the uploaded artefact — photo id, timer session id, geofence
|
||||
/// dwell id or witness confirmation id.
|
||||
String proofRef;
|
||||
|
||||
/// Foreground seconds actually run, for Timer proof.
|
||||
int timerSeconds;
|
||||
|
||||
double latitude;
|
||||
|
||||
double longitude;
|
||||
|
||||
CompletionRequest({
|
||||
this.commitmentId = "",
|
||||
this.proofType = "Honour",
|
||||
this.proofRef = "",
|
||||
this.timerSeconds = 0,
|
||||
this.latitude = 0,
|
||||
this.longitude = 0,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['commitmentId'] = commitmentId;
|
||||
data['proofType'] = proofType;
|
||||
data['proofRef'] = proofRef;
|
||||
data['timerSeconds'] = timerSeconds;
|
||||
data['latitude'] = latitude;
|
||||
data['longitude'] = longitude;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
27
frontend/lib/Grounded/about/external/initial/DeferralRequest.dart
vendored
Normal file
27
frontend/lib/Grounded/about/external/initial/DeferralRequest.dart
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/// Deferral always carries an excuse. Free text, minimum length enforced, no
|
||||
/// template buttons — the friction is the point.
|
||||
class DeferralRequest {
|
||||
String commitmentId;
|
||||
|
||||
String excuseText;
|
||||
|
||||
String newDueStart;
|
||||
|
||||
String newDueEnd;
|
||||
|
||||
DeferralRequest({
|
||||
this.commitmentId = "",
|
||||
this.excuseText = "",
|
||||
this.newDueStart = "",
|
||||
this.newDueEnd = "",
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['commitmentId'] = commitmentId;
|
||||
data['excuseText'] = excuseText;
|
||||
data['newDueStart'] = newDueStart;
|
||||
data['newDueEnd'] = newDueEnd;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
25
frontend/lib/Grounded/about/external/initial/DeviceRequest.dart
vendored
Normal file
25
frontend/lib/Grounded/about/external/initial/DeviceRequest.dart
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
class DeviceRequest {
|
||||
String identifier;
|
||||
|
||||
String model;
|
||||
|
||||
String platform;
|
||||
|
||||
String version;
|
||||
|
||||
DeviceRequest({
|
||||
this.identifier = "",
|
||||
this.model = "",
|
||||
this.platform = "",
|
||||
this.version = "",
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['identifier'] = identifier;
|
||||
data['model'] = model;
|
||||
data['platform'] = platform;
|
||||
data['version'] = version;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
41
frontend/lib/Grounded/about/external/initial/GoalRequest.dart
vendored
Normal file
41
frontend/lib/Grounded/about/external/initial/GoalRequest.dart
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
class GoalRequest {
|
||||
String? id;
|
||||
|
||||
String title;
|
||||
|
||||
String description;
|
||||
|
||||
String category;
|
||||
|
||||
String defaultClass;
|
||||
|
||||
String startDate;
|
||||
|
||||
String targetDate;
|
||||
|
||||
String colourHex;
|
||||
|
||||
GoalRequest({
|
||||
this.id,
|
||||
this.title = "",
|
||||
this.description = "",
|
||||
this.category = "",
|
||||
this.defaultClass = "Standard",
|
||||
this.startDate = "",
|
||||
this.targetDate = "",
|
||||
this.colourHex = "",
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['title'] = title;
|
||||
data['description'] = description;
|
||||
data['category'] = category;
|
||||
data['defaultClass'] = defaultClass;
|
||||
data['startDate'] = startDate;
|
||||
data['targetDate'] = targetDate;
|
||||
data['colourHex'] = colourHex;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
11
frontend/lib/Grounded/about/external/initial/IdRequest.dart
vendored
Normal file
11
frontend/lib/Grounded/about/external/initial/IdRequest.dart
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
class IdRequest {
|
||||
String id;
|
||||
|
||||
IdRequest({this.id = ""});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
14
frontend/lib/Grounded/about/external/initial/LoginData.dart
vendored
Normal file
14
frontend/lib/Grounded/about/external/initial/LoginData.dart
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
class LoginData {
|
||||
String username;
|
||||
|
||||
String password;
|
||||
|
||||
LoginData({this.username = "", this.password = ""});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['username'] = username;
|
||||
data['password'] = password;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
14
frontend/lib/Grounded/about/external/initial/ReportCardRequest.dart
vendored
Normal file
14
frontend/lib/Grounded/about/external/initial/ReportCardRequest.dart
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
class ReportCardRequest {
|
||||
String periodStart;
|
||||
|
||||
String periodEnd;
|
||||
|
||||
ReportCardRequest({this.periodStart = "", this.periodEnd = ""});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['periodStart'] = periodStart;
|
||||
data['periodEnd'] = periodEnd;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
45
frontend/lib/Grounded/about/external/initial/SessionLogRequest.dart
vendored
Normal file
45
frontend/lib/Grounded/about/external/initial/SessionLogRequest.dart
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
class SessionLogRequest {
|
||||
String? id;
|
||||
|
||||
String templateId;
|
||||
|
||||
String startedAt;
|
||||
|
||||
String endedAt;
|
||||
|
||||
double sessionRpe;
|
||||
|
||||
int sleepScore;
|
||||
|
||||
int sorenessScore;
|
||||
|
||||
int motivationScore;
|
||||
|
||||
List<Map<String, dynamic>> sets;
|
||||
|
||||
SessionLogRequest({
|
||||
this.id,
|
||||
this.templateId = "",
|
||||
this.startedAt = "",
|
||||
this.endedAt = "",
|
||||
this.sessionRpe = 0,
|
||||
this.sleepScore = 0,
|
||||
this.sorenessScore = 0,
|
||||
this.motivationScore = 0,
|
||||
List<Map<String, dynamic>>? sets,
|
||||
}) : sets = sets ?? <Map<String, dynamic>>[];
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['templateId'] = templateId;
|
||||
data['startedAt'] = startedAt;
|
||||
data['endedAt'] = endedAt;
|
||||
data['sessionRpe'] = sessionRpe;
|
||||
data['sleepScore'] = sleepScore;
|
||||
data['sorenessScore'] = sorenessScore;
|
||||
data['motivationScore'] = motivationScore;
|
||||
data['sets'] = sets;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
18
frontend/lib/Grounded/about/external/initial/SickModeRequest.dart
vendored
Normal file
18
frontend/lib/Grounded/about/external/initial/SickModeRequest.dart
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/// Pauses debt accrual entirely. Requires a reason and is logged in history.
|
||||
class SickModeRequest {
|
||||
bool enabled;
|
||||
|
||||
String reason;
|
||||
|
||||
String until;
|
||||
|
||||
SickModeRequest({this.enabled = false, this.reason = "", this.until = ""});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['enabled'] = enabled;
|
||||
data['reason'] = reason;
|
||||
data['until'] = until;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
11
frontend/lib/Grounded/about/external/initial/ToneRequest.dart
vendored
Normal file
11
frontend/lib/Grounded/about/external/initial/ToneRequest.dart
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
class ToneRequest {
|
||||
String tone;
|
||||
|
||||
ToneRequest({this.tone = "Strict"});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['tone'] = tone;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user