Files
GroundedHelper/frontend/lib/Grounded/mock/MockComms.dart
alvocool b31bc47cc0 Add removable mock mode for a clickable preview
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
2026-11-02 08:45:41 +03:00

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();
}