Add groups with shared goals, objectives and chat; drop the water tracker
Groups let people keep each other honest: create one or join with a six-letter invite code. Each group has shared goals split into measurable objectives (km, sessions, words) that every member logs toward, a leaderboard of kept rate and streak with nudges, and a group chat where check-ins and nudges appear as cards beside the talk. The open chat polls for new messages. Reached from a new Groups tab (Training moves into the day rail), a Home "Your groups" rail that opens the chat, and a Group tile in the add sheet. Mock mode seeds two groups, a joinable one and members who reply. The water tracker is removed; it did not fit the app. Bottom sheets are now square app-wide via the theme. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
This commit is contained in:
@@ -12,6 +12,12 @@ 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/GroupGoalRequest.dart';
|
||||
import '../about/external/initial/GroupMessageRequest.dart';
|
||||
import '../about/external/initial/GroupRequest.dart';
|
||||
import '../about/external/initial/JoinGroupRequest.dart';
|
||||
import '../about/external/initial/NudgeRequest.dart';
|
||||
import '../about/external/initial/ObjectiveProgressRequest.dart';
|
||||
import '../about/external/initial/IdRequest.dart';
|
||||
import '../about/external/initial/LoginData.dart';
|
||||
import '../about/external/initial/ReportCardRequest.dart';
|
||||
@@ -19,6 +25,7 @@ import '../about/external/initial/SessionLogRequest.dart';
|
||||
import '../about/external/initial/SickModeRequest.dart';
|
||||
import '../about/external/initial/ToneRequest.dart';
|
||||
import '../comms/ConnectComms.dart';
|
||||
import 'MockGroups.dart';
|
||||
import 'MockStore.dart';
|
||||
|
||||
/// A fake backend. Answers every call from [MockStore] after a short delay,
|
||||
@@ -440,4 +447,36 @@ class MockComms implements ConnectComms {
|
||||
|
||||
@override
|
||||
Future<Response> acknowledgeNudgeEntry(IdRequest request) => _done();
|
||||
|
||||
// ── Groups ────────────────────────────────────────────────────────────────
|
||||
|
||||
@override
|
||||
Future<Response> getMyGroups(HistoryRequest request) => _ok(MockGroups.mine());
|
||||
|
||||
@override
|
||||
Future<Response> getGroupEntry(IdRequest request) => _ok(MockGroups.one(request.id));
|
||||
|
||||
@override
|
||||
Future<Response> saveGroupEntry(GroupRequest request) => _ok(MockGroups.save(request));
|
||||
|
||||
@override
|
||||
Future<Response> joinGroupEntry(JoinGroupRequest request) => _ok(MockGroups.join(request));
|
||||
|
||||
@override
|
||||
Future<Response> leaveGroupEntry(IdRequest request) => _ok(MockGroups.leave(request.id));
|
||||
|
||||
@override
|
||||
Future<Response> saveGroupGoalEntry(GroupGoalRequest request) => _ok(MockGroups.saveGoal(request));
|
||||
|
||||
@override
|
||||
Future<Response> logObjectiveProgress(ObjectiveProgressRequest request) => _ok(MockGroups.logProgress(request));
|
||||
|
||||
@override
|
||||
Future<Response> getGroupMessages(IdRequest request) => _ok(MockGroups.messages(request.id));
|
||||
|
||||
@override
|
||||
Future<Response> sendGroupMessage(GroupMessageRequest request) => _ok(MockGroups.send(request));
|
||||
|
||||
@override
|
||||
Future<Response> nudgeGroupMember(NudgeRequest request) => _ok(MockGroups.nudge(request));
|
||||
}
|
||||
|
||||
472
frontend/lib/Grounded/mock/MockGroups.dart
Normal file
472
frontend/lib/Grounded/mock/MockGroups.dart
Normal file
@@ -0,0 +1,472 @@
|
||||
// MOCK DATA — TEMPORARY. See MockSwitch.dart.
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import '../about/external/initial/GroupGoalRequest.dart';
|
||||
import '../about/external/initial/GroupMessageRequest.dart';
|
||||
import '../about/external/initial/GroupRequest.dart';
|
||||
import '../about/external/initial/JoinGroupRequest.dart';
|
||||
import '../about/external/initial/NudgeRequest.dart';
|
||||
import '../about/external/initial/ObjectiveProgressRequest.dart';
|
||||
import '../utils/Colors.dart';
|
||||
import '../utils/CommonUtils.dart';
|
||||
import 'MockStore.dart';
|
||||
|
||||
/// The fake backend's groups: a couple already joined, one waiting behind an
|
||||
/// invite code, and members who answer in the chat a moment after you post.
|
||||
class MockGroups {
|
||||
static const String _me = "mock-user";
|
||||
|
||||
static const String _myName = "Alex";
|
||||
|
||||
static bool _seeded = false;
|
||||
|
||||
static final List<Map<String, dynamic>> _groups = <Map<String, dynamic>>[];
|
||||
|
||||
/// Groups that exist but you are not in yet, keyed by invite code.
|
||||
static final Map<String, Map<String, dynamic>> _joinable =
|
||||
<String, Map<String, dynamic>>{};
|
||||
|
||||
static final Map<String, List<Map<String, dynamic>>> _messages =
|
||||
<String, List<Map<String, dynamic>>>{};
|
||||
|
||||
/// Replies that "arrive" once their time has passed.
|
||||
static final List<Map<String, dynamic>> _pending = <Map<String, dynamic>>[];
|
||||
|
||||
static final Random _random = Random(7);
|
||||
|
||||
static Map<String, dynamic> _member(String id, String name,
|
||||
{String role = "Member",
|
||||
double kept = 0.7,
|
||||
int streak = 2,
|
||||
String standing = "Good",
|
||||
int checkedInHoursAgo = 5}) {
|
||||
return <String, dynamic>{
|
||||
'id': id,
|
||||
'name': name,
|
||||
'role': role,
|
||||
'keptRate': kept,
|
||||
'streak': streak,
|
||||
'standing': standing,
|
||||
'lastCheckIn': DateTime.now()
|
||||
.subtract(Duration(hours: checkedInHoursAgo))
|
||||
.toIso8601String(),
|
||||
'me': id == _me,
|
||||
};
|
||||
}
|
||||
|
||||
static Map<String, dynamic> _objective(
|
||||
String id, String title, String unit, double target, double current,
|
||||
double mine) {
|
||||
return <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
'unit': unit,
|
||||
'target': target,
|
||||
'current': current,
|
||||
'mine': mine,
|
||||
};
|
||||
}
|
||||
|
||||
static Map<String, dynamic> _message(String groupId, String authorId,
|
||||
String authorName, String body, int minutesAgo,
|
||||
{String kind = "Text"}) {
|
||||
return <String, dynamic>{
|
||||
'id': MockStore.newId('msg'),
|
||||
'groupId': groupId,
|
||||
'authorId': authorId,
|
||||
'authorName': authorName,
|
||||
'body': body,
|
||||
'kind': kind,
|
||||
'sentAt': DateTime.now()
|
||||
.subtract(Duration(minutes: minutesAgo))
|
||||
.toIso8601String(),
|
||||
'mine': authorId == _me,
|
||||
};
|
||||
}
|
||||
|
||||
static void _ensureSeeded() {
|
||||
if (_seeded) {
|
||||
return;
|
||||
}
|
||||
_seeded = true;
|
||||
|
||||
final DateTime now = DateTime.now();
|
||||
|
||||
_groups.addAll(<Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'id': 'group-run',
|
||||
'name': 'Morning Crew',
|
||||
'description': 'Out the door before 7, five days a week.',
|
||||
'colourHex': colorToHex(goalAccents[1]),
|
||||
'inviteCode': 'MRN7AM',
|
||||
'unread': 3,
|
||||
'members': <Map<String, dynamic>>[
|
||||
_member('m-sam', 'Sam',
|
||||
role: 'Owner', kept: 0.92, streak: 11, checkedInHoursAgo: 2),
|
||||
_member(_me, _myName, kept: 0.78, streak: 4, checkedInHoursAgo: 4),
|
||||
_member('m-priya', 'Priya', kept: 0.85, streak: 7, checkedInHoursAgo: 3),
|
||||
_member('m-jordan', 'Jordan',
|
||||
kept: 0.41, streak: 0, standing: 'Warned', checkedInHoursAgo: 52),
|
||||
],
|
||||
'goals': <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'id': 'gg-run',
|
||||
'title': 'Run 200 km this month',
|
||||
'description': 'Every kilometre counts, treadmill included.',
|
||||
'targetDate': DateTime(now.year, now.month + 1, 0).toIso8601String(),
|
||||
'colourHex': colorToHex(goalAccents[1]),
|
||||
'objectives': <Map<String, dynamic>>[
|
||||
_objective('ob-km', 'Distance run', 'km', 200, 86, 22),
|
||||
_objective('ob-sessions', 'Runs logged', 'runs', 40, 17, 5),
|
||||
],
|
||||
},
|
||||
<String, dynamic>{
|
||||
'id': 'gg-sleep',
|
||||
'title': 'In bed before 23:00',
|
||||
'description': 'Mornings start the night before.',
|
||||
'targetDate': '',
|
||||
'colourHex': colorToHex(goalAccents[4]),
|
||||
'objectives': <Map<String, dynamic>>[
|
||||
_objective('ob-nights', 'Early nights', 'nights', 60, 31, 9),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
<String, dynamic>{
|
||||
'id': 'group-thesis',
|
||||
'name': 'Thesis Sprint',
|
||||
'description': 'Drafts in by the 15th. No heroics, just pages.',
|
||||
'colourHex': colorToHex(goalAccents[3]),
|
||||
'inviteCode': 'THS15D',
|
||||
'unread': 0,
|
||||
'members': <Map<String, dynamic>>[
|
||||
_member(_me, _myName,
|
||||
role: 'Owner', kept: 0.78, streak: 4, checkedInHoursAgo: 4),
|
||||
_member('m-mei', 'Mei', kept: 0.88, streak: 9, checkedInHoursAgo: 6),
|
||||
_member('m-tom', 'Tom', kept: 0.63, streak: 1, checkedInHoursAgo: 20),
|
||||
],
|
||||
'goals': <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'id': 'gg-drafts',
|
||||
'title': 'Full drafts submitted',
|
||||
'description': 'Three theses, one deadline.',
|
||||
'targetDate': DateTime(now.year, now.month + 1, 15).toIso8601String(),
|
||||
'colourHex': colorToHex(goalAccents[3]),
|
||||
'objectives': <Map<String, dynamic>>[
|
||||
_objective('ob-words', 'Words written', 'words', 30000, 18400, 7200),
|
||||
_objective('ob-chapters', 'Chapters reviewed', 'chapters', 9, 3, 1),
|
||||
_objective('ob-hours', 'Deep-work hours', 'hours', 120, 64, 23),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
_joinable['WLK2GO'] = <String, dynamic>{
|
||||
'id': 'group-walk',
|
||||
'name': 'Weekend Walkers',
|
||||
'description': 'Ten thousand steps, Saturday and Sunday, no excuses.',
|
||||
'colourHex': colorToHex(goalAccents[5]),
|
||||
'inviteCode': 'WLK2GO',
|
||||
'unread': 1,
|
||||
'members': <Map<String, dynamic>>[
|
||||
_member('m-ade', 'Ade', role: 'Owner', kept: 0.81, streak: 6),
|
||||
_member('m-lena', 'Lena', kept: 0.74, streak: 3),
|
||||
],
|
||||
'goals': <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'id': 'gg-steps',
|
||||
'title': '1 million steps together',
|
||||
'description': '',
|
||||
'targetDate': '',
|
||||
'colourHex': colorToHex(goalAccents[5]),
|
||||
'objectives': <Map<String, dynamic>>[
|
||||
_objective('ob-steps', 'Steps', 'steps', 1000000, 412000, 0),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
_messages['group-run'] = <Map<String, dynamic>>[
|
||||
_message('group-run', 'm-sam', 'Sam',
|
||||
'Same route tomorrow? Gate at 6:15.', 60 * 20),
|
||||
_message('group-run', _me, _myName, "I'm in. Bringing Priya's spare torch.",
|
||||
60 * 19 + 40),
|
||||
_message('group-run', 'm-priya', 'Priya', 'logged 6 km on Distance run',
|
||||
60 * 3, kind: 'CheckIn'),
|
||||
_message('group-run', 'm-sam', 'Sam', 'logged 8 km on Distance run',
|
||||
60 * 2, kind: 'CheckIn'),
|
||||
_message('group-run', 'm-sam', 'Sam',
|
||||
'nudged Jordan: two days without a run, we miss you out there',
|
||||
95, kind: 'Nudge'),
|
||||
_message('group-run', 'm-priya', 'Priya',
|
||||
'86 km down. We need about 9 a day to make it, doable.', 40),
|
||||
];
|
||||
_messages['group-thesis'] = <Map<String, dynamic>>[
|
||||
_message('group-thesis', _me, _myName, 'started the group', 60 * 24 * 6,
|
||||
kind: 'System'),
|
||||
_message('group-thesis', 'm-mei', 'Mei',
|
||||
'Chapter 3 is with my supervisor. Terrified.', 60 * 26),
|
||||
_message('group-thesis', _me, _myName, 'logged 1,200 words on Words written',
|
||||
60 * 22, kind: 'CheckIn'),
|
||||
_message('group-thesis', 'm-tom', 'Tom',
|
||||
'Library 9 to 1 tomorrow if anyone wants a co-working block.', 60 * 7),
|
||||
];
|
||||
_messages['group-walk'] = <Map<String, dynamic>>[
|
||||
_message('group-walk', 'm-ade', 'Ade',
|
||||
'Welcome! Saturday loop starts at the bridge, 9:00.', 60 * 30),
|
||||
];
|
||||
}
|
||||
|
||||
static Map<String, dynamic>? _find(String id) {
|
||||
for (Map<String, dynamic> group in _groups) {
|
||||
if (group['id'] == id) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Moves any replies whose moment has come into their threads.
|
||||
static void _deliver() {
|
||||
final DateTime now = DateTime.now();
|
||||
final List<Map<String, dynamic>> due = _pending
|
||||
.where((item) => DateTime.parse(item['sentAt']).isBefore(now))
|
||||
.toList();
|
||||
for (Map<String, dynamic> message in due) {
|
||||
_pending.remove(message);
|
||||
_messages.putIfAbsent(message['groupId'], () => <Map<String, dynamic>>[])
|
||||
.add(message);
|
||||
final Map<String, dynamic>? group = _find(message['groupId']);
|
||||
if (group != null) {
|
||||
group['unread'] = (group['unread'] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Map<String, dynamic> _withLast(Map<String, dynamic> group) {
|
||||
final List<Map<String, dynamic>> thread =
|
||||
_messages[group['id']] ?? <Map<String, dynamic>>[];
|
||||
final Map<String, dynamic> copy = Map<String, dynamic>.from(group);
|
||||
if (thread.isNotEmpty) {
|
||||
final Map<String, dynamic> last = thread.last;
|
||||
copy['lastMessage'] = last['body'];
|
||||
copy['lastAuthor'] = last['authorName'];
|
||||
copy['lastMessageAt'] = last['sentAt'];
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
static void _post(String groupId, String body, String kind) {
|
||||
_messages.putIfAbsent(groupId, () => <Map<String, dynamic>>[]).add(
|
||||
_message(groupId, _me, _myName, body, 0, kind: kind));
|
||||
}
|
||||
|
||||
/// Someone answers a few seconds later, so the chat feels inhabited.
|
||||
static void _queueReply(String groupId) {
|
||||
final Map<String, dynamic>? group = _find(groupId);
|
||||
if (group == null) {
|
||||
return;
|
||||
}
|
||||
final List<dynamic> others = (group['members'] as List)
|
||||
.where((member) => member['id'] != _me)
|
||||
.toList();
|
||||
if (others.isEmpty) {
|
||||
return;
|
||||
}
|
||||
const List<String> lines = <String>[
|
||||
"Love it. Keep that going.",
|
||||
"Same here, just finished mine.",
|
||||
"That's the spirit.",
|
||||
"Noted. I'll match you tomorrow.",
|
||||
"Good — that puts us back on pace.",
|
||||
"Who's in for the next one?",
|
||||
];
|
||||
final Map<String, dynamic> author = others[_random.nextInt(others.length)];
|
||||
final Map<String, dynamic> reply = _message(groupId, author['id'],
|
||||
author['name'], lines[_random.nextInt(lines.length)], 0);
|
||||
reply['sentAt'] =
|
||||
DateTime.now().add(const Duration(seconds: 3)).toIso8601String();
|
||||
_pending.add(reply);
|
||||
}
|
||||
|
||||
static String _code() {
|
||||
const String letters = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||
return List<String>.generate(
|
||||
6, (index) => letters[_random.nextInt(letters.length)]).join();
|
||||
}
|
||||
|
||||
// ── Endpoints ─────────────────────────────────────────────────────────────
|
||||
|
||||
static List<Map<String, dynamic>> mine() {
|
||||
_ensureSeeded();
|
||||
_deliver();
|
||||
return _groups.map(_withLast).toList();
|
||||
}
|
||||
|
||||
static Map<String, dynamic> one(String id) {
|
||||
_ensureSeeded();
|
||||
_deliver();
|
||||
return _withLast(_find(id) ?? <String, dynamic>{});
|
||||
}
|
||||
|
||||
static Map<String, dynamic> save(GroupRequest request) {
|
||||
_ensureSeeded();
|
||||
final Map<String, dynamic>? existing =
|
||||
request.id == null ? null : _find(request.id!);
|
||||
if (existing != null) {
|
||||
existing['name'] = request.name;
|
||||
existing['description'] = request.description;
|
||||
existing['colourHex'] = request.colourHex;
|
||||
return _withLast(existing);
|
||||
}
|
||||
|
||||
final String id = MockStore.newId('group');
|
||||
final Map<String, dynamic> group = <String, dynamic>{
|
||||
'id': id,
|
||||
'name': request.name,
|
||||
'description': request.description,
|
||||
'colourHex': request.colourHex,
|
||||
'inviteCode': _code(),
|
||||
'unread': 0,
|
||||
'members': <Map<String, dynamic>>[
|
||||
_member(_me, _myName, role: 'Owner', kept: 0.78, streak: 4,
|
||||
checkedInHoursAgo: 0),
|
||||
],
|
||||
'goals': <Map<String, dynamic>>[],
|
||||
};
|
||||
_groups.insert(0, group);
|
||||
_post(id, 'started the group', 'System');
|
||||
return _withLast(group);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> join(JoinGroupRequest request) {
|
||||
_ensureSeeded();
|
||||
final String code = request.inviteCode.trim().toUpperCase();
|
||||
|
||||
for (Map<String, dynamic> group in _groups) {
|
||||
if (group['inviteCode'] == code) {
|
||||
return _withLast(group);
|
||||
}
|
||||
}
|
||||
|
||||
// Any well-formed code finds the waiting group, so the preview always
|
||||
// has something to join.
|
||||
final Map<String, dynamic> group = _joinable.remove(code) ??
|
||||
(_joinable.isNotEmpty
|
||||
? _joinable.remove(_joinable.keys.first)!
|
||||
: <String, dynamic>{
|
||||
'id': MockStore.newId('group'),
|
||||
'name': 'Group $code',
|
||||
'description': '',
|
||||
'colourHex': colorToHex(goalAccents[0]),
|
||||
'inviteCode': code,
|
||||
'unread': 0,
|
||||
'members': <Map<String, dynamic>>[],
|
||||
'goals': <Map<String, dynamic>>[],
|
||||
});
|
||||
(group['members'] as List).add(_member(_me, _myName,
|
||||
kept: 0.78, streak: 4, checkedInHoursAgo: 0));
|
||||
_groups.insert(0, group);
|
||||
_post(group['id'], 'joined the group', 'System');
|
||||
_queueReply(group['id']);
|
||||
return _withLast(group);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> leave(String id) {
|
||||
_ensureSeeded();
|
||||
_groups.removeWhere((group) => group['id'] == id);
|
||||
return <String, dynamic>{'success': true};
|
||||
}
|
||||
|
||||
static Map<String, dynamic> saveGoal(GroupGoalRequest request) {
|
||||
_ensureSeeded();
|
||||
final Map<String, dynamic>? group = _find(request.groupId);
|
||||
if (group == null) {
|
||||
return <String, dynamic>{};
|
||||
}
|
||||
final Map<String, dynamic> goal = <String, dynamic>{
|
||||
'id': MockStore.newId('gg'),
|
||||
'title': request.title,
|
||||
'description': request.description,
|
||||
'targetDate': request.targetDate,
|
||||
'colourHex': request.colourHex,
|
||||
'objectives': request.objectives
|
||||
.map((draft) => _objective(MockStore.newId('ob'), draft.title,
|
||||
draft.unit, draft.target, 0, 0))
|
||||
.toList(),
|
||||
};
|
||||
(group['goals'] as List).add(goal);
|
||||
_post(group['id'], 'added a shared goal: ${request.title}', 'System');
|
||||
return _withLast(group);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> logProgress(ObjectiveProgressRequest request) {
|
||||
_ensureSeeded();
|
||||
final Map<String, dynamic>? group = _find(request.groupId);
|
||||
if (group == null) {
|
||||
return <String, dynamic>{};
|
||||
}
|
||||
for (Map<String, dynamic> goal in (group['goals'] as List)) {
|
||||
if (goal['id'] != request.goalId) {
|
||||
continue;
|
||||
}
|
||||
for (Map<String, dynamic> objective in (goal['objectives'] as List)) {
|
||||
if (objective['id'] != request.objectiveId) {
|
||||
continue;
|
||||
}
|
||||
objective['current'] = (objective['current'] as num) + request.amount;
|
||||
objective['mine'] = (objective['mine'] as num) + request.amount;
|
||||
final String amount = formatAmount(request.amount);
|
||||
_post(
|
||||
group['id'],
|
||||
'logged $amount ${objective['unit']} on ${objective['title']}'
|
||||
'${request.note.isEmpty ? "" : " — ${request.note}"}',
|
||||
'CheckIn');
|
||||
_queueReply(group['id']);
|
||||
}
|
||||
}
|
||||
for (Map<String, dynamic> member in (group['members'] as List)) {
|
||||
if (member['id'] == _me) {
|
||||
member['lastCheckIn'] = DateTime.now().toIso8601String();
|
||||
}
|
||||
}
|
||||
return _withLast(group);
|
||||
}
|
||||
|
||||
static List<Map<String, dynamic>> messages(String groupId) {
|
||||
_ensureSeeded();
|
||||
_deliver();
|
||||
final Map<String, dynamic>? group = _find(groupId);
|
||||
if (group != null) {
|
||||
group['unread'] = 0;
|
||||
}
|
||||
return List<Map<String, dynamic>>.from(
|
||||
_messages[groupId] ?? <Map<String, dynamic>>[]);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> send(GroupMessageRequest request) {
|
||||
_ensureSeeded();
|
||||
_post(request.groupId, request.body, request.kind);
|
||||
_queueReply(request.groupId);
|
||||
return _messages[request.groupId]!.last;
|
||||
}
|
||||
|
||||
static Map<String, dynamic> nudge(NudgeRequest request) {
|
||||
_ensureSeeded();
|
||||
final Map<String, dynamic>? group = _find(request.groupId);
|
||||
if (group == null) {
|
||||
return <String, dynamic>{'success': false};
|
||||
}
|
||||
String who = "everyone";
|
||||
for (Map<String, dynamic> member in (group['members'] as List)) {
|
||||
if (member['id'] == request.memberId) {
|
||||
who = member['name'];
|
||||
}
|
||||
}
|
||||
_post(request.groupId, 'nudged $who: today still counts, go get it',
|
||||
'Nudge');
|
||||
return <String, dynamic>{'success': true};
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,8 @@ class MockMemory extends InternalMemory {
|
||||
setMyDescription(
|
||||
MeDescription(id: "mock-user", name: "Alex", token: "mock-token"));
|
||||
setUserDetails(UserDetails(pic: "", name: "Alex"));
|
||||
setWaterToday(1250);
|
||||
setTodos(<TodoItem>[
|
||||
TodoItem(id: "t1", title: "Refill water bottle"),
|
||||
TodoItem(id: "t1", title: "Pick up gym kit"),
|
||||
TodoItem(id: "t2", title: "Book physio for Thursday"),
|
||||
TodoItem(id: "t3", title: "Reply to Sam about Saturday", done: true),
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user