3890 activity start page changes (#3901)

* comment out unreferenced files

* decouple courses, topics, and activities

* update start page

* disable 'join open session' button
This commit is contained in:
ggurdin 2025-09-08 15:25:56 -04:00 committed by GitHub
parent e4626dc2c0
commit 5ce2a787b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 5249 additions and 4847 deletions

View file

@ -28,8 +28,7 @@ import 'package:fluffychat/pages/settings_notifications/settings_notifications.d
import 'package:fluffychat/pages/settings_password/settings_password.dart';
import 'package:fluffychat/pages/settings_security/settings_security.dart';
import 'package:fluffychat/pages/settings_style/settings_style.dart';
import 'package:fluffychat/pangea/activity_generator/activity_generator.dart';
import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart';
import 'package:fluffychat/pangea/analytics_page/analytics_page.dart';
import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart';
import 'package:fluffychat/pangea/chat_settings/pages/pangea_invitation_selection.dart';
@ -597,6 +596,19 @@ abstract class AppRoutes {
routes: roomDetailsRoutes('spaceid'),
),
...roomDetailsRoutes('spaceid'),
GoRoute(
path: 'activity/:activityid',
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
ActivitySessionStartPage(
activityId: state.pathParameters['activityid']!,
isNew: state.uri.queryParameters['new'] == 'true',
parentId: state.pathParameters['spaceid']!,
),
),
redirect: loggedOutRedirect,
),
GoRoute(
path: ':roomid',
pageBuilder: (context, state) {
@ -911,30 +923,6 @@ abstract class AppRoutes {
),
),
),
GoRoute(
path: 'planner',
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
ActivityPlannerPage(
roomID: state.pathParameters[roomKey]!,
),
),
redirect: loggedOutRedirect,
routes: [
GoRoute(
path: '/generator',
redirect: loggedOutRedirect,
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
ActivityGenerator(
roomID: state.pathParameters[roomKey]!,
),
),
),
],
),
GoRoute(
path: 'access',
pageBuilder: (context, state) => defaultPageBuilder(

View file

@ -5196,5 +5196,9 @@
"@goToCourse": {
"type": "String",
"course": {}
}
},
"startNewSession": "Start new session",
"joinOpenSession": "Join open session",
"less": "less",
"activityNotFound": "Activity not found"
}

View file

@ -2242,8 +2242,14 @@ class ChatController extends State<ChatPageWithRoom>
);
}
if (room.isActivitySession == true && !room.activityHasStarted) {
return ActivitySessionStartPage(room: room);
if (room.isActivitySession == true &&
!room.activityHasStarted &&
room.courseParent != null) {
return ActivitySessionStartPage(
activityId: room.activityId!,
room: room,
parentId: room.courseParent!.id,
);
}
// Pangea#
final theme = Theme.of(context);

View file

@ -8,9 +8,7 @@ import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pages/chat/events/message.dart';
import 'package:fluffychat/pages/chat/seen_by_row.dart';
import 'package:fluffychat/pages/chat/typing_indicators.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_message.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_user_summaries_widget.dart';
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
import 'package:fluffychat/utils/account_config.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
@ -144,67 +142,48 @@ class ChatEventList extends StatelessWidget {
key: ValueKey(event.eventId),
index: i,
controller: controller.scrollController,
child:
// #Pangea
event.isActivityMessage
? ActivityPlanMessage(
event,
controller: controller,
timeline: timeline,
animateIn: animateIn,
resetAnimateIn: () {
controller.animateInEventIndex = null;
},
highlightMarker:
controller.scrollToEventIdMarker == event.eventId,
selected: controller.selectedEvents
.any((e) => e.eventId == event.eventId),
)
:
// Pangea#
Message(
event,
animateIn: animateIn,
resetAnimateIn: () {
controller.animateInEventIndex = null;
},
onSwipe: () => controller.replyAction(replyTo: event),
// #Pangea
onInfoTab: (_) => {},
// onInfoTab: controller.showEventInfo,
// Pangea#
onMention: () => controller.sendController.text +=
'${event.senderFromMemoryOrFallback.mention} ',
highlightMarker:
controller.scrollToEventIdMarker == event.eventId,
// #Pangea
// onSelect: controller.onSelectMessage,
onSelect: (_) {},
// Pangea#
scrollToEventId: (String eventId) =>
controller.scrollToEventId(eventId),
longPressSelect: controller.selectedEvents.isNotEmpty,
// #Pangea
immersionMode: controller.choreographer.immersionMode,
controller: controller,
isButton: event.eventId == controller.buttonEventID,
// Pangea#
selected: controller.selectedEvents
.any((e) => e.eventId == event.eventId),
singleSelected:
controller.selectedEvents.singleOrNull?.eventId ==
event.eventId,
onEdit: () => controller.editSelectedEventAction(),
timeline: timeline,
displayReadMarker: i > 0 &&
controller.readMarkerEventId == event.eventId,
nextEvent:
i + 1 < events.length ? events[i + 1] : null,
previousEvent: i > 0 ? events[i - 1] : null,
wallpaperMode: hasWallpaper,
scrollController: controller.scrollController,
colors: colors,
),
child: Message(
event,
animateIn: animateIn,
resetAnimateIn: () {
controller.animateInEventIndex = null;
},
onSwipe: () => controller.replyAction(replyTo: event),
// #Pangea
onInfoTab: (_) => {},
// onInfoTab: controller.showEventInfo,
// Pangea#
onMention: () => controller.sendController.text +=
'${event.senderFromMemoryOrFallback.mention} ',
highlightMarker:
controller.scrollToEventIdMarker == event.eventId,
// #Pangea
// onSelect: controller.onSelectMessage,
onSelect: (_) {},
// Pangea#
scrollToEventId: (String eventId) =>
controller.scrollToEventId(eventId),
longPressSelect: controller.selectedEvents.isNotEmpty,
// #Pangea
immersionMode: controller.choreographer.immersionMode,
controller: controller,
isButton: event.eventId == controller.buttonEventID,
// Pangea#
selected: controller.selectedEvents
.any((e) => e.eventId == event.eventId),
singleSelected:
controller.selectedEvents.singleOrNull?.eventId ==
event.eventId,
onEdit: () => controller.editSelectedEventAction(),
timeline: timeline,
displayReadMarker:
i > 0 && controller.readMarkerEventId == event.eventId,
nextEvent: i + 1 < events.length ? events[i + 1] : null,
previousEvent: i > 0 ? events[i - 1] : null,
wallpaperMode: hasWallpaper,
scrollController: controller.scrollController,
colors: colors,
),
);
},
// #Pangea

View file

@ -137,8 +137,10 @@ class Message extends StatelessWidget {
}
// #Pangea
if (event.type == PangeaEventTypes.activityPlan) {
if (event.type == PangeaEventTypes.activityPlan &&
event.room.activityPlan != null) {
return ActivitySummary(
activity: event.room.activityPlan!,
room: event.room,
showInstructions: controller.showInstructions,
toggleInstructions: controller.toggleShowInstructions,

View file

@ -1,267 +1,267 @@
import 'package:flutter/material.dart';
// import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:matrix/matrix.dart';
// import 'package:collection/collection.dart';
// import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/activity_generator/activity_generator_view.dart';
import 'package:fluffychat/pangea/activity_generator/activity_mode_list_repo.dart';
import 'package:fluffychat/pangea/activity_generator/activity_plan_generation_repo.dart';
import 'package:fluffychat/pangea/activity_generator/learning_objective_list_repo.dart';
import 'package:fluffychat/pangea/activity_generator/list_request_schema.dart';
import 'package:fluffychat/pangea/activity_generator/media_enum.dart';
import 'package:fluffychat/pangea/activity_generator/topic_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_suggestions/activity_suggestions_constants.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
import 'package:fluffychat/widgets/matrix.dart';
// import 'package:fluffychat/config/app_config.dart';
// import 'package:fluffychat/l10n/l10n.dart';
// import 'package:fluffychat/pangea/activity_generator/activity_generator_view.dart';
// import 'package:fluffychat/pangea/activity_generator/activity_mode_list_repo.dart';
// import 'package:fluffychat/pangea/activity_generator/activity_plan_generation_repo.dart';
// import 'package:fluffychat/pangea/activity_generator/learning_objective_list_repo.dart';
// import 'package:fluffychat/pangea/activity_generator/list_request_schema.dart';
// import 'package:fluffychat/pangea/activity_generator/media_enum.dart';
// import 'package:fluffychat/pangea/activity_generator/topic_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_suggestions/activity_suggestions_constants.dart';
// import 'package:fluffychat/pangea/common/utils/error_handler.dart';
// import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
// import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
// import 'package:fluffychat/widgets/matrix.dart';
class ActivityGenerator extends StatefulWidget {
final String roomID;
const ActivityGenerator({
required this.roomID,
super.key,
});
// class ActivityGenerator extends StatefulWidget {
// final String roomID;
// const ActivityGenerator({
// required this.roomID,
// super.key,
// });
@override
ActivityGeneratorState createState() => ActivityGeneratorState();
}
// @override
// ActivityGeneratorState createState() => ActivityGeneratorState();
// }
class ActivityGeneratorState extends State<ActivityGenerator> {
bool loading = false;
Object? error;
List<ActivityPlanModel>? activities;
// class ActivityGeneratorState extends State<ActivityGenerator> {
// bool loading = false;
// Object? error;
// List<ActivityPlanModel>? activities;
final formKey = GlobalKey<FormState>();
// final formKey = GlobalKey<FormState>();
final topicController = TextEditingController();
final objectiveController = TextEditingController();
final modeController = TextEditingController();
// final topicController = TextEditingController();
// final objectiveController = TextEditingController();
// final modeController = TextEditingController();
MediaEnum selectedMedia = MediaEnum.nan;
String? selectedLanguageOfInstructions;
String? selectedTargetLanguage;
LanguageLevelTypeEnum? selectedCefrLevel;
int? selectedNumberOfParticipants;
// MediaEnum selectedMedia = MediaEnum.nan;
// String? selectedLanguageOfInstructions;
// String? selectedTargetLanguage;
// LanguageLevelTypeEnum? selectedCefrLevel;
// int? selectedNumberOfParticipants;
String? filename;
// String? filename;
List<ActivitySettingResponseSchema>? topicItems;
List<ActivitySettingResponseSchema>? modeItems;
List<ActivitySettingResponseSchema>? objectiveItems;
// List<ActivitySettingResponseSchema>? topicItems;
// List<ActivitySettingResponseSchema>? modeItems;
// List<ActivitySettingResponseSchema>? objectiveItems;
@override
void initState() {
super.initState();
// @override
// void initState() {
// super.initState();
selectedLanguageOfInstructions =
MatrixState.pangeaController.languageController.userL1?.langCode;
selectedTargetLanguage =
MatrixState.pangeaController.languageController.userL2?.langCode;
selectedCefrLevel = LanguageLevelTypeEnum.a1;
selectedNumberOfParticipants = 3;
_setMode();
_setTopic();
_setObjective();
}
// selectedLanguageOfInstructions =
// MatrixState.pangeaController.languageController.userL1?.langCode;
// selectedTargetLanguage =
// MatrixState.pangeaController.languageController.userL2?.langCode;
// selectedCefrLevel = LanguageLevelTypeEnum.a1;
// selectedNumberOfParticipants = 3;
// _setMode();
// _setTopic();
// _setObjective();
// }
@override
void dispose() {
topicController.dispose();
objectiveController.dispose();
modeController.dispose();
super.dispose();
}
// @override
// void dispose() {
// topicController.dispose();
// objectiveController.dispose();
// modeController.dispose();
// super.dispose();
// }
ActivitySettingRequestSchema get req => ActivitySettingRequestSchema(
langCode:
MatrixState.pangeaController.languageController.userL1?.langCode ??
LanguageKeys.defaultLanguage,
);
// ActivitySettingRequestSchema get req => ActivitySettingRequestSchema(
// langCode:
// MatrixState.pangeaController.languageController.userL1?.langCode ??
// LanguageKeys.defaultLanguage,
// );
ActivityPlanRequest get planRequest => ActivityPlanRequest(
topic: topicController.text,
mode: modeController.text,
objective: objectiveController.text,
media: selectedMedia,
languageOfInstructions: selectedLanguageOfInstructions!,
targetLanguage: selectedTargetLanguage!,
cefrLevel: selectedCefrLevel!,
numberOfParticipants: selectedNumberOfParticipants!,
);
// ActivityPlanRequest get planRequest => ActivityPlanRequest(
// topic: topicController.text,
// mode: modeController.text,
// objective: objectiveController.text,
// media: selectedMedia,
// languageOfInstructions: selectedLanguageOfInstructions!,
// targetLanguage: selectedTargetLanguage!,
// cefrLevel: selectedCefrLevel!,
// numberOfParticipants: selectedNumberOfParticipants!,
// );
Room? get room => Matrix.of(context).client.getRoomById(widget.roomID);
// Room? get room => Matrix.of(context).client.getRoomById(widget.roomID);
String? validateNotNull(String? value) {
if (value == null || value.isEmpty) {
return L10n.of(context).interactiveTranslatorRequired;
}
return null;
}
// String? validateNotNull(String? value) {
// if (value == null || value.isEmpty) {
// return L10n.of(context).interactiveTranslatorRequired;
// }
// return null;
// }
String? get _randomTopic => (topicItems?..shuffle())?.first.name;
// String? get _randomTopic => (topicItems?..shuffle())?.first.name;
String? get _randomObjective => (objectiveItems?..shuffle())?.first.name;
// String? get _randomObjective => (objectiveItems?..shuffle())?.first.name;
String? get _randomMode => (modeItems?..shuffle())?.first.name;
// String? get _randomMode => (modeItems?..shuffle())?.first.name;
bool get randomizeEnabled =>
topicItems != null && objectiveItems != null && modeItems != null;
// bool get randomizeEnabled =>
// topicItems != null && objectiveItems != null && modeItems != null;
void randomizeSelections() {
final selectedTopic = _randomTopic;
final selectedObjective = _randomObjective;
final selectedMode = _randomMode;
// void randomizeSelections() {
// final selectedTopic = _randomTopic;
// final selectedObjective = _randomObjective;
// final selectedMode = _randomMode;
if (selectedTopic == null ||
selectedObjective == null ||
selectedMode == null) {
return;
}
// if (selectedTopic == null ||
// selectedObjective == null ||
// selectedMode == null) {
// return;
// }
if (mounted) {
setState(() {
topicController.text = selectedTopic;
objectiveController.text = selectedObjective;
modeController.text = selectedMode;
});
}
}
// if (mounted) {
// setState(() {
// topicController.text = selectedTopic;
// objectiveController.text = selectedObjective;
// modeController.text = selectedMode;
// });
// }
// }
void clearSelections() async {
setState(() {
topicController.clear();
objectiveController.clear();
modeController.clear();
selectedMedia = MediaEnum.nan;
selectedLanguageOfInstructions =
MatrixState.pangeaController.languageController.userL1?.langCode;
selectedTargetLanguage =
MatrixState.pangeaController.languageController.userL2?.langCode;
selectedCefrLevel = LanguageLevelTypeEnum.a1;
selectedNumberOfParticipants = 3;
});
}
// void clearSelections() async {
// setState(() {
// topicController.clear();
// objectiveController.clear();
// modeController.clear();
// selectedMedia = MediaEnum.nan;
// selectedLanguageOfInstructions =
// MatrixState.pangeaController.languageController.userL1?.langCode;
// selectedTargetLanguage =
// MatrixState.pangeaController.languageController.userL2?.langCode;
// selectedCefrLevel = LanguageLevelTypeEnum.a1;
// selectedNumberOfParticipants = 3;
// });
// }
void setSelectedNumberOfParticipants(int? value) {
setState(() => selectedNumberOfParticipants = value);
}
// void setSelectedNumberOfParticipants(int? value) {
// setState(() => selectedNumberOfParticipants = value);
// }
void setSelectedTargetLanguage(String? value) {
setState(() => selectedTargetLanguage = value);
}
// void setSelectedTargetLanguage(String? value) {
// setState(() => selectedTargetLanguage = value);
// }
void setSelectedLanguageOfInstructions(String? value) {
setState(() => selectedLanguageOfInstructions = value);
}
// void setSelectedLanguageOfInstructions(String? value) {
// setState(() => selectedLanguageOfInstructions = value);
// }
void setSelectedCefrLevel(LanguageLevelTypeEnum? value) {
setState(() => selectedCefrLevel = value);
}
// void setSelectedCefrLevel(LanguageLevelTypeEnum? value) {
// setState(() => selectedCefrLevel = value);
// }
ActivitySettingResponseSchema? get _selectedMode {
return modeItems?.firstWhereOrNull(
(element) => element.name.toLowerCase() == planRequest.mode.toLowerCase(),
);
}
// ActivitySettingResponseSchema? get _selectedMode {
// return modeItems?.firstWhereOrNull(
// (element) => element.name.toLowerCase() == planRequest.mode.toLowerCase(),
// );
// }
Future<void> _setTopic() async {
final topic = await TopicListRepo.get(req);
// Future<void> _setTopic() async {
// final topic = await TopicListRepo.get(req);
if (mounted) {
setState(() {
topicItems = topic;
});
}
}
// if (mounted) {
// setState(() {
// topicItems = topic;
// });
// }
// }
Future<void> _setMode() async {
final mode = await ActivityModeListRepo.get(req);
// Future<void> _setMode() async {
// final mode = await ActivityModeListRepo.get(req);
if (mounted) {
setState(() {
modeItems = mode;
});
_setModeImageURL();
}
}
// if (mounted) {
// setState(() {
// modeItems = mode;
// });
// _setModeImageURL();
// }
// }
Future<void> _setObjective() async {
final objective = await LearningObjectiveListRepo.get(req);
// Future<void> _setObjective() async {
// final objective = await LearningObjectiveListRepo.get(req);
if (mounted) {
setState(() {
objectiveItems = objective;
});
}
}
// if (mounted) {
// setState(() {
// objectiveItems = objective;
// });
// }
// }
Future<void> _setModeImageURL() async {
final mode = _selectedMode;
if (mode == null) return;
// Future<void> _setModeImageURL() async {
// final mode = _selectedMode;
// if (mode == null) return;
final modeName =
mode.defaultName.toLowerCase().replaceAll(RegExp(r'\s+'), '');
// final modeName =
// mode.defaultName.toLowerCase().replaceAll(RegExp(r'\s+'), '');
if (!mounted || activities == null) return;
final imageUrl =
"${AppConfig.assetsBaseURL}/${ActivitySuggestionsConstants.modeImageFileStart}$modeName.jpg";
setState(() {
filename = imageUrl;
for (ActivityPlanModel activity in activities!) {
activity = ActivityPlanModel(
req: activity.req,
title: activity.title,
learningObjective: activity.learningObjective,
instructions: activity.instructions,
vocab: activity.vocab,
imageURL: imageUrl,
roles: activity.roles,
activityId: activity.activityId,
);
}
});
}
// if (!mounted || activities == null) return;
// final imageUrl =
// "${AppConfig.assetsBaseURL}/${ActivitySuggestionsConstants.modeImageFileStart}$modeName.jpg";
// setState(() {
// filename = imageUrl;
// for (ActivityPlanModel activity in activities!) {
// activity = ActivityPlanModel(
// req: activity.req,
// title: activity.title,
// learningObjective: activity.learningObjective,
// instructions: activity.instructions,
// vocab: activity.vocab,
// imageURL: imageUrl,
// roles: activity.roles,
// activityId: activity.activityId,
// );
// }
// });
// }
void clearActivities() {
setState(() {
activities = null;
filename = null;
});
}
// void clearActivities() {
// setState(() {
// activities = null;
// filename = null;
// });
// }
Future<void> generate({bool force = false}) async {
setState(() {
loading = true;
error = null;
activities = null;
});
// Future<void> generate({bool force = false}) async {
// setState(() {
// loading = true;
// error = null;
// activities = null;
// });
try {
final resp = await ActivityPlanGenerationRepo.get(
planRequest,
force: force,
);
activities = resp.activityPlans;
await _setModeImageURL();
} catch (e, s) {
error = e;
ErrorHandler.logError(
e: e,
s: s,
data: {
'activityPlanRequest': planRequest,
},
);
} finally {
if (mounted) setState(() => loading = false);
}
}
// try {
// final resp = await ActivityPlanGenerationRepo.get(
// planRequest,
// force: force,
// );
// activities = resp.activityPlans;
// await _setModeImageURL();
// } catch (e, s) {
// error = e;
// ErrorHandler.logError(
// e: e,
// s: s,
// data: {
// 'activityPlanRequest': planRequest,
// },
// );
// } finally {
// if (mounted) setState(() => loading = false);
// }
// }
@override
Widget build(BuildContext context) => ActivityGeneratorView(this);
}
// @override
// Widget build(BuildContext context) => ActivityGeneratorView(this);
// }

View file

@ -1,309 +1,309 @@
import 'package:flutter/material.dart';
// import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:material_symbols_icons/symbols.dart';
// import 'package:cached_network_image/cached_network_image.dart';
// import 'package:material_symbols_icons/symbols.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/activity_generator/activity_generator.dart';
import 'package:fluffychat/pangea/activity_generator/activity_plan_card.dart';
import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
import 'package:fluffychat/pangea/activity_planner/suggestion_form_field.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_constants.dart';
import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart';
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
import 'package:fluffychat/pangea/learning_settings/widgets/p_language_dropdown.dart';
import 'package:fluffychat/widgets/matrix.dart';
// import 'package:fluffychat/config/app_config.dart';
// import 'package:fluffychat/l10n/l10n.dart';
// import 'package:fluffychat/pangea/activity_generator/activity_generator.dart';
// import 'package:fluffychat/pangea/activity_generator/activity_plan_card.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
// import 'package:fluffychat/pangea/activity_planner/suggestion_form_field.dart';
// import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_constants.dart';
// import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart';
// import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
// import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
// import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
// import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
// import 'package:fluffychat/pangea/learning_settings/widgets/p_language_dropdown.dart';
// import 'package:fluffychat/widgets/matrix.dart';
class ActivityGeneratorView extends StatelessWidget {
final ActivityGeneratorState controller;
// class ActivityGeneratorView extends StatelessWidget {
// final ActivityGeneratorState controller;
const ActivityGeneratorView(
this.controller, {
super.key,
});
// const ActivityGeneratorView(
// this.controller, {
// super.key,
// });
@override
Widget build(BuildContext context) {
final l10n = L10n.of(context);
// @override
// Widget build(BuildContext context) {
// final l10n = L10n.of(context);
if (controller.loading) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(L10n.of(context).makeYourOwnActivity),
),
body: const Padding(
padding: EdgeInsets.all(32.0),
child: Center(child: CircularProgressIndicator()),
),
),
);
} else if (controller.error != null || controller.room == null) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(L10n.of(context).makeYourOwnActivity),
),
body: Center(
child: Column(
spacing: 16.0,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ErrorIndicator(
message: l10n.errorGenerateActivityMessage,
),
ElevatedButton(
onPressed: controller.generate,
child: Text(l10n.tryAgain),
),
],
),
),
),
);
} else if (controller.activities != null &&
controller.activities!.isNotEmpty) {
return SafeArea(
child: ActivityPlannerBuilder(
initialActivity: controller.activities!.first,
initialFilename: controller.filename,
room: controller.room!,
builder: (c) {
return Scaffold(
appBar: AppBar(
title: Text(L10n.of(context).makeYourOwnActivity),
leading: BackButton(
onPressed: () {
c.isLaunching
? c.setLaunchState(ActivityLaunchState.base)
: controller.clearActivities();
},
),
),
body: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: controller.activities!.length,
itemBuilder: (context, index) {
return ActivityPlanCard(
regenerate: () => controller.generate(force: true),
controller: c,
);
},
),
);
},
),
);
}
// if (controller.loading) {
// return SafeArea(
// child: Scaffold(
// appBar: AppBar(
// title: Text(L10n.of(context).makeYourOwnActivity),
// ),
// body: const Padding(
// padding: EdgeInsets.all(32.0),
// child: Center(child: CircularProgressIndicator()),
// ),
// ),
// );
// } else if (controller.error != null || controller.room == null) {
// return SafeArea(
// child: Scaffold(
// appBar: AppBar(
// title: Text(L10n.of(context).makeYourOwnActivity),
// ),
// body: Center(
// child: Column(
// spacing: 16.0,
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// ErrorIndicator(
// message: l10n.errorGenerateActivityMessage,
// ),
// ElevatedButton(
// onPressed: controller.generate,
// child: Text(l10n.tryAgain),
// ),
// ],
// ),
// ),
// ),
// );
// } else if (controller.activities != null &&
// controller.activities!.isNotEmpty) {
// return SafeArea(
// child: ActivityPlannerBuilder(
// initialActivity: controller.activities!.first,
// initialFilename: controller.filename,
// room: controller.room!,
// builder: (c) {
// return Scaffold(
// appBar: AppBar(
// title: Text(L10n.of(context).makeYourOwnActivity),
// leading: BackButton(
// onPressed: () {
// c.isLaunching
// ? c.setLaunchState(ActivityLaunchState.base)
// : controller.clearActivities();
// },
// ),
// ),
// body: ListView.builder(
// padding: const EdgeInsets.all(16),
// itemCount: controller.activities!.length,
// itemBuilder: (context, index) {
// return ActivityPlanCard(
// regenerate: () => controller.generate(force: true),
// controller: c,
// );
// },
// ),
// );
// },
// ),
// );
// }
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(L10n.of(context).makeYourOwnActivity),
leading: BackButton(
onPressed: () {
if (controller.activities != null &&
controller.activities!.isNotEmpty) {
controller.clearActivities();
} else {
Navigator.of(context).pop();
}
},
),
),
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: Form(
key: controller.formKey,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
const InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.activityPlannerOverview,
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0),
),
clipBehavior: Clip.hardEdge,
alignment: Alignment.center,
child: ClipRRect(
child: CachedNetworkImage(
fit: BoxFit.cover,
imageUrl:
"${AppConfig.assetsBaseURL}/${ActivitySuggestionsConstants.makeActivityAssetPath}",
placeholder: (context, url) {
return const Center(
child: CircularProgressIndicator(),
);
},
errorWidget: (context, url, error) => const SizedBox(),
),
),
),
const SizedBox(height: 16.0),
PLanguageDropdown(
languages:
MatrixState.pangeaController.pLanguageStore.baseOptions,
onChange: (val) => controller
.setSelectedLanguageOfInstructions(val.langCode),
initialLanguage:
controller.selectedLanguageOfInstructions != null
? PLanguageStore.byLangCode(
controller.selectedLanguageOfInstructions!,
)
: MatrixState
.pangeaController.languageController.userL1,
isL2List: false,
decorationText:
L10n.of(context).languageOfInstructionsLabel,
),
const SizedBox(height: 16.0),
PLanguageDropdown(
languages: MatrixState
.pangeaController.pLanguageStore.targetOptions,
onChange: (val) =>
controller.setSelectedTargetLanguage(val.langCode),
initialLanguage: controller.selectedTargetLanguage != null
? PLanguageStore.byLangCode(
controller.selectedTargetLanguage!,
)
: MatrixState
.pangeaController.languageController.userL2,
decorationText: L10n.of(context).targetLanguageLabel,
isL2List: true,
),
const SizedBox(height: 16.0),
SuggestionFormField(
suggestions: controller.topicItems,
validator: controller.validateNotNull,
maxLength: 50,
label: l10n.topicLabel,
placeholder: l10n.topicPlaceholder,
controller: controller.topicController,
),
const SizedBox(height: 16.0),
SuggestionFormField(
suggestions: controller.objectiveItems,
validator: controller.validateNotNull,
maxLength: 140,
label: l10n.learningObjectiveLabel,
placeholder: l10n.learningObjectivePlaceholder,
controller: controller.objectiveController,
),
const SizedBox(height: 16.0),
SuggestionFormField(
suggestions: controller.modeItems,
validator: controller.validateNotNull,
maxLength: 50,
label: l10n.modeLabel,
placeholder: l10n.modePlaceholder,
controller: controller.modeController,
),
const SizedBox(height: 16.0),
TextFormField(
decoration: InputDecoration(
labelText: l10n.numberOfLearners,
),
textInputAction: TextInputAction.done,
validator: (value) {
if (value == null || value.isEmpty) {
return l10n.mustBeInteger;
}
final n = int.tryParse(value);
if (n == null || n <= 0) {
return l10n.mustBeInteger;
}
if (n > 50) {
return l10n.maxFifty;
}
return null;
},
onChanged: (val) => controller
.setSelectedNumberOfParticipants(int.tryParse(val)),
initialValue:
controller.selectedNumberOfParticipants?.toString(),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onFieldSubmitted: (_) {
if (controller.formKey.currentState?.validate() ??
false) {
controller.generate();
}
},
),
const SizedBox(height: 16.0),
LanguageLevelDropdown(
onChanged: controller.setSelectedCefrLevel,
initialLevel: controller.selectedCefrLevel,
),
const SizedBox(height: 16.0),
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: ElevatedButton(
onPressed: controller.clearSelections,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Symbols.reset_focus),
const SizedBox(width: 8),
Text(L10n.of(context).clear),
],
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: ElevatedButton(
onPressed: controller.randomizeEnabled
? controller.randomizeSelections
: null,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.shuffle),
const SizedBox(width: 8),
Text(L10n.of(context).randomize),
],
),
),
),
),
],
),
const SizedBox(height: 24.0),
ElevatedButton(
onPressed: () {
if (controller.formKey.currentState?.validate() ??
false) {
controller.generate();
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.lightbulb_outline),
const SizedBox(width: 8),
Text(l10n.generateActivitiesButton),
],
),
),
],
),
),
),
),
),
);
}
}
// return SafeArea(
// child: Scaffold(
// appBar: AppBar(
// title: Text(L10n.of(context).makeYourOwnActivity),
// leading: BackButton(
// onPressed: () {
// if (controller.activities != null &&
// controller.activities!.isNotEmpty) {
// controller.clearActivities();
// } else {
// Navigator.of(context).pop();
// }
// },
// ),
// ),
// body: Center(
// child: ConstrainedBox(
// constraints: const BoxConstraints(maxWidth: 600),
// child: Form(
// key: controller.formKey,
// child: ListView(
// padding: const EdgeInsets.all(16),
// children: [
// const InstructionsInlineTooltip(
// instructionsEnum: InstructionsEnum.activityPlannerOverview,
// ),
// Container(
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(12.0),
// ),
// clipBehavior: Clip.hardEdge,
// alignment: Alignment.center,
// child: ClipRRect(
// child: CachedNetworkImage(
// fit: BoxFit.cover,
// imageUrl:
// "${AppConfig.assetsBaseURL}/${ActivitySuggestionsConstants.makeActivityAssetPath}",
// placeholder: (context, url) {
// return const Center(
// child: CircularProgressIndicator(),
// );
// },
// errorWidget: (context, url, error) => const SizedBox(),
// ),
// ),
// ),
// const SizedBox(height: 16.0),
// PLanguageDropdown(
// languages:
// MatrixState.pangeaController.pLanguageStore.baseOptions,
// onChange: (val) => controller
// .setSelectedLanguageOfInstructions(val.langCode),
// initialLanguage:
// controller.selectedLanguageOfInstructions != null
// ? PLanguageStore.byLangCode(
// controller.selectedLanguageOfInstructions!,
// )
// : MatrixState
// .pangeaController.languageController.userL1,
// isL2List: false,
// decorationText:
// L10n.of(context).languageOfInstructionsLabel,
// ),
// const SizedBox(height: 16.0),
// PLanguageDropdown(
// languages: MatrixState
// .pangeaController.pLanguageStore.targetOptions,
// onChange: (val) =>
// controller.setSelectedTargetLanguage(val.langCode),
// initialLanguage: controller.selectedTargetLanguage != null
// ? PLanguageStore.byLangCode(
// controller.selectedTargetLanguage!,
// )
// : MatrixState
// .pangeaController.languageController.userL2,
// decorationText: L10n.of(context).targetLanguageLabel,
// isL2List: true,
// ),
// const SizedBox(height: 16.0),
// SuggestionFormField(
// suggestions: controller.topicItems,
// validator: controller.validateNotNull,
// maxLength: 50,
// label: l10n.topicLabel,
// placeholder: l10n.topicPlaceholder,
// controller: controller.topicController,
// ),
// const SizedBox(height: 16.0),
// SuggestionFormField(
// suggestions: controller.objectiveItems,
// validator: controller.validateNotNull,
// maxLength: 140,
// label: l10n.learningObjectiveLabel,
// placeholder: l10n.learningObjectivePlaceholder,
// controller: controller.objectiveController,
// ),
// const SizedBox(height: 16.0),
// SuggestionFormField(
// suggestions: controller.modeItems,
// validator: controller.validateNotNull,
// maxLength: 50,
// label: l10n.modeLabel,
// placeholder: l10n.modePlaceholder,
// controller: controller.modeController,
// ),
// const SizedBox(height: 16.0),
// TextFormField(
// decoration: InputDecoration(
// labelText: l10n.numberOfLearners,
// ),
// textInputAction: TextInputAction.done,
// validator: (value) {
// if (value == null || value.isEmpty) {
// return l10n.mustBeInteger;
// }
// final n = int.tryParse(value);
// if (n == null || n <= 0) {
// return l10n.mustBeInteger;
// }
// if (n > 50) {
// return l10n.maxFifty;
// }
// return null;
// },
// onChanged: (val) => controller
// .setSelectedNumberOfParticipants(int.tryParse(val)),
// initialValue:
// controller.selectedNumberOfParticipants?.toString(),
// onTapOutside: (_) =>
// FocusManager.instance.primaryFocus?.unfocus(),
// onFieldSubmitted: (_) {
// if (controller.formKey.currentState?.validate() ??
// false) {
// controller.generate();
// }
// },
// ),
// const SizedBox(height: 16.0),
// LanguageLevelDropdown(
// onChanged: controller.setSelectedCefrLevel,
// initialLevel: controller.selectedCefrLevel,
// ),
// const SizedBox(height: 16.0),
// Row(
// children: [
// Expanded(
// child: Padding(
// padding: const EdgeInsets.only(right: 8.0),
// child: ElevatedButton(
// onPressed: controller.clearSelections,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// const Icon(Symbols.reset_focus),
// const SizedBox(width: 8),
// Text(L10n.of(context).clear),
// ],
// ),
// ),
// ),
// ),
// Expanded(
// child: Padding(
// padding: const EdgeInsets.only(left: 8.0),
// child: ElevatedButton(
// onPressed: controller.randomizeEnabled
// ? controller.randomizeSelections
// : null,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// const Icon(Icons.shuffle),
// const SizedBox(width: 8),
// Text(L10n.of(context).randomize),
// ],
// ),
// ),
// ),
// ),
// ],
// ),
// const SizedBox(height: 24.0),
// ElevatedButton(
// onPressed: () {
// if (controller.formKey.currentState?.validate() ??
// false) {
// controller.generate();
// }
// },
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// const Icon(Icons.lightbulb_outline),
// const SizedBox(width: 8),
// Text(l10n.generateActivitiesButton),
// ],
// ),
// ),
// ],
// ),
// ),
// ),
// ),
// ),
// );
// }
// }

View file

@ -1,56 +1,56 @@
import 'dart:convert';
// import 'dart:convert';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart';
// import 'package:get_storage/get_storage.dart';
// import 'package:http/http.dart';
import 'package:fluffychat/pangea/activity_generator/list_request_schema.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/network/urls.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../common/network/requests.dart';
// import 'package:fluffychat/pangea/activity_generator/list_request_schema.dart';
// import 'package:fluffychat/pangea/common/config/environment.dart';
// import 'package:fluffychat/pangea/common/network/urls.dart';
// import 'package:fluffychat/widgets/matrix.dart';
// import '../common/network/requests.dart';
class ActivityModeListRepo {
static final GetStorage _modeListStorage = GetStorage('mode_list_storage');
// class ActivityModeListRepo {
// static final GetStorage _modeListStorage = GetStorage('mode_list_storage');
static void set(
ActivitySettingRequestSchema request,
List<ActivitySettingResponseSchema> response,
) {
_modeListStorage.write(
request.storageKey,
response.map((e) => e.toJson()).toList(),
);
}
// static void set(
// ActivitySettingRequestSchema request,
// List<ActivitySettingResponseSchema> response,
// ) {
// _modeListStorage.write(
// request.storageKey,
// response.map((e) => e.toJson()).toList(),
// );
// }
static List<ActivitySettingResponseSchema> fromJson(Iterable json) {
return List<ActivitySettingResponseSchema>.from(
json.map((x) => ActivitySettingResponseSchema.fromJson(x)),
);
}
// static List<ActivitySettingResponseSchema> fromJson(Iterable json) {
// return List<ActivitySettingResponseSchema>.from(
// json.map((x) => ActivitySettingResponseSchema.fromJson(x)),
// );
// }
static Future<List<ActivitySettingResponseSchema>> get(
ActivitySettingRequestSchema request,
) async {
final cachedJson = _modeListStorage.read(request.storageKey);
if (cachedJson != null) {
return ActivityModeListRepo.fromJson(cachedJson);
}
// static Future<List<ActivitySettingResponseSchema>> get(
// ActivitySettingRequestSchema request,
// ) async {
// final cachedJson = _modeListStorage.read(request.storageKey);
// if (cachedJson != null) {
// return ActivityModeListRepo.fromJson(cachedJson);
// }
final Requests req = Requests(
choreoApiKey: Environment.choreoApiKey,
accessToken: MatrixState.pangeaController.userController.accessToken,
);
// final Requests req = Requests(
// choreoApiKey: Environment.choreoApiKey,
// accessToken: MatrixState.pangeaController.userController.accessToken,
// );
final Response res = await req.post(
url: PApiUrls.activityModeList,
body: request.toJson(),
);
// final Response res = await req.post(
// url: PApiUrls.activityModeList,
// body: request.toJson(),
// );
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
final response = ActivityModeListRepo.fromJson(decodedBody);
// final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
// final response = ActivityModeListRepo.fromJson(decodedBody);
set(request, response);
// set(request, response);
return response;
}
}
// return response;
// }
// }

File diff suppressed because it is too large Load diff

View file

@ -1,49 +1,49 @@
import 'dart:convert';
// import 'dart:convert';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart';
// import 'package:get_storage/get_storage.dart';
// import 'package:http/http.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_response.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/network/urls.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../common/network/requests.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_plan_response.dart';
// import 'package:fluffychat/pangea/common/config/environment.dart';
// import 'package:fluffychat/pangea/common/network/urls.dart';
// import 'package:fluffychat/widgets/matrix.dart';
// import '../common/network/requests.dart';
class ActivityPlanGenerationRepo {
static final GetStorage _activityPlanStorage =
GetStorage('activity_plan_storage');
// class ActivityPlanGenerationRepo {
// static final GetStorage _activityPlanStorage =
// GetStorage('activity_plan_storage');
static void set(ActivityPlanRequest request, ActivityPlanResponse response) {
_activityPlanStorage.write(request.storageKey, response.toJson());
}
// static void set(ActivityPlanRequest request, ActivityPlanResponse response) {
// _activityPlanStorage.write(request.storageKey, response.toJson());
// }
static Future<ActivityPlanResponse> get(
ActivityPlanRequest request, {
bool force = false,
}) async {
final cachedJson = _activityPlanStorage.read(request.storageKey);
if (cachedJson != null && !force) {
final cached = ActivityPlanResponse.fromJson(cachedJson);
// static Future<ActivityPlanResponse> get(
// ActivityPlanRequest request, {
// bool force = false,
// }) async {
// final cachedJson = _activityPlanStorage.read(request.storageKey);
// if (cachedJson != null && !force) {
// final cached = ActivityPlanResponse.fromJson(cachedJson);
return cached;
}
// return cached;
// }
final Requests req = Requests(
choreoApiKey: Environment.choreoApiKey,
accessToken: MatrixState.pangeaController.userController.accessToken,
);
// final Requests req = Requests(
// choreoApiKey: Environment.choreoApiKey,
// accessToken: MatrixState.pangeaController.userController.accessToken,
// );
final Response res = await req.post(
url: PApiUrls.activityPlanGeneration,
body: request.toJson(),
);
// final Response res = await req.post(
// url: PApiUrls.activityPlanGeneration,
// body: request.toJson(),
// );
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
final response = ActivityPlanResponse.fromJson(decodedBody);
// final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
// final response = ActivityPlanResponse.fromJson(decodedBody);
set(request, response);
// set(request, response);
return response;
}
}
// return response;
// }
// }

View file

@ -1,57 +1,57 @@
import 'dart:convert';
// import 'dart:convert';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart';
// import 'package:get_storage/get_storage.dart';
// import 'package:http/http.dart';
import 'package:fluffychat/pangea/activity_generator/list_request_schema.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/network/urls.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../common/network/requests.dart';
// import 'package:fluffychat/pangea/activity_generator/list_request_schema.dart';
// import 'package:fluffychat/pangea/common/config/environment.dart';
// import 'package:fluffychat/pangea/common/network/urls.dart';
// import 'package:fluffychat/widgets/matrix.dart';
// import '../common/network/requests.dart';
class LearningObjectiveListRepo {
static final GetStorage _objectiveListStorage =
GetStorage('objective_list_storage');
// class LearningObjectiveListRepo {
// static final GetStorage _objectiveListStorage =
// GetStorage('objective_list_storage');
static void set(
ActivitySettingRequestSchema request,
List<ActivitySettingResponseSchema> response,
) {
_objectiveListStorage.write(
request.storageKey,
response.map((e) => e.toJson()).toList(),
);
}
// static void set(
// ActivitySettingRequestSchema request,
// List<ActivitySettingResponseSchema> response,
// ) {
// _objectiveListStorage.write(
// request.storageKey,
// response.map((e) => e.toJson()).toList(),
// );
// }
static List<ActivitySettingResponseSchema> fromJson(Iterable json) {
return List<ActivitySettingResponseSchema>.from(
json.map((x) => ActivitySettingResponseSchema.fromJson(x)),
);
}
// static List<ActivitySettingResponseSchema> fromJson(Iterable json) {
// return List<ActivitySettingResponseSchema>.from(
// json.map((x) => ActivitySettingResponseSchema.fromJson(x)),
// );
// }
static Future<List<ActivitySettingResponseSchema>> get(
ActivitySettingRequestSchema request,
) async {
final cachedJson = _objectiveListStorage.read(request.storageKey);
if (cachedJson != null) {
return LearningObjectiveListRepo.fromJson(cachedJson);
}
// static Future<List<ActivitySettingResponseSchema>> get(
// ActivitySettingRequestSchema request,
// ) async {
// final cachedJson = _objectiveListStorage.read(request.storageKey);
// if (cachedJson != null) {
// return LearningObjectiveListRepo.fromJson(cachedJson);
// }
final Requests req = Requests(
choreoApiKey: Environment.choreoApiKey,
accessToken: MatrixState.pangeaController.userController.accessToken,
);
// final Requests req = Requests(
// choreoApiKey: Environment.choreoApiKey,
// accessToken: MatrixState.pangeaController.userController.accessToken,
// );
final Response res = await req.post(
url: PApiUrls.objectiveList,
body: request.toJson(),
);
// final Response res = await req.post(
// url: PApiUrls.objectiveList,
// body: request.toJson(),
// );
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
final response = LearningObjectiveListRepo.fromJson(decodedBody);
// final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
// final response = LearningObjectiveListRepo.fromJson(decodedBody);
set(request, response);
// set(request, response);
return response;
}
}
// return response;
// }
// }

View file

@ -1,37 +1,37 @@
class ActivitySettingRequestSchema {
final String langCode;
// class ActivitySettingRequestSchema {
// final String langCode;
ActivitySettingRequestSchema({required this.langCode});
// ActivitySettingRequestSchema({required this.langCode});
Map<String, dynamic> toJson() {
return {
'lang_code': langCode,
};
}
// Map<String, dynamic> toJson() {
// return {
// 'lang_code': langCode,
// };
// }
String get storageKey => 'topic_list-$langCode';
}
// String get storageKey => 'topic_list-$langCode';
// }
class ActivitySettingResponseSchema {
final String defaultName;
final String name;
// class ActivitySettingResponseSchema {
// final String defaultName;
// final String name;
ActivitySettingResponseSchema({
required this.defaultName,
required this.name,
});
// ActivitySettingResponseSchema({
// required this.defaultName,
// required this.name,
// });
factory ActivitySettingResponseSchema.fromJson(Map<String, dynamic> json) {
return ActivitySettingResponseSchema(
defaultName: json['default_name'],
name: json['name'],
);
}
// factory ActivitySettingResponseSchema.fromJson(Map<String, dynamic> json) {
// return ActivitySettingResponseSchema(
// defaultName: json['default_name'],
// name: json['name'],
// );
// }
Map<String, dynamic> toJson() {
return {
'default_name': defaultName,
'name': name,
};
}
}
// Map<String, dynamic> toJson() {
// return {
// 'default_name': defaultName,
// 'name': name,
// };
// }
// }

View file

@ -1,56 +1,56 @@
import 'dart:convert';
// import 'dart:convert';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart';
// import 'package:get_storage/get_storage.dart';
// import 'package:http/http.dart';
import 'package:fluffychat/pangea/activity_generator/list_request_schema.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/network/urls.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../common/network/requests.dart';
// import 'package:fluffychat/pangea/activity_generator/list_request_schema.dart';
// import 'package:fluffychat/pangea/common/config/environment.dart';
// import 'package:fluffychat/pangea/common/network/urls.dart';
// import 'package:fluffychat/widgets/matrix.dart';
// import '../common/network/requests.dart';
class TopicListRepo {
static final GetStorage _topicListStorage = GetStorage('topic_list_storage');
// class TopicListRepo {
// static final GetStorage _topicListStorage = GetStorage('topic_list_storage');
static void set(
ActivitySettingRequestSchema request,
List<ActivitySettingResponseSchema> response,
) {
_topicListStorage.write(
request.storageKey,
response.map((e) => e.toJson()).toList(),
);
}
// static void set(
// ActivitySettingRequestSchema request,
// List<ActivitySettingResponseSchema> response,
// ) {
// _topicListStorage.write(
// request.storageKey,
// response.map((e) => e.toJson()).toList(),
// );
// }
static List<ActivitySettingResponseSchema> fromJson(Iterable json) {
return List<ActivitySettingResponseSchema>.from(
json.map((x) => ActivitySettingResponseSchema.fromJson(x)),
);
}
// static List<ActivitySettingResponseSchema> fromJson(Iterable json) {
// return List<ActivitySettingResponseSchema>.from(
// json.map((x) => ActivitySettingResponseSchema.fromJson(x)),
// );
// }
static Future<List<ActivitySettingResponseSchema>> get(
ActivitySettingRequestSchema request,
) async {
final cachedJson = _topicListStorage.read(request.storageKey);
if (cachedJson != null) {
return TopicListRepo.fromJson(cachedJson);
}
// static Future<List<ActivitySettingResponseSchema>> get(
// ActivitySettingRequestSchema request,
// ) async {
// final cachedJson = _topicListStorage.read(request.storageKey);
// if (cachedJson != null) {
// return TopicListRepo.fromJson(cachedJson);
// }
final Requests req = Requests(
choreoApiKey: Environment.choreoApiKey,
accessToken: MatrixState.pangeaController.userController.accessToken,
);
// final Requests req = Requests(
// choreoApiKey: Environment.choreoApiKey,
// accessToken: MatrixState.pangeaController.userController.accessToken,
// );
final Response res = await req.post(
url: PApiUrls.topicList,
body: request.toJson(),
);
// final Response res = await req.post(
// url: PApiUrls.topicList,
// body: request.toJson(),
// );
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
final response = TopicListRepo.fromJson(decodedBody);
// final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
// final response = TopicListRepo.fromJson(decodedBody);
set(request, response);
// set(request, response);
return response;
}
}
// return response;
// }
// }

View file

@ -1,286 +1,286 @@
import 'package:flutter/material.dart';
// import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:swipe_to_action/swipe_to_action.dart';
// import 'package:matrix/matrix.dart';
// import 'package:swipe_to_action/swipe_to_action.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pages/chat/events/message_content.dart';
import 'package:fluffychat/pages/chat/events/pangea_message_reactions.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../../../config/app_config.dart';
// import 'package:fluffychat/config/themes.dart';
// import 'package:fluffychat/pages/chat/chat.dart';
// import 'package:fluffychat/pages/chat/events/message_content.dart';
// import 'package:fluffychat/pages/chat/events/pangea_message_reactions.dart';
// import 'package:fluffychat/utils/date_time_extension.dart';
// import 'package:fluffychat/widgets/matrix.dart';
// import '../../../config/app_config.dart';
class ActivityPlanMessage extends StatelessWidget {
final Event event;
final Timeline timeline;
final bool animateIn;
final void Function()? resetAnimateIn;
final ChatController controller;
final bool highlightMarker;
final bool selected;
// class ActivityPlanMessage extends StatelessWidget {
// final Event event;
// final Timeline timeline;
// final bool animateIn;
// final void Function()? resetAnimateIn;
// final ChatController controller;
// final bool highlightMarker;
// final bool selected;
const ActivityPlanMessage(
this.event, {
required this.timeline,
required this.controller,
required this.selected,
this.animateIn = false,
this.resetAnimateIn,
this.highlightMarker = false,
super.key,
});
// const ActivityPlanMessage(
// this.event, {
// required this.timeline,
// required this.controller,
// required this.selected,
// this.animateIn = false,
// this.resetAnimateIn,
// this.highlightMarker = false,
// super.key,
// });
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (controller.pangeaEditingEvent?.eventId == event.eventId) {
controller.clearEditingEvent();
}
});
// @override
// Widget build(BuildContext context) {
// WidgetsBinding.instance.addPostFrameCallback((_) {
// if (controller.pangeaEditingEvent?.eventId == event.eventId) {
// controller.clearEditingEvent();
// }
// });
final theme = Theme.of(context);
final color = theme.brightness == Brightness.dark
? theme.colorScheme.onSecondary
: theme.colorScheme.primary;
final textColor = ThemeData.light().colorScheme.onPrimary;
// final theme = Theme.of(context);
// final color = theme.brightness == Brightness.dark
// ? theme.colorScheme.onSecondary
// : theme.colorScheme.primary;
// final textColor = ThemeData.light().colorScheme.onPrimary;
final displayEvent = event.getDisplayEvent(timeline);
const roundedCorner = Radius.circular(AppConfig.borderRadius);
const borderRadius = BorderRadius.all(roundedCorner);
// final displayEvent = event.getDisplayEvent(timeline);
// const roundedCorner = Radius.circular(AppConfig.borderRadius);
// const borderRadius = BorderRadius.all(roundedCorner);
final resetAnimateIn = this.resetAnimateIn;
var animateIn = this.animateIn;
// final resetAnimateIn = this.resetAnimateIn;
// var animateIn = this.animateIn;
final row = StatefulBuilder(
builder: (context, setState) {
if (animateIn && resetAnimateIn != null) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
animateIn = false;
if (context.mounted) {
setState(resetAnimateIn);
}
});
}
return AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
clipBehavior: Clip.none,
alignment: Alignment.bottomLeft,
child: animateIn
? const SizedBox(height: 0, width: double.infinity)
: Stack(
children: [
Positioned(
top: 0,
bottom: 0,
left: 0,
right: 0,
child: InkWell(
onTap: () => controller.showToolbar(event),
onLongPress: () => controller.showToolbar(event),
borderRadius:
BorderRadius.circular(AppConfig.borderRadius / 2),
child: Material(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius / 2),
color: highlightMarker
? theme.colorScheme.secondaryContainer
.withAlpha(128)
: Colors.transparent,
),
),
),
Container(
alignment: Alignment.center,
child: GestureDetector(
onTap: () => controller.showToolbar(event),
onLongPress: () => controller.showToolbar(event),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
AnimatedOpacity(
opacity: animateIn
? 0
: event.messageType ==
MessageTypes.BadEncrypted ||
event.status.isSending
? 0.5
: 1,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
child: Container(
decoration: BoxDecoration(
color: color,
borderRadius: borderRadius,
),
clipBehavior: Clip.antiAlias,
child: CompositedTransformTarget(
link: MatrixState.pAnyState
.layerLinkAndKey(
event.eventId,
)
.link,
child: Container(
key: MatrixState.pAnyState
.layerLinkAndKey(
event.eventId,
)
.key,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
),
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 1.5,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
MessageContent(
displayEvent,
textColor: textColor,
borderRadius: borderRadius,
controller: controller,
immersionMode: false,
timeline: timeline,
linkColor: theme.brightness ==
Brightness.light
? theme.colorScheme.primary
: theme.colorScheme.onPrimary,
selected: selected,
),
if (event.hasAggregatedEvents(
timeline,
RelationshipTypes.edit,
))
Padding(
padding: const EdgeInsets.only(
top: 4.0,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (event.hasAggregatedEvents(
timeline,
RelationshipTypes.edit,
)) ...[
Icon(
Icons.edit_outlined,
color: textColor
.withAlpha(164),
size: 14,
),
Text(
' - ${displayEvent.originServerTs.localizedTimeShort(context)}',
style: TextStyle(
color:
textColor.withAlpha(
164,
),
fontSize: 12,
),
),
],
],
),
),
],
),
),
),
),
),
Padding(
padding: const EdgeInsets.only(
top: 4.0,
right: 4.0,
),
child: PangeaMessageReactions(
event,
timeline,
controller,
),
),
],
),
),
),
],
),
);
},
);
// final row = StatefulBuilder(
// builder: (context, setState) {
// if (animateIn && resetAnimateIn != null) {
// WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
// animateIn = false;
// if (context.mounted) {
// setState(resetAnimateIn);
// }
// });
// }
// return AnimatedSize(
// duration: FluffyThemes.animationDuration,
// curve: FluffyThemes.animationCurve,
// clipBehavior: Clip.none,
// alignment: Alignment.bottomLeft,
// child: animateIn
// ? const SizedBox(height: 0, width: double.infinity)
// : Stack(
// children: [
// Positioned(
// top: 0,
// bottom: 0,
// left: 0,
// right: 0,
// child: InkWell(
// onTap: () => controller.showToolbar(event),
// onLongPress: () => controller.showToolbar(event),
// borderRadius:
// BorderRadius.circular(AppConfig.borderRadius / 2),
// child: Material(
// borderRadius:
// BorderRadius.circular(AppConfig.borderRadius / 2),
// color: highlightMarker
// ? theme.colorScheme.secondaryContainer
// .withAlpha(128)
// : Colors.transparent,
// ),
// ),
// ),
// Container(
// alignment: Alignment.center,
// child: GestureDetector(
// onTap: () => controller.showToolbar(event),
// onLongPress: () => controller.showToolbar(event),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.end,
// children: [
// AnimatedOpacity(
// opacity: animateIn
// ? 0
// : event.messageType ==
// MessageTypes.BadEncrypted ||
// event.status.isSending
// ? 0.5
// : 1,
// duration: FluffyThemes.animationDuration,
// curve: FluffyThemes.animationCurve,
// child: Container(
// decoration: BoxDecoration(
// color: color,
// borderRadius: borderRadius,
// ),
// clipBehavior: Clip.antiAlias,
// child: CompositedTransformTarget(
// link: MatrixState.pAnyState
// .layerLinkAndKey(
// event.eventId,
// )
// .link,
// child: Container(
// key: MatrixState.pAnyState
// .layerLinkAndKey(
// event.eventId,
// )
// .key,
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(
// AppConfig.borderRadius,
// ),
// ),
// constraints: const BoxConstraints(
// maxWidth: FluffyThemes.columnWidth * 1.5,
// ),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment:
// CrossAxisAlignment.start,
// children: <Widget>[
// MessageContent(
// displayEvent,
// textColor: textColor,
// borderRadius: borderRadius,
// controller: controller,
// immersionMode: false,
// timeline: timeline,
// linkColor: theme.brightness ==
// Brightness.light
// ? theme.colorScheme.primary
// : theme.colorScheme.onPrimary,
// selected: selected,
// ),
// if (event.hasAggregatedEvents(
// timeline,
// RelationshipTypes.edit,
// ))
// Padding(
// padding: const EdgeInsets.only(
// top: 4.0,
// ),
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// if (event.hasAggregatedEvents(
// timeline,
// RelationshipTypes.edit,
// )) ...[
// Icon(
// Icons.edit_outlined,
// color: textColor
// .withAlpha(164),
// size: 14,
// ),
// Text(
// ' - ${displayEvent.originServerTs.localizedTimeShort(context)}',
// style: TextStyle(
// color:
// textColor.withAlpha(
// 164,
// ),
// fontSize: 12,
// ),
// ),
// ],
// ],
// ),
// ),
// ],
// ),
// ),
// ),
// ),
// ),
// Padding(
// padding: const EdgeInsets.only(
// top: 4.0,
// right: 4.0,
// ),
// child: PangeaMessageReactions(
// event,
// timeline,
// controller,
// ),
// ),
// ],
// ),
// ),
// ),
// ],
// ),
// );
// },
// );
Widget container;
// Widget container;
container = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (event.messageType == MessageTypes.Text)
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Center(
child: Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Material(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius * 2),
color: theme.colorScheme.surface.withAlpha(128),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 2.0,
),
child: Text(
event.originServerTs.localizedTime(context),
style: TextStyle(
fontSize: 12 * AppConfig.fontSizeFactor,
fontWeight: FontWeight.bold,
color: theme.colorScheme.secondary,
),
),
),
),
),
),
),
row,
],
);
// container = Column(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.start,
// children: <Widget>[
// if (event.messageType == MessageTypes.Text)
// Padding(
// padding: const EdgeInsets.symmetric(vertical: 8.0),
// child: Center(
// child: Padding(
// padding: const EdgeInsets.only(top: 4.0),
// child: Material(
// borderRadius:
// BorderRadius.circular(AppConfig.borderRadius * 2),
// color: theme.colorScheme.surface.withAlpha(128),
// child: Padding(
// padding: const EdgeInsets.symmetric(
// horizontal: 8.0,
// vertical: 2.0,
// ),
// child: Text(
// event.originServerTs.localizedTime(context),
// style: TextStyle(
// fontSize: 12 * AppConfig.fontSizeFactor,
// fontWeight: FontWeight.bold,
// color: theme.colorScheme.secondary,
// ),
// ),
// ),
// ),
// ),
// ),
// ),
// row,
// ],
// );
container = Material(type: MaterialType.transparency, child: container);
// container = Material(type: MaterialType.transparency, child: container);
return Center(
child: Swipeable(
key: ValueKey(event.eventId),
background: const Padding(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: Center(
child: Icon(Icons.check_outlined),
),
),
direction: AppConfig.swipeRightToLeftToReply
? SwipeDirection.endToStart
: SwipeDirection.startToEnd,
onSwipe: (_) {},
child: Container(
constraints: const BoxConstraints(
maxWidth: FluffyThemes.maxTimelineWidth,
),
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
top: 4.0,
bottom: 4.0,
),
child: container,
),
),
);
}
}
// return Center(
// child: Swipeable(
// key: ValueKey(event.eventId),
// background: const Padding(
// padding: EdgeInsets.symmetric(horizontal: 12.0),
// child: Center(
// child: Icon(Icons.check_outlined),
// ),
// ),
// direction: AppConfig.swipeRightToLeftToReply
// ? SwipeDirection.endToStart
// : SwipeDirection.startToEnd,
// onSwipe: (_) {},
// child: Container(
// constraints: const BoxConstraints(
// maxWidth: FluffyThemes.maxTimelineWidth,
// ),
// padding: const EdgeInsets.only(
// left: 8.0,
// right: 8.0,
// top: 4.0,
// bottom: 4.0,
// ),
// child: container,
// ),
// ),
// );
// }
// }

View file

@ -49,32 +49,6 @@ class ActivityPlanModel {
return defaultRoles;
}
ActivityPlanModel copyWith({
String? title,
String? description,
String? learningObjective,
String? instructions,
List<Vocab>? vocab,
String? imageURL,
DateTime? endAt,
Duration? duration,
Map<String, ActivityRole>? roles,
}) {
return ActivityPlanModel(
req: req,
title: title ?? this.title,
description: description ?? this.description,
learningObjective: learningObjective ?? this.learningObjective,
instructions: instructions ?? this.instructions,
vocab: vocab ?? this.vocab,
imageURL: imageURL ?? this.imageURL,
endAt: endAt ?? this.endAt,
duration: duration ?? this.duration,
roles: roles ?? _roles,
activityId: activityId,
);
}
factory ActivityPlanModel.fromJson(Map<String, dynamic> json) {
final req =
ActivityPlanRequest.fromJson(json[ModelKey.activityPlanRequest]);

View file

@ -1,21 +1,21 @@
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
class ActivityPlanResponse {
final List<ActivityPlanModel> activityPlans;
// class ActivityPlanResponse {
// final List<ActivityPlanModel> activityPlans;
ActivityPlanResponse({required this.activityPlans});
// ActivityPlanResponse({required this.activityPlans});
factory ActivityPlanResponse.fromJson(Map<String, dynamic> json) {
return ActivityPlanResponse(
activityPlans: (json['activity_plans'] as List)
.map((e) => ActivityPlanModel.fromJson(e))
.toList(),
);
}
// factory ActivityPlanResponse.fromJson(Map<String, dynamic> json) {
// return ActivityPlanResponse(
// activityPlans: (json['activity_plans'] as List)
// .map((e) => ActivityPlanModel.fromJson(e))
// .toList(),
// );
// }
Map<String, dynamic> toJson() {
return {
'activity_plans': activityPlans.map((e) => e.toJson()).toList(),
};
}
}
// Map<String, dynamic> toJson() {
// return {
// 'activity_plans': activityPlans.map((e) => e.toJson()).toList(),
// };
// }
// }

View file

@ -1,420 +1,420 @@
import 'dart:async';
// import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' hide Visibility;
// import 'package:flutter/foundation.dart';
// import 'package:flutter/material.dart' hide Visibility;
import 'package:collection/collection.dart';
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import 'package:matrix/matrix.dart';
// import 'package:collection/collection.dart';
// import 'package:http/http.dart' as http;
// import 'package:http/http.dart';
// import 'package:matrix/matrix.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_suggestions/activity_plan_repo.dart';
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/extensions/join_rule_extension.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
import 'package:fluffychat/pangea/user/controllers/user_controller.dart';
import 'package:fluffychat/utils/client_download_content_extension.dart';
import 'package:fluffychat/utils/file_selector.dart';
import 'package:fluffychat/widgets/matrix.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_suggestions/activity_plan_repo.dart';
// import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
// import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart';
// import 'package:fluffychat/pangea/common/utils/error_handler.dart';
// import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
// import 'package:fluffychat/pangea/extensions/join_rule_extension.dart';
// import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
// import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
// import 'package:fluffychat/pangea/user/controllers/user_controller.dart';
// import 'package:fluffychat/utils/client_download_content_extension.dart';
// import 'package:fluffychat/utils/file_selector.dart';
// import 'package:fluffychat/widgets/matrix.dart';
enum ActivityLaunchState {
base,
editing,
launching,
}
// enum ActivityLaunchState {
// base,
// editing,
// launching,
// }
class ActivityPlannerBuilder extends StatefulWidget {
final ActivityPlanModel initialActivity;
final String? initialFilename;
final Room room;
// class ActivityPlannerBuilder extends StatefulWidget {
// final ActivityPlanModel initialActivity;
// final String? initialFilename;
// final Room room;
final bool enabledEdits;
final bool enableMultiLaunch;
// final bool enabledEdits;
// final bool enableMultiLaunch;
final Widget Function(ActivityPlannerBuilderState) builder;
// final Widget Function(ActivityPlannerBuilderState) builder;
const ActivityPlannerBuilder({
super.key,
required this.initialActivity,
this.initialFilename,
required this.room,
required this.builder,
this.enabledEdits = false,
this.enableMultiLaunch = false,
});
// const ActivityPlannerBuilder({
// super.key,
// required this.initialActivity,
// this.initialFilename,
// required this.room,
// required this.builder,
// this.enabledEdits = false,
// this.enableMultiLaunch = false,
// });
@override
State<ActivityPlannerBuilder> createState() => ActivityPlannerBuilderState();
}
// @override
// State<ActivityPlannerBuilder> createState() => ActivityPlannerBuilderState();
// }
class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
ActivityLaunchState launchState = ActivityLaunchState.base;
Uint8List? avatar;
String? imageURL;
String? filename;
// class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
// ActivityLaunchState launchState = ActivityLaunchState.base;
// Uint8List? avatar;
// String? imageURL;
// String? filename;
int numActivities = 1;
// int numActivities = 1;
final TextEditingController titleController = TextEditingController();
final TextEditingController instructionsController = TextEditingController();
final TextEditingController vocabController = TextEditingController();
final TextEditingController participantsController = TextEditingController();
final TextEditingController learningObjectivesController =
TextEditingController();
// final TextEditingController titleController = TextEditingController();
// final TextEditingController instructionsController = TextEditingController();
// final TextEditingController vocabController = TextEditingController();
// final TextEditingController participantsController = TextEditingController();
// final TextEditingController learningObjectivesController =
// TextEditingController();
final List<Vocab> vocab = [];
late LanguageLevelTypeEnum languageLevel;
// final List<Vocab> vocab = [];
// late LanguageLevelTypeEnum languageLevel;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
// final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final StreamController stateStream = StreamController.broadcast();
// final StreamController stateStream = StreamController.broadcast();
@override
void initState() {
super.initState();
resetActivity();
}
// @override
// void initState() {
// super.initState();
// resetActivity();
// }
@override
void dispose() {
titleController.dispose();
learningObjectivesController.dispose();
instructionsController.dispose();
vocabController.dispose();
participantsController.dispose();
stateStream.close();
super.dispose();
}
// @override
// void dispose() {
// titleController.dispose();
// learningObjectivesController.dispose();
// instructionsController.dispose();
// vocabController.dispose();
// participantsController.dispose();
// stateStream.close();
// super.dispose();
// }
void update() {
if (mounted) setState(() {});
if (!stateStream.isClosed) {
stateStream.add(null);
}
}
// void update() {
// if (mounted) setState(() {});
// if (!stateStream.isClosed) {
// stateStream.add(null);
// }
// }
Room get room => widget.room;
// Room get room => widget.room;
bool get isEditing => launchState == ActivityLaunchState.editing;
bool get isLaunching => launchState == ActivityLaunchState.launching;
// bool get isEditing => launchState == ActivityLaunchState.editing;
// bool get isLaunching => launchState == ActivityLaunchState.launching;
ActivityPlanRequest get updatedRequest {
final int participants = int.tryParse(participantsController.text.trim()) ??
widget.initialActivity.req.numberOfParticipants;
final updatedReq = widget.initialActivity.req;
updatedReq.numberOfParticipants = participants;
updatedReq.cefrLevel = languageLevel;
return updatedReq;
}
// ActivityPlanRequest get updatedRequest {
// final int participants = int.tryParse(participantsController.text.trim()) ??
// widget.initialActivity.req.numberOfParticipants;
// final updatedReq = widget.initialActivity.req;
// updatedReq.numberOfParticipants = participants;
// updatedReq.cefrLevel = languageLevel;
// return updatedReq;
// }
ActivityPlanModel get updatedActivity {
return ActivityPlanModel(
req: updatedRequest,
title: titleController.text,
learningObjective: learningObjectivesController.text,
instructions: instructionsController.text,
vocab: vocab,
imageURL: imageURL,
roles: widget.initialActivity.roles,
activityId: widget.initialActivity.activityId,
);
}
// ActivityPlanModel get updatedActivity {
// return ActivityPlanModel(
// req: updatedRequest,
// title: titleController.text,
// learningObjective: learningObjectivesController.text,
// instructions: instructionsController.text,
// vocab: vocab,
// imageURL: imageURL,
// roles: widget.initialActivity.roles,
// activityId: widget.initialActivity.activityId,
// );
// }
Future<void> resetActivity() async {
avatar = null;
filename = null;
imageURL = null;
// Future<void> resetActivity() async {
// avatar = null;
// filename = null;
// imageURL = null;
titleController.text = widget.initialActivity.title;
learningObjectivesController.text =
widget.initialActivity.learningObjective;
instructionsController.text = widget.initialActivity.instructions;
participantsController.text =
widget.initialActivity.req.numberOfParticipants.toString();
// titleController.text = widget.initialActivity.title;
// learningObjectivesController.text =
// widget.initialActivity.learningObjective;
// instructionsController.text = widget.initialActivity.instructions;
// participantsController.text =
// widget.initialActivity.req.numberOfParticipants.toString();
vocab.clear();
vocab.addAll(widget.initialActivity.vocab);
// vocab.clear();
// vocab.addAll(widget.initialActivity.vocab);
languageLevel = widget.initialActivity.req.cefrLevel;
// languageLevel = widget.initialActivity.req.cefrLevel;
imageURL = widget.initialActivity.imageURL;
filename = widget.initialFilename;
if (widget.initialActivity.imageURL != null) {
await _setAvatarByURL(widget.initialActivity.imageURL!);
}
// imageURL = widget.initialActivity.imageURL;
// filename = widget.initialFilename;
// if (widget.initialActivity.imageURL != null) {
// await _setAvatarByURL(widget.initialActivity.imageURL!);
// }
update();
}
// update();
// }
Future<void> overrideActivity(ActivityPlanModel override) async {
avatar = null;
filename = null;
imageURL = null;
// Future<void> overrideActivity(ActivityPlanModel override) async {
// avatar = null;
// filename = null;
// imageURL = null;
titleController.text = override.title;
learningObjectivesController.text = override.learningObjective;
instructionsController.text = override.instructions;
participantsController.text = override.req.numberOfParticipants.toString();
vocab.clear();
vocab.addAll(override.vocab);
languageLevel = override.req.cefrLevel;
// titleController.text = override.title;
// learningObjectivesController.text = override.learningObjective;
// instructionsController.text = override.instructions;
// participantsController.text = override.req.numberOfParticipants.toString();
// vocab.clear();
// vocab.addAll(override.vocab);
// languageLevel = override.req.cefrLevel;
if (override.imageURL != null) {
await _setAvatarByURL(override.imageURL!);
}
// if (override.imageURL != null) {
// await _setAvatarByURL(override.imageURL!);
// }
update();
}
// update();
// }
void startEditing() {
setLaunchState(ActivityLaunchState.editing);
}
// void startEditing() {
// setLaunchState(ActivityLaunchState.editing);
// }
void setLaunchState(ActivityLaunchState state) {
if (state == ActivityLaunchState.launching) {
_addBookmarkedActivity();
}
// void setLaunchState(ActivityLaunchState state) {
// if (state == ActivityLaunchState.launching) {
// _addBookmarkedActivity();
// }
launchState = state;
update();
}
// launchState = state;
// update();
// }
void addVocab() {
vocab.insert(
0,
Vocab(
lemma: vocabController.text.trim(),
pos: "",
),
);
vocabController.clear();
update();
}
// void addVocab() {
// vocab.insert(
// 0,
// Vocab(
// lemma: vocabController.text.trim(),
// pos: "",
// ),
// );
// vocabController.clear();
// update();
// }
void removeVocab(int index) {
vocab.removeAt(index);
update();
}
// void removeVocab(int index) {
// vocab.removeAt(index);
// update();
// }
void setLanguageLevel(LanguageLevelTypeEnum level) {
languageLevel = level;
update();
}
// void setLanguageLevel(LanguageLevelTypeEnum level) {
// languageLevel = level;
// update();
// }
Future<void> selectAvatar() async {
final photo = await selectFiles(
context,
type: FileSelectorType.images,
allowMultiple: false,
);
final bytes = await photo.singleOrNull?.readAsBytes();
avatar = bytes;
imageURL = null;
filename = photo.singleOrNull?.name;
update();
}
// Future<void> selectAvatar() async {
// final photo = await selectFiles(
// context,
// type: FileSelectorType.images,
// allowMultiple: false,
// );
// final bytes = await photo.singleOrNull?.readAsBytes();
// avatar = bytes;
// imageURL = null;
// filename = photo.singleOrNull?.name;
// update();
// }
void setNumActivities(int count) {
numActivities = count;
update();
}
// void setNumActivities(int count) {
// numActivities = count;
// update();
// }
Future<void> _setAvatarByURL(String url) async {
try {
if (avatar == null) {
if (url.startsWith("mxc")) {
final client = Matrix.of(context).client;
final mxcUri = Uri.parse(url);
final data = await client.downloadMxcCached(mxcUri);
avatar = data;
filename = Uri.encodeComponent(
mxcUri.pathSegments.last,
);
} else {
final Response response = await http.get(
Uri.parse(url),
headers: {
'Authorization':
'Bearer ${MatrixState.pangeaController.userController.accessToken}',
},
);
if (response.statusCode != 200) {
throw Exception(
"Failed to download image from URL: ${response.statusCode}",
);
}
avatar = response.bodyBytes;
filename = Uri.encodeComponent(
Uri.parse(url).pathSegments.last,
);
}
}
} catch (err, s) {
ErrorHandler.logError(
e: err,
s: s,
data: {
"imageURL": widget.initialActivity.imageURL,
},
);
}
}
// Future<void> _setAvatarByURL(String url) async {
// try {
// if (avatar == null) {
// if (url.startsWith("mxc")) {
// final client = Matrix.of(context).client;
// final mxcUri = Uri.parse(url);
// final data = await client.downloadMxcCached(mxcUri);
// avatar = data;
// filename = Uri.encodeComponent(
// mxcUri.pathSegments.last,
// );
// } else {
// final Response response = await http.get(
// Uri.parse(url),
// headers: {
// 'Authorization':
// 'Bearer ${MatrixState.pangeaController.userController.accessToken}',
// },
// );
// if (response.statusCode != 200) {
// throw Exception(
// "Failed to download image from URL: ${response.statusCode}",
// );
// }
// avatar = response.bodyBytes;
// filename = Uri.encodeComponent(
// Uri.parse(url).pathSegments.last,
// );
// }
// }
// } catch (err, s) {
// ErrorHandler.logError(
// e: err,
// s: s,
// data: {
// "imageURL": widget.initialActivity.imageURL,
// },
// );
// }
// }
Future<void> updateImageURL() async {
if (avatar == null) return;
final url = await Matrix.of(context).client.uploadContent(
avatar!,
filename: filename,
);
imageURL = url.toString();
update();
}
// Future<void> updateImageURL() async {
// if (avatar == null) return;
// final url = await Matrix.of(context).client.uploadContent(
// avatar!,
// filename: filename,
// );
// imageURL = url.toString();
// update();
// }
Future<void> saveEdits() async {
if (!formKey.currentState!.validate()) return;
await updateImageURL();
setLaunchState(ActivityLaunchState.base);
// Future<void> saveEdits() async {
// if (!formKey.currentState!.validate()) return;
// await updateImageURL();
// setLaunchState(ActivityLaunchState.base);
await _updateBookmarkedActivity();
update();
}
// await _updateBookmarkedActivity();
// update();
// }
Future<void> clearEdits() async {
await resetActivity();
setLaunchState(ActivityLaunchState.base);
}
// Future<void> clearEdits() async {
// await resetActivity();
// setLaunchState(ActivityLaunchState.base);
// }
UserController get _userController =>
MatrixState.pangeaController.userController;
// UserController get _userController =>
// MatrixState.pangeaController.userController;
bool get isBookmarked =>
_userController.isBookmarked(updatedActivity.activityId);
// bool get isBookmarked =>
// _userController.isBookmarked(updatedActivity.activityId);
Future<void> toggleBookmarkedActivity() async {
isBookmarked
? await _removeBookmarkedActivity()
: await _addBookmarkedActivity();
update();
}
// Future<void> toggleBookmarkedActivity() async {
// isBookmarked
// ? await _removeBookmarkedActivity()
// : await _addBookmarkedActivity();
// update();
// }
Future<void> _addBookmarkedActivity() async {
await _userController.addBookmarkedActivity(
activityId: updatedActivity.activityId,
);
await ActivityPlanRepo.set(updatedActivity);
}
// Future<void> _addBookmarkedActivity() async {
// await _userController.addBookmarkedActivity(
// activityId: updatedActivity.activityId,
// );
// await ActivityPlanRepo.set(updatedActivity);
// }
Future<void> _updateBookmarkedActivity() async {
// save updates locally, in case choreo results in error
await ActivityPlanRepo.set(updatedActivity);
// Future<void> _updateBookmarkedActivity() async {
// // save updates locally, in case choreo results in error
// await ActivityPlanRepo.set(updatedActivity);
// prevent an error or delay from the choreo endpoint bubbling up
// in the UI, since the changes are still stored locally
ActivityPlanRepo.update(
updatedActivity,
).then((resp) {
_userController.updateBookmarkedActivity(
activityId: widget.initialActivity.activityId,
newActivityId: resp.activityId,
);
});
}
// // prevent an error or delay from the choreo endpoint bubbling up
// // in the UI, since the changes are still stored locally
// ActivityPlanRepo.update(
// updatedActivity,
// ).then((resp) {
// _userController.updateBookmarkedActivity(
// activityId: widget.initialActivity.activityId,
// newActivityId: resp.activityId,
// );
// });
// }
Future<void> _removeBookmarkedActivity() async {
await _userController.removeBookmarkedActivity(
activityId: updatedActivity.activityId,
);
await ActivityPlanRepo.remove(updatedActivity.activityId);
}
// Future<void> _removeBookmarkedActivity() async {
// await _userController.removeBookmarkedActivity(
// activityId: updatedActivity.activityId,
// );
// await ActivityPlanRepo.remove(updatedActivity.activityId);
// }
Future<List<String>> launchToSpace() async {
final List<String> activityRoomIDs = [];
try {
return Future.wait(
List.generate(numActivities, (i) async {
final id = await _launchActivityRoom(i);
activityRoomIDs.add(id);
return id;
}),
);
} catch (e) {
_cleanupFailedLaunch(activityRoomIDs);
rethrow;
}
}
// Future<List<String>> launchToSpace() async {
// final List<String> activityRoomIDs = [];
// try {
// return Future.wait(
// List.generate(numActivities, (i) async {
// final id = await _launchActivityRoom(i);
// activityRoomIDs.add(id);
// return id;
// }),
// );
// } catch (e) {
// _cleanupFailedLaunch(activityRoomIDs);
// rethrow;
// }
// }
Future<String> _launchActivityRoom(int index) async {
await updateImageURL();
final roomID = await Matrix.of(context).client.createRoom(
creationContent: {
'type':
"${PangeaRoomTypes.activitySession}:${updatedActivity.activityId}",
},
visibility: Visibility.private,
name: "${updatedActivity.title} ${index + 1}",
initialState: [
StateEvent(
type: PangeaEventTypes.activityPlan,
content: updatedActivity.toJson(),
),
if (imageURL != null)
StateEvent(
type: EventTypes.RoomAvatar,
content: {'url': imageURL},
),
RoomDefaults.defaultPowerLevels(
Matrix.of(context).client.userID!,
),
await Matrix.of(context).client.pangeaJoinRules(
'knock_restricted',
allow: [
{
"type": "m.room_membership",
"room_id": room.id,
}
],
),
],
);
// Future<String> _launchActivityRoom(int index) async {
// await updateImageURL();
// final roomID = await Matrix.of(context).client.createRoom(
// creationContent: {
// 'type':
// "${PangeaRoomTypes.activitySession}:${updatedActivity.activityId}",
// },
// visibility: Visibility.private,
// name: "${updatedActivity.title} ${index + 1}",
// initialState: [
// StateEvent(
// type: PangeaEventTypes.activityPlan,
// content: updatedActivity.toJson(),
// ),
// if (imageURL != null)
// StateEvent(
// type: EventTypes.RoomAvatar,
// content: {'url': imageURL},
// ),
// RoomDefaults.defaultPowerLevels(
// Matrix.of(context).client.userID!,
// ),
// await Matrix.of(context).client.pangeaJoinRules(
// 'knock_restricted',
// allow: [
// {
// "type": "m.room_membership",
// "room_id": room.id,
// }
// ],
// ),
// ],
// );
Room? activityRoom = room.client.getRoomById(roomID);
if (activityRoom == null) {
await room.client.waitForRoomInSync(roomID);
activityRoom = room.client.getRoomById(roomID);
if (activityRoom == null) {
throw Exception("Failed to create activity room");
}
}
// Room? activityRoom = room.client.getRoomById(roomID);
// if (activityRoom == null) {
// await room.client.waitForRoomInSync(roomID);
// activityRoom = room.client.getRoomById(roomID);
// if (activityRoom == null) {
// throw Exception("Failed to create activity room");
// }
// }
await room.addToSpace(activityRoom.id);
if (activityRoom.pangeaSpaceParents.isEmpty) {
await room.client.waitForRoomInSync(activityRoom.id);
}
// await room.addToSpace(activityRoom.id);
// if (activityRoom.pangeaSpaceParents.isEmpty) {
// await room.client.waitForRoomInSync(activityRoom.id);
// }
return activityRoom.id;
}
// return activityRoom.id;
// }
Future<void> _cleanupFailedLaunch(List<String> roomIds) async {
final futures = roomIds.map((id) async {
final room = Matrix.of(context).client.getRoomById(id);
if (room == null) return;
// Future<void> _cleanupFailedLaunch(List<String> roomIds) async {
// final futures = roomIds.map((id) async {
// final room = Matrix.of(context).client.getRoomById(id);
// if (room == null) return;
try {
await room.leave();
} catch (e) {
debugPrint("Failed to leave room $id: $e");
}
});
// try {
// await room.leave();
// } catch (e) {
// debugPrint("Failed to leave room $id: $e");
// }
// });
await Future.wait(futures);
}
// await Future.wait(futures);
// }
@override
Widget build(BuildContext context) => widget.builder(this);
}
// @override
// Widget build(BuildContext context) => widget.builder(this);
// }

View file

@ -1,158 +1,158 @@
import 'package:flutter/material.dart';
// import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:matrix/matrix.dart';
// import 'package:go_router/go_router.dart';
// import 'package:material_symbols_icons/symbols.dart';
// import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/activity_planner/activity_planner_page_appbar.dart';
import 'package:fluffychat/pangea/activity_planner/bookmarked_activity_list.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_area.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_constants.dart';
import 'package:fluffychat/pangea/common/widgets/customized_svg.dart';
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
import 'package:fluffychat/widgets/matrix.dart';
// import 'package:fluffychat/config/app_config.dart';
// import 'package:fluffychat/l10n/l10n.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_planner_page_appbar.dart';
// import 'package:fluffychat/pangea/activity_planner/bookmarked_activity_list.dart';
// import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_area.dart';
// import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_constants.dart';
// import 'package:fluffychat/pangea/common/widgets/customized_svg.dart';
// import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
// import 'package:fluffychat/widgets/matrix.dart';
enum PageMode {
featuredActivities,
savedActivities,
}
// enum PageMode {
// featuredActivities,
// savedActivities,
// }
class ActivityPlannerPage extends StatefulWidget {
final String roomID;
const ActivityPlannerPage({super.key, required this.roomID});
// class ActivityPlannerPage extends StatefulWidget {
// final String roomID;
// const ActivityPlannerPage({super.key, required this.roomID});
@override
ActivityPlannerPageState createState() => ActivityPlannerPageState();
}
// @override
// ActivityPlannerPageState createState() => ActivityPlannerPageState();
// }
class ActivityPlannerPageState extends State<ActivityPlannerPage> {
PageMode pageMode = PageMode.featuredActivities;
Room? get room => Matrix.of(context).client.getRoomById(widget.roomID);
// class ActivityPlannerPageState extends State<ActivityPlannerPage> {
// PageMode pageMode = PageMode.featuredActivities;
// Room? get room => Matrix.of(context).client.getRoomById(widget.roomID);
void _setPageMode(PageMode? mode) {
if (mode == null) return;
setState(() => pageMode = mode);
}
// void _setPageMode(PageMode? mode) {
// if (mode == null) return;
// setState(() => pageMode = mode);
// }
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
Widget? body;
switch (pageMode) {
case PageMode.savedActivities:
if (room != null) {
body = BookmarkedActivitiesList(room: room!);
}
break;
case PageMode.featuredActivities:
if (room != null) {
body = Expanded(
child: SingleChildScrollView(
child: ActivitySuggestionsArea(
scrollDirection: Axis.vertical,
room: room!,
),
),
);
}
break;
}
// @override
// Widget build(BuildContext context) {
// final theme = Theme.of(context);
// Widget? body;
// switch (pageMode) {
// case PageMode.savedActivities:
// if (room != null) {
// body = BookmarkedActivitiesList(room: room!);
// }
// break;
// case PageMode.featuredActivities:
// if (room != null) {
// body = Expanded(
// child: SingleChildScrollView(
// child: ActivitySuggestionsArea(
// scrollDirection: Axis.vertical,
// room: room!,
// ),
// ),
// );
// }
// break;
// }
return SafeArea(
child: Scaffold(
appBar: ActivityPlannerPageAppBar(
pageMode: pageMode,
roomID: widget.roomID,
),
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 800.0),
child: Column(
children: [
if ([PageMode.featuredActivities, PageMode.savedActivities]
.contains(pageMode))
Padding(
padding: const EdgeInsets.all(16.0),
child: Wrap(
spacing: 12.0,
runSpacing: 12.0,
alignment: WrapAlignment.center,
children: [
FilterChip(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(32),
),
label: Row(
spacing: 8.0,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Symbols.star_shine, size: 24.0),
Text(L10n.of(context).featuredActivities),
],
),
selected: pageMode == PageMode.featuredActivities,
onSelected: (_) => _setPageMode(
PageMode.featuredActivities,
),
),
FilterChip(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(32),
),
label: Row(
spacing: 8.0,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.save_outlined, size: 24.0),
Text(L10n.of(context).saved),
],
),
selected: pageMode == PageMode.savedActivities,
onSelected: (_) => _setPageMode(
PageMode.savedActivities,
),
),
FilterChip(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(32),
),
label: Row(
spacing: 8.0,
mainAxisSize: MainAxisSize.min,
children: [
CustomizedSvg(
svgUrl:
"${AppConfig.assetsBaseURL}/${ActivitySuggestionsConstants.crayonIconPath}",
colorReplacements: {
"#CDBEF9": colorToHex(
theme.colorScheme.secondary,
),
},
height: 24.0,
width: 24.0,
),
Text(L10n.of(context).createActivityPlan),
],
),
selected: false,
onSelected: (_) => context.go(
'/rooms/spaces/${widget.roomID}/details/planner/generator',
),
),
],
),
),
body ??
ErrorIndicator(
message: L10n.of(context).oopsSomethingWentWrong,
),
],
),
),
),
),
);
}
}
// return SafeArea(
// child: Scaffold(
// appBar: ActivityPlannerPageAppBar(
// pageMode: pageMode,
// roomID: widget.roomID,
// ),
// body: Center(
// child: ConstrainedBox(
// constraints: const BoxConstraints(maxWidth: 800.0),
// child: Column(
// children: [
// if ([PageMode.featuredActivities, PageMode.savedActivities]
// .contains(pageMode))
// Padding(
// padding: const EdgeInsets.all(16.0),
// child: Wrap(
// spacing: 12.0,
// runSpacing: 12.0,
// alignment: WrapAlignment.center,
// children: [
// FilterChip(
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(32),
// ),
// label: Row(
// spacing: 8.0,
// mainAxisSize: MainAxisSize.min,
// children: [
// const Icon(Symbols.star_shine, size: 24.0),
// Text(L10n.of(context).featuredActivities),
// ],
// ),
// selected: pageMode == PageMode.featuredActivities,
// onSelected: (_) => _setPageMode(
// PageMode.featuredActivities,
// ),
// ),
// FilterChip(
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(32),
// ),
// label: Row(
// spacing: 8.0,
// mainAxisSize: MainAxisSize.min,
// children: [
// const Icon(Icons.save_outlined, size: 24.0),
// Text(L10n.of(context).saved),
// ],
// ),
// selected: pageMode == PageMode.savedActivities,
// onSelected: (_) => _setPageMode(
// PageMode.savedActivities,
// ),
// ),
// FilterChip(
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(32),
// ),
// label: Row(
// spacing: 8.0,
// mainAxisSize: MainAxisSize.min,
// children: [
// CustomizedSvg(
// svgUrl:
// "${AppConfig.assetsBaseURL}/${ActivitySuggestionsConstants.crayonIconPath}",
// colorReplacements: {
// "#CDBEF9": colorToHex(
// theme.colorScheme.secondary,
// ),
// },
// height: 24.0,
// width: 24.0,
// ),
// Text(L10n.of(context).createActivityPlan),
// ],
// ),
// selected: false,
// onSelected: (_) => context.go(
// '/rooms/spaces/${widget.roomID}/details/planner/generator',
// ),
// ),
// ],
// ),
// ),
// body ??
// ErrorIndicator(
// message: L10n.of(context).oopsSomethingWentWrong,
// ),
// ],
// ),
// ),
// ),
// ),
// );
// }
// }

View file

@ -1,64 +1,64 @@
import 'package:flutter/material.dart';
// import 'package:flutter/material.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart';
// import 'package:fluffychat/config/themes.dart';
// import 'package:fluffychat/l10n/l10n.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart';
class ActivityPlannerPageAppBar extends StatelessWidget
implements PreferredSizeWidget {
final PageMode pageMode;
final String roomID;
// class ActivityPlannerPageAppBar extends StatelessWidget
// implements PreferredSizeWidget {
// final PageMode pageMode;
// final String roomID;
const ActivityPlannerPageAppBar({
required this.pageMode,
required this.roomID,
super.key,
});
// const ActivityPlannerPageAppBar({
// required this.pageMode,
// required this.roomID,
// super.key,
// });
@override
Size get preferredSize => const Size.fromHeight(72);
// @override
// Size get preferredSize => const Size.fromHeight(72);
@override
Widget build(BuildContext context) {
final l10n = L10n.of(context);
// @override
// Widget build(BuildContext context) {
// final l10n = L10n.of(context);
return AppBar(
leading: FluffyThemes.isColumnMode(context)
? Row(
children: [
const SizedBox(width: 8.0),
BackButton(
onPressed: Navigator.of(context).pop,
),
],
)
: null,
title: pageMode == PageMode.savedActivities
? Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.save),
const SizedBox(width: 8),
Flexible(
child: Text(l10n.mySavedActivities),
),
],
)
: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.event_note_outlined),
const SizedBox(width: 8),
Flexible(
child: Text(
l10n.activityPlannerTitle,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
}
// return AppBar(
// leading: FluffyThemes.isColumnMode(context)
// ? Row(
// children: [
// const SizedBox(width: 8.0),
// BackButton(
// onPressed: Navigator.of(context).pop,
// ),
// ],
// )
// : null,
// title: pageMode == PageMode.savedActivities
// ? Row(
// mainAxisSize: MainAxisSize.min,
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// const Icon(Icons.save),
// const SizedBox(width: 8),
// Flexible(
// child: Text(l10n.mySavedActivities),
// ),
// ],
// )
// : Row(
// mainAxisSize: MainAxisSize.min,
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// const Icon(Icons.event_note_outlined),
// const SizedBox(width: 8),
// Flexible(
// child: Text(
// l10n.activityPlannerTitle,
// overflow: TextOverflow.ellipsis,
// ),
// ),
// ],
// ),
// );
// }
// }

View file

@ -1,122 +1,122 @@
import 'package:flutter/material.dart';
// import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
// import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/user/controllers/user_controller.dart';
import 'package:fluffychat/widgets/matrix.dart';
// import 'package:fluffychat/config/themes.dart';
// import 'package:fluffychat/l10n/l10n.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
// import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card.dart';
// import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog.dart';
// import 'package:fluffychat/pangea/common/utils/error_handler.dart';
// import 'package:fluffychat/pangea/user/controllers/user_controller.dart';
// import 'package:fluffychat/widgets/matrix.dart';
class BookmarkedActivitiesList extends StatefulWidget {
final Room room;
const BookmarkedActivitiesList({
super.key,
required this.room,
});
// class BookmarkedActivitiesList extends StatefulWidget {
// final Room room;
// const BookmarkedActivitiesList({
// super.key,
// required this.room,
// });
@override
BookmarkedActivitiesListState createState() =>
BookmarkedActivitiesListState();
}
// @override
// BookmarkedActivitiesListState createState() =>
// BookmarkedActivitiesListState();
// }
class BookmarkedActivitiesListState extends State<BookmarkedActivitiesList> {
bool _loading = true;
// class BookmarkedActivitiesListState extends State<BookmarkedActivitiesList> {
// bool _loading = true;
@override
void initState() {
super.initState();
_loadBookmarkedActivities();
}
// @override
// void initState() {
// super.initState();
// _loadBookmarkedActivities();
// }
List<ActivityPlanModel> get _bookmarkedActivities =>
_userController.getBookmarkedActivitiesSync();
// List<ActivityPlanModel> get _bookmarkedActivities =>
// _userController.getBookmarkedActivitiesSync();
bool get _isColumnMode => FluffyThemes.isColumnMode(context);
// bool get _isColumnMode => FluffyThemes.isColumnMode(context);
double get cardHeight => _isColumnMode ? 325.0 : 250.0;
double get cardWidth => _isColumnMode ? 225.0 : 150.0;
// double get cardHeight => _isColumnMode ? 325.0 : 250.0;
// double get cardWidth => _isColumnMode ? 225.0 : 150.0;
UserController get _userController =>
MatrixState.pangeaController.userController;
// UserController get _userController =>
// MatrixState.pangeaController.userController;
Future<void> _loadBookmarkedActivities() async {
try {
setState(() => _loading = true);
await _userController.getBookmarkedActivities();
} catch (e, s) {
ErrorHandler.logError(
e: e,
s: s,
data: {
'roomId': widget.room.id,
},
);
} finally {
if (mounted) setState(() => _loading = false);
}
}
// Future<void> _loadBookmarkedActivities() async {
// try {
// setState(() => _loading = true);
// await _userController.getBookmarkedActivities();
// } catch (e, s) {
// ErrorHandler.logError(
// e: e,
// s: s,
// data: {
// 'roomId': widget.room.id,
// },
// );
// } finally {
// if (mounted) setState(() => _loading = false);
// }
// }
@override
Widget build(BuildContext context) {
final l10n = L10n.of(context);
if (_loading) {
return const Center(
child: CircularProgressIndicator.adaptive(),
);
}
// @override
// Widget build(BuildContext context) {
// final l10n = L10n.of(context);
// if (_loading) {
// return const Center(
// child: CircularProgressIndicator.adaptive(),
// );
// }
if (_bookmarkedActivities.isEmpty) {
return Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 200),
child: Text(
l10n.noSavedActivities,
textAlign: TextAlign.center,
),
),
);
}
// if (_bookmarkedActivities.isEmpty) {
// return Center(
// child: Container(
// constraints: const BoxConstraints(maxWidth: 200),
// child: Text(
// l10n.noSavedActivities,
// textAlign: TextAlign.center,
// ),
// ),
// );
// }
return Expanded(
child: SingleChildScrollView(
child: SizedBox(
width: 800.0,
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
runSpacing: 16.0,
spacing: 4.0,
children: _bookmarkedActivities.map((activity) {
return ActivityPlannerBuilder(
initialActivity: activity,
room: widget.room,
builder: (controller) {
return ActivitySuggestionCard(
controller: controller,
onPressed: () {
showDialog(
context: context,
builder: (context) {
return ActivitySuggestionDialog(
controller: controller,
buttonText: l10n.launchActivityButton,
);
},
);
},
width: cardWidth,
height: cardHeight,
);
},
);
}).toList(),
),
),
),
);
}
}
// return Expanded(
// child: SingleChildScrollView(
// child: SizedBox(
// width: 800.0,
// child: Wrap(
// alignment: WrapAlignment.spaceEvenly,
// runSpacing: 16.0,
// spacing: 4.0,
// children: _bookmarkedActivities.map((activity) {
// return ActivityPlannerBuilder(
// initialActivity: activity,
// room: widget.room,
// builder: (controller) {
// return ActivitySuggestionCard(
// controller: controller,
// onPressed: () {
// showDialog(
// context: context,
// builder: (context) {
// return ActivitySuggestionDialog(
// controller: controller,
// buttonText: l10n.launchActivityButton,
// );
// },
// );
// },
// width: cardWidth,
// height: cardHeight,
// );
// },
// );
// }).toList(),
// ),
// ),
// ),
// );
// }
// }

View file

@ -1,60 +1,60 @@
import 'package:flutter/material.dart';
// import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/activity_generator/list_request_schema.dart';
// import 'package:fluffychat/pangea/activity_generator/list_request_schema.dart';
class SuggestionFormField extends StatelessWidget {
final List<ActivitySettingResponseSchema>? suggestions;
final String? Function(String?)? validator;
final int? maxLength;
final String label;
final String placeholder;
final TextEditingController controller;
// class SuggestionFormField extends StatelessWidget {
// final List<ActivitySettingResponseSchema>? suggestions;
// final String? Function(String?)? validator;
// final int? maxLength;
// final String label;
// final String placeholder;
// final TextEditingController controller;
const SuggestionFormField({
super.key,
this.suggestions,
required this.placeholder,
this.validator,
this.maxLength,
required this.label,
required this.controller,
});
// const SuggestionFormField({
// super.key,
// this.suggestions,
// required this.placeholder,
// this.validator,
// this.maxLength,
// required this.label,
// required this.controller,
// });
@override
Widget build(BuildContext context) {
return Autocomplete<String>(
initialValue: TextEditingValue(text: controller.text),
optionsBuilder: (TextEditingValue textEditingValue) {
return (suggestions ?? [])
.where((ActivitySettingResponseSchema option) {
return option.name
.toLowerCase()
.contains(textEditingValue.text.toLowerCase());
}).map((ActivitySettingResponseSchema e) => e.name);
},
onSelected: (val) => controller.text = val,
fieldViewBuilder: (
BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted,
) {
textEditingController.value = controller.value;
textEditingController.addListener(() {
controller.value = textEditingController.value;
});
return TextFormField(
controller: textEditingController,
focusNode: focusNode,
decoration: InputDecoration(
labelText: label,
hintText: placeholder,
),
validator: validator,
maxLength: maxLength,
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
);
},
);
}
}
// @override
// Widget build(BuildContext context) {
// return Autocomplete<String>(
// initialValue: TextEditingValue(text: controller.text),
// optionsBuilder: (TextEditingValue textEditingValue) {
// return (suggestions ?? [])
// .where((ActivitySettingResponseSchema option) {
// return option.name
// .toLowerCase()
// .contains(textEditingValue.text.toLowerCase());
// }).map((ActivitySettingResponseSchema e) => e.name);
// },
// onSelected: (val) => controller.text = val,
// fieldViewBuilder: (
// BuildContext context,
// TextEditingController textEditingController,
// FocusNode focusNode,
// VoidCallback onFieldSubmitted,
// ) {
// textEditingController.value = controller.value;
// textEditingController.addListener(() {
// controller.value = textEditingController.value;
// });
// return TextFormField(
// controller: textEditingController,
// focusNode: focusNode,
// decoration: InputDecoration(
// labelText: label,
// hintText: placeholder,
// ),
// validator: validator,
// maxLength: maxLength,
// onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
// );
// },
// );
// }
// }

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_participant_indicator.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
@ -10,7 +11,8 @@ import 'package:fluffychat/pangea/spaces/utils/load_participants_util.dart';
import 'package:fluffychat/widgets/avatar.dart';
class ActivityParticipantList extends StatelessWidget {
final Room room;
final ActivityPlanModel activity;
final Room? room;
final Function(String)? onTap;
final bool Function(String)? canSelect;
@ -19,7 +21,8 @@ class ActivityParticipantList extends StatelessWidget {
const ActivityParticipantList({
super.key,
required this.room,
required this.activity,
this.room,
this.onTap,
this.canSelect,
this.isSelected,
@ -32,8 +35,8 @@ class ActivityParticipantList extends StatelessWidget {
room: room,
builder: (context, participants) {
final theme = Theme.of(context);
final availableRoles = room.activityPlan!.roles;
final assignedRoles = room.assignedRoles ?? {};
final availableRoles = activity.roles;
final assignedRoles = room?.assignedRoles ?? {};
final remainingMembers = participants.participants.where(
(p) => !assignedRoles.values.any((r) => r.userId == p.id),

View file

@ -335,6 +335,14 @@ extension ActivityRoomExtension on Room {
bool get showActivityFinished =>
showActivityChatUI && ownRole != null && hasCompletedActivity;
String? get activityId {
if (!isActivitySession) return null;
if (isActivityRoomType) {
return roomType!.split(":").last;
}
return activityPlan?.activityId;
}
Future<ActivitySummaryAnalyticsModel> getActivityAnalytics() async {
// wait for local storage box to init in getAnalytics initialization
if (!MatrixState.pangeaController.getAnalytics.initCompleter.isCompleted) {

View file

@ -60,6 +60,7 @@ class ActivityFinishedStatusMessage extends StatelessWidget {
throw L10n.of(context).noCourseFound;
}
await coursePlan.init();
final activityId = controller.room.activityPlan!.activityId;
final topicId = coursePlan.topicID(activityId);
if (topicId == null) {

View file

@ -10,7 +10,7 @@ import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_constants.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_constants.dart';
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
@ -144,7 +144,7 @@ class ActivityPinnedMessageState extends State<ActivityPinnedMessage> {
),
CachedNetworkImage(
imageUrl:
"${AppConfig.assetsBaseURL}/${ActivitySuggestionsConstants.endActivityAssetPath}",
"${AppConfig.assetsBaseURL}/${ActivitySessionConstants.endActivityAssetPath}",
width: isColumnMode ? 240.0 : 120.0,
),
Row(

View file

@ -1,94 +0,0 @@
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_analytics_chip.dart';
import 'package:fluffychat/pangea/activity_summary/activity_summary_analytics_model.dart';
import 'package:fluffychat/pangea/activity_summary/activity_summary_response_model.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart';
class ActivityResultsCarousel extends StatelessWidget {
final String userId;
final ActivityRoleModel selectedRole;
final ParticipantSummaryModel summary;
final ActivitySummaryAnalyticsModel? analytics;
final User? user;
const ActivityResultsCarousel({
super.key,
required this.userId,
required this.selectedRole,
required this.summary,
required this.analytics,
this.user,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isColumnMode = FluffyThemes.isColumnMode(context);
return Container(
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHighest,
),
padding: const EdgeInsets.all(12.0),
alignment: Alignment.centerLeft,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: isColumnMode ? 80.0 : 125.0,
child: SingleChildScrollView(
child: Text(
summary.feedback,
style: const TextStyle(fontSize: 12.0),
),
),
),
const SizedBox(height: 10.0),
Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
if (analytics != null)
ActivityAnalyticsChip(
ConstructTypeEnum.vocab.indicator.icon,
"${analytics!.uniqueConstructCountForUser(userId, ConstructTypeEnum.vocab)}",
),
if (analytics != null)
ActivityAnalyticsChip(
ConstructTypeEnum.morph.indicator.icon,
"${analytics!.uniqueConstructCountForUser(userId, ConstructTypeEnum.morph)}",
),
ActivityAnalyticsChip(
Icons.school,
summary.cefrLevel,
),
...summary.superlatives.map(
(sup) => Container(
padding: const EdgeInsets.all(4.0),
decoration: BoxDecoration(
color: theme.colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(20),
),
child: Text(
sup,
style: const TextStyle(
fontSize: 12.0,
),
),
),
),
],
),
],
),
);
}
}

View file

@ -1,4 +1,4 @@
class ActivitySuggestionsConstants {
class ActivitySessionConstants {
static const String plusIconPath = "add_icon.svg";
static const String crayonIconPath = "make_your_own_icon.svg";
static const String modeImageFileStart = "activityplanner_mode_";

View file

@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
class ActivitySuggestionCardRow extends StatelessWidget {
class ActivitySessionDetailsRow extends StatelessWidget {
final IconData? icon;
final Widget? leading;
final Widget child;
final double? iconSize;
const ActivitySuggestionCardRow({
const ActivitySessionDetailsRow({
required this.child,
this.icon,
this.leading,

View file

@ -2,15 +2,20 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart';
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
import 'package:fluffychat/pangea/course_plans/course_activity_repo.dart';
import 'package:fluffychat/pangea/course_plans/course_plan_room_extension.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
enum SessionState {
notStarted,
@ -20,10 +25,16 @@ enum SessionState {
}
class ActivitySessionStartPage extends StatefulWidget {
final Room room;
final String activityId;
final bool isNew;
final Room? room;
final String parentId;
const ActivitySessionStartPage({
super.key,
required this.room,
required this.activityId,
this.isNew = false,
this.room,
required this.parentId,
});
@override
@ -32,69 +43,86 @@ class ActivitySessionStartPage extends StatefulWidget {
}
class ActivitySessionStartController extends State<ActivitySessionStartPage> {
bool _started = false;
ActivityPlanModel? activity;
bool loading = true;
Object? error;
bool showInstructions = false;
String? _selectedRoleId;
@override
void initState() {
super.initState();
_loadActivity();
}
@override
void didUpdateWidget(covariant ActivitySessionStartPage oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.room.id != widget.room.id) {
if (oldWidget.room?.id != widget.room?.id) {
setState(() {
_started = false;
_selectedRoleId = null;
showInstructions = false;
});
}
if (oldWidget.activityId != widget.activityId) {
_loadActivity();
}
}
Room get room => widget.room;
Room? get room => widget.room;
bool get isBotRoomMember => room.getParticipants().any(
(p) => p.id == BotName.byEnvironment,
Room? get parent => Matrix.of(context).client.getRoomById(
widget.parentId,
);
String get displayname => room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)),
);
bool get isBotRoomMember =>
room?.getParticipants().any(
(p) => p.id == BotName.byEnvironment,
) ??
false;
SessionState get state {
if (room.ownRole != null) return SessionState.confirmedRole;
if (room?.ownRole != null) return SessionState.confirmedRole;
if (_selectedRoleId != null) return SessionState.selectedRole;
if (room.isRoomAdmin && !_started) return SessionState.notStarted;
if (room == null) {
return widget.isNew
? SessionState.notSelectedRole
: SessionState.notStarted;
}
return SessionState.notSelectedRole;
}
String get descriptionText {
String? get descriptionText {
switch (state) {
case SessionState.confirmedRole:
return L10n.of(context).waitingToFillRole(room.remainingRoles);
return L10n.of(context).waitingToFillRole(room!.remainingRoles);
case SessionState.selectedRole:
return room.activityPlan!.learningObjective;
return activity!.description;
case SessionState.notStarted:
return L10n.of(context).letsGo;
return null;
case SessionState.notSelectedRole:
return room.isRoomAdmin
return room?.isRoomAdmin ?? false
? L10n.of(context).chooseRole
: L10n.of(context).chooseRoleToParticipate;
}
}
String get buttonText => state == SessionState.notStarted
? L10n.of(context).start
: L10n.of(context).confirm;
bool get enableButtons => [
SessionState.notStarted,
SessionState.selectedRole,
].contains(state);
bool canSelectParticipant(String id) {
if (state == SessionState.confirmedRole) return false;
if (state == SessionState.confirmedRole ||
state == SessionState.notStarted) {
return false;
}
final availableRoles = room.activityPlan!.roles;
final assignedRoles = room.assignedRoles ?? {};
final availableRoles = activity!.roles;
final assignedRoles = room?.assignedRoles ?? {};
final unassignedIds = availableRoles.keys
.where((id) => !assignedRoles.containsKey(id))
.toList();
@ -103,7 +131,7 @@ class ActivitySessionStartController extends State<ActivitySessionStartPage> {
bool isParticipantSelected(String id) {
if (state == SessionState.confirmedRole) {
return room.ownRole?.id == id;
return room?.ownRole?.id == id;
}
return _selectedRoleId == id;
}
@ -120,33 +148,63 @@ class ActivitySessionStartController extends State<ActivitySessionStartPage> {
if (mounted) setState(() => _selectedRoleId = id);
}
Future<void> _loadActivity() async {
try {
setState(() {
loading = true;
error = null;
});
final activities = await CourseActivityRepo.get(
widget.activityId,
[widget.activityId],
);
if (activities.isEmpty) {
throw Exception("Activity not found");
}
if (mounted) setState(() => activity = activities.first);
} catch (e) {
if (mounted) setState(() => error = e);
} finally {
if (mounted) setState(() => loading = false);
}
}
Future<void> onTap() async {
switch (state) {
case SessionState.notStarted:
if (mounted) setState(() => _started = true);
case SessionState.selectedRole:
await showFutureLoadingDialog(
context: context,
future: () => room.joinActivity(
room.activityPlan!.roles[_selectedRoleId!]!,
),
);
if (mounted) setState(() {});
case SessionState.notSelectedRole:
case SessionState.confirmedRole:
break;
if (state != SessionState.selectedRole) return;
if (room != null) {
await showFutureLoadingDialog(
context: context,
future: () => room!.joinActivity(
activity!.roles[_selectedRoleId!]!,
),
);
} else {
final resp = await showFutureLoadingDialog(
context: context,
future: () => parent!.launchActivityRoom(
activity!,
activity!.roles[_selectedRoleId!],
),
);
if (!resp.isError) {
context.go("/rooms/spaces/${widget.parentId}/${resp.result}");
}
}
}
Future<void> pingCourse() async {
if (room.courseParent == null) {
if (room?.courseParent == null) {
throw Exception("Activity is not part of a course");
}
await room.courseParent!.sendTextEvent(
await room!.courseParent!.sendTextEvent(
L10n.of(context).pingParticipantsNotification(
room.client.userID!.localpart ?? room.client.userID!,
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
room!.client.userID!.localpart ?? room!.client.userID!,
room!.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
),
);
}

View file

@ -8,10 +8,12 @@ import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart
import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_summary_widget.dart';
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
import 'package:fluffychat/pangea/common/widgets/share_room_button.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/utils/stream_extension.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
class ActivitySessionStartView extends StatelessWidget {
final ActivitySessionStartController controller;
@ -23,14 +25,28 @@ class ActivitySessionStartView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final buttonStyle = ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primaryContainer,
foregroundColor: theme.colorScheme.onPrimaryContainer,
padding: const EdgeInsets.all(8.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
);
return StreamBuilder(
stream: controller.room.client.onRoomState.stream
stream: Matrix.of(context)
.client
.onRoomState
.stream
.rateLimit(const Duration(seconds: 1)),
builder: (context, snapshot) {
return Scaffold(
appBar: AppBar(
leadingWidth: 52.0,
title: Text(controller.displayname),
title: controller.activity == null
? null
: Text(controller.activity!.title),
leading: Padding(
padding: const EdgeInsets.only(left: 12.0),
child: Center(
@ -41,168 +57,190 @@ class ActivitySessionStartView extends StatelessWidget {
),
),
actions: [
Padding(
padding: const EdgeInsets.only(right: 12.0),
child: SizedBox(
width: 40.0,
height: 40.0,
child: Center(
child: ShareRoomButton(room: controller.room),
if (controller.room != null)
Padding(
padding: const EdgeInsets.only(right: 12.0),
child: SizedBox(
width: 40.0,
height: 40.0,
child: Center(
child: ShareRoomButton(room: controller.room!),
),
),
),
),
],
),
body: SafeArea(
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Container(
constraints: const BoxConstraints(
maxWidth: 600.0,
),
padding: const EdgeInsets.all(12.0),
child: Column(
spacing: 12.0,
children: [
ActivitySummary(
room: controller.room,
showInstructions: controller.showInstructions,
toggleInstructions: controller.toggleInstructions,
onTapParticipant: controller.selectRole,
isParticipantSelected:
controller.isParticipantSelected,
canSelectParticipant:
controller.canSelectParticipant,
),
],
),
),
),
),
AnimatedSize(
duration: FluffyThemes.animationDuration,
child: Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: theme.dividerColor),
),
color: theme.colorScheme.surface,
),
padding: const EdgeInsets.all(24.0),
child: Column(
spacing: 16.0,
children: [
Text(
controller.descriptionText,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
child: controller.loading
? const Center(child: CircularProgressIndicator.adaptive())
: controller.error != null
? Center(
child: ErrorIndicator(
message: L10n.of(context).activityNotFound,
),
if (controller.state == SessionState.confirmedRole) ...[
if (controller.room.courseParent != null)
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
theme.colorScheme.primaryContainer,
foregroundColor:
theme.colorScheme.onPrimaryContainer,
padding: const EdgeInsets.all(8.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
)
: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Container(
constraints: const BoxConstraints(
maxWidth: 600.0,
),
),
onPressed: () => showFutureLoadingDialog(
context: context,
future: controller.pingCourse,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
L10n.of(context).pingParticipants,
),
],
),
),
if (controller.room.isRoomAdmin) ...[
if (!controller.isBotRoomMember)
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
theme.colorScheme.primaryContainer,
foregroundColor:
theme.colorScheme.onPrimaryContainer,
padding: const EdgeInsets.all(8.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
),
onPressed: () => showFutureLoadingDialog(
context: context,
future: () => controller.room
.invite(BotName.byEnvironment),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
padding: const EdgeInsets.all(12.0),
child: Column(
spacing: 12.0,
children: [
Text(L10n.of(context).playWithBot),
ActivitySummary(
activity: controller.activity!,
room: controller.room,
showInstructions:
controller.showInstructions,
toggleInstructions:
controller.toggleInstructions,
onTapParticipant: controller.selectRole,
isParticipantSelected:
controller.isParticipantSelected,
canSelectParticipant:
controller.canSelectParticipant,
),
],
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
theme.colorScheme.primaryContainer,
foregroundColor:
theme.colorScheme.onPrimaryContainer,
padding: const EdgeInsets.all(8.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
),
AnimatedSize(
duration: FluffyThemes.animationDuration,
child: Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: theme.dividerColor),
),
color: theme.colorScheme.surface,
),
onPressed: () => context.go(
"/rooms/${controller.room.id}/invite",
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
padding: const EdgeInsets.all(24.0),
child: Column(
spacing: 16.0,
children: [
Text(L10n.of(context).inviteFriends),
if (controller.descriptionText != null)
Text(
controller.descriptionText!,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
if (controller.state ==
SessionState.notStarted) ...[
ElevatedButton(
style: buttonStyle,
onPressed: () => context.go(
"/rooms/spaces/${controller.widget.parentId}/activity/${controller.widget.activityId}?new=true",
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
L10n.of(context).startNewSession,
),
],
),
),
ElevatedButton(
style: buttonStyle,
onPressed: null,
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
L10n.of(context).joinOpenSession,
),
],
),
),
] else if (controller.state ==
SessionState.confirmedRole) ...[
if (controller.room!.courseParent != null)
ElevatedButton(
style: buttonStyle,
onPressed: () =>
showFutureLoadingDialog(
context: context,
future: controller.pingCourse,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
L10n.of(context).pingParticipants,
),
],
),
),
if (controller.room!.isRoomAdmin) ...[
if (!controller.isBotRoomMember)
ElevatedButton(
style: buttonStyle,
onPressed: () =>
showFutureLoadingDialog(
context: context,
future: () => controller.room!
.invite(BotName.byEnvironment),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
L10n.of(context).playWithBot,
),
],
),
),
ElevatedButton(
style: buttonStyle,
onPressed: () => context.go(
"/rooms/${controller.room!.id}/invite",
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
L10n.of(context).inviteFriends,
),
],
),
),
],
] else
ElevatedButton(
style: buttonStyle,
onPressed: controller.enableButtons
? controller.onTap
: null,
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
controller.room?.isRoomAdmin ?? true
? L10n.of(context).start
: L10n.of(context).confirm,
),
],
),
),
],
),
),
],
] else
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
theme.colorScheme.primaryContainer,
foregroundColor:
theme.colorScheme.onPrimaryContainer,
padding: const EdgeInsets.all(8.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
),
onPressed: controller.enableButtons
? controller.onTap
: null,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(controller.buttonText),
],
),
),
],
),
),
),
],
),
],
),
),
);
},

View file

@ -5,15 +5,16 @@ import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_participant_list.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card_row.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_details_row.dart';
import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart';
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
class ActivitySummary extends StatelessWidget {
final Room room;
final ActivityPlanModel activity;
final Room? room;
final bool showInstructions;
final VoidCallback toggleInstructions;
@ -25,22 +26,18 @@ class ActivitySummary extends StatelessWidget {
const ActivitySummary({
super.key,
required this.room,
required this.activity,
required this.showInstructions,
required this.toggleInstructions,
this.onTapParticipant,
this.canSelectParticipant,
this.isParticipantSelected,
this.getParticipantOpacity,
this.room,
});
@override
Widget build(BuildContext context) {
final activity = room.activityPlan;
if (activity == null) {
return const SizedBox();
}
final theme = Theme.of(context);
return Center(
child: Container(
@ -49,21 +46,15 @@ class ActivitySummary extends StatelessWidget {
maxWidth: FluffyThemes.columnWidth * 1.5,
),
child: Column(
spacing: 12.0,
spacing: 4.0,
children: [
Padding(
padding: const EdgeInsets.all(4.0),
child: ImageByUrl(
imageUrl: activity.imageURL,
width: 80.0,
borderRadius: BorderRadius.circular(20),
),
),
Text(
activity.learningObjective,
style: const TextStyle(fontSize: 12.0),
ImageByUrl(
imageUrl: activity.imageURL,
width: 80.0,
borderRadius: BorderRadius.circular(20),
),
ActivityParticipantList(
activity: activity,
room: room,
onTap: onTapParticipant,
canSelect: canSelectParticipant,
@ -78,60 +69,61 @@ class ActivitySummary extends StatelessWidget {
spacing: 4.0,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 6.0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
Stack(
alignment: Alignment.bottomRight,
children: [
Text(
activity.description,
style: const TextStyle(
fontSize: 12.0,
),
),
TextButton(
onPressed: toggleInstructions,
style: TextButton.styleFrom(
minimumSize: Size.zero,
padding: EdgeInsets.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0.0),
),
backgroundColor: theme.colorScheme.surface,
),
child: Text(
showInstructions
? L10n.of(context).less
: L10n.of(context).more,
style: TextStyle(
fontSize: 12.0,
color: theme.colorScheme.primary,
),
),
),
],
),
if (showInstructions) ...[
Row(
spacing: 8.0,
mainAxisSize: MainAxisSize.min,
children: [
Text(
activity.req.mode,
style: const TextStyle(fontSize: 12.0),
),
Row(
spacing: 8.0,
spacing: 4.0,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.school, size: 12.0),
Text(
activity.req.mode,
activity.req.cefrLevel.string,
style: const TextStyle(fontSize: 12.0),
),
Row(
spacing: 4.0,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.school, size: 12.0),
Text(
activity.req.cefrLevel.string,
style: const TextStyle(fontSize: 12.0),
),
],
),
],
),
GestureDetector(
onTap: toggleInstructions,
child: Row(
spacing: 4.0,
mainAxisSize: MainAxisSize.min,
children: [
Text(
showInstructions
? L10n.of(context).hideInstructions
: L10n.of(context).seeInstructions,
style: const TextStyle(fontSize: 12.0),
),
Icon(
showInstructions
? Icons.arrow_drop_up
: Icons.arrow_drop_down,
size: 12.0,
),
],
),
),
],
),
),
if (showInstructions) ...[
ActivitySuggestionCardRow(
ActivitySessionDetailsRow(
icon: Symbols.target,
iconSize: 16.0,
child: Text(
@ -139,7 +131,7 @@ class ActivitySummary extends StatelessWidget {
style: const TextStyle(fontSize: 12.0),
),
),
ActivitySuggestionCardRow(
ActivitySessionDetailsRow(
icon: Symbols.steps,
iconSize: 16.0,
child: Text(
@ -147,7 +139,7 @@ class ActivitySummary extends StatelessWidget {
style: const TextStyle(fontSize: 12.0),
),
),
ActivitySuggestionCardRow(
ActivitySessionDetailsRow(
icon: Symbols.dictionary,
iconSize: 16.0,
child: Wrap(

View file

@ -1,79 +1,79 @@
import 'dart:convert';
// import 'dart:convert';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart';
// import 'package:get_storage/get_storage.dart';
// import 'package:http/http.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/network/requests.dart';
import 'package:fluffychat/pangea/common/network/urls.dart';
import 'package:fluffychat/widgets/matrix.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
// import 'package:fluffychat/pangea/common/config/environment.dart';
// import 'package:fluffychat/pangea/common/network/requests.dart';
// import 'package:fluffychat/pangea/common/network/urls.dart';
// import 'package:fluffychat/widgets/matrix.dart';
class ActivityPlanRepo {
static final GetStorage _activityPlanStorage =
GetStorage('activity_plan_by_id_storage');
// class ActivityPlanRepo {
// static final GetStorage _activityPlanStorage =
// GetStorage('activity_plan_by_id_storage');
static ActivityPlanModel? getCached(String id) {
final cachedJson = _activityPlanStorage.read(id);
if (cachedJson == null) return null;
// static ActivityPlanModel? getCached(String id) {
// final cachedJson = _activityPlanStorage.read(id);
// if (cachedJson == null) return null;
try {
return ActivityPlanModel.fromJson(cachedJson);
} catch (e) {
_removeCached(id);
return null;
}
}
// try {
// return ActivityPlanModel.fromJson(cachedJson);
// } catch (e) {
// _removeCached(id);
// return null;
// }
// }
static Future<void> _setCached(ActivityPlanModel response) =>
_activityPlanStorage.write(response.activityId, response.toJson());
// static Future<void> _setCached(ActivityPlanModel response) =>
// _activityPlanStorage.write(response.activityId, response.toJson());
static Future<void> _removeCached(String id) =>
_activityPlanStorage.remove(id);
// static Future<void> _removeCached(String id) =>
// _activityPlanStorage.remove(id);
static Future<void> set(ActivityPlanModel activity) => _setCached(activity);
// static Future<void> set(ActivityPlanModel activity) => _setCached(activity);
static Future<ActivityPlanModel> get(String id) async {
final cached = getCached(id);
if (cached != null) return cached;
// static Future<ActivityPlanModel> get(String id) async {
// final cached = getCached(id);
// if (cached != null) return cached;
final Requests req = Requests(
choreoApiKey: Environment.choreoApiKey,
accessToken: MatrixState.pangeaController.userController.accessToken,
);
// final Requests req = Requests(
// choreoApiKey: Environment.choreoApiKey,
// accessToken: MatrixState.pangeaController.userController.accessToken,
// );
final Response res = await req.get(
url: "${PApiUrls.activityPlan}/$id",
);
// final Response res = await req.get(
// url: "${PApiUrls.activityPlan}/$id",
// );
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
final response = ActivityPlanModel.fromJson(decodedBody["plan"]);
// final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
// final response = ActivityPlanModel.fromJson(decodedBody["plan"]);
_setCached(response);
return response;
}
// _setCached(response);
// return response;
// }
static Future<ActivityPlanModel> update(
ActivityPlanModel update,
) async {
final Requests req = Requests(
choreoApiKey: Environment.choreoApiKey,
accessToken: MatrixState.pangeaController.userController.accessToken,
);
// static Future<ActivityPlanModel> update(
// ActivityPlanModel update,
// ) async {
// final Requests req = Requests(
// choreoApiKey: Environment.choreoApiKey,
// accessToken: MatrixState.pangeaController.userController.accessToken,
// );
final Response res = await req.patch(
url: "${PApiUrls.activityPlan}/${update.activityId}",
body: update.toJson(),
);
// final Response res = await req.patch(
// url: "${PApiUrls.activityPlan}/${update.activityId}",
// body: update.toJson(),
// );
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
final response = ActivityPlanModel.fromJson(decodedBody["plan"]);
// final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
// final response = ActivityPlanModel.fromJson(decodedBody["plan"]);
_removeCached(update.activityId);
_setCached(response);
// _removeCached(update.activityId);
// _setCached(response);
return response;
}
// return response;
// }
static Future<void> remove(String id) => _removeCached(id);
}
// static Future<void> remove(String id) => _removeCached(id);
// }

View file

@ -1,102 +1,102 @@
import 'dart:convert';
// import 'dart:convert';
import 'package:flutter/material.dart';
// import 'package:flutter/material.dart';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart';
// import 'package:get_storage/get_storage.dart';
// import 'package:http/http.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_response.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/network/requests.dart';
import 'package:fluffychat/pangea/common/network/urls.dart';
import 'package:fluffychat/widgets/matrix.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_plan_response.dart';
// import 'package:fluffychat/pangea/common/config/environment.dart';
// import 'package:fluffychat/pangea/common/network/requests.dart';
// import 'package:fluffychat/pangea/common/network/urls.dart';
// import 'package:fluffychat/widgets/matrix.dart';
class ActivitySearchRepo {
static final GetStorage _activityPlanStorage =
GetStorage('activity_plan_search_storage');
// class ActivitySearchRepo {
// static final GetStorage _activityPlanStorage =
// GetStorage('activity_plan_search_storage');
static void set(
String storageKey,
ActivityPlanResponseWrapper wrappedResponse,
) {
_activityPlanStorage.write(storageKey, wrappedResponse.toJson());
}
// static void set(
// String storageKey,
// ActivityPlanResponseWrapper wrappedResponse,
// ) {
// _activityPlanStorage.write(storageKey, wrappedResponse.toJson());
// }
static Future<ActivityPlanResponseWrapper> get(
ActivityPlanRequest request,
) async {
final storageKey = "${request.storageKey}_wrapper";
final cachedJson = _activityPlanStorage.read(storageKey);
if (cachedJson != null) {
ActivityPlanResponseWrapper? cached;
try {
cached = ActivityPlanResponseWrapper.fromJson(cachedJson);
} catch (e) {
_activityPlanStorage.remove(storageKey);
}
// static Future<ActivityPlanResponseWrapper> get(
// ActivityPlanRequest request,
// ) async {
// final storageKey = "${request.storageKey}_wrapper";
// final cachedJson = _activityPlanStorage.read(storageKey);
// if (cachedJson != null) {
// ActivityPlanResponseWrapper? cached;
// try {
// cached = ActivityPlanResponseWrapper.fromJson(cachedJson);
// } catch (e) {
// _activityPlanStorage.remove(storageKey);
// }
if (cached is ActivityPlanResponseWrapper) {
return cached;
}
}
// if (cached is ActivityPlanResponseWrapper) {
// return cached;
// }
// }
final Requests req = Requests(
choreoApiKey: Environment.choreoApiKey,
accessToken: MatrixState.pangeaController.userController.accessToken,
);
// final Requests req = Requests(
// choreoApiKey: Environment.choreoApiKey,
// accessToken: MatrixState.pangeaController.userController.accessToken,
// );
Response? res;
try {
res = await req.post(
url: PApiUrls.activityPlanSearch,
body: request.toJson(),
);
} catch (err) {
debugPrint("err: $err, err is http response: ${err is Response}");
if (err is Response) {
return ActivityPlanResponseWrapper(
response: ActivityPlanResponse(activityPlans: []),
statusCode: err.statusCode,
);
}
}
// Response? res;
// try {
// res = await req.post(
// url: PApiUrls.activityPlanSearch,
// body: request.toJson(),
// );
// } catch (err) {
// debugPrint("err: $err, err is http response: ${err is Response}");
// if (err is Response) {
// return ActivityPlanResponseWrapper(
// response: ActivityPlanResponse(activityPlans: []),
// statusCode: err.statusCode,
// );
// }
// }
final decodedBody = jsonDecode(utf8.decode(res!.bodyBytes));
final response = ActivityPlanResponse.fromJson(decodedBody);
final wrappedResponse = ActivityPlanResponseWrapper(
response: response,
statusCode: res.statusCode,
);
// final decodedBody = jsonDecode(utf8.decode(res!.bodyBytes));
// final response = ActivityPlanResponse.fromJson(decodedBody);
// final wrappedResponse = ActivityPlanResponseWrapper(
// response: response,
// statusCode: res.statusCode,
// );
if (res.statusCode == 200) {
set(storageKey, wrappedResponse);
}
// if (res.statusCode == 200) {
// set(storageKey, wrappedResponse);
// }
return wrappedResponse;
}
}
// return wrappedResponse;
// }
// }
class ActivityPlanResponseWrapper {
final ActivityPlanResponse response;
final int statusCode;
// class ActivityPlanResponseWrapper {
// final ActivityPlanResponse response;
// final int statusCode;
ActivityPlanResponseWrapper({
required this.response,
required this.statusCode,
});
// ActivityPlanResponseWrapper({
// required this.response,
// required this.statusCode,
// });
factory ActivityPlanResponseWrapper.fromJson(Map<String, dynamic> json) {
return ActivityPlanResponseWrapper(
response: json['activity_plan_response'].fromJson,
statusCode: json['activity_response_status'],
);
}
// factory ActivityPlanResponseWrapper.fromJson(Map<String, dynamic> json) {
// return ActivityPlanResponseWrapper(
// response: json['activity_plan_response'].fromJson,
// statusCode: json['activity_response_status'],
// );
// }
Map<String, dynamic> toJson() {
return {
'activity_plan_response': response.toJson(),
'activity_response_status': statusCode,
};
}
}
// Map<String, dynamic> toJson() {
// return {
// 'activity_plan_response': response.toJson(),
// 'activity_response_status': statusCode,
// };
// }
// }

View file

@ -1,51 +1,51 @@
import 'package:fluffychat/l10n/l10n.dart';
// import 'package:fluffychat/l10n/l10n.dart';
/// 200: All activities successfully retrieved
/// 202: Waiting for activities to load
/// 504: Timeout
/// Other: Error
enum ActivitySearchEnum {
complete,
waiting,
timeout,
error,
}
// /// 200: All activities successfully retrieved
// /// 202: Waiting for activities to load
// /// 504: Timeout
// /// Other: Error
// enum ActivitySearchEnum {
// complete,
// waiting,
// timeout,
// error,
// }
extension ActivitySearchExtension on ActivitySearchEnum {
ActivitySearchEnum fromCode(int statusCode) {
switch (statusCode) {
case 200:
return ActivitySearchEnum.complete;
case 202:
return ActivitySearchEnum.waiting;
case 504:
return ActivitySearchEnum.timeout;
default:
return ActivitySearchEnum.error;
}
}
// extension ActivitySearchExtension on ActivitySearchEnum {
// ActivitySearchEnum fromCode(int statusCode) {
// switch (statusCode) {
// case 200:
// return ActivitySearchEnum.complete;
// case 202:
// return ActivitySearchEnum.waiting;
// case 504:
// return ActivitySearchEnum.timeout;
// default:
// return ActivitySearchEnum.error;
// }
// }
bool get hideCards {
switch (this) {
case ActivitySearchEnum.complete:
case ActivitySearchEnum.waiting:
return false;
case ActivitySearchEnum.timeout:
case ActivitySearchEnum.error:
return true;
}
}
// bool get hideCards {
// switch (this) {
// case ActivitySearchEnum.complete:
// case ActivitySearchEnum.waiting:
// return false;
// case ActivitySearchEnum.timeout:
// case ActivitySearchEnum.error:
// return true;
// }
// }
String message(L10n l10n) {
switch (this) {
case ActivitySearchEnum.waiting:
return l10n.activitySuggestionTimeoutMessage;
case ActivitySearchEnum.timeout:
return l10n.generatingNewActivities;
case ActivitySearchEnum.error:
return l10n.errorFetchingActivitiesMessage;
default:
return '';
}
}
}
// String message(L10n l10n) {
// switch (this) {
// case ActivitySearchEnum.waiting:
// return l10n.activitySuggestionTimeoutMessage;
// case ActivitySearchEnum.timeout:
// return l10n.generatingNewActivities;
// case ActivitySearchEnum.error:
// return l10n.errorFetchingActivitiesMessage;
// default:
// return '';
// }
// }
// }

View file

@ -1,38 +1,38 @@
import 'package:fluffychat/pangea/activity_generator/media_enum.dart';
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
// import 'package:fluffychat/pangea/activity_generator/media_enum.dart';
// import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
class ActivitySearchRequest {
final String targetLanguage;
final String languageOfInstructions;
final LanguageLevelTypeEnum? languageLevel;
// class ActivitySearchRequest {
// final String targetLanguage;
// final String languageOfInstructions;
// final LanguageLevelTypeEnum? languageLevel;
final String? mode;
final String? learningObjective;
final String? topic;
final MediaEnum? media;
final int? numberOfParticipants;
// final String? mode;
// final String? learningObjective;
// final String? topic;
// final MediaEnum? media;
// final int? numberOfParticipants;
ActivitySearchRequest({
required this.targetLanguage,
required this.languageOfInstructions,
this.mode,
this.learningObjective,
this.topic,
this.media,
this.numberOfParticipants = 2,
this.languageLevel = LanguageLevelTypeEnum.preA1,
});
// ActivitySearchRequest({
// required this.targetLanguage,
// required this.languageOfInstructions,
// this.mode,
// this.learningObjective,
// this.topic,
// this.media,
// this.numberOfParticipants = 2,
// this.languageLevel = LanguageLevelTypeEnum.preA1,
// });
Map<String, dynamic> toJson() {
return {
'target_language': targetLanguage,
'language_of_instructions': languageOfInstructions,
'language_level': languageLevel,
'mode': mode,
'objective': learningObjective,
'topic': topic,
'media': media,
'number_of_participants': numberOfParticipants,
};
}
}
// Map<String, dynamic> toJson() {
// return {
// 'target_language': targetLanguage,
// 'language_of_instructions': languageOfInstructions,
// 'language_level': languageLevel,
// 'mode': mode,
// 'objective': learningObjective,
// 'topic': topic,
// 'media': media,
// 'number_of_participants': numberOfParticipants,
// };
// }
// }

View file

@ -3,11 +3,10 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart';
class ActivitySuggestionCard extends StatelessWidget {
final ActivityPlannerBuilderState controller;
final ActivityPlanModel activity;
final VoidCallback onPressed;
final double width;
final double height;
@ -18,7 +17,7 @@ class ActivitySuggestionCard extends StatelessWidget {
const ActivitySuggestionCard({
super.key,
required this.controller,
required this.activity,
required this.onPressed,
required this.width,
required this.height,
@ -27,8 +26,6 @@ class ActivitySuggestionCard extends StatelessWidget {
this.iconSize,
});
ActivityPlanModel get activity => controller.updatedActivity;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);

View file

@ -1,312 +1,312 @@
import 'dart:async';
// import 'dart:async';
import 'package:flutter/material.dart';
// import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:go_router/go_router.dart';
// import 'package:collection/collection.dart';
// import 'package:go_router/go_router.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/activity_generator/activity_plan_generation_repo.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog_content.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
import 'package:fluffychat/pangea/common/widgets/full_width_dialog.dart';
// import 'package:fluffychat/config/themes.dart';
// import 'package:fluffychat/l10n/l10n.dart';
// import 'package:fluffychat/pangea/activity_generator/activity_plan_generation_repo.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
// import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog_content.dart';
// import 'package:fluffychat/pangea/common/utils/error_handler.dart';
// import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
// import 'package:fluffychat/pangea/common/widgets/full_width_dialog.dart';
class ActivitySuggestionDialog extends StatefulWidget {
final ActivityPlannerBuilderState controller;
final String buttonText;
// class ActivitySuggestionDialog extends StatefulWidget {
// final ActivityPlannerBuilderState controller;
// final String buttonText;
final Function(ActivityPlanModel)? replaceActivity;
// final Function(ActivityPlanModel)? replaceActivity;
const ActivitySuggestionDialog({
required this.controller,
required this.buttonText,
this.replaceActivity,
super.key,
});
// const ActivitySuggestionDialog({
// required this.controller,
// required this.buttonText,
// this.replaceActivity,
// super.key,
// });
@override
ActivitySuggestionDialogState createState() =>
ActivitySuggestionDialogState();
}
// @override
// ActivitySuggestionDialogState createState() =>
// ActivitySuggestionDialogState();
// }
class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
bool _loading = false;
String? _regenerateError;
String? _launchError;
// class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
// bool _loading = false;
// String? _regenerateError;
// String? _launchError;
double get _width => FluffyThemes.isColumnMode(context)
? 400.0
: MediaQuery.of(context).size.width;
// double get _width => FluffyThemes.isColumnMode(context)
// ? 400.0
// : MediaQuery.of(context).size.width;
StreamSubscription? _stateSubscription;
// StreamSubscription? _stateSubscription;
@override
void initState() {
super.initState();
_stateSubscription = widget.controller.stateStream.stream.listen((state) {
if (mounted) setState(() {});
});
}
// @override
// void initState() {
// super.initState();
// _stateSubscription = widget.controller.stateStream.stream.listen((state) {
// if (mounted) setState(() {});
// });
// }
@override
void dispose() {
_stateSubscription?.cancel();
super.dispose();
}
// @override
// void dispose() {
// _stateSubscription?.cancel();
// super.dispose();
// }
Future<void> launchActivity() async {
try {
if (!widget.controller.room.isSpace) {
throw Exception(
"Cannot launch activity in a non-space room",
);
}
// Future<void> launchActivity() async {
// try {
// if (!widget.controller.room.isSpace) {
// throw Exception(
// "Cannot launch activity in a non-space room",
// );
// }
setState(() {
_loading = true;
_regenerateError = null;
_launchError = null;
});
// setState(() {
// _loading = true;
// _regenerateError = null;
// _launchError = null;
// });
final ids = await widget.controller.launchToSpace();
ids.length == 1
? context
.go("/rooms/spaces/${widget.controller.room.id}/${ids.first}")
: context.go("/rooms/spaces/${widget.controller.room.id}/details");
Navigator.of(context).pop();
} catch (e, s) {
_launchError = L10n.of(context).errorLaunchActivityMessage;
ErrorHandler.logError(
e: e,
s: s,
data: {
"request": widget.controller.updatedRequest.toJson(),
},
);
} finally {
if (mounted) {
setState(() {
_loading = false;
});
}
}
}
// final ids = await widget.controller.launchToSpace();
// ids.length == 1
// ? context
// .go("/rooms/spaces/${widget.controller.room.id}/${ids.first}")
// : context.go("/rooms/spaces/${widget.controller.room.id}/details");
// Navigator.of(context).pop();
// } catch (e, s) {
// _launchError = L10n.of(context).errorLaunchActivityMessage;
// ErrorHandler.logError(
// e: e,
// s: s,
// data: {
// "request": widget.controller.updatedRequest.toJson(),
// },
// );
// } finally {
// if (mounted) {
// setState(() {
// _loading = false;
// });
// }
// }
// }
Future<void> onRegenerate() async {
setState(() {
_loading = true;
_regenerateError = null;
_launchError = null;
});
// Future<void> onRegenerate() async {
// setState(() {
// _loading = true;
// _regenerateError = null;
// _launchError = null;
// });
try {
final resp = await ActivityPlanGenerationRepo.get(
widget.controller.updatedRequest,
force: true,
);
final plan = resp.activityPlans.firstOrNull;
if (plan == null) {
throw Exception("No activity plan generated");
}
// try {
// final resp = await ActivityPlanGenerationRepo.get(
// widget.controller.updatedRequest,
// force: true,
// );
// final plan = resp.activityPlans.firstOrNull;
// if (plan == null) {
// throw Exception("No activity plan generated");
// }
widget.replaceActivity?.call(plan);
await widget.controller.overrideActivity(plan);
} catch (e, s) {
_regenerateError = L10n.of(context).errorRegenerateActivityMessage;
ErrorHandler.logError(
e: e,
s: s,
data: {
"request": widget.controller.updatedRequest.toJson(),
},
);
return;
} finally {
if (mounted) {
setState(() {
_loading = false;
});
}
}
}
// widget.replaceActivity?.call(plan);
// await widget.controller.overrideActivity(plan);
// } catch (e, s) {
// _regenerateError = L10n.of(context).errorRegenerateActivityMessage;
// ErrorHandler.logError(
// e: e,
// s: s,
// data: {
// "request": widget.controller.updatedRequest.toJson(),
// },
// );
// return;
// } finally {
// if (mounted) {
// setState(() {
// _loading = false;
// });
// }
// }
// }
void _resetActivity() {
widget.controller.resetActivity();
setState(() {
_loading = false;
_regenerateError = null;
_launchError = null;
});
}
// void _resetActivity() {
// widget.controller.resetActivity();
// setState(() {
// _loading = false;
// _regenerateError = null;
// _launchError = null;
// });
// }
ButtonStyle get buttonStyle => ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer,
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
),
);
// ButtonStyle get buttonStyle => ElevatedButton.styleFrom(
// backgroundColor: Theme.of(context).colorScheme.primaryContainer,
// foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer,
// padding: const EdgeInsets.symmetric(
// horizontal: 12.0,
// ),
// );
double get width => FluffyThemes.isColumnMode(context)
? 400.0
: MediaQuery.of(context).size.width;
// double get width => FluffyThemes.isColumnMode(context)
// ? 400.0
// : MediaQuery.of(context).size.width;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final buttonStyle = this.buttonStyle;
// @override
// Widget build(BuildContext context) {
// final theme = Theme.of(context);
// final buttonStyle = this.buttonStyle;
final body = Stack(
alignment: Alignment.topCenter,
children: [
DecoratedBox(
decoration: BoxDecoration(
color: theme.colorScheme.surface,
),
child: Builder(
builder: (context) {
if (_regenerateError != null || _launchError != null) {
return Center(
child: Column(
spacing: 16.0,
mainAxisSize: MainAxisSize.min,
children: [
ErrorIndicator(
message: _regenerateError ?? _launchError!,
),
if (_regenerateError != null)
Row(
spacing: 8.0,
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: onRegenerate,
style: buttonStyle,
child: Text(L10n.of(context).tryAgain),
),
ElevatedButton(
onPressed: _resetActivity,
style: buttonStyle,
child: Text(L10n.of(context).reset),
),
],
)
else
ElevatedButton(
onPressed: launchActivity,
style: buttonStyle,
child: Text(L10n.of(context).tryAgain),
),
],
),
);
}
// final body = Stack(
// alignment: Alignment.topCenter,
// children: [
// DecoratedBox(
// decoration: BoxDecoration(
// color: theme.colorScheme.surface,
// ),
// child: Builder(
// builder: (context) {
// if (_regenerateError != null || _launchError != null) {
// return Center(
// child: Column(
// spacing: 16.0,
// mainAxisSize: MainAxisSize.min,
// children: [
// ErrorIndicator(
// message: _regenerateError ?? _launchError!,
// ),
// if (_regenerateError != null)
// Row(
// spacing: 8.0,
// mainAxisSize: MainAxisSize.min,
// children: [
// ElevatedButton(
// onPressed: onRegenerate,
// style: buttonStyle,
// child: Text(L10n.of(context).tryAgain),
// ),
// ElevatedButton(
// onPressed: _resetActivity,
// style: buttonStyle,
// child: Text(L10n.of(context).reset),
// ),
// ],
// )
// else
// ElevatedButton(
// onPressed: launchActivity,
// style: buttonStyle,
// child: Text(L10n.of(context).tryAgain),
// ),
// ],
// ),
// );
// }
if (_loading) {
return const Center(
child: CircularProgressIndicator.adaptive(),
);
}
// if (_loading) {
// return const Center(
// child: CircularProgressIndicator.adaptive(),
// );
// }
return Form(
key: widget.controller.formKey,
child: ActivitySuggestionDialogContent(controller: this),
);
},
),
),
Positioned(
top: 4.0,
left: 4.0,
child: IconButton.filled(
style: IconButton.styleFrom(
backgroundColor: theme.colorScheme.surface.withAlpha(170),
),
icon: Icon(
Icons.close_outlined,
color: theme.colorScheme.onSurface,
),
onPressed: Navigator.of(context).pop,
tooltip: L10n.of(context).close,
),
),
],
);
// return Form(
// key: widget.controller.formKey,
// child: ActivitySuggestionDialogContent(controller: this),
// );
// },
// ),
// ),
// Positioned(
// top: 4.0,
// left: 4.0,
// child: IconButton.filled(
// style: IconButton.styleFrom(
// backgroundColor: theme.colorScheme.surface.withAlpha(170),
// ),
// icon: Icon(
// Icons.close_outlined,
// color: theme.colorScheme.onSurface,
// ),
// onPressed: Navigator.of(context).pop,
// tooltip: L10n.of(context).close,
// ),
// ),
// ],
// );
return FullWidthDialog(
dialogContent: body,
maxWidth: _width,
maxHeight: 650.0,
);
}
}
// return FullWidthDialog(
// dialogContent: body,
// maxWidth: _width,
// maxHeight: 650.0,
// );
// }
// }
class NumberCounter extends StatelessWidget {
final int count;
final Function(int) update;
// class NumberCounter extends StatelessWidget {
// final int count;
// final Function(int) update;
final int? min;
final int? max;
// final int? min;
// final int? max;
const NumberCounter({
super.key,
required this.count,
required this.update,
this.min,
this.max,
});
// const NumberCounter({
// super.key,
// required this.count,
// required this.update,
// this.min,
// this.max,
// });
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0),
color: Theme.of(context).colorScheme.surfaceContainerHighest,
),
child: Row(
spacing: 4.0,
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove),
iconSize: 24.0,
style: IconButton.styleFrom(
padding: const EdgeInsets.all(0.0),
),
onPressed: min == null || count - 1 >= min!
? () {
if (count > 0) {
update(count - 1);
}
}
: null,
),
Text(
count.toString(),
style: const TextStyle(
fontSize: 16.0,
),
),
IconButton(
icon: const Icon(Icons.add),
iconSize: 24.0,
style: max == null || count + 1 <= max!
? IconButton.styleFrom(
padding: const EdgeInsets.all(0.0),
)
: null,
onPressed: max == null || count + 1 <= max!
? () {
update(count + 1);
}
: null,
),
],
),
);
}
}
// @override
// Widget build(BuildContext context) {
// return Container(
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(12.0),
// color: Theme.of(context).colorScheme.surfaceContainerHighest,
// ),
// child: Row(
// spacing: 4.0,
// mainAxisSize: MainAxisSize.min,
// children: [
// IconButton(
// icon: const Icon(Icons.remove),
// iconSize: 24.0,
// style: IconButton.styleFrom(
// padding: const EdgeInsets.all(0.0),
// ),
// onPressed: min == null || count - 1 >= min!
// ? () {
// if (count > 0) {
// update(count - 1);
// }
// }
// : null,
// ),
// Text(
// count.toString(),
// style: const TextStyle(
// fontSize: 16.0,
// ),
// ),
// IconButton(
// icon: const Icon(Icons.add),
// iconSize: 24.0,
// style: max == null || count + 1 <= max!
// ? IconButton.styleFrom(
// padding: const EdgeInsets.all(0.0),
// )
// : null,
// onPressed: max == null || count + 1 <= max!
// ? () {
// update(count + 1);
// }
// : null,
// ),
// ],
// ),
// );
// }
// }

View file

@ -1,317 +1,317 @@
// shows n rows of activity suggestions vertically, where n is the number of rows
// as the user tries to scroll horizontally to the right, the client will fetch more activity suggestions
// // shows n rows of activity suggestions vertically, where n is the number of rows
// // as the user tries to scroll horizontally to the right, the client will fetch more activity suggestions
import 'dart:async';
// import 'dart:async';
import 'package:flutter/material.dart';
// import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:matrix/matrix.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:shimmer/shimmer.dart';
// import 'package:collection/collection.dart';
// import 'package:matrix/matrix.dart';
// import 'package:sentry_flutter/sentry_flutter.dart';
// import 'package:shimmer/shimmer.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/activity_generator/media_enum.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/activity_planner_builder.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_plan_search_repo.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_search_enum.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
import 'package:fluffychat/widgets/matrix.dart';
// import 'package:fluffychat/config/themes.dart';
// import 'package:fluffychat/l10n/l10n.dart';
// import 'package:fluffychat/pangea/activity_generator/media_enum.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/activity_planner_builder.dart';
// import 'package:fluffychat/pangea/activity_suggestions/activity_plan_search_repo.dart';
// import 'package:fluffychat/pangea/activity_suggestions/activity_search_enum.dart';
// import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card.dart';
// import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog.dart';
// import 'package:fluffychat/pangea/common/utils/error_handler.dart';
// import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
// import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
// import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
// import 'package:fluffychat/widgets/matrix.dart';
class ActivitySuggestionsArea extends StatefulWidget {
final Axis? scrollDirection;
final Room room;
// class ActivitySuggestionsArea extends StatefulWidget {
// final Axis? scrollDirection;
// final Room room;
const ActivitySuggestionsArea({
super.key,
this.scrollDirection,
required this.room,
});
@override
ActivitySuggestionsAreaState createState() => ActivitySuggestionsAreaState();
}
// const ActivitySuggestionsArea({
// super.key,
// this.scrollDirection,
// required this.room,
// });
// @override
// ActivitySuggestionsAreaState createState() => ActivitySuggestionsAreaState();
// }
class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
StreamSubscription? _languageStream;
// class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
// StreamSubscription? _languageStream;
@override
void initState() {
super.initState();
_setActivityItems();
// @override
// void initState() {
// super.initState();
// _setActivityItems();
_languageStream ??= MatrixState
.pangeaController.userController.languageStream.stream
.listen((update) {
WidgetsBinding.instance.addPostFrameCallback(
(_) => _setActivityItems(),
);
});
}
// _languageStream ??= MatrixState
// .pangeaController.userController.languageStream.stream
// .listen((update) {
// WidgetsBinding.instance.addPostFrameCallback(
// (_) => _setActivityItems(),
// );
// });
// }
@override
void dispose() {
_scrollController.dispose();
_languageStream?.cancel();
super.dispose();
}
// @override
// void dispose() {
// _scrollController.dispose();
// _languageStream?.cancel();
// super.dispose();
// }
// _loading is true when _setActivityItems is currently requesting activities
bool _loading = true;
ActivitySearchEnum _status = ActivitySearchEnum.waiting;
// // _loading is true when _setActivityItems is currently requesting activities
// bool _loading = true;
// ActivitySearchEnum _status = ActivitySearchEnum.waiting;
bool get _isColumnMode => FluffyThemes.isColumnMode(context);
// bool get _isColumnMode => FluffyThemes.isColumnMode(context);
final List<ActivityPlanModel> _activityItems = [];
final ScrollController _scrollController = ScrollController();
double get cardHeight => _isColumnMode ? 325.0 : 250.0;
double get cardWidth => _isColumnMode ? 225.0 : 150.0;
// final List<ActivityPlanModel> _activityItems = [];
// final ScrollController _scrollController = ScrollController();
// double get cardHeight => _isColumnMode ? 325.0 : 250.0;
// double get cardWidth => _isColumnMode ? 225.0 : 150.0;
String get instructionLanguage =>
MatrixState.pangeaController.languageController.userL1?.langCode ??
LanguageKeys.defaultLanguage;
String get targetLanguage =>
MatrixState.pangeaController.languageController.userL2?.langCode ??
LanguageKeys.defaultLanguage;
// String get instructionLanguage =>
// MatrixState.pangeaController.languageController.userL1?.langCode ??
// LanguageKeys.defaultLanguage;
// String get targetLanguage =>
// MatrixState.pangeaController.languageController.userL2?.langCode ??
// LanguageKeys.defaultLanguage;
ActivityPlanRequest get _request {
return ActivityPlanRequest(
topic: "",
mode: "",
objective: "",
media: MediaEnum.nan,
cefrLevel: LanguageLevelTypeEnum.a1,
languageOfInstructions: instructionLanguage,
targetLanguage: targetLanguage,
numberOfParticipants: 3,
count: 5,
);
}
// ActivityPlanRequest get _request {
// return ActivityPlanRequest(
// topic: "",
// mode: "",
// objective: "",
// media: MediaEnum.nan,
// cefrLevel: LanguageLevelTypeEnum.a1,
// languageOfInstructions: instructionLanguage,
// targetLanguage: targetLanguage,
// numberOfParticipants: 3,
// count: 5,
// );
// }
Future<void> _setActivityItems({int retries = 0}) async {
if (retries > 3) {
if (mounted) {
setState(() {
_status = ActivitySearchEnum.timeout;
_loading = false;
});
}
return;
}
// Future<void> _setActivityItems({int retries = 0}) async {
// if (retries > 3) {
// if (mounted) {
// setState(() {
// _status = ActivitySearchEnum.timeout;
// _loading = false;
// });
// }
// return;
// }
try {
if (retries == 0 && mounted) {
setState(() {
_activityItems.clear();
_loading = true;
_status = ActivitySearchEnum.waiting;
});
}
// try {
// if (retries == 0 && mounted) {
// setState(() {
// _activityItems.clear();
// _loading = true;
// _status = ActivitySearchEnum.waiting;
// });
// }
final resp = await ActivitySearchRepo.get(_request).timeout(
const Duration(seconds: 5),
onTimeout: () {
if (mounted) setState(() => _status = ActivitySearchEnum.timeout);
// final resp = await ActivitySearchRepo.get(_request).timeout(
// const Duration(seconds: 5),
// onTimeout: () {
// if (mounted) setState(() => _status = ActivitySearchEnum.timeout);
Future.delayed(const Duration(seconds: 5), () {
if (mounted) _setActivityItems(retries: retries + 1);
});
// Future.delayed(const Duration(seconds: 5), () {
// if (mounted) _setActivityItems(retries: retries + 1);
// });
return Future<ActivityPlanResponseWrapper>.error(
TimeoutException(
L10n.of(context).activitySuggestionTimeoutMessage,
),
);
},
);
_activityItems.addAll(resp.response.activityPlans);
_status = _status.fromCode(resp.statusCode);
if (_status != ActivitySearchEnum.error) {
if (_activityItems.isEmpty) {
if (mounted && retries != 0) {
setState(() => _status = ActivitySearchEnum.timeout);
}
// return Future<ActivityPlanResponseWrapper>.error(
// TimeoutException(
// L10n.of(context).activitySuggestionTimeoutMessage,
// ),
// );
// },
// );
// _activityItems.addAll(resp.response.activityPlans);
// _status = _status.fromCode(resp.statusCode);
// if (_status != ActivitySearchEnum.error) {
// if (_activityItems.isEmpty) {
// if (mounted && retries != 0) {
// setState(() => _status = ActivitySearchEnum.timeout);
// }
Future.delayed(const Duration(seconds: 5), () {
if (mounted) _setActivityItems(retries: retries + 1);
});
} else {
if (mounted) setState(() => _loading = false);
}
}
} on TimeoutException {
rethrow;
} catch (e, s) {
ErrorHandler.logError(
e: e,
s: s,
data: {
'retries': retries,
'request': _request.toJson(),
},
level: SentryLevel.warning,
);
}
}
// Future.delayed(const Duration(seconds: 5), () {
// if (mounted) _setActivityItems(retries: retries + 1);
// });
// } else {
// if (mounted) setState(() => _loading = false);
// }
// }
// } on TimeoutException {
// rethrow;
// } catch (e, s) {
// ErrorHandler.logError(
// e: e,
// s: s,
// data: {
// 'retries': retries,
// 'request': _request.toJson(),
// },
// level: SentryLevel.warning,
// );
// }
// }
void _onReplaceActivity(int index, ActivityPlanModel a) {
setState(() => _activityItems[index] = a);
}
// void _onReplaceActivity(int index, ActivityPlanModel a) {
// setState(() => _activityItems[index] = a);
// }
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
// @override
// Widget build(BuildContext context) {
// final theme = Theme.of(context);
// Show all loaded activities, or a loading view if there are none
final List<Widget> cards = _activityItems.isEmpty
? List.generate(5, (i) {
return Shimmer.fromColors(
baseColor: theme.colorScheme.primary.withAlpha(20),
highlightColor: theme.colorScheme.primary.withAlpha(50),
child: Container(
height: cardHeight,
width: cardWidth,
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(24.0),
),
),
);
})
: _activityItems
.mapIndexed((index, activity) {
return ActivityPlannerBuilder(
initialActivity: activity,
room: widget.room,
builder: (controller) {
return ActivitySuggestionCard(
controller: controller,
onPressed: () {
showDialog(
context: context,
builder: (context) {
return ActivitySuggestionDialog(
controller: controller,
buttonText: L10n.of(context).saveAndLaunch,
replaceActivity: (a) =>
_onReplaceActivity(index, a),
);
},
);
},
width: cardWidth,
height: cardHeight,
);
},
);
})
.cast<Widget>()
.toList();
// // Show all loaded activities, or a loading view if there are none
// final List<Widget> cards = _activityItems.isEmpty
// ? List.generate(5, (i) {
// return Shimmer.fromColors(
// baseColor: theme.colorScheme.primary.withAlpha(20),
// highlightColor: theme.colorScheme.primary.withAlpha(50),
// child: Container(
// height: cardHeight,
// width: cardWidth,
// decoration: BoxDecoration(
// color: theme.colorScheme.surfaceContainer,
// borderRadius: BorderRadius.circular(24.0),
// ),
// ),
// );
// })
// : _activityItems
// .mapIndexed((index, activity) {
// return ActivityPlannerBuilder(
// initialActivity: activity,
// room: widget.room,
// builder: (controller) {
// return ActivitySuggestionCard(
// controller: controller,
// onPressed: () {
// showDialog(
// context: context,
// builder: (context) {
// return ActivitySuggestionDialog(
// controller: controller,
// buttonText: L10n.of(context).saveAndLaunch,
// replaceActivity: (a) =>
// _onReplaceActivity(index, a),
// );
// },
// );
// },
// width: cardWidth,
// height: cardHeight,
// );
// },
// );
// })
// .cast<Widget>()
// .toList();
final scrollDirection = widget.scrollDirection ??
(_isColumnMode ? Axis.horizontal : Axis.vertical);
// final scrollDirection = widget.scrollDirection ??
// (_isColumnMode ? Axis.horizontal : Axis.vertical);
return Column(
spacing: 8.0,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AnimatedSize(
duration: FluffyThemes.animationDuration,
child: _status.hideCards
? Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
spacing: 16.0,
mainAxisSize: MainAxisSize.min,
children: [
ErrorIndicator(
message: _status.message(L10n.of(context)),
),
if (_loading && _status == ActivitySearchEnum.timeout)
const CircularProgressIndicator(),
if (!_loading && _status == ActivitySearchEnum.timeout)
ElevatedButton(
onPressed: _setActivityItems,
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primaryContainer,
foregroundColor:
theme.colorScheme.onPrimaryContainer,
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
),
),
child: Text(L10n.of(context).tryAgain),
),
],
),
)
: Container(
decoration: const BoxDecoration(),
child: Column(
children: [
scrollDirection == Axis.horizontal
? Scrollbar(
thumbVisibility: true,
controller: _scrollController,
child: Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: SingleChildScrollView(
controller: _scrollController,
scrollDirection: Axis.horizontal,
child: Row(
spacing: 8.0,
children: cards,
),
),
),
)
: SizedBox(
width: MediaQuery.of(context).size.width,
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
runSpacing: 16.0,
spacing: 4.0,
children: cards,
),
),
if (cards.length < 5)
Padding(
padding: const EdgeInsetsGeometry.all(16.0),
child: ErrorIndicator(
message: _status.message(L10n.of(context)),
),
),
if (cards.length < 5 && _loading)
const CircularProgressIndicator(),
if (cards.length < 5 && !_loading)
Padding(
padding: const EdgeInsetsGeometry.only(bottom: 16),
child: ElevatedButton(
onPressed: _setActivityItems,
style: ElevatedButton.styleFrom(
backgroundColor:
theme.colorScheme.primaryContainer,
foregroundColor:
theme.colorScheme.onPrimaryContainer,
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
),
),
child: Text(L10n.of(context).tryAgain),
),
),
],
),
),
),
],
);
}
}
// return Column(
// spacing: 8.0,
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// AnimatedSize(
// duration: FluffyThemes.animationDuration,
// child: _status.hideCards
// ? Padding(
// padding: const EdgeInsets.all(8.0),
// child: Column(
// spacing: 16.0,
// mainAxisSize: MainAxisSize.min,
// children: [
// ErrorIndicator(
// message: _status.message(L10n.of(context)),
// ),
// if (_loading && _status == ActivitySearchEnum.timeout)
// const CircularProgressIndicator(),
// if (!_loading && _status == ActivitySearchEnum.timeout)
// ElevatedButton(
// onPressed: _setActivityItems,
// style: ElevatedButton.styleFrom(
// backgroundColor: theme.colorScheme.primaryContainer,
// foregroundColor:
// theme.colorScheme.onPrimaryContainer,
// padding: const EdgeInsets.symmetric(
// horizontal: 12.0,
// ),
// ),
// child: Text(L10n.of(context).tryAgain),
// ),
// ],
// ),
// )
// : Container(
// decoration: const BoxDecoration(),
// child: Column(
// children: [
// scrollDirection == Axis.horizontal
// ? Scrollbar(
// thumbVisibility: true,
// controller: _scrollController,
// child: Padding(
// padding: const EdgeInsets.only(bottom: 16.0),
// child: SingleChildScrollView(
// controller: _scrollController,
// scrollDirection: Axis.horizontal,
// child: Row(
// spacing: 8.0,
// children: cards,
// ),
// ),
// ),
// )
// : SizedBox(
// width: MediaQuery.of(context).size.width,
// child: Wrap(
// alignment: WrapAlignment.spaceEvenly,
// runSpacing: 16.0,
// spacing: 4.0,
// children: cards,
// ),
// ),
// if (cards.length < 5)
// Padding(
// padding: const EdgeInsetsGeometry.all(16.0),
// child: ErrorIndicator(
// message: _status.message(L10n.of(context)),
// ),
// ),
// if (cards.length < 5 && _loading)
// const CircularProgressIndicator(),
// if (cards.length < 5 && !_loading)
// Padding(
// padding: const EdgeInsetsGeometry.only(bottom: 16),
// child: ElevatedButton(
// onPressed: _setActivityItems,
// style: ElevatedButton.styleFrom(
// backgroundColor:
// theme.colorScheme.primaryContainer,
// foregroundColor:
// theme.colorScheme.onPrimaryContainer,
// padding: const EdgeInsets.symmetric(
// horizontal: 12.0,
// ),
// ),
// child: Text(L10n.of(context).tryAgain),
// ),
// ),
// ],
// ),
// ),
// ),
// ],
// );
// }
// }

View file

@ -111,7 +111,6 @@ class PangeaController {
static final List<String> _storageKeys = [
'mode_list_storage',
'activity_plan_storage',
'activity_plan_by_id_storage',
'bookmarked_activities',
'objective_list_storage',
'topic_list_storage',
@ -130,6 +129,11 @@ class PangeaController {
'onboarding_storage',
'analytics_request_storage',
'activity_analytics_storage',
'course_topic_storage',
'course_media_storage',
'course_location_storage',
'course_activity_storage',
'course_location_media_storage',
];
Future<void> clearCache({List<String> exclude = const []}) async {

View file

@ -16,6 +16,7 @@ import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/course_chats/course_chats_view.dart';
import 'package:fluffychat/pangea/course_plans/course_plan_model.dart';
import 'package:fluffychat/pangea/course_plans/course_plan_room_extension.dart';
import 'package:fluffychat/pangea/course_plans/course_topic_model.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/public_spaces/public_room_bottom_sheet.dart';
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
@ -100,29 +101,29 @@ class CourseChatsController extends State<CourseChats> {
}
int get _selectedTopicIndex =>
course?.topics.indexWhere((t) => t.uuid == selectedTopicId) ?? -1;
course?.loadedTopics.indexWhere((t) => t.uuid == selectedTopicId) ?? -1;
bool get canMoveLeft => _selectedTopicIndex > 0;
bool get canMoveRight {
if (course == null) return false;
final endIndex =
room?.ownCurrentTopicIndex(course!) ?? (course!.topics.length - 1);
final endIndex = room?.ownCurrentTopicIndex(course!) ??
(course!.loadedTopics.length - 1);
return _selectedTopicIndex < endIndex;
}
void moveLeft() {
if (canMoveLeft) {
setSelectedTopicId(course!.topics[_selectedTopicIndex - 1].uuid);
setSelectedTopicId(course!.loadedTopics[_selectedTopicIndex - 1].uuid);
}
}
void moveRight() {
if (canMoveRight) {
setSelectedTopicId(course!.topics[_selectedTopicIndex + 1].uuid);
setSelectedTopicId(course!.loadedTopics[_selectedTopicIndex + 1].uuid);
}
}
Topic? get selectedTopic => course?.topics.firstWhereOrNull(
CourseTopicModel? get selectedTopic => course?.loadedTopics.firstWhereOrNull(
(topic) => topic.uuid == selectedTopicId,
);

View file

@ -13,8 +13,8 @@ import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart
import 'package:fluffychat/pangea/course_chats/course_chats_page.dart';
import 'package:fluffychat/pangea/course_chats/unjoined_chat_list_item.dart';
import 'package:fluffychat/pangea/course_plans/course_plan_builder.dart';
import 'package:fluffychat/pangea/course_plans/course_plan_model.dart';
import 'package:fluffychat/pangea/course_plans/course_plan_room_extension.dart';
import 'package:fluffychat/pangea/course_plans/course_topic_model.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/space_analytics/analytics_request_indicator.dart';
import 'package:fluffychat/pangea/spaces/widgets/knocking_users_indicator.dart';
@ -41,13 +41,13 @@ class CourseChatsView extends StatelessWidget {
return CoursePlanBuilder(
courseId: room.coursePlan?.uuid,
onFound: (course) {
onLoaded: (course) {
controller.setCourse(course);
final topic = room.ownCurrentTopic(course);
if (topic != null) controller.setSelectedTopicId(topic.uuid);
},
builder: (context, courseController) {
final Topic? topic = controller.selectedTopic;
final CourseTopicModel? topic = controller.selectedTopic;
final List<String> activityIds = topic?.activityIds ?? [];
return StreamBuilder(
@ -220,23 +220,24 @@ class CourseChatsView extends StatelessWidget {
),
),
),
Row(
spacing: 6.0,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.location_on,
size: 24.0,
),
Text(
topic.location,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
if (topic.location != null)
Row(
spacing: 6.0,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.location_on,
size: 24.0,
),
),
],
),
Text(
topic.location!,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
MouseRegion(
cursor: controller.canMoveRight
? SystemMouseCursors.click

View file

@ -83,14 +83,14 @@ class CourseInfoChips extends StatelessWidget {
),
CourseInfoChip(
icon: Icons.location_on,
text: L10n.of(context).numModules(course.topics.length),
text: L10n.of(context).numModules(course.topicIds.length),
fontSize: fontSize,
iconSize: iconSize,
padding: padding,
),
CourseInfoChip(
icon: Icons.event_note_outlined,
text: L10n.of(context).numActivityPlans(course.activities),
text: L10n.of(context).numActivityPlans(course.totalActivities),
fontSize: fontSize,
iconSize: iconSize,
padding: padding,

View file

@ -52,7 +52,7 @@ class SelectedCourseView extends StatelessWidget {
Padding(
padding: const EdgeInsets.all(12.0),
child: ListView.builder(
itemCount: course.topics.length + 2,
itemCount: course.loadedTopics.length + 2,
itemBuilder: (context, index) {
if (index == 0) {
return Column(
@ -112,11 +112,11 @@ class SelectedCourseView extends StatelessWidget {
index--;
if (index == course.topics.length) {
if (index == course.loadedTopics.length) {
return const SizedBox(height: 150.0);
}
final topic = course.topics[index];
final topic = course.loadedTopics[index];
return Padding(
padding:
const EdgeInsets.symmetric(vertical: 4.0),
@ -186,17 +186,18 @@ class SelectedCourseView extends StatelessWidget {
spacing: 8.0,
mainAxisSize: MainAxisSize.min,
children: [
CourseInfoChip(
icon: Icons.location_on,
text: topic.location,
fontSize: descFontSize,
iconSize: smallIconSize,
),
if (topic.location != null)
CourseInfoChip(
icon: Icons.location_on,
text: topic.location!,
fontSize: descFontSize,
iconSize: smallIconSize,
),
CourseInfoChip(
icon: Icons.event_note_outlined,
text: L10n.of(context)
.numActivityPlans(
topic.activities.length,
topic.loadedActivities.length,
),
fontSize: descFontSize,
iconSize: smallIconSize,

View file

@ -0,0 +1,127 @@
import 'dart:async';
import 'package:get_storage/get_storage.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_activity.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_activity_media.dart';
import 'package:fluffychat/pangea/payload_client/payload_repo.dart';
class CourseActivityRepo {
static final Map<String, Completer<List<ActivityPlanModel>>> _cache = {};
static final GetStorage _storage = GetStorage('course_activity_storage');
static ActivityPlanModel? _getCached(String uuid) {
final json = _storage.read<Map<String, dynamic>>(uuid);
if (json != null) {
try {
return ActivityPlanModel.fromJson(json);
} catch (e) {
// ignore invalid cached data
_storage.remove(uuid);
}
}
return null;
}
static Future<void> _setCached(ActivityPlanModel activity) =>
_storage.write(activity.activityId, activity.toJson());
static List<ActivityPlanModel> getSync(List<String> uuids) {
return uuids
.map((uuid) => _getCached(uuid))
.whereType<ActivityPlanModel>()
.toList();
}
static Future<List<ActivityPlanModel>> get(
String topicId,
List<String> uuids,
) async {
final activities = <ActivityPlanModel>[];
final toFetch = <String>[];
for (final uuid in uuids) {
final cached = _getCached(uuid);
if (cached != null) {
activities.add(cached);
} else {
toFetch.add(uuid);
}
}
if (toFetch.isNotEmpty) {
final fetchedActivities = await _fetch(topicId, toFetch);
activities.addAll(fetchedActivities);
for (final activity in fetchedActivities) {
await _setCached(activity);
}
}
return activities;
}
static Future<List<ActivityPlanModel>> _fetch(
String topicId,
List<String> uuids,
) async {
if (_cache.containsKey(topicId)) {
return _cache[topicId]!.future;
}
final completer = Completer<List<ActivityPlanModel>>();
_cache[topicId] = completer;
final where = {
"id": {"in": uuids.join(",")},
};
final limit = uuids.length;
try {
final cmsCoursePlanActivitiesResult = await PayloadRepo.payload.find(
CmsCoursePlanActivity.slug,
CmsCoursePlanActivity.fromJson,
where: where,
limit: limit,
page: 1,
sort: "createdAt",
);
final imageUrls = await _fetchImageUrls(
cmsCoursePlanActivitiesResult.docs.map((a) => a.id).toList(),
);
final activities = cmsCoursePlanActivitiesResult.docs
.map((a) => a.toActivityPlanModel(imageUrls[a.id]))
.toList();
completer.complete(activities);
return activities;
} catch (e) {
completer.completeError(e);
rethrow;
} finally {
_cache.remove(topicId);
}
}
static Future<Map<String, String>> _fetchImageUrls(
List<String> activityIds,
) async {
final where = {
"id": {"in": activityIds.join(",")},
};
final limit = activityIds.length;
final cmsCoursePlanActivityMediasResult = await PayloadRepo.payload.find(
CmsCoursePlanActivityMedia.slug,
CmsCoursePlanActivityMedia.fromJson,
where: where,
limit: limit,
page: 1,
sort: "createdAt",
);
return Map.fromEntries(
cmsCoursePlanActivityMediasResult.docs.map((e) => MapEntry(e.id, e.url!)),
);
}
}

View file

@ -0,0 +1,102 @@
import 'dart:async';
import 'package:get_storage/get_storage.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_topic_location_media.dart';
import 'package:fluffychat/pangea/payload_client/payload_repo.dart';
class CourseLocationMediaRepo {
static final Map<String, Completer<Map<String, String>>> _cache = {};
static final GetStorage _storage =
GetStorage('course_location_media_storage');
static String? _getCached(String uuid) {
try {
return _storage.read(uuid) as String?;
} catch (e) {
// If parsing fails, remove the corrupted cache entry
_storage.remove(uuid);
}
return null;
}
static Future<void> _setCached(String uuid, String url) =>
_storage.write(uuid, url);
static List<String> getSync(List<String> uuids) {
final urls = <String>[];
for (final uuid in uuids) {
final cached = _getCached(uuid);
if (cached != null) {
urls.add(cached);
}
}
return urls;
}
static Future<List<String>> get(String topicId, List<String> uuids) async {
final urls = <String>[];
final toFetch = <String>[];
for (final uuid in uuids) {
final cached = _getCached(uuid);
if (cached != null) {
urls.add(cached);
} else {
toFetch.add(uuid);
}
}
if (toFetch.isNotEmpty) {
final fetched = await _fetch(topicId, toFetch);
urls.addAll(fetched.values);
for (final entry in fetched.entries) {
await _setCached(entry.key, entry.value);
}
}
return urls;
}
static Future<Map<String, String>> _fetch(
String topicId,
List<String> uuids,
) async {
if (_cache.containsKey(topicId)) {
return _cache[topicId]!.future;
}
final completer = Completer<Map<String, String>>();
_cache[topicId] = completer;
final where = {
"id": {"in": uuids.join(",")},
};
final limit = uuids.length;
try {
final cmsCoursePlanTopicLocationMediasResult =
await PayloadRepo.payload.find(
CmsCoursePlanTopicLocationMedia.slug,
CmsCoursePlanTopicLocationMedia.fromJson,
where: where,
limit: limit,
page: 1,
sort: "createdAt",
);
final media = Map.fromEntries(
cmsCoursePlanTopicLocationMediasResult.docs
.map((e) => MapEntry(e.id, e.url!)),
);
completer.complete(media);
return media;
} catch (e) {
completer.completeError(e);
rethrow;
} finally {
_cache.remove(topicId);
}
}
}

View file

@ -0,0 +1,30 @@
class CourseLocationModel {
String uuid;
String name;
List<String> mediaIds;
CourseLocationModel({
required this.uuid,
required this.name,
required this.mediaIds,
});
factory CourseLocationModel.fromJson(Map<String, dynamic> json) {
return CourseLocationModel(
uuid: json['uuid'] as String,
name: json['name'] as String,
mediaIds: (json['media_ids'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[],
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'name': name,
'media_ids': mediaIds,
};
}
}

View file

@ -0,0 +1,107 @@
import 'dart:async';
import 'package:get_storage/get_storage.dart';
import 'package:fluffychat/pangea/course_plans/course_location_model.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_topic_location.dart';
import 'package:fluffychat/pangea/payload_client/payload_repo.dart';
class CourseLocationRepo {
static final Map<String, Completer<List<CourseLocationModel>>> _cache = {};
static final GetStorage _storage = GetStorage('course_location_storage');
static CourseLocationModel? _getCached(String uuid) {
final json = _storage.read(uuid);
if (json != null) {
try {
return CourseLocationModel.fromJson(Map<String, dynamic>.from(json));
} catch (e) {
// If parsing fails, remove the corrupted cache entry
_storage.remove(uuid);
}
}
return null;
}
static Future<void> _setCached(String uuid, CourseLocationModel location) =>
_storage.write(uuid, location.toJson());
static List<CourseLocationModel> getSync(List<String> uuids) {
final locations = <CourseLocationModel>[];
for (final uuid in uuids) {
final cached = _getCached(uuid);
if (cached != null) {
locations.add(cached);
}
}
return locations;
}
static Future<List<CourseLocationModel>> get(
String courseId,
List<String> uuids,
) async {
final locations = <CourseLocationModel>[];
final toFetch = <String>[];
for (final uuid in uuids) {
final cached = _getCached(uuid);
if (cached != null) {
locations.add(cached);
} else {
toFetch.add(uuid);
}
}
if (toFetch.isNotEmpty) {
final fetchedLocations = await _fetch(courseId, toFetch);
locations.addAll(fetchedLocations);
for (int i = 0; i < toFetch.length; i++) {
await _setCached(toFetch[i], fetchedLocations[i]);
}
}
return locations;
}
static Future<List<CourseLocationModel>> _fetch(
String topicId,
List<String> uuids,
) async {
if (_cache.containsKey(topicId)) {
return _cache[topicId]!.future;
}
final completer = Completer<List<CourseLocationModel>>();
_cache[topicId] = completer;
final where = {
"id": {"in": uuids.join(",")},
};
final limit = uuids.length;
try {
final cmsCoursePlanTopicLocationsResult = await PayloadRepo.payload.find(
CmsCoursePlanTopicLocation.slug,
CmsCoursePlanTopicLocation.fromJson,
where: where,
limit: limit,
page: 1,
sort: "createdAt",
);
final locations = cmsCoursePlanTopicLocationsResult.docs
.map((location) => location.toCourseLocationModel())
.toList();
completer.complete(locations);
return locations;
} catch (e) {
completer.completeError(e);
rethrow;
} finally {
_cache.remove(topicId);
}
}
}

View file

@ -0,0 +1,98 @@
import 'dart:async';
import 'package:get_storage/get_storage.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_media.dart';
import 'package:fluffychat/pangea/payload_client/payload_repo.dart';
class CourseMediaRepo {
static final Map<String, Completer<Map<String, String>>> _cache = {};
static final GetStorage _storage = GetStorage('course_media_storage');
static List<String> getSync(List<String> uuids) {
final urls = <String>[];
for (final uuid in uuids) {
final cached = _getCached(uuid);
if (cached != null) {
urls.add(cached);
}
}
return urls;
}
static Future<List<String>> get(String courseId, List<String> uuids) async {
final urls = <String>[];
final toFetch = <String>[];
for (final uuid in uuids) {
final cached = _getCached(uuid);
if (cached != null) {
urls.add(cached);
} else {
toFetch.add(uuid);
}
}
if (toFetch.isNotEmpty) {
final fetchedUrls = await _fetch(courseId, toFetch);
urls.addAll(fetchedUrls.values.toList());
for (final entry in fetchedUrls.entries) {
await _setCached(entry.key, entry.value);
}
}
return urls;
}
static String? _getCached(String uuid) => _storage.read(uuid);
static Future<void> _setCached(String uuid, String url) =>
_storage.write(uuid, url);
static Future<Map<String, String>> _fetch(
String courseId,
List<String> uuids,
) async {
if (_cache.containsKey(courseId)) {
return _cache[courseId]!.future;
}
final completer = Completer<Map<String, String>>();
_cache[courseId] = completer;
final where = {
"id": {"in": uuids.join(",")},
};
final limit = uuids.length;
try {
final cmsCoursePlanMediaResult = await PayloadRepo.payload.find(
CmsCoursePlanMedia.slug,
CmsCoursePlanMedia.fromJson,
where: where,
limit: limit,
page: 1,
sort: "createdAt",
);
if (cmsCoursePlanMediaResult.docs.isEmpty) {
return {};
}
final media = Map.fromEntries(
cmsCoursePlanMediaResult.docs
.where((media) => media.url != null)
.map((media) => MapEntry(media.id, media.url!)),
);
completer.complete(media);
return media;
} catch (e) {
completer.completeError(e);
rethrow;
} finally {
_cache.remove(courseId);
}
}
}

View file

@ -6,7 +6,7 @@ import 'package:fluffychat/pangea/course_plans/course_plans_repo.dart';
class CoursePlanBuilder extends StatefulWidget {
final String? courseId;
final VoidCallback? onNotFound;
final Function(CoursePlanModel course)? onFound;
final Function(CoursePlanModel course)? onLoaded;
final Widget Function(
BuildContext context,
CoursePlanController controller,
@ -17,7 +17,7 @@ class CoursePlanBuilder extends StatefulWidget {
required this.courseId,
required this.builder,
this.onNotFound,
this.onFound,
this.onLoaded,
});
@override
@ -61,9 +61,13 @@ class CoursePlanController extends State<CoursePlanBuilder> {
});
course = await CoursePlansRepo.get(widget.courseId!);
course == null
? widget.onNotFound?.call()
: widget.onFound?.call(course!);
if (course == null) {
widget.onNotFound?.call();
} else {
await course!.init();
}
widget.onLoaded?.call(course!);
} catch (e) {
error = e;
} finally {

View file

@ -1,71 +1,15 @@
import 'package:fluffychat/pangea/activity_generator/media_enum.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/common/config/environment.dart';
import 'package:fluffychat/pangea/course_plans/course_media_repo.dart';
import 'package:fluffychat/pangea/course_plans/course_topic_model.dart';
import 'package:fluffychat/pangea/course_plans/course_topic_repo.dart';
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_activity.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_activity_media.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_media.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_topic.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_topic_location.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_topic_location_media.dart';
/// Represents a topic in the course planner response.
class Topic {
final String title;
final String description;
final String location;
final String uuid;
final String? imageUrl;
final List<ActivityPlanModel> activities;
Topic({
required this.title,
required this.description,
this.location = "Unknown",
required this.uuid,
List<ActivityPlanModel>? activities,
this.imageUrl,
}) : activities = activities ?? [];
/// Deserialize from JSON
factory Topic.fromJson(Map<String, dynamic> json) {
return Topic(
title: json['title'] as String,
description: json['description'] as String,
location: json['location'] as String? ?? "Unknown",
uuid: json['uuid'] as String,
activities: (json['activities'] as List<dynamic>?)
?.map(
(e) => ActivityPlanModel.fromJson(e as Map<String, dynamic>),
)
.toList() ??
[],
imageUrl: json['image_url'] as String?,
);
}
/// Serialize to JSON
Map<String, dynamic> toJson() {
return {
'title': title,
'description': description,
'location': location,
'uuid': uuid,
'activities': activities.map((e) => e.toJson()).toList(),
'image_url': imageUrl,
};
}
List<String> get activityIds => activities.map((e) => e.activityId).toList();
}
/// Represents a course plan in the course planner response.
class CoursePlanModel {
final String uuid;
final String targetLanguage;
final String languageOfInstructions;
final LanguageLevelTypeEnum cefrLevel;
@ -73,10 +17,8 @@ class CoursePlanModel {
final String title;
final String description;
final String uuid;
final List<Topic> topics;
final String? imageUrl;
final List<String> topicIds;
final List<String> mediaIds;
CoursePlanModel({
required this.targetLanguage,
@ -85,12 +27,9 @@ class CoursePlanModel {
required this.title,
required this.description,
required this.uuid,
List<Topic>? topics,
this.imageUrl,
}) : topics = topics ?? [];
int get activities =>
topics.map((t) => t.activities.length).reduce((a, b) => a + b);
required this.topicIds,
required this.mediaIds,
});
LanguageModel? get targetLanguageModel =>
PLanguageStore.byLangCode(targetLanguage);
@ -107,16 +46,17 @@ class CoursePlanModel {
languageOfInstructions.toUpperCase();
String? topicID(String activityID) {
for (final topic in topics) {
for (final activity in topic.activities) {
if (activity.activityId == activityID) {
return topic.uuid;
}
for (final topic in loadedTopics) {
if (topic.activityIds.any((id) => id == activityID)) {
return topic.uuid;
}
}
return null;
}
int get totalActivities =>
loadedTopics.fold(0, (sum, topic) => sum + topic.activityIds.length);
/// Deserialize from JSON
factory CoursePlanModel.fromJson(Map<String, dynamic> json) {
return CoursePlanModel(
@ -126,11 +66,14 @@ class CoursePlanModel {
title: json['title'] as String,
description: json['description'] as String,
uuid: json['uuid'] as String,
topics: (json['topics'] as List<dynamic>?)
?.map((e) => Topic.fromJson(e as Map<String, dynamic>))
topicIds: (json['topic_ids'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[],
mediaIds: (json['media_ids'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[],
imageUrl: json['image_url'] as String?,
);
}
@ -143,144 +86,41 @@ class CoursePlanModel {
'title': title,
'description': description,
'uuid': uuid,
'topics': topics.map((e) => e.toJson()).toList(),
'image_url': imageUrl,
'topic_ids': topicIds,
'media_ids': mediaIds,
};
}
factory CoursePlanModel.fromCmsDocs(
CmsCoursePlan cmsCoursePlan,
List<CmsCoursePlanMedia>? cmsCoursePlanMedias,
List<CmsCoursePlanTopic>? cmsCoursePlanTopics,
List<CmsCoursePlanTopicLocation>? cmsCoursePlanTopicLocations,
List<CmsCoursePlanTopicLocationMedia>? cmsCoursePlanTopicLocationMedias,
List<CmsCoursePlanActivity>? cmsCoursePlanActivities,
List<CmsCoursePlanActivityMedia>? cmsCoursePlanActivityMedias,
) {
// fetch topics
List<Topic>? topics;
if (cmsCoursePlanTopics != null) {
for (final topic in cmsCoursePlanTopics) {
// select locations of current topic
List<CmsCoursePlanTopicLocation>? topicLocations;
if (cmsCoursePlanTopicLocations != null) {
for (final location in cmsCoursePlanTopicLocations) {
if (location.coursePlanTopics.contains(topic.id)) {
topicLocations ??= [];
topicLocations.add(location);
}
}
}
bool get topicListComplete => topicIds.length == loadedTopics.length;
List<CourseTopicModel> get loadedTopics => CourseTopicRepo.getSync(topicIds);
Future<List<CourseTopicModel>> fetchTopics() =>
CourseTopicRepo.get(uuid, topicIds);
// select activities of current topic
List<CmsCoursePlanActivity>? topicActivities;
if (cmsCoursePlanActivities != null) {
for (final activity in cmsCoursePlanActivities) {
if (activity.coursePlanTopics.contains(topic.id)) {
topicActivities ??= [];
topicActivities.add(activity);
}
}
}
bool get mediaListComplete => mediaIds.length == loadedMediaUrls.length;
List<String> get loadedMediaUrls => CourseMediaRepo.getSync(mediaIds);
Future<List<String>> fetchMediaUrls() => CourseMediaRepo.get(uuid, mediaIds);
String? get imageUrl => loadedMediaUrls.isEmpty
? null
: "${Environment.cmsApi}${loadedMediaUrls.first}";
List<ActivityPlanModel>? activityPlans;
if (topicActivities != null) {
for (final activity in topicActivities) {
// select media of current activity
List<CmsCoursePlanActivityMedia>? activityMedias;
if (cmsCoursePlanActivityMedias != null) {
for (final media in cmsCoursePlanActivityMedias) {
if (media.coursePlanActivities.contains(activity.id)) {
activityMedias ??= [];
activityMedias.add(media);
}
}
}
Future<void> init() async {
final courseFutures = <Future>[
fetchMediaUrls(),
fetchTopics(),
];
await Future.wait(courseFutures);
final Map<String, ActivityRole> roles = {};
for (final role in activity.roles) {
roles[role.id] = ActivityRole(
id: role.id,
name: role.name,
avatarUrl: role.avatarUrl,
goal: role.goal,
);
}
activityPlans ??= [];
activityPlans.add(
ActivityPlanModel(
req: ActivityPlanRequest(
topic: "",
mode: "",
objective: "",
media: MediaEnum.nan,
cefrLevel: activity.cefrLevel,
languageOfInstructions: activity.l1,
targetLanguage: activity.l2,
numberOfParticipants: activity.roles.length,
),
activityId: activity.id,
title: activity.title,
description: activity.description,
learningObjective: activity.learningObjective,
instructions: activity.instructions,
vocab: activity.vocabs
.map((v) => Vocab(lemma: v.lemma, pos: v.pos))
.toList(),
roles: roles,
imageURL: activityMedias != null && activityMedias.isNotEmpty
? '${Environment.cmsApi}${activityMedias.first.url}'
: null,
),
);
}
}
List<CmsCoursePlanTopicLocationMedia>? topicLocationMedias;
if (cmsCoursePlanTopicLocationMedias != null &&
topicLocations != null &&
topicLocations.isNotEmpty) {
final location = topicLocations.first;
for (final media in cmsCoursePlanTopicLocationMedias) {
if (media.coursePlanTopicLocations.contains(location.id)) {
topicLocationMedias ??= [];
topicLocationMedias.add(media);
}
}
}
topics ??= [];
topics.add(
Topic(
uuid: topic.id,
title: topic.title,
description: topic.description,
location: topicLocations != null && topicLocations.isNotEmpty
? topicLocations.first.name
: "Any",
activities: activityPlans,
imageUrl:
topicLocationMedias != null && topicLocationMedias.isNotEmpty
? '${Environment.cmsApi}${topicLocationMedias.last.url}'
: null,
),
);
}
}
return CoursePlanModel(
uuid: cmsCoursePlan.id,
title: cmsCoursePlan.title,
description: cmsCoursePlan.description,
cefrLevel:
LanguageLevelTypeEnumExtension.fromString(cmsCoursePlan.cefrLevel),
languageOfInstructions: cmsCoursePlan.l1,
targetLanguage: cmsCoursePlan.l2,
topics: topics,
imageUrl: cmsCoursePlanMedias != null && cmsCoursePlanMedias.isNotEmpty
? '${Environment.cmsApi}${cmsCoursePlanMedias.first.url}'
: null,
final topicFutures = <Future>[];
topicFutures.addAll(
loadedTopics.map(
(topic) => topic.fetchActivities(),
),
);
topicFutures.addAll(
loadedTopics.map(
(topic) => topic.fetchLocationMedia(),
),
);
await Future.wait(topicFutures);
}
}

View file

@ -1,10 +1,18 @@
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_roles_model.dart';
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart';
import 'package:fluffychat/pangea/course_plans/course_plan_event.dart';
import 'package:fluffychat/pangea/course_plans/course_plan_model.dart';
import 'package:fluffychat/pangea/course_plans/course_topic_model.dart';
import 'package:fluffychat/pangea/course_plans/course_user_event.dart';
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/extensions/join_rule_extension.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
extension CoursePlanRoomExtension on Room {
CoursePlanEvent? get coursePlan {
@ -32,7 +40,7 @@ extension CoursePlanRoomExtension on Room {
final state = _courseUserState(userID);
if (state == null) return false;
final topicIndex = course.topics.indexWhere(
final topicIndex = course.loadedTopics.indexWhere(
(t) => t.uuid == topicID,
);
@ -40,27 +48,28 @@ extension CoursePlanRoomExtension on Room {
throw Exception('Topic not found');
}
final activityIds =
course.topics[topicIndex].activities.map((a) => a.activityId).toList();
final activityIds = course.loadedTopics[topicIndex].loadedActivities
.map((a) => a.activityId)
.toList();
return state.completedActivities(topicID).toSet().containsAll(activityIds);
}
Topic? currentTopic(
CourseTopicModel? currentTopic(
String userID,
CoursePlanModel course,
) {
if (coursePlan == null) return null;
final topicIDs = course.topics.map((t) => t.uuid).toList();
final topicIDs = course.loadedTopics.map((t) => t.uuid).toList();
if (topicIDs.isEmpty) return null;
final index = topicIDs.indexWhere(
(t) => !_hasCompletedTopic(userID, t, course),
);
return index == -1 ? null : course.topics[index];
return index == -1 ? null : course.loadedTopics[index];
}
Topic? ownCurrentTopic(CoursePlanModel course) =>
CourseTopicModel? ownCurrentTopic(CoursePlanModel course) =>
currentTopic(client.userID!, course);
int currentTopicIndex(
@ -68,7 +77,7 @@ extension CoursePlanRoomExtension on Room {
CoursePlanModel course,
) {
if (coursePlan == null) return -1;
final topicIDs = course.topics.map((t) => t.uuid).toList();
final topicIDs = course.loadedTopics.map((t) => t.uuid).toList();
if (topicIDs.isEmpty) return -1;
final index = topicIDs.indexWhere(
@ -93,7 +102,7 @@ extension CoursePlanRoomExtension on Room {
if (user.id == BotName.byEnvironment) continue;
final topicIndex = currentTopicIndex(user.id, course);
if (topicIndex != -1) {
final topicID = course.topics[topicIndex].uuid;
final topicID = course.loadedTopics[topicIndex].uuid;
topicUserMap.putIfAbsent(topicID, () => []).add(user);
}
}
@ -117,4 +126,57 @@ extension CoursePlanRoomExtension on Room {
state.toJson(),
);
}
Future<String> launchActivityRoom(
ActivityPlanModel activity,
ActivityRole? role,
) async {
final roomID = await client.createRoom(
creationContent: {
'type': "${PangeaRoomTypes.activitySession}:${activity.activityId}",
},
visibility: Visibility.private,
name: activity.title,
initialState: [
StateEvent(
type: PangeaEventTypes.activityPlan,
content: activity.toJson(),
),
if (activity.imageURL != null)
StateEvent(
type: EventTypes.RoomAvatar,
content: {'url': activity.imageURL},
),
if (role != null)
StateEvent(
type: PangeaEventTypes.activityRole,
content: ActivityRolesModel({
role.id: ActivityRoleModel(
id: role.id,
userId: client.userID!,
role: role.name,
),
}).toJson(),
),
RoomDefaults.defaultPowerLevels(
client.userID!,
),
await client.pangeaJoinRules(
'knock_restricted',
allow: [
{
"type": "m.room_membership",
"room_id": id,
}
],
),
],
);
await addToSpace(roomID);
if (pangeaSpaceParents.isEmpty) {
await client.waitForRoomInSync(roomID);
}
return roomID;
}
}

View file

@ -1,18 +1,12 @@
import 'dart:async';
import 'package:get_storage/get_storage.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/course_plans/course_plan_model.dart';
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_activity.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_activity_media.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_media.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_topic.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_topic_location.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_topic_location_media.dart';
import 'package:fluffychat/pangea/payload_client/payload_client.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/pangea/payload_client/payload_repo.dart';
class CourseFilter {
final LanguageModel? targetLanguage;
@ -43,13 +37,9 @@ class CourseFilter {
}
class CoursePlansRepo {
static final Map<String, Completer<CoursePlanModel>> cache = {};
static final GetStorage _courseStorage = GetStorage("course_storage");
static final PayloadClient payload = PayloadClient(
baseUrl: Environment.cmsApi,
accessToken: MatrixState.pangeaController.userController.accessToken,
);
static CoursePlanModel? _getCached(String id) {
final json = _courseStorage.read(id);
if (json != null) {
@ -107,15 +97,30 @@ class CoursePlansRepo {
return cached;
}
final cmsCoursePlan = await payload.findById(
"course-plans",
id,
CmsCoursePlan.fromJson,
);
if (cache.containsKey(id)) {
return cache[id]!.future;
}
final coursePlan = await _fromCmsCoursePlan(cmsCoursePlan);
await _setCached(coursePlan);
return coursePlan;
final completer = Completer<CoursePlanModel>();
cache[id] = completer;
try {
final cmsCoursePlan = await PayloadRepo.payload.findById(
"course-plans",
id,
CmsCoursePlan.fromJson,
);
final coursePlan = cmsCoursePlan.toCoursePlanModel();
await _setCached(coursePlan);
completer.complete(coursePlan);
return coursePlan;
} catch (e) {
completer.completeError(e);
rethrow;
} finally {
cache.remove(id);
}
}
static Future<List<CoursePlanModel>> search({CourseFilter? filter}) async {
@ -170,7 +175,7 @@ class CoursePlansRepo {
}
}
final result = await payload.find(
final result = await PayloadRepo.payload.find(
CmsCoursePlan.slug,
CmsCoursePlan.fromJson,
page: 1,
@ -178,13 +183,11 @@ class CoursePlansRepo {
where: where,
);
final coursePlans = await Future.wait(
result.docs.map(
(cmsCoursePlan) => _fromCmsCoursePlan(
cmsCoursePlan,
),
),
);
final coursePlans = result.docs
.map(
(cmsCoursePlan) => cmsCoursePlan.toCoursePlanModel(),
)
.toList();
await _setCachedSearchResults(
filter ?? CourseFilter(),
@ -193,170 +196,4 @@ class CoursePlansRepo {
return coursePlans;
}
static Future<CoursePlanModel> _fromCmsCoursePlan(
CmsCoursePlan cmsCoursePlan,
) async {
final medias = await _getMedia(cmsCoursePlan);
final topics = await _getTopics(cmsCoursePlan);
final locations = await _getTopicLocations(topics ?? []);
final locationMedias = await _getTopicLocationMedia(locations ?? []);
final activities = await _getTopicActivities(topics ?? []);
final activityMedias = await _getActivityMedia(activities ?? []);
return CoursePlanModel.fromCmsDocs(
cmsCoursePlan,
medias,
topics,
locations,
locationMedias,
activities,
activityMedias,
);
}
static Future<List<CmsCoursePlanMedia>?> _getMedia(
CmsCoursePlan cmsCoursePlan,
) async {
final docs = cmsCoursePlan.coursePlanMedia?.docs;
if (docs == null || docs.isEmpty) return null;
final where = {
"id": {"in": docs.join(",")},
};
final limit = docs.length;
final cmsCoursePlanMediaResult = await payload.find(
CmsCoursePlanMedia.slug,
CmsCoursePlanMedia.fromJson,
where: where,
limit: limit,
page: 1,
sort: "createdAt",
);
return cmsCoursePlanMediaResult.docs;
}
static Future<List<CmsCoursePlanTopic>?> _getTopics(
CmsCoursePlan cmsCoursePlan,
) async {
final docs = cmsCoursePlan.coursePlanTopics?.docs;
if (docs == null || docs.isEmpty) return null;
final where = {
"id": {"in": docs.join(",")},
};
final limit = docs.length;
final cmsCourseTopicsResult = await payload.find(
CmsCoursePlanTopic.slug,
CmsCoursePlanTopic.fromJson,
where: where,
limit: limit,
page: 1,
sort: "createdAt",
);
return cmsCourseTopicsResult.docs;
}
static Future<List<CmsCoursePlanTopicLocation>?> _getTopicLocations(
List<CmsCoursePlanTopic> topics,
) async {
final List<String> locations = [];
for (final top in topics) {
if (top.coursePlanTopicLocations?.docs != null) {
locations.addAll(top.coursePlanTopicLocations!.docs!);
}
}
if (locations.isEmpty) return null;
final where = {
"id": {"in": locations.join(",")},
};
final limit = locations.length;
final cmsCoursePlanTopicLocationsResult = await payload.find(
CmsCoursePlanTopicLocation.slug,
CmsCoursePlanTopicLocation.fromJson,
where: where,
limit: limit,
page: 1,
sort: "createdAt",
);
return cmsCoursePlanTopicLocationsResult.docs;
}
static Future<List<CmsCoursePlanTopicLocationMedia>?> _getTopicLocationMedia(
List<CmsCoursePlanTopicLocation> locations,
) async {
final List<String> mediaIds = [];
for (final loc in locations) {
if (loc.coursePlanTopicLocationMedia?.docs != null) {
mediaIds.addAll(loc.coursePlanTopicLocationMedia!.docs!);
}
}
if (mediaIds.isEmpty) return null;
final where = {
"id": {"in": mediaIds.join(",")},
};
final limit = mediaIds.length;
final cmsCoursePlanTopicLocationMediasResult = await payload.find(
CmsCoursePlanTopicLocationMedia.slug,
CmsCoursePlanTopicLocationMedia.fromJson,
where: where,
limit: limit,
page: 1,
sort: "createdAt",
);
return cmsCoursePlanTopicLocationMediasResult.docs;
}
static Future<List<CmsCoursePlanActivity>?> _getTopicActivities(
List<CmsCoursePlanTopic> topics,
) async {
final List<String> activities = [];
for (final top in topics) {
if (top.coursePlanActivities?.docs != null) {
activities.addAll(top.coursePlanActivities!.docs!);
}
}
if (activities.isEmpty) return null;
final where = {
"id": {"in": activities.join(",")},
};
final limit = activities.length;
final cmsCoursePlanActivitiesResult = await payload.find(
CmsCoursePlanActivity.slug,
CmsCoursePlanActivity.fromJson,
where: where,
limit: limit,
page: 1,
sort: "createdAt",
);
return cmsCoursePlanActivitiesResult.docs;
}
static Future<List<CmsCoursePlanActivityMedia>?> _getActivityMedia(
List<CmsCoursePlanActivity> activity,
) async {
final List<String> mediaIds = [];
for (final act in activity) {
if (act.coursePlanActivityMedia?.docs != null) {
mediaIds.addAll(act.coursePlanActivityMedia!.docs!);
}
}
if (mediaIds.isEmpty) return null;
final where = {
"id": {"in": mediaIds.join(",")},
};
final limit = mediaIds.length;
final cmsCoursePlanActivityMediasResult = await payload.find(
CmsCoursePlanActivityMedia.slug,
CmsCoursePlanActivityMedia.fromJson,
where: where,
limit: limit,
page: 1,
sort: "createdAt",
);
return cmsCoursePlanActivityMediasResult.docs;
}
}

View file

@ -0,0 +1,86 @@
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/course_plans/course_activity_repo.dart';
import 'package:fluffychat/pangea/course_plans/course_location_media_repo.dart';
import 'package:fluffychat/pangea/course_plans/course_location_model.dart';
import 'package:fluffychat/pangea/course_plans/course_location_repo.dart';
/// Represents a topic in the course planner response.
class CourseTopicModel {
final String title;
final String description;
final String uuid;
final List<String> locationIds;
final List<String> activityIds;
CourseTopicModel({
required this.title,
required this.description,
required this.uuid,
required this.activityIds,
required this.locationIds,
});
bool get locationListComplete => locationIds.length == loadedLocations.length;
List<CourseLocationModel> get loadedLocations =>
CourseLocationRepo.getSync(locationIds);
Future<List<CourseLocationModel>> fetchLocations() =>
CourseLocationRepo.get(uuid, locationIds);
String? get location => loadedLocations.firstOrNull?.name;
bool get locationMediaListComplete =>
loadedLocationMediaIds.length ==
loadedLocations.map((e) => e.mediaIds.length).fold(0, (a, b) => a + b);
List<String> get loadedLocationMediaIds => loadedLocations
.map((location) => CourseLocationMediaRepo.getSync(location.mediaIds))
.expand((e) => e)
.toList();
Future<List<String>> fetchLocationMedia() async {
final allLocationMedia = <String>[];
for (final location in await fetchLocations()) {
allLocationMedia.addAll(
await CourseLocationMediaRepo.get(uuid, location.mediaIds),
);
}
return allLocationMedia;
}
String? get imageUrl => loadedLocationMediaIds.isEmpty
? null
: "${Environment.cmsApi}${loadedLocationMediaIds.first}";
bool get activityListComplete =>
activityIds.length == loadedActivities.length;
List<ActivityPlanModel> get loadedActivities =>
CourseActivityRepo.getSync(activityIds);
Future<List<ActivityPlanModel>> fetchActivities() =>
CourseActivityRepo.get(uuid, activityIds);
/// Deserialize from JSON
factory CourseTopicModel.fromJson(Map<String, dynamic> json) {
return CourseTopicModel(
title: json['title'] as String,
description: json['description'] as String,
uuid: json['uuid'] as String,
activityIds: (json['activity_ids'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[],
locationIds: (json['location_ids'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[],
);
}
/// Serialize to JSON
Map<String, dynamic> toJson() {
return {
'title': title,
'description': description,
'uuid': uuid,
'activity_ids': activityIds,
'location_ids': locationIds,
};
}
}

View file

@ -0,0 +1,109 @@
import 'dart:async';
import 'package:get_storage/get_storage.dart';
import 'package:fluffychat/pangea/course_plans/course_topic_model.dart';
import 'package:fluffychat/pangea/payload_client/models/course_plan/cms_course_plan_topic.dart';
import 'package:fluffychat/pangea/payload_client/payload_repo.dart';
class CourseTopicRepo {
static final Map<String, Completer<List<CourseTopicModel>>> _cache = {};
static final GetStorage _storage = GetStorage('course_topic_storage');
static List<CourseTopicModel> getSync(List<String> uuids) {
final topics = <CourseTopicModel>[];
for (final uuid in uuids) {
final cached = _getCached(uuid);
if (cached != null) {
topics.add(cached);
}
}
return topics;
}
static Future<List<CourseTopicModel>> get(
String courseId,
List<String> uuids,
) async {
final topics = <CourseTopicModel>[];
final toFetch = <String>[];
for (final uuid in uuids) {
final cached = _getCached(uuid);
if (cached != null) {
topics.add(cached);
} else {
toFetch.add(uuid);
}
}
if (toFetch.isNotEmpty) {
final fetchedTopics = await _fetch(courseId, toFetch);
topics.addAll(fetchedTopics);
await _setCached(fetchedTopics);
}
return topics;
}
static CourseTopicModel? _getCached(String uuid) {
final json = _storage.read(uuid);
if (json != null) {
try {
return CourseTopicModel.fromJson(Map<String, dynamic>.from(json));
} catch (e) {
_storage.remove(uuid);
}
}
return null;
}
static Future<void> _setCached(List<CourseTopicModel> topics) async {
for (final topic in topics) {
await _storage.write(topic.uuid, topic.toJson());
}
}
static Future<List<CourseTopicModel>> _fetch(
String courseId,
List<String> uuids,
) async {
if (_cache.containsKey(courseId)) {
return _cache[courseId]!.future;
}
final completer = Completer<List<CourseTopicModel>>();
_cache[courseId] = completer;
final where = {
"id": {"in": uuids.join(",")},
};
final limit = uuids.length;
try {
final cmsCourseTopicsResult = await PayloadRepo.payload.find(
CmsCoursePlanTopic.slug,
CmsCoursePlanTopic.fromJson,
where: where,
limit: limit,
page: 1,
sort: "createdAt",
);
final topics = cmsCourseTopicsResult.docs.map((topic) {
return topic.toCourseTopicModel();
}).toList();
completer.complete(topics);
return topics;
} catch (e) {
completer.completeError(e);
rethrow;
} finally {
_cache.remove(courseId);
}
}
}

View file

@ -2,23 +2,20 @@
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
import 'package:collection/collection.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog.dart';
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart';
import 'package:fluffychat/pangea/course_creation/course_info_chip_widget.dart';
import 'package:fluffychat/pangea/course_plans/course_plan_builder.dart';
import 'package:fluffychat/pangea/course_plans/course_plan_room_extension.dart';
import 'package:fluffychat/pangea/course_settings/pin_clipper.dart';
import 'package:fluffychat/pangea/course_settings/topic_participant_list.dart';
import 'package:fluffychat/widgets/matrix.dart';
class CourseSettings extends StatelessWidget {
final Room room;
@ -64,7 +61,7 @@ class CourseSettings extends StatelessWidget {
return Column(
spacing: isColumnMode ? 40.0 : 36.0,
mainAxisSize: MainAxisSize.min,
children: course.topics.mapIndexed((index, topic) {
children: course.loadedTopics.mapIndexed((index, topic) {
final unlocked = index <= currentTopicIndex;
final usersInTopic = topicsToUsers[topic.uuid] ?? [];
return AbsorbPointer(
@ -91,45 +88,18 @@ class CourseSettings extends StatelessWidget {
children: [
ClipPath(
clipper: PinClipper(),
child: topic.imageUrl != null
? CachedNetworkImage(
imageRenderMethodForWeb:
ImageRenderMethodForWeb
.HttpGet,
httpHeaders: {
'Authorization':
'Bearer ${MatrixState.pangeaController.userController.accessToken}',
},
width: 54.0,
height: 54.0,
fit: BoxFit.cover,
imageUrl: topic.imageUrl!,
placeholder: (context, url) {
return const Center(
child:
CircularProgressIndicator(),
);
},
errorWidget:
(context, url, error) {
return Container(
width: 54.0,
height: 54.0,
decoration: BoxDecoration(
color: theme.colorScheme
.secondary,
),
);
},
)
: Container(
width: 54.0,
height: 54.0,
decoration: BoxDecoration(
color: theme
.colorScheme.secondary,
),
),
child: ImageByUrl(
imageUrl: topic.imageUrl,
width: 54.0,
replacement: Container(
width: 54.0,
height: 54.0,
decoration: BoxDecoration(
color:
theme.colorScheme.secondary,
),
),
),
),
if (!unlocked)
const Positioned(
@ -153,12 +123,13 @@ class CourseSettings extends StatelessWidget {
fontSize: titleFontSize,
),
),
CourseInfoChip(
icon: Icons.location_on,
text: topic.location,
fontSize: descFontSize,
iconSize: iconSize,
),
if (topic.location != null)
CourseInfoChip(
icon: Icons.location_on,
text: topic.location!,
fontSize: descFontSize,
iconSize: iconSize,
),
if (constraints.maxWidth < 700.0)
Padding(
padding: const EdgeInsetsGeometry
@ -196,36 +167,21 @@ class CourseSettings extends StatelessWidget {
height: 210.0,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: topic.activities.length,
itemCount: topic.loadedActivities.length,
itemBuilder: (context, index) {
final activity = topic.activities[index];
return Padding(
padding: const EdgeInsets.only(right: 24.0),
child: ActivityPlannerBuilder(
initialActivity: activity,
room: room,
builder: (activityController) {
return ActivitySuggestionCard(
controller: activityController,
onPressed: () {
showDialog(
context: context,
builder: (context) {
return ActivitySuggestionDialog(
controller: activityController,
buttonText:
L10n.of(context).launchToSpace,
);
},
);
},
width: 120.0,
height: 200.0,
fontSize: 12.0,
fontSizeSmall: 8.0,
iconSize: 8.0,
);
},
child: ActivitySuggestionCard(
activity: topic.loadedActivities[index],
// TODO: go to activity start page
onPressed: () => context.go(
"/rooms/spaces/${room.id}/activity/${topic.loadedActivities[index].activityId}",
),
width: 120.0,
height: 200.0,
fontSize: 12.0,
fontSizeSmall: 8.0,
iconSize: 8.0,
),
);
},

View file

@ -1,3 +1,5 @@
import 'package:fluffychat/pangea/course_plans/course_plan_model.dart';
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
import 'package:fluffychat/pangea/payload_client/join_field.dart';
import 'package:fluffychat/pangea/payload_client/polymorphic_relationship.dart';
@ -65,4 +67,17 @@ class CmsCoursePlan {
'createdAt': createdAt,
};
}
CoursePlanModel toCoursePlanModel() {
return CoursePlanModel(
uuid: id,
targetLanguage: l2,
languageOfInstructions: l1,
cefrLevel: LanguageLevelTypeEnumExtension.fromString(cefrLevel),
title: title,
description: description,
mediaIds: coursePlanMedia?.docs ?? [],
topicIds: coursePlanTopics?.docs ?? [],
);
}
}

View file

@ -1,3 +1,6 @@
import 'package:fluffychat/pangea/activity_generator/media_enum.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/learning_settings/enums/language_level_type_enum.dart';
import 'package:fluffychat/pangea/payload_client/join_field.dart';
import 'package:fluffychat/pangea/payload_client/polymorphic_relationship.dart';
@ -33,6 +36,15 @@ class CmsCoursePlanActivityRole {
'avatarUrl': avatarUrl,
};
}
ActivityRole toActivityRole() {
return ActivityRole(
id: id,
name: name,
goal: goal,
avatarUrl: avatarUrl,
);
}
}
/// Represents vocabulary in a course plan activity
@ -75,6 +87,7 @@ class CmsCoursePlanActivity {
final String instructions;
final String l1; // Language of instruction
final String l2; // Target language
final String mode;
final LanguageLevelTypeEnum cefrLevel;
final List<CmsCoursePlanActivityRole> roles;
final List<CmsCoursePlanVocab> vocabs;
@ -93,6 +106,7 @@ class CmsCoursePlanActivity {
required this.instructions,
required this.l1,
required this.l2,
required this.mode,
required this.cefrLevel,
required this.roles,
required this.vocabs,
@ -113,6 +127,7 @@ class CmsCoursePlanActivity {
instructions: json['instructions'] as String,
l1: json['l1'] as String,
l2: json['l2'] as String,
mode: json['mode'] as String,
cefrLevel: LanguageLevelTypeEnumExtension.fromString(
json['cefrLevel'] as String,
),
@ -152,6 +167,7 @@ class CmsCoursePlanActivity {
'instructions': instructions,
'l1': l1,
'l2': l2,
'mode': mode,
'cefrLevel': cefrLevel.string,
'roles': roles.map((role) => role.toJson()).toList(),
'vocabs': vocabs.map((vocab) => vocab.toJson()).toList(),
@ -163,4 +179,29 @@ class CmsCoursePlanActivity {
'createdAt': createdAt,
};
}
ActivityPlanModel toActivityPlanModel(String? imageUrl) {
return ActivityPlanModel(
req: ActivityPlanRequest(
topic: "",
mode: mode,
objective: "",
media: MediaEnum.nan,
cefrLevel: cefrLevel,
languageOfInstructions: l1,
targetLanguage: l2,
numberOfParticipants: roles.length,
),
activityId: id,
title: title,
description: description,
learningObjective: learningObjective,
instructions: instructions,
vocab: vocabs.map((v) => Vocab(lemma: v.lemma, pos: v.pos)).toList(),
roles: Map.fromEntries(
roles.map((role) => MapEntry(role.id, role.toActivityRole())),
),
imageURL: imageUrl,
);
}
}

View file

@ -1,3 +1,4 @@
import 'package:fluffychat/pangea/course_plans/course_topic_model.dart';
import 'package:fluffychat/pangea/payload_client/join_field.dart';
import 'package:fluffychat/pangea/payload_client/polymorphic_relationship.dart';
@ -65,4 +66,14 @@ class CmsCoursePlanTopic {
'createdAt': createdAt,
};
}
CourseTopicModel toCourseTopicModel() {
return CourseTopicModel(
title: title,
description: description,
uuid: id,
activityIds: coursePlanActivities?.docs ?? [],
locationIds: coursePlanTopicLocations?.docs ?? [],
);
}
}

View file

@ -1,3 +1,4 @@
import 'package:fluffychat/pangea/course_plans/course_location_model.dart';
import 'package:fluffychat/pangea/payload_client/join_field.dart';
import 'package:fluffychat/pangea/payload_client/polymorphic_relationship.dart';
@ -62,4 +63,12 @@ class CmsCoursePlanTopicLocation {
'createdAt': createdAt,
};
}
CourseLocationModel toCourseLocationModel() {
return CourseLocationModel(
uuid: id,
name: name,
mediaIds: coursePlanTopicLocationMedia?.docs ?? [],
);
}
}

View file

@ -0,0 +1,10 @@
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/payload_client/payload_client.dart';
import 'package:fluffychat/widgets/matrix.dart';
class PayloadRepo {
static final PayloadClient payload = PayloadClient(
baseUrl: Environment.cmsApi,
accessToken: MatrixState.pangeaController.userController.accessToken,
);
}

View file

@ -9,7 +9,7 @@ import 'package:fluffychat/pangea/user/models/analytics_profile_model.dart';
import 'package:fluffychat/widgets/matrix.dart';
class LoadParticipantsBuilder extends StatefulWidget {
final Room room;
final Room? room;
final bool loadProfiles;
final Widget Function(
BuildContext context,
@ -34,7 +34,7 @@ class LoadParticipantsBuilderState extends State<LoadParticipantsBuilder> {
final Map<String, AnalyticsProfileModel> _levelsCache = {};
List<User> get participants => widget.room.getParticipants();
List<User> get participants => widget.room?.getParticipants() ?? [];
@override
void initState() {
@ -45,7 +45,7 @@ class LoadParticipantsBuilderState extends State<LoadParticipantsBuilder> {
@override
void didUpdateWidget(LoadParticipantsBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.room.id != widget.room.id) {
if (oldWidget.room?.id != widget.room?.id) {
_loadParticipants();
}
}
@ -57,7 +57,7 @@ class LoadParticipantsBuilderState extends State<LoadParticipantsBuilder> {
error = null;
});
await widget.room.requestParticipants(
await widget.room?.requestParticipants(
[Membership.join, Membership.invite, Membership.knock],
false,
true,
@ -70,7 +70,7 @@ class LoadParticipantsBuilderState extends State<LoadParticipantsBuilder> {
e: err,
s: s,
data: {
'roomId': widget.room.id,
'roomId': widget.room?.id,
},
);
} finally {

View file

@ -1,12 +1,10 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:get_storage/get_storage.dart';
import 'package:jwt_decode/jwt_decode.dart';
import 'package:matrix/matrix.dart' as matrix;
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart';
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
@ -150,7 +148,6 @@ class UserController {
_initializing = true;
try {
await GetStorage.init('activity_plan_by_id_storage');
await _initialize();
_addProfileListener();
_addAnalyticsRoomIdsToPublicProfile();
@ -458,21 +455,21 @@ class UserController {
);
}
Future<List<ActivityPlanModel>> getBookmarkedActivities() async {
if (activitiesProfile == null) {
throw Exception("Activities profile is not initialized");
}
// Future<List<ActivityPlanModel>> getBookmarkedActivities() async {
// if (activitiesProfile == null) {
// throw Exception("Activities profile is not initialized");
// }
return activitiesProfile!.getBookmarkedActivities();
}
// return activitiesProfile!.getBookmarkedActivities();
// }
List<ActivityPlanModel> getBookmarkedActivitiesSync() {
if (activitiesProfile == null) {
throw Exception("Activities profile is not initialized");
}
// List<ActivityPlanModel> getBookmarkedActivitiesSync() {
// if (activitiesProfile == null) {
// throw Exception("Activities profile is not initialized");
// }
return activitiesProfile!.getBookmarkedActivitiesSync();
}
// return activitiesProfile!.getBookmarkedActivitiesSync();
// }
Future<void> updateBookmarkedActivity({
required String activityId,

View file

@ -1,5 +1,3 @@
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_plan_repo.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
@ -26,14 +24,14 @@ class ActivitiesProfileModel {
_bookmarkedActivities.remove(activityId);
}
Future<List<ActivityPlanModel>> getBookmarkedActivities() => Future.wait(
_bookmarkedActivities.map((id) => ActivityPlanRepo.get(id)).toList(),
);
// Future<List<ActivityPlanModel>> getBookmarkedActivities() => Future.wait(
// _bookmarkedActivities.map((id) => ActivityPlanRepo.get(id)).toList(),
// );
List<ActivityPlanModel> getBookmarkedActivitiesSync() => _bookmarkedActivities
.map((id) => ActivityPlanRepo.getCached(id))
.whereType<ActivityPlanModel>()
.toList();
// List<ActivityPlanModel> getBookmarkedActivitiesSync() => _bookmarkedActivities
// .map((id) => ActivityPlanRepo.getCached(id))
// .whereType<ActivityPlanModel>()
// .toList();
static ActivitiesProfileModel fromJson(Map<String, dynamic> json) {
if (!json.containsKey(PangeaEventTypes.profileActivities)) {