Skip to content

Commit

Permalink
Merge pull request #407 from pangeachat/practice
Browse files Browse the repository at this point in the history
Practice
  • Loading branch information
ggurdin authored Jun 28, 2024
2 parents 3d1e354 + 466136e commit 0789106
Show file tree
Hide file tree
Showing 13 changed files with 270 additions and 55,048 deletions.
1 change: 1 addition & 0 deletions assets/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -4065,6 +4065,7 @@
"practice": "Practice",
"noLanguagesSet": "No languages set",
"noActivitiesFound": "No practice activities found for this message",
"previous": "Previous",
"languageButtonLabel": "Language: {currentLanguage}",
"@languageButtonLabel": {
"type": "text",
Expand Down
10 changes: 8 additions & 2 deletions lib/pangea/constants/pangea_event_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ class PangeaEventTypes {
static const String report = 'm.report';
static const textToSpeechRule = "p.rule.text_to_speech";

static const pangeaActivityRes = "pangea.activity_res";
static const acitivtyRequest = "pangea.activity_req";
/// A request to the server to generate activities
static const activityRequest = "pangea.activity_req";

/// A practice activity that is related to a message
static const pangeaActivity = "pangea.activity_res";

/// A record of completion of an activity. There
/// can be one per user per activity.
static const activityRecord = "pangea.activity_completion";
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class PracticeGenerationController {
final Event? activityEvent = await pangeaMessageEvent.room.sendPangeaEvent(
content: model.toJson(),
parentEventId: pangeaMessageEvent.eventId,
type: PangeaEventTypes.pangeaActivityRes,
type: PangeaEventTypes.pangeaActivity,
);

if (activityEvent == null) {
Expand Down
2 changes: 1 addition & 1 deletion lib/pangea/extensions/pangea_event_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension PangeaEvent on Event {
return PangeaRepresentation.fromJson(json) as V;
case PangeaEventTypes.choreoRecord:
return ChoreoRecord.fromJson(json) as V;
case PangeaEventTypes.pangeaActivityRes:
case PangeaEventTypes.pangeaActivity:
return PracticeActivityModel.fromJson(json) as V;
case PangeaEventTypes.activityRecord:
return PracticeActivityRecordModel.fromJson(json) as V;
Expand Down
62 changes: 31 additions & 31 deletions lib/pangea/matrix_event_wrappers/pangea_message_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -566,18 +566,8 @@ class PangeaMessageEvent {
/// If any activity is not complete, it returns true, indicating that the activity icon should be shown.
/// Otherwise, it returns false.
bool get hasUncompletedActivity {
if (l2Code == null) return false;
final List<PracticeActivityEvent> activities = practiceActivities(l2Code!);
if (activities.isEmpty) return false;

// for now, only show the button if the event has no completed activities
// TODO - revert this after adding logic to show next activity
for (final activity in activities) {
if (activity.isComplete) return false;
}
return true;
// if (activities.isEmpty) return false;
// return !activities.every((activity) => activity.isComplete);
if (practiceActivities.isEmpty) return false;
return practiceActivities.any((activity) => !(activity.isComplete));
}

String? get l2Code =>
Expand Down Expand Up @@ -611,34 +601,36 @@ class PangeaMessageEvent {
return steps;
}

List<PracticeActivityEvent> get _practiceActivityEvents => _latestEdit
.aggregatedEvents(
timeline,
PangeaEventTypes.pangeaActivityRes,
)
.map(
(e) => PracticeActivityEvent(
timeline: timeline,
event: e,
),
)
.toList();
/// Returns a list of all [PracticeActivityEvent] objects
/// associated with this message event.
List<PracticeActivityEvent> get _practiceActivityEvents {
return _latestEdit
.aggregatedEvents(
timeline,
PangeaEventTypes.pangeaActivity,
)
.map(
(e) => PracticeActivityEvent(
timeline: timeline,
event: e,
),
)
.toList();
}

/// Returns a boolean value indicating whether there are any
/// activities associated with this message event for the user's active l2
bool get hasActivities {
try {
final String? l2code =
MatrixState.pangeaController.languageController.activeL2Code();

if (l2code == null) return false;

return practiceActivities(l2code).isNotEmpty;
return practiceActivities.isNotEmpty;
} catch (e, s) {
ErrorHandler.logError(e: e, s: s);
return false;
}
}

List<PracticeActivityEvent> practiceActivities(
/// Returns a list of [PracticeActivityEvent] objects for the given [langCode].
List<PracticeActivityEvent> practiceActivitiesByLangCode(
String langCode, {
bool debug = false,
}) {
Expand All @@ -658,6 +650,14 @@ class PangeaMessageEvent {
}
}

/// Returns a list of [PracticeActivityEvent] for the user's active l2.
List<PracticeActivityEvent> get practiceActivities {
final String? l2code =
MatrixState.pangeaController.languageController.activeL2Code();
if (l2code == null) return [];
return practiceActivitiesByLangCode(l2code);
}

// List<SpanData> get activities =>
//each match is turned into an activity that other students can access
//they're not told the answer but have to find it themselves
Expand Down
30 changes: 20 additions & 10 deletions lib/pangea/matrix_event_wrappers/practice_activity_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class PracticeActivityEvent {
_content = content;
}
}
if (event.type != PangeaEventTypes.pangeaActivityRes) {
if (event.type != PangeaEventTypes.pangeaActivity) {
throw Exception(
"${event.type} should not be used to make a PracticeActivityEvent",
);
Expand All @@ -39,7 +39,7 @@ class PracticeActivityEvent {
return _content!;
}

//in aggregatedEvents for the event, find all practiceActivityRecordEvents whose sender matches the client's userId
/// All completion records assosiated with this activity
List<PracticeActivityRecordEvent> get allRecords {
if (timeline == null) {
debugger(when: kDebugMode);
Expand All @@ -54,14 +54,24 @@ class PracticeActivityEvent {
.toList();
}

List<PracticeActivityRecordEvent> get userRecords => allRecords
.where(
(recordEvent) =>
recordEvent.event.senderId == recordEvent.event.room.client.userID,
)
.toList();
/// Completion record assosiated with this activity
/// for the logged in user, null if there is none
PracticeActivityRecordEvent? get userRecord {
final List<PracticeActivityRecordEvent> records = allRecords
.where(
(recordEvent) =>
recordEvent.event.senderId ==
recordEvent.event.room.client.userID,
)
.toList();
if (records.length > 1) {
debugPrint("There should only be one record per user per activity");
debugger(when: kDebugMode);
}
return records.firstOrNull;
}

/// Checks if there are any user records in the list for this activity,
/// Checks if there is a user record for this activity,
/// and, if so, then the activity is complete
bool get isComplete => userRecords.isNotEmpty;
bool get isComplete => userRecord != null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class MultipleChoice {
return MultipleChoice(
question: json['question'] as String,
choices: (json['choices'] as List).map((e) => e as String).toList(),
answer: json['answer'] as String,
answer: json['answer'] ?? json['correct_answer'] as String,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,11 @@ class PracticeActivityModel {
.toList(),
langCode: json['lang_code'] as String,
msgId: json['msg_id'] as String,
activityType: ActivityTypeEnum.values.firstWhere(
(e) => e.string == json['activity_type'],
),
activityType: json['activity_type'] == "multipleChoice"
? ActivityTypeEnum.multipleChoice
: ActivityTypeEnum.values.firstWhere(
(e) => e.string == json['activity_type'],
),
multipleChoice: json['multiple_choice'] != null
? MultipleChoice.fromJson(
json['multiple_choice'] as Map<String, dynamic>,
Expand Down
1 change: 0 additions & 1 deletion lib/pangea/widgets/chat/message_toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,6 @@ class MessageToolbarState extends State<MessageToolbar> {
void showPracticeActivity() {
toolbarContent = PracticeActivityCard(
pangeaMessageEvent: widget.pangeaMessageEvent,
controller: this,
);
}

Expand Down
91 changes: 73 additions & 18 deletions lib/pangea/widgets/practice_activity/multiple_choice_activity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,89 @@ import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/choreographer/widgets/choice_array.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart';
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_content.dart';
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart';
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart';
import 'package:flutter/material.dart';

class MultipleChoiceActivity extends StatelessWidget {
final MessagePracticeActivityContentState card;
final Function(int) updateChoice;
final bool isActive;
/// The multiple choice activity view
class MultipleChoiceActivity extends StatefulWidget {
final MessagePracticeActivityCardState controller;
final PracticeActivityEvent? currentActivity;

const MultipleChoiceActivity({
super.key,
required this.card,
required this.updateChoice,
required this.isActive,
required this.controller,
required this.currentActivity,
});

PracticeActivityEvent get practiceEvent => card.practiceEvent;
@override
MultipleChoiceActivityState createState() => MultipleChoiceActivityState();
}

class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
int? selectedChoiceIndex;

PracticeActivityRecordModel? get currentRecordModel =>
widget.controller.currentRecordModel;

int? get selectedChoiceIndex => card.selectedChoiceIndex;
bool get isSubmitted =>
widget.currentActivity?.userRecord?.record?.latestResponse != null;

bool get submitted => card.recordSubmittedThisSession;
@override
void initState() {
super.initState();
setCompletionRecord();
}

@override
void didUpdateWidget(covariant MultipleChoiceActivity oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.currentActivity?.event.eventId !=
widget.currentActivity?.event.eventId) {
setCompletionRecord();
}
}

/// Sets the completion record for the multiple choice activity.
/// If the user record is null, it creates a new record model with the question
/// from the current activity and sets the selected choice index to null.
/// Otherwise, it sets the current model to the user record's record and
/// determines the selected choice index.
void setCompletionRecord() {
if (widget.currentActivity?.userRecord?.record == null) {
widget.controller.setCurrentModel(
PracticeActivityRecordModel(
question:
widget.currentActivity?.practiceActivity.multipleChoice!.question,
),
);
selectedChoiceIndex = null;
} else {
widget.controller
.setCurrentModel(widget.currentActivity!.userRecord!.record);
selectedChoiceIndex = widget
.currentActivity?.practiceActivity.multipleChoice!
.choiceIndex(currentRecordModel!.latestResponse!);
}
setState(() {});
}

void updateChoice(int index) {
currentRecordModel?.addResponse(
text: widget.controller.currentActivity?.practiceActivity.multipleChoice!
.choices[index],
);
setState(() => selectedChoiceIndex = index);
}

@override
Widget build(BuildContext context) {
final PracticeActivityModel practiceActivity =
practiceEvent.practiceActivity;
final PracticeActivityModel? practiceActivity =
widget.currentActivity?.practiceActivity;

if (practiceActivity == null) {
return const SizedBox();
}

return Container(
padding: const EdgeInsets.all(8),
Expand All @@ -50,17 +108,14 @@ class MultipleChoiceActivity extends StatelessWidget {
.mapIndexed(
(index, value) => Choice(
text: value,
color: (selectedChoiceIndex == index ||
practiceActivity.multipleChoice!
.isCorrect(index)) &&
submitted
color: selectedChoiceIndex == index
? practiceActivity.multipleChoice!.choiceColor(index)
: null,
isGold: practiceActivity.multipleChoice!.isCorrect(index),
),
)
.toList(),
isActive: isActive,
isActive: !isSubmitted,
),
],
),
Expand Down
Loading

0 comments on commit 0789106

Please sign in to comment.