2223 make your own activity page (#2245)
* feat: added make your own activity page * chore: center content of activity planner page
This commit is contained in:
parent
66ac13f3bf
commit
b7b5522649
12 changed files with 731 additions and 565 deletions
|
|
@ -4836,5 +4836,11 @@
|
|||
"selectForGrammar": "Select a grammar icon for activities and details.",
|
||||
"newChatActivityTitle": "Add a fun activity",
|
||||
"newChatActivityDesc": "Make every group chat an adventure with Activity Planner! Set captivating topics and objectives for the group, and bring conversations to life with stunning images. Spark imaginative discussions and keep the fun flowing effortlessly!",
|
||||
"exploreMore": "Explore more"
|
||||
"exploreMore": "Explore more",
|
||||
"randomize": "Randomize",
|
||||
"clear": "Clear",
|
||||
"makeYourOwnActivity": "Make your own activity",
|
||||
"makeYourOwn": "Make your own",
|
||||
"featuredActivities": "Featured activities",
|
||||
"yourBookmarks": "Your bookmarks"
|
||||
}
|
||||
|
|
@ -29,6 +29,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_suggestions/suggestions_page.dart';
|
||||
import 'package:fluffychat/pangea/guard/p_vguard.dart';
|
||||
|
|
@ -255,6 +256,15 @@ abstract class AppRoutes {
|
|||
const SuggestionsPage(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/planner',
|
||||
redirect: loggedOutRedirect,
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const ActivityGenerator(),
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
GoRoute(
|
||||
path: 'archive',
|
||||
|
|
|
|||
233
lib/pangea/activity_generator/activity_generator.dart
Normal file
233
lib/pangea/activity_generator/activity_generator.dart
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/activity_generator/activity_generator_view.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_mode_list_repo.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_generation_repo.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_response.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/learning_objective_list_repo.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/list_request_schema.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/media_enum.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/topic_list_repo.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 {
|
||||
const ActivityGenerator({super.key});
|
||||
|
||||
@override
|
||||
ActivityGeneratorState createState() => ActivityGeneratorState();
|
||||
}
|
||||
|
||||
class ActivityGeneratorState extends State<ActivityGenerator> {
|
||||
bool loading = false;
|
||||
String? error;
|
||||
List<ActivityPlanModel>? activities;
|
||||
|
||||
final formKey = GlobalKey<FormState>();
|
||||
|
||||
final topicController = TextEditingController();
|
||||
final objectiveController = TextEditingController();
|
||||
final modeController = TextEditingController();
|
||||
|
||||
MediaEnum selectedMedia = MediaEnum.nan;
|
||||
String? selectedLanguageOfInstructions;
|
||||
String? selectedTargetLanguage;
|
||||
LanguageLevelTypeEnum? selectedCefrLevel;
|
||||
int? selectedNumberOfParticipants;
|
||||
|
||||
String? avatarURL;
|
||||
String? filename;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedLanguageOfInstructions =
|
||||
MatrixState.pangeaController.languageController.userL1?.langCode;
|
||||
selectedTargetLanguage =
|
||||
MatrixState.pangeaController.languageController.userL2?.langCode;
|
||||
selectedCefrLevel = LanguageLevelTypeEnum.a1;
|
||||
selectedNumberOfParticipants = 3;
|
||||
_setModeImageURL();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
topicController.dispose();
|
||||
objectiveController.dispose();
|
||||
modeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
ActivitySettingRequestSchema get req => ActivitySettingRequestSchema(
|
||||
langCode:
|
||||
MatrixState.pangeaController.languageController.userL2?.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!,
|
||||
);
|
||||
|
||||
Future<List<ActivitySettingResponseSchema>> get topicItems =>
|
||||
TopicListRepo.get(req);
|
||||
|
||||
Future<List<ActivitySettingResponseSchema>> get modeItems =>
|
||||
ActivityModeListRepo.get(req);
|
||||
|
||||
Future<List<ActivitySettingResponseSchema>> get objectiveItems =>
|
||||
LearningObjectiveListRepo.get(req);
|
||||
|
||||
String? validateNotNull(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return L10n.of(context).interactiveTranslatorRequired;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<String> _randomTopic() async {
|
||||
final topics = await topicItems;
|
||||
return (topics..shuffle()).first.name;
|
||||
}
|
||||
|
||||
Future<String> _randomObjective() async {
|
||||
final objectives = await objectiveItems;
|
||||
return (objectives..shuffle()).first.name;
|
||||
}
|
||||
|
||||
Future<String> _randomMode() async {
|
||||
final modes = await modeItems;
|
||||
return (modes..shuffle()).first.name;
|
||||
}
|
||||
|
||||
void randomizeSelections() async {
|
||||
final selectedTopic = await _randomTopic();
|
||||
final selectedObjective = await _randomObjective();
|
||||
final selectedMode = await _randomMode();
|
||||
|
||||
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 setSelectedNumberOfParticipants(int? value) {
|
||||
setState(() => selectedNumberOfParticipants = value);
|
||||
}
|
||||
|
||||
void setSelectedTargetLanguage(String? value) {
|
||||
setState(() => selectedTargetLanguage = value);
|
||||
}
|
||||
|
||||
void setSelectedLanguageOfInstructions(String? value) {
|
||||
setState(() => selectedLanguageOfInstructions = value);
|
||||
}
|
||||
|
||||
void setSelectedCefrLevel(LanguageLevelTypeEnum? value) {
|
||||
setState(() => selectedCefrLevel = value);
|
||||
}
|
||||
|
||||
void setSelectedMedia(MediaEnum? value) {
|
||||
if (value == null) return;
|
||||
setState(() => selectedMedia = value);
|
||||
}
|
||||
|
||||
Future<ActivitySettingResponseSchema?> get _selectedMode async {
|
||||
final modes = await modeItems;
|
||||
return modes.firstWhereOrNull(
|
||||
(element) => element.name.toLowerCase() == planRequest.mode.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _setModeImageURL() async {
|
||||
final mode = await _selectedMode;
|
||||
if (mode == null) return;
|
||||
|
||||
final modeName =
|
||||
mode.defaultName.toLowerCase().replaceAll(RegExp(r'\s+'), '');
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
filename =
|
||||
"${ActivitySuggestionsConstants.modeImageFileStart}$modeName.jpg";
|
||||
avatarURL = "${AppConfig.assetsBaseURL}/$filename";
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> onEdit(int index, ActivityPlanModel updatedActivity) async {
|
||||
// in this case we're editing an activity plan that was generated recently
|
||||
// via the repo and should be updated in the cached response
|
||||
if (activities != null) {
|
||||
activities?[index] = updatedActivity;
|
||||
ActivityPlanGenerationRepo.set(
|
||||
planRequest,
|
||||
ActivityPlanResponse(activityPlans: activities!),
|
||||
);
|
||||
}
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void update() => setState(() {});
|
||||
|
||||
Future<void> generate() async {
|
||||
setState(() {
|
||||
loading = true;
|
||||
error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
await _setModeImageURL();
|
||||
final resp = await ActivityPlanGenerationRepo.get(planRequest);
|
||||
for (final activity in resp.activityPlans) {
|
||||
activity.imageURL = avatarURL;
|
||||
}
|
||||
activities = resp.activityPlans;
|
||||
} catch (e, s) {
|
||||
error = e.toString();
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'activityPlanRequest': planRequest,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
if (mounted) setState(() => loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ActivityGeneratorView(this);
|
||||
}
|
||||
253
lib/pangea/activity_generator/activity_generator_view.dart
Normal file
253
lib/pangea/activity_generator/activity_generator_view.dart
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/activity_generator/activity_generator.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_card.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/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;
|
||||
|
||||
const ActivityGeneratorView(
|
||||
this.controller, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = L10n.of(context);
|
||||
Widget? body;
|
||||
|
||||
if (controller.loading) {
|
||||
body = const Padding(
|
||||
padding: EdgeInsets.all(32.0),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
} else if (controller.error != null) {
|
||||
body = Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(l10n.oopsSomethingWentWrong),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: controller.generate,
|
||||
child: Text(l10n.tryAgain),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (controller.activities != null &&
|
||||
controller.activities!.isNotEmpty) {
|
||||
body = ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: controller.activities!.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ActivityPlanCard(
|
||||
activity: controller.activities![index],
|
||||
room: null,
|
||||
onEdit: (updatedActivity) =>
|
||||
controller.onEdit(index, updatedActivity),
|
||||
onChange: controller.update,
|
||||
avatarURL: controller.avatarURL,
|
||||
initialFilename: controller.filename,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(L10n.of(context).makeYourOwnActivity),
|
||||
),
|
||||
body: 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,
|
||||
label: l10n.topicLabel,
|
||||
placeholder: l10n.topicPlaceholder,
|
||||
controller: controller.topicController,
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
SuggestionFormField(
|
||||
suggestions: controller.objectiveItems,
|
||||
validator: controller.validateNotNull,
|
||||
label: l10n.learningObjectiveLabel,
|
||||
placeholder: l10n.learningObjectivePlaceholder,
|
||||
controller: controller.objectiveController,
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
SuggestionFormField(
|
||||
suggestions: controller.modeItems,
|
||||
validator: controller.validateNotNull,
|
||||
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;
|
||||
}
|
||||
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),
|
||||
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.randomizeSelections,
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,16 +5,20 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:matrix/matrix.dart' as sdk;
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/bookmarked_activities_repo.dart';
|
||||
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class ActivityPlanCard extends StatefulWidget {
|
||||
final ActivityPlanModel activity;
|
||||
|
|
@ -149,9 +153,11 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> _onLaunch() => showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
Future<void> _onLaunch() async {
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
if (widget.room != null) {
|
||||
await widget.room?.sendActivityPlan(
|
||||
widget.activity,
|
||||
avatar: _avatar,
|
||||
|
|
@ -160,8 +166,43 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final client = Matrix.of(context).client;
|
||||
final roomId = await client.createGroupChat(
|
||||
preset: CreateRoomPreset.publicChat,
|
||||
visibility: sdk.Visibility.private,
|
||||
groupName:
|
||||
widget.activity.title.isNotEmpty ? widget.activity.title : null,
|
||||
initialState: [
|
||||
StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: defaultPowerLevels(client.userID!),
|
||||
),
|
||||
],
|
||||
enableEncryption: false,
|
||||
);
|
||||
|
||||
Room? room = client.getRoomById(roomId);
|
||||
if (room == null) {
|
||||
await client.waitForRoomInSync(roomId);
|
||||
room = client.getRoomById(roomId);
|
||||
}
|
||||
if (room == null) return;
|
||||
|
||||
await room.sendActivityPlan(
|
||||
widget.activity,
|
||||
avatar: _avatar,
|
||||
avatarURL: _avatarURL,
|
||||
filename: _filename,
|
||||
);
|
||||
|
||||
context.go("/rooms/$roomId");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bool get isBookmarked =>
|
||||
BookmarkedActivitiesRepo.isBookmarked(widget.activity);
|
||||
|
|
|
|||
|
|
@ -1,30 +1,16 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_mode_list_repo.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart';
|
||||
import 'package:fluffychat/config/themes.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_planner/generated_activity_list.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/learning_objective_list_repo.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/list_request_schema.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/media_enum.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/new_activity_form.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/topic_list_repo.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_area.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.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';
|
||||
|
||||
enum PageMode {
|
||||
settings,
|
||||
generatedActivities,
|
||||
featuredActivities,
|
||||
savedActivities,
|
||||
}
|
||||
|
|
@ -38,164 +24,18 @@ class ActivityPlannerPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class ActivityPlannerPageState extends State<ActivityPlannerPage> {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
PageMode pageMode = PageMode.featuredActivities;
|
||||
|
||||
MediaEnum selectedMedia = MediaEnum.nan;
|
||||
String? selectedLanguageOfInstructions;
|
||||
String? selectedTargetLanguage;
|
||||
LanguageLevelTypeEnum? selectedCefrLevel;
|
||||
int? selectedNumberOfParticipants;
|
||||
|
||||
List<String> activities = [];
|
||||
|
||||
Room? get room => Matrix.of(context).client.getRoomById(widget.roomID);
|
||||
|
||||
ActivityPlanModel? get _initialActivity => room?.activityPlan;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (room == null) {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_initialActivity == null) {
|
||||
selectedLanguageOfInstructions =
|
||||
MatrixState.pangeaController.languageController.userL1?.langCode;
|
||||
selectedTargetLanguage =
|
||||
MatrixState.pangeaController.languageController.userL2?.langCode;
|
||||
selectedCefrLevel = LanguageLevelTypeEnum.a1;
|
||||
selectedNumberOfParticipants =
|
||||
max(room?.getParticipants().length ?? 1, 1);
|
||||
} else {
|
||||
selectedMedia = _initialActivity!.req.media;
|
||||
selectedLanguageOfInstructions =
|
||||
_initialActivity!.req.languageOfInstructions;
|
||||
selectedTargetLanguage = _initialActivity!.req.targetLanguage;
|
||||
selectedCefrLevel = _initialActivity!.req.cefrLevel;
|
||||
selectedNumberOfParticipants = _initialActivity!.req.numberOfParticipants;
|
||||
topicController.text = _initialActivity!.req.topic;
|
||||
objectiveController.text = _initialActivity!.req.objective;
|
||||
modeController.text = _initialActivity!.req.mode;
|
||||
}
|
||||
}
|
||||
|
||||
final topicController = TextEditingController();
|
||||
final objectiveController = TextEditingController();
|
||||
final modeController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
topicController.dispose();
|
||||
objectiveController.dispose();
|
||||
modeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
ActivitySettingRequestSchema get req => ActivitySettingRequestSchema(
|
||||
langCode:
|
||||
MatrixState.pangeaController.languageController.userL2?.langCode ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
);
|
||||
|
||||
Future<List<ActivitySettingResponseSchema>> get topicItems =>
|
||||
TopicListRepo.get(req);
|
||||
|
||||
Future<List<ActivitySettingResponseSchema>> get modeItems =>
|
||||
ActivityModeListRepo.get(req);
|
||||
|
||||
Future<List<ActivitySettingResponseSchema>> get objectiveItems =>
|
||||
LearningObjectiveListRepo.get(req);
|
||||
|
||||
void _setPageMode(PageMode? mode) {
|
||||
if (mode == null) return;
|
||||
setState(() => pageMode = mode);
|
||||
}
|
||||
|
||||
void setSelectedNumberOfParticipants(int? value) {
|
||||
setState(() => selectedNumberOfParticipants = value);
|
||||
}
|
||||
|
||||
void setSelectedTargetLanguage(String? value) {
|
||||
setState(() => selectedTargetLanguage = value);
|
||||
}
|
||||
|
||||
void setSelectedLanguageOfInstructions(String? value) {
|
||||
setState(() => selectedLanguageOfInstructions = value);
|
||||
}
|
||||
|
||||
void setSelectedCefrLevel(LanguageLevelTypeEnum? value) {
|
||||
setState(() => selectedCefrLevel = value);
|
||||
}
|
||||
|
||||
void setSelectedMedia(MediaEnum? value) {
|
||||
if (value == null) return;
|
||||
setState(() => selectedMedia = value);
|
||||
}
|
||||
|
||||
Future<void> generateActivities() async =>
|
||||
_setPageMode(PageMode.generatedActivities);
|
||||
|
||||
Future<String> _randomTopic() async {
|
||||
final topics = await topicItems;
|
||||
return (topics..shuffle()).first.name;
|
||||
}
|
||||
|
||||
Future<String> _randomObjective() async {
|
||||
final objectives = await objectiveItems;
|
||||
return (objectives..shuffle()).first.name;
|
||||
}
|
||||
|
||||
Future<String> _randomMode() async {
|
||||
final modes = await modeItems;
|
||||
return (modes..shuffle()).first.name;
|
||||
}
|
||||
|
||||
void randomizeSelections() async {
|
||||
final selectedTopic = await _randomTopic();
|
||||
final selectedObjective = await _randomObjective();
|
||||
final selectedMode = await _randomMode();
|
||||
|
||||
setState(() {
|
||||
topicController.text = selectedTopic;
|
||||
objectiveController.text = selectedObjective;
|
||||
modeController.text = selectedMode;
|
||||
});
|
||||
}
|
||||
|
||||
// Add validation logic
|
||||
String? validateNotNull(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return L10n.of(context).interactiveTranslatorRequired;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
ActivityPlanRequest get planRequest => ActivityPlanRequest(
|
||||
topic: topicController.text,
|
||||
mode: modeController.text,
|
||||
objective: objectiveController.text,
|
||||
media: selectedMedia,
|
||||
languageOfInstructions: selectedLanguageOfInstructions!,
|
||||
targetLanguage: selectedTargetLanguage!,
|
||||
cefrLevel: selectedCefrLevel!,
|
||||
numberOfParticipants: selectedNumberOfParticipants!,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget body = const SizedBox();
|
||||
switch (pageMode) {
|
||||
case PageMode.settings:
|
||||
body = NewActivityForm(this);
|
||||
break;
|
||||
case PageMode.generatedActivities:
|
||||
body = GeneratedActivitiesList(
|
||||
controller: this,
|
||||
);
|
||||
break;
|
||||
case PageMode.savedActivities:
|
||||
body = BookmarkedActivitiesList(
|
||||
room: room,
|
||||
|
|
@ -207,6 +47,7 @@ class ActivityPlannerPageState extends State<ActivityPlannerPage> {
|
|||
child: SingleChildScrollView(
|
||||
child: ActivitySuggestionsArea(
|
||||
scrollDirection: Axis.vertical,
|
||||
includeCustomCards: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -218,36 +59,61 @@ class ActivityPlannerPageState extends State<ActivityPlannerPage> {
|
|||
pageMode: pageMode,
|
||||
setPageMode: _setPageMode,
|
||||
),
|
||||
body: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 800.0),
|
||||
child: Column(
|
||||
children: [
|
||||
if ([PageMode.featuredActivities, PageMode.savedActivities]
|
||||
.contains(pageMode))
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SegmentedButton<PageMode>(
|
||||
selected: {pageMode},
|
||||
onSelectionChanged: (modes) => _setPageMode(modes.first),
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: PageMode.featuredActivities,
|
||||
label: Text("Featured activities"),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: PageMode.savedActivities,
|
||||
label: Text("Your bookmarks"),
|
||||
body: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 800.0),
|
||||
child: Column(
|
||||
children: [
|
||||
if ([PageMode.featuredActivities, PageMode.savedActivities]
|
||||
.contains(pageMode))
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SegmentedButton<PageMode>(
|
||||
selected: {pageMode},
|
||||
onSelectionChanged: (modes) =>
|
||||
_setPageMode(modes.first),
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: PageMode.featuredActivities,
|
||||
label: Text(L10n.of(context).featuredActivities),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: PageMode.savedActivities,
|
||||
label: Text(L10n.of(context).yourBookmarks),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body,
|
||||
if (!FluffyThemes.isColumnMode(context))
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: ElevatedButton(
|
||||
onPressed: () => context.go("/rooms/planner"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 0.0,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).makeYourOwn,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body,
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart';
|
||||
|
||||
class ActivityPlannerPageAppBar extends StatelessWidget
|
||||
|
|
@ -21,20 +23,15 @@ class ActivityPlannerPageAppBar extends StatelessWidget
|
|||
Widget build(BuildContext context) {
|
||||
final l10n = L10n.of(context);
|
||||
return AppBar(
|
||||
leading: pageMode != PageMode.settings &&
|
||||
pageMode != PageMode.generatedActivities
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
)
|
||||
: IconButton(
|
||||
onPressed: () => setPageMode(
|
||||
pageMode == PageMode.settings
|
||||
? PageMode.featuredActivities
|
||||
: PageMode.settings,
|
||||
),
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
leadingWidth: FluffyThemes.isColumnMode(context) ? 150.0 : 50.0,
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
title: pageMode == PageMode.savedActivities
|
||||
? Center(
|
||||
child: Row(
|
||||
|
|
@ -64,10 +61,25 @@ class ActivityPlannerPageAppBar extends StatelessWidget
|
|||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => setPageMode(PageMode.settings),
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
),
|
||||
FluffyThemes.isColumnMode(context)
|
||||
? Container(
|
||||
width: 150.0,
|
||||
alignment: Alignment.centerRight,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => context.go("/rooms/planner"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 0.0,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
L10n.of(context).makeYourOwn,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox(width: 50.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,151 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_generation_repo.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_response.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/list_request_schema.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_constants.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'activity_plan_card.dart';
|
||||
|
||||
class GeneratedActivitiesList extends StatefulWidget {
|
||||
final ActivityPlannerPageState controller;
|
||||
|
||||
const GeneratedActivitiesList({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
GeneratedActivitiesListState createState() => GeneratedActivitiesListState();
|
||||
}
|
||||
|
||||
class GeneratedActivitiesListState extends State<GeneratedActivitiesList> {
|
||||
List<ActivityPlanModel>? _activities;
|
||||
bool _isLoading = true;
|
||||
Object? _error;
|
||||
|
||||
String? _avatarURL;
|
||||
String? _filename;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadActivities();
|
||||
_setModeImageURL();
|
||||
}
|
||||
|
||||
Future<void> _loadActivities() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final resp = await ActivityPlanGenerationRepo.get(
|
||||
widget.controller.planRequest,
|
||||
);
|
||||
_activities = resp.activityPlans;
|
||||
} catch (e, s) {
|
||||
_error = e;
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'activityPlanRequest': widget.controller.planRequest,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setModeImageURL() async {
|
||||
final mode = await _selectedMode;
|
||||
if (mode == null) return;
|
||||
|
||||
final modeName =
|
||||
mode.defaultName.toLowerCase().replaceAll(RegExp(r'\s+'), '');
|
||||
final filename =
|
||||
"${ActivitySuggestionsConstants.modeImageFileStart}$modeName.jpg";
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_avatarURL = "${AppConfig.assetsBaseURL}/$filename";
|
||||
_filename = filename;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onEdit(int index, ActivityPlanModel updatedActivity) async {
|
||||
// in this case we're editing an activity plan that was generated recently
|
||||
// via the repo and should be updated in the cached response
|
||||
if (_activities != null) {
|
||||
final activities = _activities;
|
||||
activities?[index] = updatedActivity;
|
||||
ActivityPlanGenerationRepo.set(
|
||||
widget.controller.planRequest,
|
||||
ActivityPlanResponse(activityPlans: _activities!),
|
||||
);
|
||||
}
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Future<ActivitySettingResponseSchema?> get _selectedMode async {
|
||||
final modes = await widget.controller.modeItems;
|
||||
return modes.firstWhereOrNull(
|
||||
(element) =>
|
||||
element.name.toLowerCase() ==
|
||||
widget.controller.planRequest.mode.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = L10n.of(context);
|
||||
if (_isLoading) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(32.0),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
} else if (_error != null) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(l10n.oopsSomethingWentWrong),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: _loadActivities,
|
||||
child: Text(l10n.tryAgain),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (_activities == null || _activities!.isEmpty) {
|
||||
return Center(child: Text(l10n.noDataFound));
|
||||
} else {
|
||||
return Expanded(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: _activities!.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ActivityPlanCard(
|
||||
activity: _activities![index],
|
||||
room: widget.controller.room,
|
||||
onEdit: (updatedActivity) => _onEdit(index, updatedActivity),
|
||||
avatarURL: _avatarURL,
|
||||
initialFilename: _filename,
|
||||
onChange: () => setState(() {}),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/media_enum.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/suggestion_form_field.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/dropdown_text_button.dart';
|
||||
import 'package:fluffychat/pangea/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 NewActivityForm extends StatelessWidget {
|
||||
final ActivityPlannerPageState controller;
|
||||
const NewActivityForm(this.controller, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = L10n.of(context);
|
||||
return Expanded(
|
||||
child: 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,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
SuggestionFormField(
|
||||
suggestions: controller.topicItems,
|
||||
validator: controller.validateNotNull,
|
||||
label: l10n.topicLabel,
|
||||
placeholder: l10n.topicPlaceholder,
|
||||
controller: controller.topicController,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SuggestionFormField(
|
||||
suggestions: controller.objectiveItems,
|
||||
validator: controller.validateNotNull,
|
||||
label: l10n.learningObjectiveLabel,
|
||||
placeholder: l10n.learningObjectivePlaceholder,
|
||||
controller: controller.objectiveController,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SuggestionFormField(
|
||||
suggestions: controller.modeItems,
|
||||
validator: controller.validateNotNull,
|
||||
label: l10n.modeLabel,
|
||||
placeholder: l10n.modePlaceholder,
|
||||
controller: controller.modeController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Column(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.shuffle),
|
||||
onPressed: controller.randomizeSelections,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
DropdownButtonFormField2<MediaEnum>(
|
||||
customButton: CustomDropdownTextButton(
|
||||
text: controller.selectedMedia
|
||||
.toDisplayCopyUsingL10n(context),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.zero, // Remove default padding
|
||||
),
|
||||
decoration: InputDecoration(labelText: l10n.mediaLabel),
|
||||
isExpanded: true,
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
),
|
||||
items: MediaEnum.values
|
||||
.map(
|
||||
(e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: DropdownTextButton(
|
||||
text: e.toDisplayCopyUsingL10n(context),
|
||||
isSelected: controller.selectedMedia == e,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: controller.setSelectedMedia,
|
||||
value: controller.selectedMedia,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
LanguageLevelDropdown(
|
||||
initialLevel: controller.selectedCefrLevel,
|
||||
onChanged: controller.setSelectedCefrLevel,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
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: 24),
|
||||
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: 24),
|
||||
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;
|
||||
}
|
||||
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.generateActivities();
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (controller.formKey.currentState?.validate() ?? false) {
|
||||
controller.generateActivities();
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.lightbulb_outline),
|
||||
const SizedBox(width: 8),
|
||||
Text(l10n.generateActivitiesButton),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,13 +11,19 @@ import 'package:fluffychat/pangea/activity_suggestions/activity_plan_search_repo
|
|||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/create_chat_card.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/make_activity_card.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;
|
||||
const ActivitySuggestionsArea({super.key, this.scrollDirection});
|
||||
final bool includeCustomCards;
|
||||
const ActivitySuggestionsArea({
|
||||
super.key,
|
||||
this.scrollDirection,
|
||||
this.includeCustomCards = true,
|
||||
});
|
||||
@override
|
||||
ActivitySuggestionsAreaState createState() => ActivitySuggestionsAreaState();
|
||||
}
|
||||
|
|
@ -89,14 +95,25 @@ class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
|
|||
.cast<Widget>()
|
||||
.toList();
|
||||
|
||||
cards.insert(
|
||||
0,
|
||||
CreateChatCard(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
padding: cardPadding,
|
||||
),
|
||||
);
|
||||
if (widget.includeCustomCards) {
|
||||
cards.insert(
|
||||
0,
|
||||
CreateChatCard(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
padding: cardPadding,
|
||||
),
|
||||
);
|
||||
|
||||
cards.insert(
|
||||
1,
|
||||
MakeActivityCard(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
padding: cardPadding,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final scrollDirection = widget.scrollDirection ??
|
||||
(_isColumnMode ? Axis.horizontal : Axis.vertical);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
class ActivitySuggestionsConstants {
|
||||
static const String plusIconPath = "add_icon.svg";
|
||||
static const String crayonIconPath = "make_your_own_icon.svg";
|
||||
static const String modeImageFileStart = "activityplanner_mode_";
|
||||
static const String makeActivityAssetPath = "Spark+imaginative.png";
|
||||
}
|
||||
|
|
|
|||
70
lib/pangea/activity_suggestions/make_activity_card.dart
Normal file
70
lib/pangea/activity_suggestions/make_activity_card.dart
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.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/pressable_button.dart';
|
||||
|
||||
class MakeActivityCard extends StatelessWidget {
|
||||
final double width;
|
||||
final double height;
|
||||
final double padding;
|
||||
|
||||
const MakeActivityCard({
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.padding,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(padding),
|
||||
child: PressableButton(
|
||||
onPressed: () => context.go('/rooms/planner'),
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
color: theme.colorScheme.primary,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
),
|
||||
height: height,
|
||||
width: width,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Center(
|
||||
child: CustomizedSvg(
|
||||
svgUrl:
|
||||
"${AppConfig.assetsBaseURL}/${ActivitySuggestionsConstants.crayonIconPath}",
|
||||
colorReplacements: {
|
||||
"#CDBEF9":
|
||||
colorToHex(Theme.of(context).colorScheme.secondary),
|
||||
},
|
||||
height: 80,
|
||||
width: 80,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
L10n.of(context).makeYourOwn,
|
||||
style: theme.textTheme.bodyLarge
|
||||
?.copyWith(color: theme.colorScheme.secondary),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue