Runs the whole app on seeded in-memory data with no backend or device storage, behind useMockData in lib/Grounded/mock/MockSwitch.dart. To remove: delete lib/Grounded/mock/ and the lines tagged "MOCK:" in ParentViewModel (or revert this commit, keeping the notification guard). Also skips local notifications on web, where the plugin and dart:io Platform both throw. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
444 lines
16 KiB
Dart
444 lines
16 KiB
Dart
// MOCK DATA — TEMPORARY. See MockSwitch.dart.
|
|
|
|
import 'package:dio/dio.dart';
|
|
|
|
import '../about/external/data/RoutineChain.dart';
|
|
import '../about/external/data/pages/request/CommitmentsRequest.dart';
|
|
import '../about/external/data/pages/request/HistoryRequest.dart';
|
|
import '../about/external/initial/AbandonRequest.dart';
|
|
import '../about/external/initial/AmnestyRequest.dart';
|
|
import '../about/external/initial/CommitmentRequest.dart';
|
|
import '../about/external/initial/CompletionRequest.dart';
|
|
import '../about/external/initial/DeferralRequest.dart';
|
|
import '../about/external/initial/DeviceRequest.dart';
|
|
import '../about/external/initial/GoalRequest.dart';
|
|
import '../about/external/initial/IdRequest.dart';
|
|
import '../about/external/initial/LoginData.dart';
|
|
import '../about/external/initial/ReportCardRequest.dart';
|
|
import '../about/external/initial/SessionLogRequest.dart';
|
|
import '../about/external/initial/SickModeRequest.dart';
|
|
import '../about/external/initial/ToneRequest.dart';
|
|
import '../comms/ConnectComms.dart';
|
|
import 'MockStore.dart';
|
|
|
|
/// A fake backend. Answers every call from [MockStore] after a short delay,
|
|
/// so loading states still flash the way they will against the real thing.
|
|
class MockComms implements ConnectComms {
|
|
MockComms() {
|
|
MockStore.ensureSeeded();
|
|
}
|
|
|
|
Future<Response> _ok(dynamic data) async {
|
|
await Future<void>.delayed(const Duration(milliseconds: 250));
|
|
return Response(
|
|
requestOptions: RequestOptions(path: 'mock'),
|
|
statusCode: 200,
|
|
data: data,
|
|
);
|
|
}
|
|
|
|
Future<Response> _done() => _ok(<String, dynamic>{'success': true});
|
|
|
|
Future<Response> _emptyPage() => _ok(MockStore.page(<Map<String, dynamic>>[]));
|
|
|
|
// ── Auth ──────────────────────────────────────────────────────────────────
|
|
|
|
@override
|
|
Future<Response> registerDevice(DeviceRequest request) => _done();
|
|
|
|
@override
|
|
Future<Response> registerDeviceToken(String token) => _done();
|
|
|
|
@override
|
|
Future<Response> login(LoginData request) => _ok(<String, dynamic>{
|
|
'id': 'mock-user',
|
|
'name': 'Alex',
|
|
'token': 'mock-token',
|
|
'pic': '',
|
|
'tone': 'Firm',
|
|
'standing': 'Good',
|
|
'amnestyTokens': 3,
|
|
'sickMode': false,
|
|
});
|
|
|
|
@override
|
|
Future<Response> logout() => _done();
|
|
|
|
@override
|
|
Future<Response> me() => _ok(<String, dynamic>{'id': 'mock-user', 'name': 'Alex'});
|
|
|
|
// ── Goals ─────────────────────────────────────────────────────────────────
|
|
|
|
@override
|
|
Future<Response> getMyGoals(HistoryRequest request) =>
|
|
_ok(MockStore.goalsWithCounts());
|
|
|
|
@override
|
|
Future<Response> saveGoalEntry(GoalRequest request) {
|
|
final Map<String, dynamic> json = request.toJson();
|
|
json['targetDate'] = request.targetDate.isEmpty ? null : request.targetDate;
|
|
json['archived'] = false;
|
|
|
|
final int index =
|
|
MockStore.goals.indexWhere((goal) => goal['id'] == request.id);
|
|
if (request.id == null || index < 0) {
|
|
json['id'] = MockStore.newId('goal');
|
|
MockStore.goals.add(json);
|
|
} else {
|
|
MockStore.goals[index] = json;
|
|
}
|
|
return _ok(json);
|
|
}
|
|
|
|
@override
|
|
Future<Response> getGoalTasks(IdRequest request) =>
|
|
_ok(MockStore.goalTasks(request.id));
|
|
|
|
@override
|
|
Future<Response> archiveGoalEntry(IdRequest request) {
|
|
for (Map<String, dynamic> goal in MockStore.goals) {
|
|
if (goal['id'] == request.id) {
|
|
goal['archived'] = true;
|
|
}
|
|
}
|
|
return _done();
|
|
}
|
|
|
|
// ── Commitments ───────────────────────────────────────────────────────────
|
|
|
|
@override
|
|
Future<Response> getTodayPlan(CommitmentsRequest request) =>
|
|
_ok(MockStore.page(MockStore.todayPlan()));
|
|
|
|
@override
|
|
Future<Response> getMyCommitments(CommitmentsRequest request) =>
|
|
_ok(MockStore.page(MockStore.commitments));
|
|
|
|
@override
|
|
Future<Response> getOverdueQueue(HistoryRequest request) =>
|
|
_ok(MockStore.page(MockStore.overdue()));
|
|
|
|
@override
|
|
Future<Response> saveCommitmentEntry(CommitmentRequest request) {
|
|
final Map<String, dynamic> json = request.toJson();
|
|
json['id'] = MockStore.newId('task');
|
|
json['status'] = 'Open';
|
|
json['deferralCount'] = 0;
|
|
MockStore.commitments.add(json);
|
|
return _ok(json);
|
|
}
|
|
|
|
@override
|
|
Future<Response> updateCommitmentEntry(CommitmentRequest request) {
|
|
final Map<String, dynamic>? item = MockStore.commitment(request.id ?? '');
|
|
if (item != null) {
|
|
item.addAll(request.toJson());
|
|
}
|
|
return _done();
|
|
}
|
|
|
|
@override
|
|
Future<Response> deleteCommitmentEntry(IdRequest request) {
|
|
MockStore.commitments.removeWhere((item) => item['id'] == request.id);
|
|
return _done();
|
|
}
|
|
|
|
@override
|
|
Future<Response> completeCommitmentEntry(CompletionRequest request) {
|
|
final Map<String, dynamic>? item = MockStore.commitment(request.commitmentId);
|
|
if (item != null) {
|
|
final DateTime? end = DateTime.tryParse(item['dueEnd'] ?? '');
|
|
item['status'] = end != null && end.isBefore(DateTime.now())
|
|
? 'LateCompleted'
|
|
: 'Completed';
|
|
}
|
|
return _done();
|
|
}
|
|
|
|
@override
|
|
Future<Response> deferCommitmentEntry(DeferralRequest request) {
|
|
final Map<String, dynamic>? item = MockStore.commitment(request.commitmentId);
|
|
if (item != null) {
|
|
final DateTime now = DateTime.now();
|
|
item['dueStart'] = request.newDueStart.isNotEmpty
|
|
? request.newDueStart
|
|
: now.add(const Duration(hours: 1)).toIso8601String();
|
|
item['dueEnd'] = request.newDueEnd.isNotEmpty
|
|
? request.newDueEnd
|
|
: now.add(const Duration(days: 1)).toIso8601String();
|
|
item['deferralCount'] = (item['deferralCount'] ?? 0) + 1;
|
|
item['status'] = 'Deferred';
|
|
}
|
|
return _done();
|
|
}
|
|
|
|
@override
|
|
Future<Response> abandonCommitmentEntry(AbandonRequest request) {
|
|
final Map<String, dynamic>? item = MockStore.commitment(request.commitmentId);
|
|
if (item != null) {
|
|
item['status'] = 'Abandoned';
|
|
}
|
|
return _done();
|
|
}
|
|
|
|
@override
|
|
Future<Response> getCommitmentHistory(HistoryRequest request) => _emptyPage();
|
|
|
|
@override
|
|
Future<Response> getCommitmentEvents(IdRequest request) => _emptyPage();
|
|
|
|
// ── Capacity ──────────────────────────────────────────────────────────────
|
|
|
|
@override
|
|
Future<Response> checkCapacity(CommitmentsRequest request) => _done();
|
|
|
|
@override
|
|
Future<Response> getCapacityProfileEntry() => _ok(<String, dynamic>{});
|
|
|
|
// ── Debt & standing ───────────────────────────────────────────────────────
|
|
|
|
@override
|
|
Future<Response> getDebtSummary() => _ok(<String, dynamic>{});
|
|
|
|
@override
|
|
Future<Response> getDebtLedger(HistoryRequest request) => _emptyPage();
|
|
|
|
@override
|
|
Future<Response> getDebtTrend(ReportCardRequest request) => _ok(<dynamic>[]);
|
|
|
|
@override
|
|
Future<Response> getStanding() => _ok(<String, dynamic>{'standing': 'Good'});
|
|
|
|
@override
|
|
Future<Response> getStandingHistory(HistoryRequest request) => _emptyPage();
|
|
|
|
// ── Excuses ───────────────────────────────────────────────────────────────
|
|
|
|
@override
|
|
Future<Response> getExcuseClusters(ReportCardRequest request) =>
|
|
_ok(<Map<String, dynamic>>[
|
|
{
|
|
'id': 'cluster-tired',
|
|
'label': 'Too tired',
|
|
'occurrences': 4,
|
|
'dominantCategory': 'Deep work',
|
|
'byWeekday': {'4': 3, '2': 1},
|
|
'byHour': {'19': 3, '21': 1},
|
|
'insight':
|
|
"You've said \"too tired\" 4 times this month — 3 of them on Thursday evenings. Put deep work before lunch on Thursdays.",
|
|
},
|
|
{
|
|
'id': 'cluster-time',
|
|
'label': 'Ran out of time',
|
|
'occurrences': 3,
|
|
'dominantCategory': 'Admin',
|
|
'byWeekday': {'1': 2, '5': 1},
|
|
'byHour': {'17': 3},
|
|
'insight': '',
|
|
},
|
|
]);
|
|
|
|
// ── Proof ─────────────────────────────────────────────────────────────────
|
|
|
|
@override
|
|
Future<Response> uploadPhotoProof(FormData request) =>
|
|
_ok(<String, dynamic>{'proofRef': 'mock-photo'});
|
|
|
|
@override
|
|
Future<Response> submitTimerProofEntry(CompletionRequest request) =>
|
|
completeCommitmentEntry(request);
|
|
|
|
@override
|
|
Future<Response> submitLocationProofEntry(CompletionRequest request) =>
|
|
completeCommitmentEntry(request);
|
|
|
|
// ── Guardrails ────────────────────────────────────────────────────────────
|
|
|
|
@override
|
|
Future<Response> spendAmnestyToken(AmnestyRequest request) => _done();
|
|
|
|
@override
|
|
Future<Response> getAmnestyBalance() => _ok(<String, dynamic>{'balance': 3});
|
|
|
|
@override
|
|
Future<Response> updateSickMode(SickModeRequest request) => _done();
|
|
|
|
@override
|
|
Future<Response> checkDistress() => _ok(<String, dynamic>{'distressed': false});
|
|
|
|
@override
|
|
Future<Response> updateTone(ToneRequest request) => _done();
|
|
|
|
// ── Habits & routines ─────────────────────────────────────────────────────
|
|
|
|
@override
|
|
Future<Response> getMyHabits(HistoryRequest request) => _ok(<dynamic>[]);
|
|
|
|
@override
|
|
Future<Response> saveHabitEntry(Map<String, dynamic> request) => _done();
|
|
|
|
@override
|
|
Future<Response> logHabitEntry(IdRequest request) => _done();
|
|
|
|
@override
|
|
Future<Response> getKeystoneHabits() => _ok(<dynamic>[]);
|
|
|
|
@override
|
|
Future<Response> getMyRoutines(HistoryRequest request) => _ok(
|
|
MockStore.routines.map((routine) => routine.toJson()).toList());
|
|
|
|
@override
|
|
Future<Response> saveRoutineEntry(Map<String, dynamic> request) {
|
|
final RoutineChain routine = RoutineChain.fromJson(request);
|
|
final int index = MockStore.routines
|
|
.indexWhere((item) => item.id != null && item.id == routine.id);
|
|
if (routine.id == null || index < 0) {
|
|
routine.id = MockStore.newId('routine');
|
|
MockStore.routines.add(routine);
|
|
} else {
|
|
// Keep today's progress across an edit.
|
|
final RoutineChain before = MockStore.routines[index];
|
|
for (int step = 0;
|
|
step < routine.steps.length && step < before.steps.length;
|
|
step++) {
|
|
routine.steps[step].completed = before.steps[step].completed;
|
|
}
|
|
MockStore.routines[index] = routine;
|
|
}
|
|
return _ok(routine.toJson());
|
|
}
|
|
|
|
/// A logged run marks today's progress on the routine, so the library ring
|
|
/// and the "done" tick reflect what just happened.
|
|
@override
|
|
Future<Response> logRoutineChainEntry(Map<String, dynamic> request) {
|
|
for (RoutineChain routine in MockStore.routines) {
|
|
if (routine.id == request['routineId']) {
|
|
final int done = request['completedSteps'] ?? 0;
|
|
for (int step = 0; step < routine.steps.length; step++) {
|
|
routine.steps[step].completed = step < done;
|
|
}
|
|
routine.lastBreakIndex = request['breakIndex'] ?? -1;
|
|
}
|
|
}
|
|
return _done();
|
|
}
|
|
|
|
// ── Training ──────────────────────────────────────────────────────────────
|
|
|
|
@override
|
|
Future<Response> getMyPrograms(HistoryRequest request) =>
|
|
_ok(<Map<String, dynamic>>[
|
|
{
|
|
'id': 'program-base',
|
|
'name': 'Base strength · 8 weeks',
|
|
'weeks': 8,
|
|
'sessionsPerWeek': 3,
|
|
'deloadWeeks': <int>[4, 8],
|
|
'weeklyContactCeiling': 120,
|
|
'lowerBodyRecoveryHours': 48,
|
|
'active': true,
|
|
},
|
|
]);
|
|
|
|
@override
|
|
Future<Response> saveProgramEntry(Map<String, dynamic> request) => _done();
|
|
|
|
@override
|
|
Future<Response> activateProgramEntry(IdRequest request) => _done();
|
|
|
|
@override
|
|
Future<Response> getProgramSessions(IdRequest request) =>
|
|
_ok(<Map<String, dynamic>>[
|
|
{
|
|
'id': 'session-a',
|
|
'programId': 'program-base',
|
|
'dayIndex': 0,
|
|
'name': 'Day A · Squat & press',
|
|
'highIntensityLowerBody': true,
|
|
'prescriptions': <dynamic>[{}, {}, {}, {}],
|
|
},
|
|
{
|
|
'id': 'session-b',
|
|
'programId': 'program-base',
|
|
'dayIndex': 2,
|
|
'name': 'Day B · Pull & hinge',
|
|
'highIntensityLowerBody': false,
|
|
'prescriptions': <dynamic>[{}, {}, {}],
|
|
},
|
|
]);
|
|
|
|
@override
|
|
Future<Response> startSessionEntry(IdRequest request) => _done();
|
|
|
|
@override
|
|
Future<Response> saveSessionLogEntry(SessionLogRequest request) => _done();
|
|
|
|
@override
|
|
Future<Response> getSessionHistory(HistoryRequest request) => _emptyPage();
|
|
|
|
@override
|
|
Future<Response> getLastSessionFor(IdRequest request) => _ok(<String, dynamic>{});
|
|
|
|
@override
|
|
Future<Response> getWeeklyVolume(ReportCardRequest request) => _ok(<String, dynamic>{});
|
|
|
|
@override
|
|
Future<Response> getContactVolume(ReportCardRequest request) => _ok(<String, dynamic>{});
|
|
|
|
@override
|
|
Future<Response> getPersonalRecords(HistoryRequest request) => _emptyPage();
|
|
|
|
// ── Report card ───────────────────────────────────────────────────────────
|
|
|
|
@override
|
|
Future<Response> getWeeklyReportCard(ReportCardRequest request) => _ok(_report());
|
|
|
|
@override
|
|
Future<Response> getMonthlyReportCard(ReportCardRequest request) => _ok(_report());
|
|
|
|
Map<String, dynamic> _report() {
|
|
final DateTime now = DateTime.now();
|
|
return {
|
|
'id': 'report-week',
|
|
'periodStart': now.subtract(const Duration(days: 7)).toIso8601String(),
|
|
'periodEnd': now.toIso8601String(),
|
|
'completionByClass': {
|
|
'NonNegotiable': 0.92,
|
|
'Standard': 0.71,
|
|
'Elective': 0.40,
|
|
},
|
|
'completionByCategory': {
|
|
'Deep work': 0.64,
|
|
'Admin': 0.88,
|
|
'Health': 0.79,
|
|
},
|
|
'completionByWeekday': {
|
|
'1': 0.9, '2': 0.8, '3': 0.75, '4': 0.45, '5': 0.7, '6': 0.6, '7': 0.85,
|
|
},
|
|
'worstHour': 19,
|
|
'debtTrend': <double>[8, 9.5, 11, 10.2, 7.4, 6.8, 5.9],
|
|
'deferralLeaderboard': <Map<String, dynamic>>[
|
|
{'commitmentId': 'c-email', 'title': 'Email supervisor the outline', 'count': 2},
|
|
{'commitmentId': 'c-kitchen', 'title': 'Deep clean the kitchen', 'count': 1},
|
|
],
|
|
'excuseTaxonomy': <dynamic>[],
|
|
'estimationAccuracy': {'Deep work': 1.6, 'Admin': 1.1},
|
|
'trainingAdherence': 0.83,
|
|
'programIntegrity': 0.9,
|
|
'assignedAction':
|
|
'Move deep work before noon on Thursdays. That is where the week leaks.',
|
|
'grade': 'B',
|
|
'praise': 'Every non-negotiable but one landed inside its window.',
|
|
};
|
|
}
|
|
|
|
// ── Notifications ─────────────────────────────────────────────────────────
|
|
|
|
@override
|
|
Future<Response> getMyNotifications(HistoryRequest request) => _emptyPage();
|
|
|
|
@override
|
|
Future<Response> acknowledgeNudgeEntry(IdRequest request) => _done();
|
|
}
|