commit
0789106e1b
13 changed files with 272 additions and 55050 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 =>
|
||||
|
|
@ -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,
|
||||
}) {
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -305,7 +305,6 @@ class MessageToolbarState extends State<MessageToolbar> {
|
|||
void showPracticeActivity() {
|
||||
toolbarContent = PracticeActivityCard(
|
||||
pangeaMessageEvent: widget.pangeaMessageEvent,
|
||||
controller: this,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
int? get selectedChoiceIndex => card.selectedChoiceIndex;
|
||||
class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
|
||||
int? selectedChoiceIndex;
|
||||
|
||||
bool get submitted => card.recordSubmittedThisSession;
|
||||
PracticeActivityRecordModel? get currentRecordModel =>
|
||||
widget.controller.currentRecordModel;
|
||||
|
||||
bool get isSubmitted =>
|
||||
widget.currentActivity?.userRecord?.record?.latestResponse != null;
|
||||
|
||||
@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),
|
||||
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/enum/message_mode_enum.dart';
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/bot_style.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_content.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
/// The wrapper for practice activity content.
|
||||
/// Handles the activities assosiated with a message,
|
||||
/// their navigation, and the management of completion records
|
||||
class PracticeActivityCard extends StatefulWidget {
|
||||
final PangeaMessageEvent pangeaMessageEvent;
|
||||
final MessageToolbarState controller;
|
||||
|
||||
const PracticeActivityCard({
|
||||
super.key,
|
||||
required this.pangeaMessageEvent,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -27,69 +27,129 @@ class PracticeActivityCard extends StatefulWidget {
|
|||
}
|
||||
|
||||
class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
|
||||
PracticeActivityEvent? practiceEvent;
|
||||
PracticeActivityEvent? currentActivity;
|
||||
PracticeActivityRecordModel? currentRecordModel;
|
||||
bool sending = false;
|
||||
|
||||
List<PracticeActivityEvent> get practiceActivities =>
|
||||
widget.pangeaMessageEvent.practiceActivities;
|
||||
|
||||
int get practiceEventIndex => practiceActivities.indexWhere(
|
||||
(activity) => activity.event.eventId == currentActivity?.event.eventId,
|
||||
);
|
||||
|
||||
bool get isPrevEnabled =>
|
||||
practiceEventIndex > 0 &&
|
||||
practiceActivities.length > (practiceEventIndex - 1);
|
||||
|
||||
bool get isNextEnabled =>
|
||||
practiceEventIndex >= 0 &&
|
||||
practiceEventIndex < practiceActivities.length - 1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
loadInitialData();
|
||||
setCurrentActivity();
|
||||
}
|
||||
|
||||
String? get langCode {
|
||||
final String? langCode = MatrixState.pangeaController.languageController
|
||||
.activeL2Model()
|
||||
?.langCode;
|
||||
|
||||
if (langCode == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(L10n.of(context)!.noLanguagesSet)),
|
||||
);
|
||||
debugger(when: kDebugMode);
|
||||
return null;
|
||||
}
|
||||
return langCode;
|
||||
}
|
||||
|
||||
void loadInitialData() {
|
||||
if (langCode == null) return;
|
||||
updatePracticeActivity();
|
||||
if (practiceEvent == null) {
|
||||
debugger(when: kDebugMode);
|
||||
}
|
||||
}
|
||||
|
||||
void updatePracticeActivity() {
|
||||
if (langCode == null) return;
|
||||
final List<PracticeActivityEvent> activities =
|
||||
widget.pangeaMessageEvent.practiceActivities(langCode!);
|
||||
if (activities.isEmpty) return;
|
||||
/// Initalizes the current activity.
|
||||
/// If the current activity hasn't been set yet, show the first
|
||||
/// uncompleted activity if there is one.
|
||||
/// If not, show the first activity
|
||||
void setCurrentActivity() {
|
||||
if (practiceActivities.isEmpty) return;
|
||||
final List<PracticeActivityEvent> incompleteActivities =
|
||||
activities.where((element) => !element.isComplete).toList();
|
||||
debugPrint("total events: ${activities.length}");
|
||||
debugPrint("incomplete practice events: ${incompleteActivities.length}");
|
||||
|
||||
// TODO update to show next activity
|
||||
practiceEvent = activities.first;
|
||||
// // if an incomplete activity is found, show that
|
||||
// if (incompleteActivities.isNotEmpty) {
|
||||
// practiceEvent = incompleteActivities.first;
|
||||
// }
|
||||
// // if no incomplete activity is found, show the last activity
|
||||
// else if (activities.isNotEmpty) {
|
||||
// practiceEvent = activities.last;
|
||||
// }
|
||||
practiceActivities.where((element) => !element.isComplete).toList();
|
||||
currentActivity ??= incompleteActivities.isNotEmpty
|
||||
? incompleteActivities.first
|
||||
: practiceActivities.first;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void showNextActivity() {
|
||||
if (langCode == null) return;
|
||||
updatePracticeActivity();
|
||||
widget.controller.updateMode(MessageMode.practiceActivity);
|
||||
void setCurrentModel(PracticeActivityRecordModel? recordModel) {
|
||||
currentRecordModel = recordModel;
|
||||
}
|
||||
|
||||
/// Sets the current acitivity based on the given [direction].
|
||||
void navigateActivities(Direction direction) {
|
||||
final bool enableNavigation = (direction == Direction.f && isNextEnabled) ||
|
||||
(direction == Direction.b && isPrevEnabled);
|
||||
if (enableNavigation) {
|
||||
currentActivity = practiceActivities[direction == Direction.f
|
||||
? practiceEventIndex + 1
|
||||
: practiceEventIndex - 1];
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends the current record model and activity to the server.
|
||||
/// If either the currentRecordModel or currentActivity is null, the method returns early.
|
||||
/// Sets the [sending] flag to true before sending the record and activity.
|
||||
/// Logs any errors that occur during the send operation.
|
||||
/// Sets the [sending] flag to false when the send operation is complete.
|
||||
void sendRecord() {
|
||||
if (currentRecordModel == null || currentActivity == null) return;
|
||||
setState(() => sending = true);
|
||||
MatrixState.pangeaController.activityRecordController
|
||||
.send(currentRecordModel!, currentActivity!)
|
||||
.catchError((error) {
|
||||
ErrorHandler.logError(
|
||||
e: error,
|
||||
s: StackTrace.current,
|
||||
data: {
|
||||
'recordModel': currentRecordModel?.toJson(),
|
||||
'practiceEvent': currentActivity?.event.toJson(),
|
||||
},
|
||||
);
|
||||
return null;
|
||||
}).whenComplete(() => setState(() => sending = false));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (practiceEvent == null) {
|
||||
final Widget navigationButtons = Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: isPrevEnabled ? 1.0 : 0,
|
||||
child: IconButton(
|
||||
onPressed:
|
||||
isPrevEnabled ? () => navigateActivities(Direction.b) : null,
|
||||
icon: const Icon(Icons.keyboard_arrow_left_outlined),
|
||||
tooltip: L10n.of(context)!.previous,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Opacity(
|
||||
opacity: currentActivity?.userRecord == null ? 1.0 : 0.5,
|
||||
child: sending
|
||||
? const CircularProgressIndicator.adaptive()
|
||||
: TextButton(
|
||||
onPressed:
|
||||
currentActivity?.userRecord == null ? sendRecord : null,
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
AppConfig.primaryColor,
|
||||
),
|
||||
),
|
||||
child: Text(L10n.of(context)!.submit),
|
||||
),
|
||||
),
|
||||
),
|
||||
Opacity(
|
||||
opacity: isNextEnabled ? 1.0 : 0,
|
||||
child: IconButton(
|
||||
onPressed:
|
||||
isNextEnabled ? () => navigateActivities(Direction.f) : null,
|
||||
icon: const Icon(Icons.keyboard_arrow_right_outlined),
|
||||
tooltip: L10n.of(context)!.next,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
if (currentActivity == null || practiceActivities.isEmpty) {
|
||||
return Text(
|
||||
L10n.of(context)!.noActivitiesFound,
|
||||
style: BotStyle.text(context),
|
||||
|
|
@ -99,10 +159,14 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
|
|||
// onActivityGenerated: updatePracticeActivity,
|
||||
// );
|
||||
}
|
||||
return PracticeActivityContent(
|
||||
practiceEvent: practiceEvent!,
|
||||
pangeaMessageEvent: widget.pangeaMessageEvent,
|
||||
controller: this,
|
||||
return Column(
|
||||
children: [
|
||||
PracticeActivity(
|
||||
practiceEvent: currentActivity!,
|
||||
controller: this,
|
||||
),
|
||||
navigationButtons,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,164 +1,43 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_acitivity_record_event.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/widgets/practice_activity/multiple_choice_activity.dart';
|
||||
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
class PracticeActivityContent extends StatefulWidget {
|
||||
/// Practice activity content
|
||||
class PracticeActivity extends StatefulWidget {
|
||||
final PracticeActivityEvent practiceEvent;
|
||||
final PangeaMessageEvent pangeaMessageEvent;
|
||||
final MessagePracticeActivityCardState controller;
|
||||
|
||||
const PracticeActivityContent({
|
||||
const PracticeActivity({
|
||||
super.key,
|
||||
required this.practiceEvent,
|
||||
required this.pangeaMessageEvent,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
MessagePracticeActivityContentState createState() =>
|
||||
MessagePracticeActivityContentState();
|
||||
PracticeActivityContentState createState() => PracticeActivityContentState();
|
||||
}
|
||||
|
||||
class MessagePracticeActivityContentState
|
||||
extends State<PracticeActivityContent> {
|
||||
int? selectedChoiceIndex;
|
||||
PracticeActivityRecordModel? recordModel;
|
||||
bool recordSubmittedThisSession = false;
|
||||
bool recordSubmittedPreviousSession = false;
|
||||
|
||||
PracticeActivityEvent get practiceEvent => widget.practiceEvent;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initalizeActivity();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant PracticeActivityContent oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.practiceEvent.event.eventId !=
|
||||
widget.practiceEvent.event.eventId) {
|
||||
initalizeActivity();
|
||||
}
|
||||
}
|
||||
|
||||
void initalizeActivity() {
|
||||
final PracticeActivityRecordEvent? recordEvent =
|
||||
widget.practiceEvent.userRecords.firstOrNull;
|
||||
if (recordEvent?.record == null) {
|
||||
recordModel = PracticeActivityRecordModel(
|
||||
question:
|
||||
widget.practiceEvent.practiceActivity.multipleChoice!.question,
|
||||
);
|
||||
} else {
|
||||
recordModel = recordEvent!.record;
|
||||
|
||||
//Note that only MultipleChoice activities will have this so we probably should move this logic to the MultipleChoiceActivity widget
|
||||
selectedChoiceIndex = recordModel?.latestResponse != null
|
||||
? widget.practiceEvent.practiceActivity.multipleChoice
|
||||
?.choiceIndex(recordModel!.latestResponse!)
|
||||
: null;
|
||||
|
||||
recordSubmittedPreviousSession = true;
|
||||
recordSubmittedThisSession = true;
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void updateChoice(int index) {
|
||||
setState(() {
|
||||
selectedChoiceIndex = index;
|
||||
recordModel!.addResponse(
|
||||
text: widget
|
||||
.practiceEvent.practiceActivity.multipleChoice!.choices[index],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class PracticeActivityContentState extends State<PracticeActivity> {
|
||||
Widget get activityWidget {
|
||||
switch (widget.practiceEvent.practiceActivity.activityType) {
|
||||
case ActivityTypeEnum.multipleChoice:
|
||||
return MultipleChoiceActivity(
|
||||
card: this,
|
||||
updateChoice: updateChoice,
|
||||
isActive:
|
||||
!recordSubmittedPreviousSession && !recordSubmittedThisSession,
|
||||
controller: widget.controller,
|
||||
currentActivity: widget.practiceEvent,
|
||||
);
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
void sendRecord() {
|
||||
MatrixState.pangeaController.activityRecordController
|
||||
.send(
|
||||
recordModel!,
|
||||
widget.practiceEvent,
|
||||
)
|
||||
.catchError((error) {
|
||||
ErrorHandler.logError(
|
||||
e: error,
|
||||
s: StackTrace.current,
|
||||
data: {
|
||||
'recordModel': recordModel?.toJson(),
|
||||
'practiceEvent': widget.practiceEvent.event.toJson(),
|
||||
},
|
||||
);
|
||||
return null;
|
||||
}).then((_) => widget.controller.showNextActivity());
|
||||
|
||||
setState(() {
|
||||
recordSubmittedThisSession = true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint(
|
||||
"MessagePracticeActivityContentState.build with selectedChoiceIndex: $selectedChoiceIndex",
|
||||
);
|
||||
return Column(
|
||||
children: [
|
||||
activityWidget,
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: selectedChoiceIndex != null &&
|
||||
!recordSubmittedThisSession &&
|
||||
!recordSubmittedPreviousSession
|
||||
? 1.0
|
||||
: 0.5,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
if (recordSubmittedThisSession ||
|
||||
recordSubmittedPreviousSession) {
|
||||
return;
|
||||
}
|
||||
selectedChoiceIndex != null ? sendRecord() : null;
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
AppConfig.primaryColor,
|
||||
),
|
||||
),
|
||||
child: Text(L10n.of(context)!.submit),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
54794
needed-translations.txt
54794
needed-translations.txt
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue