Show real photos for people instead of scene art
Your avatar is your photo, picked from the device by tapping it on Home or from a new Profile photo row in Settings, shrunk to 256 px and kept on the phone (there is no upload endpoint yet). Group members show the photo the server sends in a new `photo` field, everywhere avatars appear. Without a photo, an initial on a neutral disc. Mock mode bundles free Unsplash portraits for the sample people. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
This commit is contained in:
@@ -46,7 +46,8 @@ class MockComms implements ConnectComms {
|
||||
|
||||
Future<Response> _done() => _ok(<String, dynamic>{'success': true});
|
||||
|
||||
Future<Response> _emptyPage() => _ok(MockStore.page(<Map<String, dynamic>>[]));
|
||||
Future<Response> _emptyPage() =>
|
||||
_ok(MockStore.page(<Map<String, dynamic>>[]));
|
||||
|
||||
// ── Auth ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -72,7 +73,8 @@ class MockComms implements ConnectComms {
|
||||
Future<Response> logout() => _done();
|
||||
|
||||
@override
|
||||
Future<Response> me() => _ok(<String, dynamic>{'id': 'mock-user', 'name': 'Alex'});
|
||||
Future<Response> me() =>
|
||||
_ok(<String, dynamic>{'id': 'mock-user', 'name': 'Alex'});
|
||||
|
||||
// ── Goals ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -152,7 +154,8 @@ class MockComms implements ConnectComms {
|
||||
|
||||
@override
|
||||
Future<Response> completeCommitmentEntry(CompletionRequest request) {
|
||||
final Map<String, dynamic>? item = MockStore.commitment(request.commitmentId);
|
||||
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())
|
||||
@@ -164,7 +167,8 @@ class MockComms implements ConnectComms {
|
||||
|
||||
@override
|
||||
Future<Response> deferCommitmentEntry(DeferralRequest request) {
|
||||
final Map<String, dynamic>? item = MockStore.commitment(request.commitmentId);
|
||||
final Map<String, dynamic>? item =
|
||||
MockStore.commitment(request.commitmentId);
|
||||
if (item != null) {
|
||||
final DateTime now = DateTime.now();
|
||||
item['dueStart'] = request.newDueStart.isNotEmpty
|
||||
@@ -181,7 +185,8 @@ class MockComms implements ConnectComms {
|
||||
|
||||
@override
|
||||
Future<Response> abandonCommitmentEntry(AbandonRequest request) {
|
||||
final Map<String, dynamic>? item = MockStore.commitment(request.commitmentId);
|
||||
final Map<String, dynamic>? item =
|
||||
MockStore.commitment(request.commitmentId);
|
||||
if (item != null) {
|
||||
item['status'] = 'Abandoned';
|
||||
}
|
||||
@@ -271,7 +276,8 @@ class MockComms implements ConnectComms {
|
||||
Future<Response> updateSickMode(SickModeRequest request) => _done();
|
||||
|
||||
@override
|
||||
Future<Response> checkDistress() => _ok(<String, dynamic>{'distressed': false});
|
||||
Future<Response> checkDistress() =>
|
||||
_ok(<String, dynamic>{'distressed': false});
|
||||
|
||||
@override
|
||||
Future<Response> updateTone(ToneRequest request) => _done();
|
||||
@@ -291,8 +297,8 @@ class MockComms implements ConnectComms {
|
||||
Future<Response> getKeystoneHabits() => _ok(<dynamic>[]);
|
||||
|
||||
@override
|
||||
Future<Response> getMyRoutines(HistoryRequest request) => _ok(
|
||||
MockStore.routines.map((routine) => routine.toJson()).toList());
|
||||
Future<Response> getMyRoutines(HistoryRequest request) =>
|
||||
_ok(MockStore.routines.map((routine) => routine.toJson()).toList());
|
||||
|
||||
@override
|
||||
Future<Response> saveRoutineEntry(Map<String, dynamic> request) {
|
||||
@@ -385,13 +391,16 @@ class MockComms implements ConnectComms {
|
||||
Future<Response> getSessionHistory(HistoryRequest request) => _emptyPage();
|
||||
|
||||
@override
|
||||
Future<Response> getLastSessionFor(IdRequest request) => _ok(<String, dynamic>{});
|
||||
Future<Response> getLastSessionFor(IdRequest request) =>
|
||||
_ok(<String, dynamic>{});
|
||||
|
||||
@override
|
||||
Future<Response> getWeeklyVolume(ReportCardRequest request) => _ok(<String, dynamic>{});
|
||||
Future<Response> getWeeklyVolume(ReportCardRequest request) =>
|
||||
_ok(<String, dynamic>{});
|
||||
|
||||
@override
|
||||
Future<Response> getContactVolume(ReportCardRequest request) => _ok(<String, dynamic>{});
|
||||
Future<Response> getContactVolume(ReportCardRequest request) =>
|
||||
_ok(<String, dynamic>{});
|
||||
|
||||
@override
|
||||
Future<Response> getPersonalRecords(HistoryRequest request) => _emptyPage();
|
||||
@@ -399,10 +408,12 @@ class MockComms implements ConnectComms {
|
||||
// ── Report card ───────────────────────────────────────────────────────────
|
||||
|
||||
@override
|
||||
Future<Response> getWeeklyReportCard(ReportCardRequest request) => _ok(_report());
|
||||
Future<Response> getWeeklyReportCard(ReportCardRequest request) =>
|
||||
_ok(_report());
|
||||
|
||||
@override
|
||||
Future<Response> getMonthlyReportCard(ReportCardRequest request) => _ok(_report());
|
||||
Future<Response> getMonthlyReportCard(ReportCardRequest request) =>
|
||||
_ok(_report());
|
||||
|
||||
Map<String, dynamic> _report() {
|
||||
final DateTime now = DateTime.now();
|
||||
@@ -421,13 +432,27 @@ class MockComms implements ConnectComms {
|
||||
'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,
|
||||
'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},
|
||||
{
|
||||
'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},
|
||||
@@ -451,32 +476,42 @@ class MockComms implements ConnectComms {
|
||||
// ── Groups ────────────────────────────────────────────────────────────────
|
||||
|
||||
@override
|
||||
Future<Response> getMyGroups(HistoryRequest request) => _ok(MockGroups.mine());
|
||||
Future<Response> getMyGroups(HistoryRequest request) =>
|
||||
_ok(MockGroups.mine());
|
||||
|
||||
@override
|
||||
Future<Response> getGroupEntry(IdRequest request) => _ok(MockGroups.one(request.id));
|
||||
Future<Response> getGroupEntry(IdRequest request) =>
|
||||
_ok(MockGroups.one(request.id));
|
||||
|
||||
@override
|
||||
Future<Response> saveGroupEntry(GroupRequest request) => _ok(MockGroups.save(request));
|
||||
Future<Response> saveGroupEntry(GroupRequest request) =>
|
||||
_ok(MockGroups.save(request));
|
||||
|
||||
@override
|
||||
Future<Response> joinGroupEntry(JoinGroupRequest request) => _ok(MockGroups.join(request));
|
||||
Future<Response> joinGroupEntry(JoinGroupRequest request) =>
|
||||
_ok(MockGroups.join(request));
|
||||
|
||||
@override
|
||||
Future<Response> leaveGroupEntry(IdRequest request) => _ok(MockGroups.leave(request.id));
|
||||
Future<Response> leaveGroupEntry(IdRequest request) =>
|
||||
_ok(MockGroups.leave(request.id));
|
||||
|
||||
@override
|
||||
Future<Response> saveGroupGoalEntry(GroupGoalRequest request) => _ok(MockGroups.saveGoal(request));
|
||||
Future<Response> saveGroupGoalEntry(GroupGoalRequest request) =>
|
||||
_ok(MockGroups.saveGoal(request));
|
||||
|
||||
@override
|
||||
Future<Response> logObjectiveProgress(ObjectiveProgressRequest request) => _ok(MockGroups.logProgress(request));
|
||||
Future<Response> logObjectiveProgress(ObjectiveProgressRequest request) =>
|
||||
_ok(MockGroups.logProgress(request));
|
||||
|
||||
@override
|
||||
Future<Response> getGroupMessages(IdRequest request) => _ok(MockGroups.messages(request.id));
|
||||
Future<Response> getGroupMessages(IdRequest request) =>
|
||||
_ok(MockGroups.messages(request.id));
|
||||
|
||||
@override
|
||||
Future<Response> sendGroupMessage(GroupMessageRequest request) => _ok(MockGroups.send(request));
|
||||
Future<Response> sendGroupMessage(GroupMessageRequest request) =>
|
||||
_ok(MockGroups.send(request));
|
||||
|
||||
@override
|
||||
Future<Response> nudgeGroupMember(NudgeRequest request) => _ok(MockGroups.nudge(request));
|
||||
Future<Response> nudgeGroupMember(NudgeRequest request) =>
|
||||
_ok(MockGroups.nudge(request));
|
||||
}
|
||||
|
||||
@@ -35,6 +35,18 @@ class MockGroups {
|
||||
|
||||
static final Random _random = Random(7);
|
||||
|
||||
/// Sample people with a bundled portrait (assets/images/person_*.jpg).
|
||||
static const List<String> _photos = <String>[
|
||||
'alex',
|
||||
'sam',
|
||||
'priya',
|
||||
'jordan',
|
||||
'mei',
|
||||
'tom',
|
||||
'ade',
|
||||
'lena'
|
||||
];
|
||||
|
||||
static Map<String, dynamic> _member(String id, String name,
|
||||
{String role = "Member",
|
||||
double kept = 0.7,
|
||||
@@ -44,6 +56,9 @@ class MockGroups {
|
||||
return <String, dynamic>{
|
||||
'id': id,
|
||||
'name': name,
|
||||
'photo': _photos.contains(name.toLowerCase())
|
||||
? 'asset:assets/images/person_${name.toLowerCase()}.jpg'
|
||||
: '',
|
||||
'role': role,
|
||||
'keptRate': kept,
|
||||
'streak': streak,
|
||||
@@ -55,9 +70,8 @@ class MockGroups {
|
||||
};
|
||||
}
|
||||
|
||||
static Map<String, dynamic> _objective(
|
||||
String id, String title, String unit, double target, double current,
|
||||
double mine) {
|
||||
static Map<String, dynamic> _objective(String id, String title, String unit,
|
||||
double target, double current, double mine) {
|
||||
return <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
@@ -105,7 +119,8 @@ class MockGroups {
|
||||
_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-priya', 'Priya',
|
||||
kept: 0.85, streak: 7, checkedInHoursAgo: 3),
|
||||
_member('m-jordan', 'Jordan',
|
||||
kept: 0.41, streak: 0, standing: 'Warned', checkedInHoursAgo: 52),
|
||||
],
|
||||
@@ -114,7 +129,8 @@ class MockGroups {
|
||||
'id': 'gg-run',
|
||||
'title': 'Run 200 km this month',
|
||||
'description': 'Every kilometre counts, treadmill included.',
|
||||
'targetDate': DateTime(now.year, now.month + 1, 0).toIso8601String(),
|
||||
'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),
|
||||
@@ -151,11 +167,14 @@ class MockGroups {
|
||||
'id': 'gg-drafts',
|
||||
'title': 'Full drafts submitted',
|
||||
'description': 'Three theses, one deadline.',
|
||||
'targetDate': DateTime(now.year, now.month + 1, 15).toIso8601String(),
|
||||
'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-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),
|
||||
],
|
||||
},
|
||||
@@ -191,15 +210,17 @@ class MockGroups {
|
||||
_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', _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'),
|
||||
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'),
|
||||
'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),
|
||||
];
|
||||
@@ -208,10 +229,15 @@ class MockGroups {
|
||||
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),
|
||||
_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',
|
||||
@@ -236,7 +262,8 @@ class MockGroups {
|
||||
.toList();
|
||||
for (Map<String, dynamic> message in due) {
|
||||
_pending.remove(message);
|
||||
_messages.putIfAbsent(message['groupId'], () => <Map<String, dynamic>>[])
|
||||
_messages
|
||||
.putIfAbsent(message['groupId'], () => <Map<String, dynamic>>[])
|
||||
.add(message);
|
||||
final Map<String, dynamic>? group = _find(message['groupId']);
|
||||
if (group != null) {
|
||||
@@ -259,8 +286,9 @@ class MockGroups {
|
||||
}
|
||||
|
||||
static void _post(String groupId, String body, String kind) {
|
||||
_messages.putIfAbsent(groupId, () => <Map<String, dynamic>>[]).add(
|
||||
_message(groupId, _me, _myName, body, 0, kind: 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.
|
||||
@@ -331,8 +359,8 @@ class MockGroups {
|
||||
'inviteCode': _code(),
|
||||
'unread': 0,
|
||||
'members': <Map<String, dynamic>>[
|
||||
_member(_me, _myName, role: 'Owner', kept: 0.78, streak: 4,
|
||||
checkedInHoursAgo: 0),
|
||||
_member(_me, _myName,
|
||||
role: 'Owner', kept: 0.78, streak: 4, checkedInHoursAgo: 0),
|
||||
],
|
||||
'goals': <Map<String, dynamic>>[],
|
||||
};
|
||||
@@ -366,8 +394,8 @@ class MockGroups {
|
||||
'members': <Map<String, dynamic>>[],
|
||||
'goals': <Map<String, dynamic>>[],
|
||||
});
|
||||
(group['members'] as List).add(_member(_me, _myName,
|
||||
kept: 0.78, streak: 4, checkedInHoursAgo: 0));
|
||||
(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']);
|
||||
@@ -422,7 +450,7 @@ class MockGroups {
|
||||
_post(
|
||||
group['id'],
|
||||
'logged $amount ${objective['unit']} on ${objective['title']}'
|
||||
'${request.note.isEmpty ? "" : " — ${request.note}"}',
|
||||
'${request.note.isEmpty ? "" : " — ${request.note}"}',
|
||||
'CheckIn');
|
||||
_queueReply(group['id']);
|
||||
}
|
||||
@@ -465,8 +493,8 @@ class MockGroups {
|
||||
who = member['name'];
|
||||
}
|
||||
}
|
||||
_post(request.groupId, 'nudged $who: today still counts, go get it',
|
||||
'Nudge');
|
||||
_post(
|
||||
request.groupId, 'nudged $who: today still counts, go get it', 'Nudge');
|
||||
return <String, dynamic>{'success': true};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ class MockMemory extends InternalMemory {
|
||||
_seeded = true;
|
||||
setMyDescription(
|
||||
MeDescription(id: "mock-user", name: "Alex", token: "mock-token"));
|
||||
setUserDetails(UserDetails(pic: "", name: "Alex"));
|
||||
setUserDetails(UserDetails(
|
||||
pic: "asset:assets/images/person_alex.jpg", name: "Alex"));
|
||||
setTodos(<TodoItem>[
|
||||
TodoItem(id: "t1", title: "Pick up gym kit"),
|
||||
TodoItem(id: "t2", title: "Book physio for Thursday"),
|
||||
@@ -51,8 +52,9 @@ class MockMemory extends InternalMemory {
|
||||
title: title,
|
||||
kind: kind,
|
||||
startedAt: at,
|
||||
seconds: (seconds * (held ? 1 : 0.5) * (0.85 + random.nextDouble() * 0.3))
|
||||
.round(),
|
||||
seconds:
|
||||
(seconds * (held ? 1 : 0.5) * (0.85 + random.nextDouble() * 0.3))
|
||||
.round(),
|
||||
completedSteps: held ? steps : 1 + random.nextInt(steps - 1),
|
||||
totalSteps: steps,
|
||||
));
|
||||
|
||||
@@ -43,7 +43,8 @@ class MockStore {
|
||||
{
|
||||
'id': 'goal-strong',
|
||||
'title': 'Get strong',
|
||||
'description': 'Deadlift my bodyweight by spring, without getting hurt.',
|
||||
'description':
|
||||
'Deadlift my bodyweight by spring, without getting hurt.',
|
||||
'category': 'Health',
|
||||
'defaultClass': 'Standard',
|
||||
'startDate': at(-30, 0).toIso8601String(),
|
||||
@@ -145,10 +146,7 @@ class MockStore {
|
||||
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),
|
||||
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',
|
||||
@@ -159,10 +157,7 @@ class MockStore {
|
||||
status: 'Overdue',
|
||||
deferrals: 1),
|
||||
task('c-kitchen', 'Deep clean the kitchen',
|
||||
start: at(-1, 18),
|
||||
end: at(-1, 21),
|
||||
minutes: 45,
|
||||
status: 'Overdue'),
|
||||
start: at(-1, 18), end: at(-1, 21), minutes: 45, status: 'Overdue'),
|
||||
]);
|
||||
|
||||
// ── Routines ───────────────────────────────────────────────────────────
|
||||
@@ -175,7 +170,10 @@ class MockStore {
|
||||
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();
|
||||
..days = <int>[1, 2, 3, 4, 5, 6, 7]
|
||||
.where((day) => day != weekday)
|
||||
.take(2)
|
||||
.toList();
|
||||
|
||||
final RoutineChain breathing = RoutineTemplates.boxBreathing()
|
||||
..id = 'routine-breathing'
|
||||
@@ -262,12 +260,14 @@ class MockStore {
|
||||
final List<Map<String, dynamic>> tasks = goalTasks(goal['id']);
|
||||
final int completed = tasks
|
||||
.where((item) =>
|
||||
item['status'] == 'Completed' || item['status'] == 'LateCompleted')
|
||||
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
|
||||
..['totalTasks'] =
|
||||
tasks.where((item) => item['status'] != 'Abandoned').length
|
||||
..['completedTasks'] = completed
|
||||
..['overdueTasks'] = late
|
||||
..['debtContribution'] = late * 2.0;
|
||||
|
||||
Reference in New Issue
Block a user