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:
parent
57f4d66cca
commit
b0ad8a625c
15 changed files with 454 additions and 206 deletions
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue