feat: send activity plan state event

This commit is contained in:
ggurdin 2025-03-24 15:28:02 -04:00 committed by GitHub
parent 379e4a8db9
commit e3e81fbd68
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 164 additions and 95 deletions

View file

@ -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<ActivityListView> {
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();
},
);

View file

@ -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<String, dynamic> 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<Vocab>.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<String, dynamic> 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,
};
}

View file

@ -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<String, dynamic> 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<String, dynamic> 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 =>

View file

@ -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<ActivityPlannerPage> {
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<ActivityPlannerPage> {
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();

View file

@ -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<ActivitySuggestionDialog> {
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");
},
);

View file

@ -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";
}

View file

@ -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';

View file

@ -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';

View file

@ -266,6 +266,56 @@ extension EventsRoomExtension on Room {
);
}
Future<void> 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.

View file

@ -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;
}
}
}

View file

@ -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,

View file

@ -126,6 +126,7 @@ abstract class ClientManager {
EventTypes.RoomPowerLevels,
PangeaEventTypes.userSetLemmaInfo,
EventTypes.RoomJoinRules,
PangeaEventTypes.activityPlan,
// Pangea#
},
logLevel: kReleaseMode ? Level.warning : Level.verbose,