diff --git a/lib/pangea/activity_planner/activity_list_view.dart b/lib/pangea/activity_planner/activity_list_view.dart index 54ac97fe4..f851037ab 100644 --- a/lib/pangea/activity_planner/activity_list_view.dart +++ b/lib/pangea/activity_planner/activity_list_view.dart @@ -1,12 +1,9 @@ -import 'dart:developer'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:collection/collection.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:http/http.dart' as http; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; @@ -19,7 +16,6 @@ import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart'; import 'package:fluffychat/pangea/activity_planner/bookmarked_activities_repo.dart'; import 'package:fluffychat/pangea/activity_planner/list_request_schema.dart'; import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_constants.dart'; -import 'package:fluffychat/pangea/common/constants/model_keys.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/utils/file_selector.dart'; @@ -112,44 +108,13 @@ class ActivityListViewState extends State { future: () async { final activity = _activities![index]; - final eventId = await widget.room?.pangeaSendTextEvent( - activity.markdown, - messageTag: ModelKey.messageTagActivityPlan, - //include full model or should we move to a state event for this? + await widget.room?.sendActivityPlan( + activity, + avatar: _avatar, + avatarURL: _avatarURL, + filename: _filename, ); - if (eventId == null) { - debugger(when: kDebugMode); - return; - } - - Uint8List? bytes = _avatar; - if (_avatarURL != null && bytes == null) { - final resp = await http - .get(Uri.parse(_avatarURL!)) - .timeout(const Duration(seconds: 5)); - bytes = resp.bodyBytes; - } - - if (bytes != null && _filename != null) { - final file = MatrixFile( - bytes: bytes, - name: _filename!, - ); - - await widget.room?.sendFileEvent( - file, - shrinkImageMaxDimension: 1600, - extraContent: { - ModelKey.messageTags: ModelKey.messageTagActivityPlan, - }, - ); - } - - if (widget.room != null && widget.room!.canSendDefaultStates) { - await widget.room?.setPinnedEvents([eventId]); - } - Navigator.of(context).pop(); }, ); diff --git a/lib/pangea/activity_planner/activity_plan_model.dart b/lib/pangea/activity_planner/activity_plan_model.dart index 5b35ad758..4f7143040 100644 --- a/lib/pangea/activity_planner/activity_plan_model.dart +++ b/lib/pangea/activity_planner/activity_plan_model.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart'; +import 'package:fluffychat/pangea/common/constants/model_keys.dart'; class ActivityPlanModel { final ActivityPlanRequest req; @@ -23,27 +24,27 @@ class ActivityPlanModel { factory ActivityPlanModel.fromJson(Map json) { return ActivityPlanModel( - req: ActivityPlanRequest.fromJson(json['req']), - title: json['title'], - learningObjective: json['learning_objective'], - instructions: json['instructions'], + req: ActivityPlanRequest.fromJson(json[ModelKey.activityPlanRequest]), + title: json[ModelKey.activityPlanTitle], + learningObjective: json[ModelKey.activityPlanLearningObjective], + instructions: json[ModelKey.activityPlanInstructions], vocab: List.from( - json['vocab'].map((vocab) => Vocab.fromJson(vocab)), + json[ModelKey.activityPlanVocab].map((vocab) => Vocab.fromJson(vocab)), ), - bookmarkId: json['bookmark_id'], - imageURL: json['image_url'], + bookmarkId: json[ModelKey.activityPlanBookmarkId], + imageURL: json[ModelKey.activityPlanImageURL], ); } Map toJson() { return { - 'req': req.toJson(), - 'title': title, - 'learning_objective': learningObjective, - 'instructions': instructions, - 'vocab': vocab.map((vocab) => vocab.toJson()).toList(), - 'bookmark_id': bookmarkId, - 'image_url': imageURL, + ModelKey.activityPlanRequest: req.toJson(), + ModelKey.activityPlanTitle: title, + ModelKey.activityPlanLearningObjective: learningObjective, + ModelKey.activityPlanInstructions: instructions, + ModelKey.activityPlanVocab: vocab.map((vocab) => vocab.toJson()).toList(), + ModelKey.activityPlanBookmarkId: bookmarkId, + ModelKey.activityPlanImageURL: imageURL, }; } diff --git a/lib/pangea/activity_planner/activity_plan_request.dart b/lib/pangea/activity_planner/activity_plan_request.dart index d080a2e5f..76063d629 100644 --- a/lib/pangea/activity_planner/activity_plan_request.dart +++ b/lib/pangea/activity_planner/activity_plan_request.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/pangea/activity_planner/media_enum.dart'; +import 'package:fluffychat/pangea/common/constants/model_keys.dart'; import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart'; class ActivityPlanRequest { @@ -26,33 +27,35 @@ class ActivityPlanRequest { Map toJson() { return { - 'topic': topic, - 'mode': mode, - 'objective': objective, - 'media': media.string, - 'activity_cefr_level': cefrLevel.string, - 'language_of_instructions': languageOfInstructions, - 'target_language': targetLanguage, - 'count': count, - 'number_of_participants': numberOfParticipants, + ModelKey.activityRequestTopic: topic, + ModelKey.activityRequestMode: mode, + ModelKey.activityRequestObjective: objective, + ModelKey.activityRequestMedia: media.string, + ModelKey.activityRequestCefrLevel: cefrLevel.string, + ModelKey.activityRequestLanguageOfInstructions: languageOfInstructions, + ModelKey.activityRequestTargetLanguage: targetLanguage, + ModelKey.activityRequestCount: count, + ModelKey.activityRequestNumberOfParticipants: numberOfParticipants, }; } factory ActivityPlanRequest.fromJson(Map json) => ActivityPlanRequest( - topic: json['topic'], - mode: json['mode'], - objective: json['objective'], - media: MediaEnum.nan.fromString(json['media']), - cefrLevel: json['activity_cefr_level'] != null + topic: json[ModelKey.activityRequestTopic], + mode: json[ModelKey.activityRequestMode], + objective: json[ModelKey.activityRequestObjective], + media: MediaEnum.nan.fromString(json[ModelKey.activityRequestMedia]), + cefrLevel: json[ModelKey.activityRequestCefrLevel] != null ? LanguageLevelTypeEnumExtension.fromString( - json['activity_cefr_level'], + json[ModelKey.activityRequestCefrLevel], ) : LanguageLevelTypeEnum.a1, - languageOfInstructions: json['language_of_instructions'], - targetLanguage: json['target_language'], - count: json['count'], - numberOfParticipants: json['number_of_participants'], + languageOfInstructions: + json[ModelKey.activityRequestLanguageOfInstructions], + targetLanguage: json[ModelKey.activityRequestTargetLanguage], + count: json[ModelKey.activityRequestCount], + numberOfParticipants: + json[ModelKey.activityRequestNumberOfParticipants], ); String get storageKey => diff --git a/lib/pangea/activity_planner/activity_planner_page.dart b/lib/pangea/activity_planner/activity_planner_page.dart index 2abe06419..75e40d52a 100644 --- a/lib/pangea/activity_planner/activity_planner_page.dart +++ b/lib/pangea/activity_planner/activity_planner_page.dart @@ -8,6 +8,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/activity_planner/activity_list_view.dart'; import 'package:fluffychat/pangea/activity_planner/activity_mode_list_repo.dart'; +import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart'; import 'package:fluffychat/pangea/activity_planner/learning_objective_list_repo.dart'; import 'package:fluffychat/pangea/activity_planner/list_request_schema.dart'; @@ -16,6 +17,7 @@ import 'package:fluffychat/pangea/activity_planner/suggestion_form_field.dart'; import 'package:fluffychat/pangea/activity_planner/topic_list_repo.dart'; import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart'; import 'package:fluffychat/pangea/common/widgets/dropdown_text_button.dart'; +import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; @@ -54,6 +56,8 @@ class ActivityPlannerPageState extends State { Room? get room => Matrix.of(context).client.getRoomById(widget.roomID); + ActivityPlanModel? get _initialActivity => room?.activityPlan; + @override void initState() { super.initState(); @@ -62,12 +66,26 @@ class ActivityPlannerPageState extends State { return; } - _selectedLanguageOfInstructions = - MatrixState.pangeaController.languageController.userL1?.langCode; - _selectedTargetLanguage = - MatrixState.pangeaController.languageController.userL2?.langCode; - _selectedCefrLevel = LanguageLevelTypeEnum.a1; - _selectedNumberOfParticipants = max(room?.getParticipants().length ?? 1, 1); + if (_initialActivity == null) { + _selectedLanguageOfInstructions = + MatrixState.pangeaController.languageController.userL1?.langCode; + _selectedTargetLanguage = + MatrixState.pangeaController.languageController.userL2?.langCode; + _selectedCefrLevel = LanguageLevelTypeEnum.a1; + _selectedNumberOfParticipants = + max(room?.getParticipants().length ?? 1, 1); + } else { + _selectedMedia = _initialActivity!.req.media; + _selectedLanguageOfInstructions = + _initialActivity!.req.languageOfInstructions; + _selectedTargetLanguage = _initialActivity!.req.targetLanguage; + _selectedCefrLevel = _initialActivity!.req.cefrLevel; + _selectedNumberOfParticipants = + _initialActivity!.req.numberOfParticipants; + _topicController.text = _initialActivity!.req.topic; + _objectiveController.text = _initialActivity!.req.objective; + _modeController.text = _initialActivity!.req.mode; + } } final _topicController = TextEditingController(); diff --git a/lib/pangea/activity_suggestions/activity_suggestion_dialog.dart b/lib/pangea/activity_suggestions/activity_suggestion_dialog.dart index 410a9dd53..b727aecaa 100644 --- a/lib/pangea/activity_suggestions/activity_suggestion_dialog.dart +++ b/lib/pangea/activity_suggestions/activity_suggestion_dialog.dart @@ -1,4 +1,3 @@ -import 'dart:developer'; import 'dart:ui'; import 'package:flutter/foundation.dart'; @@ -17,7 +16,6 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card_row.dart'; import 'package:fluffychat/pangea/chat/constants/default_power_level.dart'; -import 'package:fluffychat/pangea/common/constants/model_keys.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/utils/file_selector.dart'; @@ -180,17 +178,7 @@ class ActivitySuggestionDialogState extends State { if (room == null) return; } - final eventId = await room.pangeaSendTextEvent( - widget.activity.markdown, - messageTag: ModelKey.messageTagActivityPlan, - ); - - if (eventId == null) { - debugger(when: kDebugMode); - return; - } - - await room.setPinnedEvents([eventId]); + await room.sendActivityPlan(widget.activity); context.go("/rooms/$roomId/invite"); }, ); diff --git a/lib/pangea/common/constants/model_keys.dart b/lib/pangea/common/constants/model_keys.dart index ecee7f939..e5c2f056b 100644 --- a/lib/pangea/common/constants/model_keys.dart +++ b/lib/pangea/common/constants/model_keys.dart @@ -156,4 +156,25 @@ class ModelKey { static const String analytics = "analytics"; static const String level = "level"; static const String xpOffset = "xp_offset"; + + // activity plan + static const String activityPlanRequest = "req"; + static const String activityPlanTitle = "title"; + static const String activityPlanLearningObjective = "learning_objective"; + static const String activityPlanInstructions = "instructions"; + static const String activityPlanVocab = "vocab"; + static const String activityPlanImageURL = "image_url"; + static const String activityPlanBookmarkId = "bookmark_id"; + + static const String activityRequestTopic = "topic"; + static const String activityRequestMode = "mode"; + static const String activityRequestObjective = "objective"; + static const String activityRequestMedia = "media"; + static const String activityRequestCefrLevel = "activity_cefr_level"; + static const String activityRequestLanguageOfInstructions = + "language_of_instructions"; + static const String activityRequestTargetLanguage = "target_language"; + static const String activityRequestCount = "count"; + static const String activityRequestNumberOfParticipants = + "number_of_participants"; } diff --git a/lib/pangea/events/constants/pangea_event_types.dart b/lib/pangea/events/constants/pangea_event_types.dart index a3e3c46a6..ba7097f57 100644 --- a/lib/pangea/events/constants/pangea_event_types.dart +++ b/lib/pangea/events/constants/pangea_event_types.dart @@ -24,6 +24,8 @@ class PangeaEventTypes { static const botOptions = "pangea.bot_options"; static const capacity = "pangea.capacity"; + static const activityPlan = "pangea.activity_plan"; + static const userAge = "pangea.user_age"; static const String report = 'm.report'; diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index 7632bf892..6e02e124d 100644 --- a/lib/pangea/extensions/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension.dart @@ -10,11 +10,13 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:html_unescape/html_unescape.dart'; +import 'package:http/http.dart' as http; import 'package:matrix/matrix.dart' as matrix; import 'package:matrix/matrix.dart'; import 'package:matrix/src/utils/markdown.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; diff --git a/lib/pangea/extensions/room_events_extension.dart b/lib/pangea/extensions/room_events_extension.dart index d7712c0d7..626b768f3 100644 --- a/lib/pangea/extensions/room_events_extension.dart +++ b/lib/pangea/extensions/room_events_extension.dart @@ -266,6 +266,56 @@ extension EventsRoomExtension on Room { ); } + Future sendActivityPlan( + ActivityPlanModel activity, { + Uint8List? avatar, + String? avatarURL, + String? filename, + }) async { + Uint8List? bytes = avatar; + if (avatarURL != null && bytes == null) { + final resp = await http + .get(Uri.parse(avatarURL)) + .timeout(const Duration(seconds: 5)); + bytes = resp.bodyBytes; + } + + MatrixFile? file; + if (filename != null && bytes != null) { + file = MatrixFile( + bytes: bytes, + name: filename, + ); + } + final eventId = await pangeaSendTextEvent( + activity.markdown, + messageTag: ModelKey.messageTagActivityPlan, + ); + + if (file != null) { + await sendFileEvent( + file, + shrinkImageMaxDimension: 1600, + extraContent: { + ModelKey.messageTags: ModelKey.messageTagActivityPlan, + }, + ); + } + + if (canSendDefaultStates) { + await client.setRoomStateWithKey( + id, + PangeaEventTypes.activityPlan, + "", + activity.toJson(), + ); + + if (eventId != null) { + await setPinnedEvents([eventId]); + } + } + } + /// Get a list of events in the room that are of type [PangeaEventTypes.construct] /// and have the sender as [userID]. If [count] is provided, the function will /// return at most [count] events. diff --git a/lib/pangea/extensions/room_settings_extension.dart b/lib/pangea/extensions/room_settings_extension.dart index 2d8912c35..e1986d7cc 100644 --- a/lib/pangea/extensions/room_settings_extension.dart +++ b/lib/pangea/extensions/room_settings_extension.dart @@ -70,4 +70,23 @@ extension RoomSettingsRoomExtension on Room { if (stateEvent == null) return null; return BotOptionsModel.fromJson(stateEvent.content); } + + ActivityPlanModel? get activityPlan { + final stateEvent = getState(PangeaEventTypes.activityPlan); + if (stateEvent == null) return null; + + try { + return ActivityPlanModel.fromJson(stateEvent.content); + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: { + "roomID": id, + "stateEvent": stateEvent.content, + }, + ); + return null; + } + } } diff --git a/lib/pangea/toolbar/widgets/overlay_header.dart b/lib/pangea/toolbar/widgets/overlay_header.dart index 549fa6c8c..27f701cd2 100644 --- a/lib/pangea/toolbar/widgets/overlay_header.dart +++ b/lib/pangea/toolbar/widgets/overlay_header.dart @@ -6,7 +6,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pangea/common/constants/model_keys.dart'; +import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; class OverlayHeader extends StatelessWidget { final ChatController controller; @@ -62,8 +62,7 @@ class OverlayHeader extends StatelessWidget { color: Theme.of(context).colorScheme.primary, ), if (controller.canEditSelectedEvents && - controller.selectedEvents.first.content[ModelKey.messageTags] != - ModelKey.messageTagActivityPlan) + !controller.selectedEvents.first.isActivityMessage) IconButton( icon: const Icon(Icons.edit_outlined), tooltip: L10n.of(context).edit, diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 822d7246a..34c91f2a0 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -126,6 +126,7 @@ abstract class ClientManager { EventTypes.RoomPowerLevels, PangeaEventTypes.userSetLemmaInfo, EventTypes.RoomJoinRules, + PangeaEventTypes.activityPlan, // Pangea# }, logLevel: kReleaseMode ? Level.warning : Level.verbose,