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
This commit is contained in:
298
frontend/lib/Grounded/mock/MockStore.dart
Normal file
298
frontend/lib/Grounded/mock/MockStore.dart
Normal file
@@ -0,0 +1,298 @@
|
||||
// MOCK DATA — TEMPORARY. See MockSwitch.dart.
|
||||
|
||||
import '../about/external/data/RoutineChain.dart';
|
||||
import '../about/internal/application/CommitmentClass.dart';
|
||||
import '../utils/RoutineTemplates.dart';
|
||||
|
||||
/// The fake backend's database: plain JSON maps, seeded relative to today,
|
||||
/// mutated by [MockComms] so taps actually change what you see. Static, so
|
||||
/// every screen shares one copy for the life of the app.
|
||||
class MockStore {
|
||||
static bool _seeded = false;
|
||||
|
||||
static int _nextId = 100;
|
||||
|
||||
static final List<Map<String, dynamic>> goals = <Map<String, dynamic>>[];
|
||||
|
||||
static final List<Map<String, dynamic>> commitments =
|
||||
<Map<String, dynamic>>[];
|
||||
|
||||
static final List<RoutineChain> routines = <RoutineChain>[];
|
||||
|
||||
static String newId(String prefix) {
|
||||
_nextId = _nextId + 1;
|
||||
return "$prefix-$_nextId";
|
||||
}
|
||||
|
||||
static void ensureSeeded() {
|
||||
if (_seeded) {
|
||||
return;
|
||||
}
|
||||
_seeded = true;
|
||||
|
||||
final DateTime now = DateTime.now();
|
||||
final DateTime today = DateTime(now.year, now.month, now.day);
|
||||
final int weekday = now.weekday;
|
||||
|
||||
DateTime at(int dayOffset, int hour, [int minute = 0]) {
|
||||
return today.add(Duration(days: dayOffset, hours: hour, minutes: minute));
|
||||
}
|
||||
|
||||
// ── Goals ──────────────────────────────────────────────────────────────
|
||||
goals.addAll(<Map<String, dynamic>>[
|
||||
{
|
||||
'id': 'goal-strong',
|
||||
'title': 'Get strong',
|
||||
'description': 'Deadlift my bodyweight by spring, without getting hurt.',
|
||||
'category': 'Health',
|
||||
'defaultClass': 'Standard',
|
||||
'startDate': at(-30, 0).toIso8601String(),
|
||||
'targetDate': at(60, 0).toIso8601String(),
|
||||
'colourHex': '#FF2F6F4E',
|
||||
'archived': false,
|
||||
},
|
||||
{
|
||||
'id': 'goal-thesis',
|
||||
'title': 'Finish the thesis',
|
||||
'description': 'Submit before the department deadline. No extensions.',
|
||||
'category': 'Work',
|
||||
'defaultClass': 'NonNegotiable',
|
||||
'startDate': at(-60, 0).toIso8601String(),
|
||||
'targetDate': at(45, 0).toIso8601String(),
|
||||
'colourHex': '#FF2C4A63',
|
||||
'archived': false,
|
||||
},
|
||||
{
|
||||
'id': 'goal-calm',
|
||||
'title': 'Calm mornings',
|
||||
'description': 'Start the day on purpose instead of on the phone.',
|
||||
'category': 'Mind',
|
||||
'defaultClass': 'Elective',
|
||||
'startDate': at(-10, 0).toIso8601String(),
|
||||
'targetDate': null,
|
||||
'colourHex': '#FF4F5D86',
|
||||
'archived': false,
|
||||
},
|
||||
]);
|
||||
|
||||
// ── Commitments ────────────────────────────────────────────────────────
|
||||
Map<String, dynamic> task(
|
||||
String id,
|
||||
String title, {
|
||||
String? goal,
|
||||
String cls = 'Standard',
|
||||
required DateTime start,
|
||||
required DateTime end,
|
||||
int minutes = 30,
|
||||
String proof = 'Honour',
|
||||
int timer = 0,
|
||||
String status = 'Open',
|
||||
int deferrals = 0,
|
||||
String category = '',
|
||||
}) {
|
||||
return {
|
||||
'id': id,
|
||||
'type': 'TASK',
|
||||
'commitmentClass': cls,
|
||||
'title': title,
|
||||
'category': category,
|
||||
'dueStart': start.toIso8601String(),
|
||||
'dueEnd': end.toIso8601String(),
|
||||
'estMinutes': minutes,
|
||||
'energy': 'Medium',
|
||||
'proofType': proof,
|
||||
'proofTimerMinutes': timer,
|
||||
'rrule': '',
|
||||
'parentId': goal,
|
||||
'status': status,
|
||||
'deferralCount': deferrals,
|
||||
};
|
||||
}
|
||||
|
||||
commitments.addAll(<Map<String, dynamic>>[
|
||||
task('c-walk', 'Morning walk, no phone',
|
||||
goal: 'goal-calm',
|
||||
cls: 'Elective',
|
||||
start: at(0, 6, 30),
|
||||
end: at(0, 8),
|
||||
minutes: 20,
|
||||
status: 'Completed'),
|
||||
task('c-chapter', 'Write 500 words of chapter 3',
|
||||
goal: 'goal-thesis',
|
||||
cls: 'NonNegotiable',
|
||||
start: at(0, 9),
|
||||
end: at(0, 23, 30),
|
||||
minutes: 25,
|
||||
proof: 'Timer',
|
||||
timer: 1,
|
||||
category: 'Deep work'),
|
||||
task('c-rent', 'Pay rent',
|
||||
cls: 'NonNegotiable',
|
||||
start: at(0, 8),
|
||||
end: at(0, 23, 59),
|
||||
minutes: 5,
|
||||
category: 'Admin'),
|
||||
task('c-physio', 'Book physio appointment',
|
||||
goal: 'goal-strong',
|
||||
start: at(0, 12),
|
||||
end: at(0, 23),
|
||||
minutes: 10,
|
||||
category: 'Admin'),
|
||||
task('c-read', 'Read two papers for the literature review',
|
||||
goal: 'goal-thesis',
|
||||
start: at(0, 18),
|
||||
end: at(0, 23, 45),
|
||||
minutes: 60,
|
||||
category: 'Deep work'),
|
||||
task('c-meal', 'Meal prep for the week',
|
||||
goal: 'goal-strong',
|
||||
start: at(1, 17),
|
||||
end: at(1, 20),
|
||||
minutes: 90),
|
||||
// Two things already past their window, so the overdue queue and debt
|
||||
// have something to show.
|
||||
task('c-email', 'Email supervisor the outline',
|
||||
goal: 'goal-thesis',
|
||||
start: at(-2, 9),
|
||||
end: at(-2, 12),
|
||||
minutes: 15,
|
||||
status: 'Overdue',
|
||||
deferrals: 1),
|
||||
task('c-kitchen', 'Deep clean the kitchen',
|
||||
start: at(-1, 18),
|
||||
end: at(-1, 21),
|
||||
minutes: 45,
|
||||
status: 'Overdue'),
|
||||
]);
|
||||
|
||||
// ── Routines ───────────────────────────────────────────────────────────
|
||||
final RoutineChain strength = RoutineTemplates.fullBody()
|
||||
..id = 'routine-strength'
|
||||
..goalId = 'goal-strong'
|
||||
..days = _withToday(<int>[1, 3, 5], weekday)
|
||||
..startMinute = 7 * 60;
|
||||
|
||||
final RoutineChain push = RoutineTemplates.upperPush()
|
||||
..id = 'routine-push'
|
||||
..goalId = 'goal-strong'
|
||||
..days = <int>[1, 2, 3, 4, 5, 6, 7].where((day) => day != weekday).take(2).toList();
|
||||
|
||||
final RoutineChain breathing = RoutineTemplates.boxBreathing()
|
||||
..id = 'routine-breathing'
|
||||
..goalId = 'goal-calm';
|
||||
|
||||
final RoutineChain morning = RoutineTemplates.morning()
|
||||
..id = 'routine-morning'
|
||||
..goalId = 'goal-calm'
|
||||
..days = _withToday(<int>[1, 2, 3, 4, 5], weekday);
|
||||
for (int index = 0; index < morning.steps.length; index++) {
|
||||
morning.steps[index].completed = true;
|
||||
}
|
||||
|
||||
final RoutineChain shutdown = RoutineTemplates.shutdown()
|
||||
..id = 'routine-shutdown'
|
||||
..days = _withToday(<int>[1, 2, 3, 4, 5], weekday)
|
||||
..commitmentClass = CommitmentClass.Standard;
|
||||
shutdown.steps.first.completed = true;
|
||||
|
||||
final RoutineChain mobility = RoutineTemplates.mobility()
|
||||
..id = 'routine-mobility';
|
||||
|
||||
routines.addAll(<RoutineChain>[
|
||||
breathing,
|
||||
morning,
|
||||
strength,
|
||||
shutdown,
|
||||
push,
|
||||
mobility,
|
||||
]);
|
||||
}
|
||||
|
||||
static List<int> _withToday(List<int> days, int weekday) {
|
||||
final List<int> result = List<int>.from(days);
|
||||
if (!result.contains(weekday)) {
|
||||
result.add(weekday);
|
||||
}
|
||||
result.sort();
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Derived views ──────────────────────────────────────────────────────
|
||||
|
||||
static bool _settled(Map<String, dynamic> item) {
|
||||
final String status = item['status'];
|
||||
return status == 'Completed' ||
|
||||
status == 'LateCompleted' ||
|
||||
status == 'Abandoned' ||
|
||||
status == 'Archived';
|
||||
}
|
||||
|
||||
static bool _windowClosed(Map<String, dynamic> item) {
|
||||
final DateTime? end = DateTime.tryParse(item['dueEnd'] ?? '');
|
||||
return end != null && end.isBefore(DateTime.now());
|
||||
}
|
||||
|
||||
static List<Map<String, dynamic>> todayPlan() {
|
||||
final DateTime now = DateTime.now();
|
||||
return commitments.where((item) {
|
||||
final DateTime? start = DateTime.tryParse(item['dueStart'] ?? '');
|
||||
return start != null &&
|
||||
start.year == now.year &&
|
||||
start.month == now.month &&
|
||||
start.day == now.day &&
|
||||
item['status'] != 'Abandoned';
|
||||
}).toList()
|
||||
..sort((a, b) => (a['dueStart'] as String).compareTo(b['dueStart']));
|
||||
}
|
||||
|
||||
static List<Map<String, dynamic>> overdue() {
|
||||
return commitments
|
||||
.where((item) => !_settled(item) && _windowClosed(item))
|
||||
.map((item) => Map<String, dynamic>.from(item)..['status'] = 'Overdue')
|
||||
.toList();
|
||||
}
|
||||
|
||||
static List<Map<String, dynamic>> goalTasks(String goalId) {
|
||||
return commitments.where((item) => item['parentId'] == goalId).toList();
|
||||
}
|
||||
|
||||
/// Goals with the server-derived counts filled in from the tasks.
|
||||
static List<Map<String, dynamic>> goalsWithCounts() {
|
||||
return goals.where((goal) => goal['archived'] != true).map((goal) {
|
||||
final List<Map<String, dynamic>> tasks = goalTasks(goal['id']);
|
||||
final int completed = tasks
|
||||
.where((item) =>
|
||||
item['status'] == 'Completed' || item['status'] == 'LateCompleted')
|
||||
.length;
|
||||
final int late =
|
||||
tasks.where((item) => !_settled(item) && _windowClosed(item)).length;
|
||||
return Map<String, dynamic>.from(goal)
|
||||
..['totalTasks'] = tasks.where((item) => item['status'] != 'Abandoned').length
|
||||
..['completedTasks'] = completed
|
||||
..['overdueTasks'] = late
|
||||
..['debtContribution'] = late * 2.0;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
static Map<String, dynamic>? commitment(String id) {
|
||||
for (Map<String, dynamic> item in commitments) {
|
||||
if (item['id'] == id) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Map<String, dynamic> page(List<Map<String, dynamic>> content) {
|
||||
return {
|
||||
'number': 0,
|
||||
'size': content.length,
|
||||
'totalElements': content.length,
|
||||
'totalPages': 1,
|
||||
'numberOfElements': content.length,
|
||||
'first': true,
|
||||
'last': true,
|
||||
'content': content,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user