diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 6875a59bf..dcdae0bed 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -4748,12 +4748,9 @@ "video": "Video", "nan": "Not applicable", "activityPlannerOverviewInstructionsBody": "Choose a topic, mode, learning objective and generate an activity for the chat!", - "myBookmarkedActivities": "My Bookmarked Activities", - "noBookmarkedActivities": "No bookmarked activities", "activityTitle": "Activity Title", "addVocabulary": "Add vocabulary", "instructions": "Instructions", - "bookmark": "Bookmark this activity", "numberOfLearners": "Number of learners", "mustBeInteger": "Must be an integer e.g. 1, 2, 3, ...", "noLemmasFound": "There's no vocabulary with more than {xp} XP. Keep practicing!", @@ -4877,7 +4874,6 @@ "clear": "Clear", "makeYourOwnActivity": "Create your own activity", "featuredActivities": "Featured", - "yourBookmarks": "Bookmarked", "goToChat": "Go to chat", "save": "Save", "selectActivity": "Select activity", @@ -5035,5 +5031,9 @@ "failedToFetchTranscription": "Failed to fetch transcription", "deleteEmptySpaceDesc": "The space will be deleted for all participants. This action cannot be undone.", "customReaction": "Custom reaction", - "regenerate": "Regenerate" + "regenerate": "Regenerate", + "mySavedActivities": "My Saved Activities", + "noSavedActivities": "No saved activities", + "saveActivity": "Save this activity", + "yourSavedActivities": "Saved Activities" } diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 2dd1d6672..55dca418e 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -5375,12 +5375,9 @@ "video": "Video", "nan": "No aplicable", "activityPlannerOverviewInstructionsBody": "¡Elige un tema, modo, objetivo de aprendizaje y genera una actividad para el chat!", - "myBookmarkedActivities": "Mis Actividades Marcadas", - "noBookmarkedActivities": "No hay actividades marcadas", "activityTitle": "Título de la Actividad", "addVocabulary": "Agregar vocabulario", "instructions": "Instrucciones", - "bookmark": "Marcar esta actividad", "numberOfLearners": "Número de aprendices", "mustBeInteger": "Debe ser un número entero, por ejemplo, 1, 2, 3, ...", "noLemmasFound": "No hay vocabulario con más de {xp} XP. ¡Sigue practicando!", @@ -5494,7 +5491,6 @@ "randomize": "Aleatorizar", "clear": "Limpiar", "featuredActivities": "Destacadas", - "yourBookmarks": "Marcados", "goToChat": "Ir al chat", "save": "Guardar", "selectActivity": "Seleccionar actividad", @@ -5748,10 +5744,6 @@ "type": "String", "placeholders": {} }, - "@yourBookmarks": { - "type": "String", - "placeholders": {} - }, "@goToChat": { "type": "String", "placeholders": {} diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb index 42268e099..e225826a1 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -3564,12 +3564,9 @@ "video": "Video", "nan": "Không áp dụng", "activityPlannerOverviewInstructionsBody": "Chọn chủ đề, chế độ, mục tiêu học tập và tạo hoạt động cho cuộc trò chuyện!", - "myBookmarkedActivities": "Hoạt động đã đánh dấu", - "noBookmarkedActivities": "Chưa có hoạt động nào được đánh dấu", "activityTitle": "Tiêu đề hoạt động", "addVocabulary": "Thêm từ vựng", "instructions": "Hướng dẫn", - "bookmark": "Đánh dấu hoạt động", "numberOfLearners": "Số lượng người học", "mustBeInteger": "Phải là số nguyên, ví dụ: 1, 2, 3...", "noLemmasFound": "Chưa có từ vựng với hơn {xp} XP. Hãy luyện tập thêm!", @@ -3834,7 +3831,6 @@ "randomize": "Ngẫu nhiên hóa", "clear": "Xóa", "featuredActivities": "Nổi bật", - "yourBookmarks": "Đã đánh dấu", "goToChat": "Đi đến trò chuyện", "save": "Lưu", "selectActivity": "Chọn hoạt động", @@ -4030,10 +4026,6 @@ "type": "String", "placeholders": {} }, - "@yourBookmarks": { - "type": "String", - "placeholders": {} - }, "@goToChat": { "type": "String", "placeholders": {} diff --git a/lib/pangea/activity_generator/activity_generator.dart b/lib/pangea/activity_generator/activity_generator.dart index 2f09675ce..e2803ef8a 100644 --- a/lib/pangea/activity_generator/activity_generator.dart +++ b/lib/pangea/activity_generator/activity_generator.dart @@ -197,6 +197,13 @@ class ActivityGeneratorState extends State { }); } + void clearActivities() { + setState(() { + activities = null; + filename = null; + }); + } + Future generate({bool force = false}) async { setState(() { loading = true; diff --git a/lib/pangea/activity_generator/activity_generator_view.dart b/lib/pangea/activity_generator/activity_generator_view.dart index b3c74551e..a7c5ffa1c 100644 --- a/lib/pangea/activity_generator/activity_generator_view.dart +++ b/lib/pangea/activity_generator/activity_generator_view.dart @@ -73,6 +73,16 @@ class ActivityGeneratorView extends StatelessWidget { return 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: body ?? Center( diff --git a/lib/pangea/activity_planner/activity_plan_card.dart b/lib/pangea/activity_planner/activity_plan_card.dart index ce0a9546c..c2fb38eca 100644 --- a/lib/pangea/activity_planner/activity_plan_card.dart +++ b/lib/pangea/activity_planner/activity_plan_card.dart @@ -110,6 +110,7 @@ class ActivityPlanCardState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); final l10n = L10n.of(context); return Center( child: ConstrainedBox( @@ -168,10 +169,10 @@ class ActivityPlanCardState extends State { child: CircleAvatar( backgroundColor: Theme.of(context).colorScheme.secondary, - radius: 16.0, + radius: 20.0, child: Icon( Icons.add_a_photo_outlined, - size: 16.0, + size: 20.0, color: Theme.of(context).colorScheme.onSecondary, ), ), @@ -214,8 +215,8 @@ class ActivityPlanCardState extends State { ), icon: Icon( _isBookmarked - ? Icons.bookmark - : Icons.bookmark_border, + ? Icons.save + : Icons.save_outlined, ), ), ], @@ -383,6 +384,15 @@ class ActivityPlanCardState extends State { children: [ Expanded( child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + theme.colorScheme.primaryContainer, + foregroundColor: + theme.colorScheme.onPrimaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), + ), onPressed: widget.controller.saveEdits, child: Row( children: [ @@ -399,6 +409,15 @@ class ActivityPlanCardState extends State { ), Expanded( child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + theme.colorScheme.primaryContainer, + foregroundColor: + theme.colorScheme.onPrimaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), + ), onPressed: widget.controller.clearEdits, child: Row( children: [ @@ -423,6 +442,15 @@ class ActivityPlanCardState extends State { children: [ Expanded( child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: theme + .colorScheme.primaryContainer, + foregroundColor: theme + .colorScheme.onPrimaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), + ), child: Row( children: [ const Icon(Icons.edit), @@ -440,6 +468,15 @@ class ActivityPlanCardState extends State { ), Expanded( child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: theme + .colorScheme.primaryContainer, + foregroundColor: theme + .colorScheme.onPrimaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), + ), onPressed: widget.regenerate, child: Row( children: [ @@ -460,6 +497,15 @@ class ActivityPlanCardState extends State { children: [ Expanded( child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: theme + .colorScheme.primaryContainer, + foregroundColor: theme + .colorScheme.onPrimaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), + ), onPressed: _onLaunch, child: Row( children: [ diff --git a/lib/pangea/activity_planner/activity_planner_builder.dart b/lib/pangea/activity_planner/activity_planner_builder.dart index d2ec4bdf2..293d75a35 100644 --- a/lib/pangea/activity_planner/activity_planner_builder.dart +++ b/lib/pangea/activity_planner/activity_planner_builder.dart @@ -7,6 +7,7 @@ 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_planner/bookmarked_activities_repo.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; @@ -70,16 +71,18 @@ class ActivityPlannerBuilderState extends State { Room? get room => widget.room; - ActivityPlanModel get updatedActivity { + 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: updatedReq, + req: updatedRequest, title: titleController.text, learningObjective: learningObjectivesController.text, instructions: instructionsController.text, @@ -107,7 +110,28 @@ class ActivityPlannerBuilderState extends State { imageURL = widget.initialActivity.imageURL; filename = widget.initialFilename; - await _setAvatarByURL(); + if (widget.initialActivity.imageURL != null) { + await _setAvatarByURL(widget.initialActivity.imageURL!); + } + if (mounted) setState(() {}); + } + + Future 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; + + if (override.imageURL != null) { + await _setAvatarByURL(override.imageURL!); + } if (mounted) setState(() {}); } @@ -153,24 +177,22 @@ class ActivityPlannerBuilderState extends State { } } - Future _setAvatarByURL() async { - if (widget.initialActivity.imageURL == null) return; + Future _setAvatarByURL(String url) async { try { if (avatar == null) { - if (widget.initialActivity.imageURL!.startsWith("mxc")) { + if (url.startsWith("mxc")) { final client = Matrix.of(context).client; - final mxcUri = Uri.parse(widget.initialActivity.imageURL!); + 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(widget.initialActivity.imageURL!)); + final Response response = await http.get(Uri.parse(url)); avatar = response.bodyBytes; filename = Uri.encodeComponent( - Uri.parse(widget.initialActivity.imageURL!).pathSegments.last, + Uri.parse(url).pathSegments.last, ); } } diff --git a/lib/pangea/activity_planner/activity_planner_page.dart b/lib/pangea/activity_planner/activity_planner_page.dart index eac6c9596..8cc9c4f4b 100644 --- a/lib/pangea/activity_planner/activity_planner_page.dart +++ b/lib/pangea/activity_planner/activity_planner_page.dart @@ -80,7 +80,7 @@ class ActivityPlannerPageState extends State { ), ButtonSegment( value: PageMode.savedActivities, - label: Text(L10n.of(context).yourBookmarks), + label: Text(L10n.of(context).yourSavedActivities), ), ], ), diff --git a/lib/pangea/activity_planner/activity_planner_page_appbar.dart b/lib/pangea/activity_planner/activity_planner_page_appbar.dart index fd7352de7..69c6a9316 100644 --- a/lib/pangea/activity_planner/activity_planner_page_appbar.dart +++ b/lib/pangea/activity_planner/activity_planner_page_appbar.dart @@ -44,10 +44,10 @@ class ActivityPlannerPageAppBar extends StatelessWidget ? Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.bookmarks), + const Icon(Icons.save), const SizedBox(width: 8), Flexible( - child: Text(l10n.myBookmarkedActivities), + child: Text(l10n.mySavedActivities), ), ], ) diff --git a/lib/pangea/activity_planner/bookmarked_activity_list.dart b/lib/pangea/activity_planner/bookmarked_activity_list.dart index 241a162ef..1d621a01d 100644 --- a/lib/pangea/activity_planner/bookmarked_activity_list.dart +++ b/lib/pangea/activity_planner/bookmarked_activity_list.dart @@ -44,7 +44,7 @@ class BookmarkedActivitiesListState extends State { child: Container( constraints: const BoxConstraints(maxWidth: 200), child: Text( - l10n.noBookmarkedActivities, + l10n.noSavedActivities, textAlign: TextAlign.center, ), ), diff --git a/lib/pangea/activity_suggestions/activity_suggestion_card.dart b/lib/pangea/activity_suggestions/activity_suggestion_card.dart index e86957bc2..62a097f11 100644 --- a/lib/pangea/activity_suggestions/activity_suggestion_card.dart +++ b/lib/pangea/activity_suggestions/activity_suggestion_card.dart @@ -180,7 +180,7 @@ class ActivitySuggestionCard extends StatelessWidget { right: 4.0, child: IconButton( icon: Icon( - isBookmarked ? Icons.bookmark : Icons.bookmark_border, + isBookmarked ? Icons.save : Icons.save_outlined, color: Theme.of(context).colorScheme.onPrimaryContainer, ), onPressed: onPressed != null diff --git a/lib/pangea/activity_suggestions/activity_suggestion_carousel.dart b/lib/pangea/activity_suggestions/activity_suggestion_carousel.dart index b2b76007f..360003f01 100644 --- a/lib/pangea/activity_suggestions/activity_suggestion_carousel.dart +++ b/lib/pangea/activity_suggestions/activity_suggestion_carousel.dart @@ -144,6 +144,15 @@ class ActivitySuggestionCarouselState }); } + void _onReplaceActivity(ActivityPlanModel a) { + final index = _currentIndex; + if (index == null || index < 0 || index >= _activityItems.length) { + return; + } + _activityItems[index] = a; + setState(() => _currentActivity = a); + } + void _onClickCard() { if (widget.selectedActivity == _currentActivity) { widget.onActivitySelected( @@ -163,6 +172,7 @@ class ActivitySuggestionCarouselState return ActivitySuggestionDialog( controller: controller, buttonText: L10n.of(context).selectActivity, + replaceActivity: _onReplaceActivity, onLaunch: () => widget.onActivitySelected( controller.updatedActivity, controller.avatar, diff --git a/lib/pangea/activity_suggestions/activity_suggestion_dialog.dart b/lib/pangea/activity_suggestions/activity_suggestion_dialog.dart index 287bffb83..2e8802bcd 100644 --- a/lib/pangea/activity_suggestions/activity_suggestion_dialog.dart +++ b/lib/pangea/activity_suggestions/activity_suggestion_dialog.dart @@ -7,10 +7,13 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.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_planner_builder.dart'; import 'package:fluffychat/pangea/activity_suggestions/activity_room_selection.dart'; import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card_row.dart'; import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/common/widgets/full_width_dialog.dart'; import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; @@ -26,11 +29,13 @@ class ActivitySuggestionDialog extends StatefulWidget { final String buttonText; final VoidCallback? onLaunch; + final Function(ActivityPlanModel)? replaceActivity; const ActivitySuggestionDialog({ required this.controller, required this.buttonText, this.onLaunch, + this.replaceActivity, super.key, }); @@ -42,6 +47,9 @@ class ActivitySuggestionDialog extends StatefulWidget { class ActivitySuggestionDialogState extends State { _PageMode _pageMode = _PageMode.activity; + bool _loading = false; + Object? _error; + double get _width => FluffyThemes.isColumnMode(context) ? 400.0 : MediaQuery.of(context).size.width; @@ -72,6 +80,43 @@ class ActivitySuggestionDialogState extends State { }); } + Future _onRegenerate() async { + setState(() { + _loading = true; + _error = 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"); + } + + widget.replaceActivity?.call(plan); + await widget.controller.overrideActivity(plan); + } catch (e, s) { + _error = e; + ErrorHandler.logError( + e: e, + s: s, + data: { + "request": widget.controller.updatedRequest.toJson(), + }, + ); + return; + } finally { + if (mounted) { + setState(() { + _loading = false; + }); + } + } + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -82,8 +127,29 @@ class ActivitySuggestionDialogState extends State { decoration: BoxDecoration( color: theme.colorScheme.surface, ), - child: _pageMode == _PageMode.activity - ? Form( + child: Builder( + builder: (context) { + if (_pageMode == _PageMode.activity) { + if (_error != null) { + return Center( + child: Row( + spacing: 8.0, + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.error, color: theme.colorScheme.error), + Text(L10n.of(context).oopsSomethingWentWrong), + ], + ), + ); + } + + if (_loading) { + return const Center( + child: CircularProgressIndicator.adaptive(), + ); + } + + return Form( key: widget.controller.formKey, child: Column( mainAxisSize: MainAxisSize.min, @@ -96,69 +162,78 @@ class ActivitySuggestionDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ Stack( - alignment: Alignment.center, + alignment: Alignment.bottomCenter, children: [ - SizedBox( - width: _width, - child: widget.controller.avatar != null - ? Image.memory( - widget.controller.avatar!, - fit: BoxFit.cover, - ) - : widget.controller.updatedActivity - .imageURL != - null - ? widget.controller.updatedActivity - .imageURL! - .startsWith("mxc") - ? MxcImage( - uri: Uri.parse( - widget + Container( + padding: const EdgeInsets.all(24.0), + width: (_width / 2) + 42.0, + child: ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: widget.controller.avatar != null + ? Image.memory( + widget.controller.avatar!, + fit: BoxFit.cover, + ) + : widget.controller.updatedActivity + .imageURL != + null + ? widget.controller + .updatedActivity.imageURL! + .startsWith("mxc") + ? MxcImage( + uri: Uri.parse( + widget + .controller + .updatedActivity + .imageURL!, + ), + width: _width / 2, + height: 200, + cacheKey: widget + .controller + .updatedActivity + .bookmarkId, + fit: BoxFit.cover, + ) + : CachedNetworkImage( + imageUrl: widget .controller .updatedActivity .imageURL!, - ), - width: _width, - height: 200, - cacheKey: widget - .controller - .updatedActivity - .bookmarkId, - fit: BoxFit.cover, - ) - : CachedNetworkImage( - imageUrl: widget - .controller - .updatedActivity - .imageURL!, - fit: BoxFit.cover, - placeholder: - (context, url) => - const Center( - child: - CircularProgressIndicator(), - ), - errorWidget: ( - context, - url, - error, - ) => - const SizedBox(), - ) - : null, + fit: BoxFit.cover, + placeholder: ( + context, + url, + ) => + const Center( + child: + CircularProgressIndicator(), + ), + errorWidget: ( + context, + url, + error, + ) => + const SizedBox(), + ) + : null, + ), ), if (widget.controller.isEditing) - Positioned( - bottom: 8.0, - child: InkWell( - borderRadius: BorderRadius.circular(90), - onTap: widget.controller.selectAvatar, - child: const CircleAvatar( - radius: 24.0, - child: Icon( - Icons.add_a_photo_outlined, - size: 24.0, - ), + InkWell( + borderRadius: BorderRadius.circular(90), + onTap: widget.controller.selectAvatar, + child: CircleAvatar( + backgroundColor: Theme.of(context) + .colorScheme + .secondary, + radius: 20.0, + child: Icon( + Icons.add_a_photo_outlined, + size: 20.0, + color: Theme.of(context) + .colorScheme + .onSecondary, ), ), ), @@ -338,7 +413,9 @@ class ActivitySuggestionDialogState extends State { decoration: BoxDecoration( color: theme .colorScheme.primary - .withAlpha(20), + .withAlpha( + 20, + ), borderRadius: BorderRadius .circular( @@ -352,14 +429,18 @@ class ActivitySuggestionDialogState extends State { child: GestureDetector( onTap: () => widget .controller - .removeVocab(i), + .removeVocab( + i, + ), child: Row( spacing: 4.0, mainAxisSize: MainAxisSize .min, children: [ - Text(vocab.lemma), + Text( + vocab.lemma, + ), const Icon( Icons.close, size: 12.0, @@ -397,7 +478,9 @@ class ActivitySuggestionDialogState extends State { decoration: BoxDecoration( color: theme .colorScheme.primary - .withAlpha(20), + .withAlpha( + 20, + ), borderRadius: BorderRadius .circular( @@ -429,8 +512,9 @@ class ActivitySuggestionDialogState extends State { controller: widget .controller.vocabController, decoration: InputDecoration( - hintText: L10n.of(context) - .addVocabulary, + hintText: L10n.of( + context, + ).addVocabulary, ), maxLines: 1, onFieldSubmitted: (_) => widget @@ -439,8 +523,9 @@ class ActivitySuggestionDialogState extends State { ), ), IconButton( - padding: - const EdgeInsets.all(0.0), + padding: const EdgeInsets.all( + 0.0, + ), constraints: const BoxConstraints(), // override default min size of 48px iconSize: 16.0, @@ -462,101 +547,173 @@ class ActivitySuggestionDialogState extends State { ), Padding( padding: const EdgeInsets.all(16.0), - child: Row( - spacing: 6.0, - children: [ - if (widget.controller.isEditing) - Expanded( - child: ElevatedButton( - onPressed: widget.controller.saveEdits, - style: ElevatedButton.styleFrom( - minimumSize: Size.zero, - padding: const EdgeInsets.all(6.0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), - ), - backgroundColor: theme.colorScheme.primary, - foregroundColor: - theme.colorScheme.onPrimary, - ), - child: Text( - L10n.of(context).save, - style: theme.textTheme.bodyLarge?.copyWith( - color: theme.colorScheme.onPrimary, + child: widget.controller.isEditing + ? Row( + spacing: 12.0, + children: [ + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + theme.colorScheme.primaryContainer, + foregroundColor: theme + .colorScheme.onPrimaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), + ), + onPressed: widget.controller.saveEdits, + child: Row( + children: [ + const Icon(Icons.save), + Expanded( + child: Text( + L10n.of(context).save, + textAlign: TextAlign.center, + ), + ), + ], + ), ), ), - ), + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + theme.colorScheme.primaryContainer, + foregroundColor: theme + .colorScheme.onPrimaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), + ), + onPressed: widget.controller.clearEdits, + child: Row( + children: [ + const Icon(Icons.cancel), + Expanded( + child: Text( + L10n.of(context).cancel, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ), + ], ) - else - Expanded( - child: ElevatedButton( - onPressed: () async { - if (!widget.controller.formKey.currentState! - .validate()) { - return; - } - _launchActivity(); - }, - style: ElevatedButton.styleFrom( - minimumSize: Size.zero, - padding: const EdgeInsets.all(6.0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), - ), - backgroundColor: theme.colorScheme.primary, - foregroundColor: - theme.colorScheme.onPrimary, + : Column( + spacing: 12.0, + children: [ + Row( + spacing: 12.0, + children: [ + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: theme + .colorScheme.primaryContainer, + foregroundColor: theme + .colorScheme.onPrimaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), + ), + child: Row( + children: [ + const Icon(Icons.edit), + Expanded( + child: Text( + L10n.of(context).edit, + textAlign: TextAlign.center, + ), + ), + ], + ), + onPressed: () => widget.controller + .setEditing(true), + ), + ), + if (widget.replaceActivity != null) + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: theme + .colorScheme.primaryContainer, + foregroundColor: theme.colorScheme + .onPrimaryContainer, + padding: + const EdgeInsets.symmetric( + horizontal: 12.0, + ), + ), + onPressed: _onRegenerate, + child: Row( + children: [ + const Icon( + Icons.lightbulb_outline, + ), + Expanded( + child: Text( + L10n.of(context).regenerate, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ), + ], ), - child: Text( - widget.buttonText, - style: theme.textTheme.bodyLarge?.copyWith( - color: theme.colorScheme.onPrimary, - ), + Row( + children: [ + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: theme + .colorScheme.primaryContainer, + foregroundColor: theme + .colorScheme.onPrimaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), + ), + onPressed: _launchActivity, + child: Row( + children: [ + const Icon(Icons.send), + Expanded( + child: Text( + L10n.of(context) + .launchActivityButton, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ), + ], ), - ), + ], ), - if (widget.controller.isEditing) - IconButton.filled( - style: IconButton.styleFrom( - backgroundColor: theme.colorScheme.primary, - ), - padding: const EdgeInsets.all(6.0), - constraints: - const BoxConstraints(), // override default min size of 48px - iconSize: 24.0, - icon: const Icon(Icons.close_outlined), - onPressed: () async { - await widget.controller.clearEdits(); - widget.controller.setEditing(false); - }, - ) - else - IconButton.filled( - style: IconButton.styleFrom( - backgroundColor: theme.colorScheme.primary, - ), - padding: const EdgeInsets.all(6.0), - constraints: - const BoxConstraints(), // override default min size of 48px - iconSize: 24.0, - icon: const Icon(Icons.edit_outlined), - onPressed: () => - widget.controller.setEditing(true), - ), - ], - ), ), ], ), - ) - : ActivityRoomSelection( - controller: widget.controller, - backButton: BackButton( - onPressed: () => _setPageMode( - _PageMode.activity, - ), + ); + } + + return ActivityRoomSelection( + controller: widget.controller, + backButton: BackButton( + onPressed: () => _setPageMode( + _PageMode.activity, ), ), + ); + }, + ), ), if (_pageMode == _PageMode.activity) Positioned( diff --git a/lib/pangea/activity_suggestions/activity_suggestions_area.dart b/lib/pangea/activity_suggestions/activity_suggestions_area.dart index 848923717..7d43c9180 100644 --- a/lib/pangea/activity_suggestions/activity_suggestions_area.dart +++ b/lib/pangea/activity_suggestions/activity_suggestions_area.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; import 'package:matrix/matrix.dart'; import 'package:shimmer/shimmer.dart'; @@ -78,6 +79,20 @@ class ActivitySuggestionsAreaState extends State { 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, + ); + } + Future _setActivityItems({int retries = 0}) async { if (retries > 3) { if (mounted) { @@ -95,18 +110,7 @@ class ActivitySuggestionsAreaState extends State { _loading = true; }); - final ActivityPlanRequest request = ActivityPlanRequest( - topic: "", - mode: "", - objective: "", - media: MediaEnum.nan, - cefrLevel: LanguageLevelTypeEnum.a1, - languageOfInstructions: instructionLanguage, - targetLanguage: targetLanguage, - numberOfParticipants: 3, - count: 5, - ); - final resp = await ActivitySearchRepo.get(request).timeout( + final resp = await ActivitySearchRepo.get(_request).timeout( const Duration(seconds: 5), onTimeout: () { if (mounted) { @@ -134,6 +138,10 @@ class ActivitySuggestionsAreaState extends State { } } + void _onReplaceActivity(int index, ActivityPlanModel a) { + setState(() => _activityItems[index] = a); + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -154,7 +162,7 @@ class ActivitySuggestionsAreaState extends State { ); }) : _activityItems - .map((activity) { + .mapIndexed((index, activity) { return ActivitySuggestionCard( activity: activity, onPressed: () { @@ -168,6 +176,8 @@ class ActivitySuggestionsAreaState extends State { return ActivitySuggestionDialog( controller: controller, buttonText: L10n.of(context).launch, + replaceActivity: (a) => + _onReplaceActivity(index, a), ); }, ); diff --git a/lib/pangea/analytics_summary/progress_indicator.dart b/lib/pangea/analytics_summary/progress_indicator.dart index 1ededda3c..fe931d19d 100644 --- a/lib/pangea/analytics_summary/progress_indicator.dart +++ b/lib/pangea/analytics_summary/progress_indicator.dart @@ -73,7 +73,9 @@ class _AnimatedFloatingNumberState extends State<_AnimatedFloatingNumber> void initState() { super.initState(); _controller = AnimationController( - vsync: this, duration: const Duration(milliseconds: 900)); + vsync: this, + duration: const Duration(milliseconds: 900), + ); _fadeAnim = CurvedAnimation(parent: _controller, curve: Curves.easeOut); _offsetAnim = Tween( begin: const Offset(0, 0),