Skip to content

Commit 1cba91a

Browse files
committed
Add option to hide incorrect submissions
This is just for collector games…? Does it make sense?
1 parent 964840a commit 1cba91a

File tree

1 file changed

+129
-84
lines changed

1 file changed

+129
-84
lines changed

waydowntown_app/lib/games/collector_game.dart

Lines changed: 129 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import 'package:flutter/material.dart';
33
import 'package:phoenix_socket/phoenix_socket.dart';
44
import 'package:waydowntown/mixins/run_state_mixin.dart';
55
import 'package:waydowntown/models/run.dart';
6+
import 'package:waydowntown/models/submission.dart';
67
import 'package:waydowntown/run_header.dart';
8+
import 'package:waydowntown/services/user_service.dart';
79

810
abstract class StringDetector {
911
Stream<String> get detectedStrings;
@@ -65,8 +67,8 @@ class CollectorGameState extends State<CollectorGame>
6567
List<HintItem> hints = [];
6668
bool isLoadingHint = false;
6769
Map<String, String> itemErrors = {};
68-
69-
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
70+
bool showIncorrectSubmissions = true;
71+
String? currentUserId;
7072

7173
@override
7274
Dio get dio => widget.dio;
@@ -81,6 +83,13 @@ class CollectorGameState extends State<CollectorGame>
8183
WidgetsBinding.instance.addObserver(this);
8284
widget.detector.detectedStrings.listen(_onItemDetected);
8385
widget.detector.startDetecting();
86+
UserService.getUserId().then((id) {
87+
if (mounted) {
88+
setState(() {
89+
currentUserId = id;
90+
});
91+
}
92+
});
8493
}
8594

8695
void _onItemDetected(String value) {
@@ -179,8 +188,6 @@ class CollectorGameState extends State<CollectorGame>
179188
if (mounted) {
180189
setState(() {
181190
detectedItems.insert(0, item);
182-
_listKey.currentState
183-
?.insertItem(0, duration: const Duration(milliseconds: 300));
184191
});
185192
}
186193
}
@@ -189,21 +196,13 @@ class CollectorGameState extends State<CollectorGame>
189196
if (mounted) {
190197
setState(() {
191198
hints.insert(0, hint);
192-
_listKey.currentState
193-
?.insertItem(0, duration: const Duration(milliseconds: 300));
194199
});
195200
}
196201
}
197202

198203
void _removeHint(HintItem hint) {
199204
final index = hints.indexOf(hint);
200205
if (index != -1) {
201-
final removedHint = hints[index];
202-
_listKey.currentState?.removeItem(
203-
index,
204-
(context, animation) => _buildHintTile(removedHint, animation),
205-
duration: const Duration(milliseconds: 300),
206-
);
207206
setState(() {
208207
hints.removeAt(index);
209208
});
@@ -248,53 +247,43 @@ class CollectorGameState extends State<CollectorGame>
248247
}
249248
}
250249

251-
Widget _buildItem(
252-
BuildContext context, DetectedItem item, Animation<double> animation) {
253-
return SlideTransition(
254-
position: Tween<Offset>(
255-
begin: const Offset(0, -1),
256-
end: Offset.zero,
257-
).animate(CurvedAnimation(
258-
parent: animation,
259-
curve: Curves.easeInOutCubic,
260-
)),
261-
child: Column(
262-
crossAxisAlignment: CrossAxisAlignment.start,
263-
children: [
264-
ListTile(
265-
title: Text(item.value),
266-
leading: _getIconForState(item.state, item.value),
267-
trailing:
268-
!widget.autoSubmit && item.state == SubmissionState.unsubmitted
269-
? ElevatedButton(
270-
onPressed: () => submitItem(item),
271-
child: const Text('Submit'),
272-
)
273-
: null,
274-
onTap: item.state == SubmissionState.unsubmitted ||
275-
item.state == SubmissionState.error
276-
? () => submitItem(item)
277-
: null,
278-
),
279-
if (item.state == SubmissionState.correct && item.matchedHint != null)
280-
Padding(
281-
padding:
282-
const EdgeInsets.only(left: 72.0, right: 16.0, bottom: 8.0),
283-
child: Text(
284-
item.matchedHint!,
285-
style: const TextStyle(
286-
fontStyle: FontStyle.italic,
287-
color: Colors.blue,
288-
),
250+
Widget _buildItem(BuildContext context, DetectedItem item) {
251+
return Column(
252+
crossAxisAlignment: CrossAxisAlignment.start,
253+
children: [
254+
ListTile(
255+
title: Text(item.value),
256+
leading: _getIconForState(item.state, item.value),
257+
trailing:
258+
!widget.autoSubmit && item.state == SubmissionState.unsubmitted
259+
? ElevatedButton(
260+
onPressed: () => submitItem(item),
261+
child: const Text('Submit'),
262+
)
263+
: null,
264+
onTap: item.state == SubmissionState.unsubmitted ||
265+
item.state == SubmissionState.error
266+
? () => submitItem(item)
267+
: null,
268+
),
269+
if (item.state == SubmissionState.correct && item.matchedHint != null)
270+
Padding(
271+
padding:
272+
const EdgeInsets.only(left: 72.0, right: 16.0, bottom: 8.0),
273+
child: Text(
274+
item.matchedHint!,
275+
style: const TextStyle(
276+
fontStyle: FontStyle.italic,
277+
color: Colors.blue,
289278
),
290279
),
291-
],
292-
),
280+
),
281+
],
293282
);
294283
}
295284

296-
Widget _buildHintTile(HintItem hint, [Animation<double>? animation]) {
297-
Widget tile = Card(
285+
Widget _buildHintTile(HintItem hint) {
286+
return Card(
298287
margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
299288
color: Colors.blue.shade50,
300289
child: ListTile(
@@ -311,38 +300,73 @@ class CollectorGameState extends State<CollectorGame>
311300
),
312301
),
313302
);
303+
}
314304

315-
if (animation != null) {
316-
return SlideTransition(
317-
position: Tween<Offset>(
318-
begin: const Offset(0, -1),
319-
end: Offset.zero,
320-
).animate(CurvedAnimation(
321-
parent: animation,
322-
curve: Curves.easeInOutCubic,
323-
)),
324-
child: tile,
325-
);
326-
}
305+
Widget _buildSubmissionTile(Submission submission) {
306+
final state = submission.correct
307+
? SubmissionState.correct
308+
: SubmissionState.incorrect;
309+
final isCurrentUser =
310+
currentUserId != null && submission.creatorId == currentUserId;
311+
312+
return ListTile(
313+
title: Text(submission.submission),
314+
leading: _getIconForState(state, submission.submission),
315+
subtitle: currentUserId == null
316+
? null
317+
: Text(isCurrentUser ? 'You' : 'Teammate'),
318+
);
319+
}
327320

328-
return tile;
321+
List<Submission> _visibleTeamSubmissions() {
322+
return currentRun.submissions
323+
.where((submission) => showIncorrectSubmissions || submission.correct)
324+
.toList();
329325
}
330326

331-
Widget _buildListItem(
332-
BuildContext context, int index, Animation<double> animation) {
333-
final allItems = [
334-
...hints.map((hint) => MapEntry(hint.receivedAt,
335-
(Animation<double> anim) => _buildHintTile(hint, anim))),
336-
...detectedItems.map((item) => MapEntry(item.submittedAt,
337-
(Animation<double> anim) => _buildItem(context, item, anim))),
338-
]..sort((a, b) => b.key.compareTo(a.key));
327+
List<_TimelineItem> _buildTimelineItems(BuildContext context) {
328+
final items = <_TimelineItem>[];
329+
final teamSubmissions = _visibleTeamSubmissions();
330+
final teamSubmissionValues =
331+
teamSubmissions.map((submission) => submission.submission).toSet();
332+
333+
for (final hint in hints) {
334+
items.add(_TimelineItem(
335+
timestamp: hint.receivedAt,
336+
widget: _buildHintTile(hint),
337+
));
338+
}
339+
340+
for (final item in detectedItems) {
341+
if (!showIncorrectSubmissions && item.state == SubmissionState.incorrect) {
342+
continue;
343+
}
344+
if ((item.state == SubmissionState.correct ||
345+
item.state == SubmissionState.incorrect) &&
346+
teamSubmissionValues.contains(item.value)) {
347+
continue;
348+
}
349+
350+
items.add(_TimelineItem(
351+
timestamp: item.submittedAt,
352+
widget: _buildItem(context, item),
353+
));
354+
}
355+
356+
for (final submission in teamSubmissions) {
357+
items.add(_TimelineItem(
358+
timestamp: submission.insertedAt,
359+
widget: _buildSubmissionTile(submission),
360+
));
361+
}
339362

340-
return allItems[index].value(animation);
363+
items.sort((a, b) => b.timestamp.compareTo(a.timestamp));
364+
return items;
341365
}
342366

343367
@override
344368
Widget build(BuildContext context) {
345-
final int totalItems = detectedItems.length + hints.length;
369+
final timelineItems = _buildTimelineItems(context);
346370

347371
final unrevealedHintsExist = widget.run.specification.answers
348372
?.any((answer) => answer.hasHint && answer.hint == null);
@@ -352,6 +376,19 @@ class CollectorGameState extends State<CollectorGame>
352376
appBar: AppBar(
353377
title: Text(widget.runtimeType.toString()),
354378
actions: [
379+
IconButton(
380+
icon: Icon(showIncorrectSubmissions
381+
? Icons.visibility
382+
: Icons.visibility_off),
383+
tooltip: showIncorrectSubmissions
384+
? 'Hide incorrect submissions'
385+
: 'Show incorrect submissions',
386+
onPressed: () {
387+
setState(() {
388+
showIncorrectSubmissions = !showIncorrectSubmissions;
389+
});
390+
},
391+
),
355392
if (showHintButton)
356393
IconButton(
357394
icon: isLoadingHint
@@ -379,13 +416,14 @@ class CollectorGameState extends State<CollectorGame>
379416
else
380417
widget.inputBuilder(context, widget.detector),
381418
Expanded(
382-
child: AnimatedList(
383-
key: _listKey,
384-
initialItemCount: totalItems,
385-
itemBuilder: (context, index, animation) {
386-
return _buildListItem(context, index, animation);
387-
},
388-
),
419+
child: timelineItems.isEmpty
420+
? const Center(child: Text('No submissions yet.'))
421+
: ListView.builder(
422+
itemCount: timelineItems.length,
423+
itemBuilder: (context, index) {
424+
return timelineItems[index].widget;
425+
},
426+
),
389427
),
390428
],
391429
),
@@ -414,3 +452,10 @@ class CollectorGameState extends State<CollectorGame>
414452
super.dispose();
415453
}
416454
}
455+
456+
class _TimelineItem {
457+
final DateTime timestamp;
458+
final Widget widget;
459+
460+
const _TimelineItem({required this.timestamp, required this.widget});
461+
}

0 commit comments

Comments
 (0)