|
|
|
|
@ -1,29 +1,24 @@
|
|
|
|
|
import 'dart:developer';
|
|
|
|
|
|
|
|
|
|
import 'package:collection/collection.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_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/bot_style.dart';
|
|
|
|
|
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
|
|
|
|
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
|
|
|
|
|
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity.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
|
|
|
|
|
@ -32,13 +27,15 @@ class PracticeActivityCard extends StatefulWidget {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
|
|
|
|
|
List<PracticeActivityEvent> practiceActivities = [];
|
|
|
|
|
PracticeActivityEvent? practiceEvent;
|
|
|
|
|
PracticeActivityRecordModel? recordModel;
|
|
|
|
|
PracticeActivityEvent? currentActivity;
|
|
|
|
|
PracticeActivityRecordModel? currentRecordModel;
|
|
|
|
|
bool sending = false;
|
|
|
|
|
|
|
|
|
|
List<PracticeActivityEvent> get practiceActivities =>
|
|
|
|
|
widget.pangeaMessageEvent.practiceActivities;
|
|
|
|
|
|
|
|
|
|
int get practiceEventIndex => practiceActivities.indexWhere(
|
|
|
|
|
(activity) => activity.event.eventId == practiceEvent?.event.eventId,
|
|
|
|
|
(activity) => activity.event.eventId == currentActivity?.event.eventId,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
bool get isPrevEnabled =>
|
|
|
|
|
@ -49,80 +46,59 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
|
|
|
|
|
practiceEventIndex >= 0 &&
|
|
|
|
|
practiceEventIndex < practiceActivities.length - 1;
|
|
|
|
|
|
|
|
|
|
// the first record for this practice activity
|
|
|
|
|
// assosiated with the current user
|
|
|
|
|
PracticeActivityRecordEvent? get recordEvent =>
|
|
|
|
|
practiceEvent?.userRecords.firstOrNull;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
setPracticeActivities();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
setCurrentActivity();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Initalizes the practice activities for the current language
|
|
|
|
|
/// and sets the first activity as the current activity
|
|
|
|
|
void setPracticeActivities() {
|
|
|
|
|
if (langCode == null) return;
|
|
|
|
|
practiceActivities =
|
|
|
|
|
widget.pangeaMessageEvent.practiceActivities(langCode!);
|
|
|
|
|
/// 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;
|
|
|
|
|
|
|
|
|
|
practiceActivities.sort(
|
|
|
|
|
(a, b) => a.event.originServerTs.compareTo(b.event.originServerTs),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// if the current activity hasn't been set yet, show the first uncompleted activity
|
|
|
|
|
// if there is one. If not, show the first activity
|
|
|
|
|
final List<PracticeActivityEvent> incompleteActivities =
|
|
|
|
|
practiceActivities.where((element) => !element.isComplete).toList();
|
|
|
|
|
practiceEvent ??= incompleteActivities.isNotEmpty
|
|
|
|
|
currentActivity ??= incompleteActivities.isNotEmpty
|
|
|
|
|
? incompleteActivities.first
|
|
|
|
|
: practiceActivities.first;
|
|
|
|
|
setState(() {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void navigateActivities({Direction? direction, int? index}) {
|
|
|
|
|
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) ||
|
|
|
|
|
(index != null && index >= 0 && index < practiceActivities.length);
|
|
|
|
|
(direction == Direction.b && isPrevEnabled);
|
|
|
|
|
if (enableNavigation) {
|
|
|
|
|
final int newIndex = index ??
|
|
|
|
|
(direction == Direction.f
|
|
|
|
|
? practiceEventIndex + 1
|
|
|
|
|
: practiceEventIndex - 1);
|
|
|
|
|
practiceEvent = practiceActivities[newIndex];
|
|
|
|
|
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 (recordModel == null || practiceEvent == null) return;
|
|
|
|
|
if (currentRecordModel == null || currentActivity == null) return;
|
|
|
|
|
setState(() => sending = true);
|
|
|
|
|
MatrixState.pangeaController.activityRecordController
|
|
|
|
|
.send(recordModel!, practiceEvent!)
|
|
|
|
|
.send(currentRecordModel!, currentActivity!)
|
|
|
|
|
.catchError((error) {
|
|
|
|
|
ErrorHandler.logError(
|
|
|
|
|
e: error,
|
|
|
|
|
s: StackTrace.current,
|
|
|
|
|
data: {
|
|
|
|
|
'recordModel': recordModel?.toJson(),
|
|
|
|
|
'practiceEvent': practiceEvent?.event.toJson(),
|
|
|
|
|
'recordModel': currentRecordModel?.toJson(),
|
|
|
|
|
'practiceEvent': currentActivity?.event.toJson(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
return null;
|
|
|
|
|
@ -138,20 +114,20 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
|
|
|
|
|
Opacity(
|
|
|
|
|
opacity: isPrevEnabled ? 1.0 : 0,
|
|
|
|
|
child: IconButton(
|
|
|
|
|
onPressed: isPrevEnabled
|
|
|
|
|
? () => navigateActivities(direction: Direction.b)
|
|
|
|
|
: null,
|
|
|
|
|
onPressed:
|
|
|
|
|
isPrevEnabled ? () => navigateActivities(Direction.b) : null,
|
|
|
|
|
icon: const Icon(Icons.keyboard_arrow_left_outlined),
|
|
|
|
|
tooltip: L10n.of(context)!.previous,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Opacity(
|
|
|
|
|
opacity: recordEvent == null ? 1.0 : 0.5,
|
|
|
|
|
opacity: currentActivity?.userRecord == null ? 1.0 : 0.5,
|
|
|
|
|
child: sending
|
|
|
|
|
? const CircularProgressIndicator.adaptive()
|
|
|
|
|
: TextButton(
|
|
|
|
|
onPressed: recordEvent == null ? sendRecord : null,
|
|
|
|
|
onPressed:
|
|
|
|
|
currentActivity?.userRecord == null ? sendRecord : null,
|
|
|
|
|
style: ButtonStyle(
|
|
|
|
|
backgroundColor: WidgetStateProperty.all<Color>(
|
|
|
|
|
AppConfig.primaryColor,
|
|
|
|
|
@ -164,9 +140,8 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
|
|
|
|
|
Opacity(
|
|
|
|
|
opacity: isNextEnabled ? 1.0 : 0,
|
|
|
|
|
child: IconButton(
|
|
|
|
|
onPressed: isNextEnabled
|
|
|
|
|
? () => navigateActivities(direction: Direction.f)
|
|
|
|
|
: null,
|
|
|
|
|
onPressed:
|
|
|
|
|
isNextEnabled ? () => navigateActivities(Direction.f) : null,
|
|
|
|
|
icon: const Icon(Icons.keyboard_arrow_right_outlined),
|
|
|
|
|
tooltip: L10n.of(context)!.next,
|
|
|
|
|
),
|
|
|
|
|
@ -174,7 +149,7 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (practiceEvent == null || practiceActivities.isEmpty) {
|
|
|
|
|
if (currentActivity == null || practiceActivities.isEmpty) {
|
|
|
|
|
return Text(
|
|
|
|
|
L10n.of(context)!.noActivitiesFound,
|
|
|
|
|
style: BotStyle.text(context),
|
|
|
|
|
@ -187,8 +162,7 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
|
|
|
|
|
return Column(
|
|
|
|
|
children: [
|
|
|
|
|
PracticeActivity(
|
|
|
|
|
pangeaMessageEvent: widget.pangeaMessageEvent,
|
|
|
|
|
practiceEvent: practiceEvent!,
|
|
|
|
|
practiceEvent: currentActivity!,
|
|
|
|
|
controller: this,
|
|
|
|
|
),
|
|
|
|
|
navigationButtons,
|
|
|
|
|
|