chore: updates to buttons in activity dialog, added regenerate button, don't reset generator input fields when pressing back button (#3356)

This commit is contained in:
ggurdin 2025-07-07 12:39:26 -04:00 committed by GitHub
parent 57f4d66cca
commit b0ad8a625c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 454 additions and 206 deletions

View file

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

View file

@ -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": {}

View file

@ -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": {}

View file

@ -197,6 +197,13 @@ class ActivityGeneratorState extends State<ActivityGenerator> {
});
}
void clearActivities() {
setState(() {
activities = null;
filename = null;
});
}
Future<void> generate({bool force = false}) async {
setState(() {
loading = true;

View file

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

View file

@ -110,6 +110,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
@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<ActivityPlanCard> {
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<ActivityPlanCard> {
),
icon: Icon(
_isBookmarked
? Icons.bookmark
: Icons.bookmark_border,
? Icons.save
: Icons.save_outlined,
),
),
],
@ -383,6 +384,15 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
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<ActivityPlanCard> {
),
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<ActivityPlanCard> {
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<ActivityPlanCard> {
),
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<ActivityPlanCard> {
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: [

View file

@ -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<ActivityPlannerBuilder> {
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<ActivityPlannerBuilder> {
imageURL = widget.initialActivity.imageURL;
filename = widget.initialFilename;
await _setAvatarByURL();
if (widget.initialActivity.imageURL != null) {
await _setAvatarByURL(widget.initialActivity.imageURL!);
}
if (mounted) setState(() {});
}
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;
if (override.imageURL != null) {
await _setAvatarByURL(override.imageURL!);
}
if (mounted) setState(() {});
}
@ -153,24 +177,22 @@ class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
}
}
Future<void> _setAvatarByURL() async {
if (widget.initialActivity.imageURL == null) return;
Future<void> _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,
);
}
}

View file

@ -80,7 +80,7 @@ class ActivityPlannerPageState extends State<ActivityPlannerPage> {
),
ButtonSegment(
value: PageMode.savedActivities,
label: Text(L10n.of(context).yourBookmarks),
label: Text(L10n.of(context).yourSavedActivities),
),
],
),

View file

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

View file

@ -44,7 +44,7 @@ class BookmarkedActivitiesListState extends State<BookmarkedActivitiesList> {
child: Container(
constraints: const BoxConstraints(maxWidth: 200),
child: Text(
l10n.noBookmarkedActivities,
l10n.noSavedActivities,
textAlign: TextAlign.center,
),
),

View file

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

View file

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

View file

@ -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<ActivitySuggestionDialog> {
_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<ActivitySuggestionDialog> {
});
}
Future<void> _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<ActivitySuggestionDialog> {
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<ActivitySuggestionDialog> {
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<ActivitySuggestionDialog> {
decoration: BoxDecoration(
color: theme
.colorScheme.primary
.withAlpha(20),
.withAlpha(
20,
),
borderRadius:
BorderRadius
.circular(
@ -352,14 +429,18 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
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<ActivitySuggestionDialog> {
decoration: BoxDecoration(
color: theme
.colorScheme.primary
.withAlpha(20),
.withAlpha(
20,
),
borderRadius:
BorderRadius
.circular(
@ -429,8 +512,9 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
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<ActivitySuggestionDialog> {
),
),
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<ActivitySuggestionDialog> {
),
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(

View file

@ -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<ActivitySuggestionsArea> {
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<void> _setActivityItems({int retries = 0}) async {
if (retries > 3) {
if (mounted) {
@ -95,18 +110,7 @@ class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
_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<ActivitySuggestionsArea> {
}
}
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<ActivitySuggestionsArea> {
);
})
: _activityItems
.map((activity) {
.mapIndexed((index, activity) {
return ActivitySuggestionCard(
activity: activity,
onPressed: () {
@ -168,6 +176,8 @@ class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
return ActivitySuggestionDialog(
controller: controller,
buttonText: L10n.of(context).launch,
replaceActivity: (a) =>
_onReplaceActivity(index, a),
);
},
);

View file

@ -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<Offset>(
begin: const Offset(0, 0),