chore: fixes for editting / bookmarking of activities (#2436)

pull/1817/head
ggurdin 7 months ago committed by GitHub
parent f6b8008fe7
commit d111b11783
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -189,10 +189,16 @@ class ActivityGeneratorState extends State<ActivityGenerator> {
final imageUrl =
"${AppConfig.assetsBaseURL}/${ActivitySuggestionsConstants.modeImageFileStart}$modeName.jpg";
setState(() {
filename =
"${ActivitySuggestionsConstants.modeImageFileStart}$modeName.jpg";
for (final activity in activities!) {
activity.imageURL = imageUrl;
filename = imageUrl;
for (ActivityPlanModel activity in activities!) {
activity = ActivityPlanModel(
req: activity.req,
title: activity.title,
learningObjective: activity.learningObjective,
instructions: activity.instructions,
vocab: activity.vocab,
imageURL: imageUrl,
);
}
});
}

@ -59,7 +59,7 @@ class ActivityGeneratorView extends StatelessWidget {
onEdit: (updatedActivity) =>
controller.onEdit(index, updatedActivity),
onChange: controller.update,
initialFilename: controller.filename,
initialImageURL: controller.filename,
);
},
);

@ -27,7 +27,7 @@ class ActivityPlanCard extends StatefulWidget {
final VoidCallback onChange;
final ValueChanged<ActivityPlanModel> onEdit;
final double maxWidth;
final String? initialFilename;
final String? initialImageURL;
const ActivityPlanCard({
super.key,
@ -36,7 +36,7 @@ class ActivityPlanCard extends StatefulWidget {
required this.onChange,
required this.onEdit,
this.maxWidth = 400,
this.initialFilename,
this.initialImageURL,
});
@override
@ -54,6 +54,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
Uint8List? _avatar;
String? _filename;
String? _imageURL;
@override
void initState() {
@ -64,7 +65,8 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
TextEditingController(text: _tempActivity.learningObjective);
_instructionsController =
TextEditingController(text: _tempActivity.instructions);
_filename = widget.initialFilename;
_filename = widget.initialImageURL?.split("/").last;
_imageURL = widget.activity.imageURL ?? widget.initialImageURL;
}
static const double itemPadding = 12;
@ -153,19 +155,39 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
_avatar = bytes;
_filename = photo.name;
});
final url = await Matrix.of(context).client.uploadContent(
bytes,
filename: photo.name,
);
final updatedActivity = ActivityPlanModel(
req: _tempActivity.req,
title: _tempActivity.title,
learningObjective: _tempActivity.learningObjective,
instructions: _tempActivity.instructions,
vocab: _tempActivity.vocab,
imageURL: url.toString(),
);
widget.onEdit(updatedActivity);
}
Future<void> _onLaunch() async {
await showFutureLoadingDialog(
context: context,
future: () async {
if (_avatar == null && widget.activity.imageURL != null) {
Future<void> _setAvatarByImageURL() async {
if (_avatar != null || _imageURL == null) return;
final resp = await http
.get(Uri.parse(widget.activity.imageURL!))
.get(Uri.parse(_imageURL!))
.timeout(const Duration(seconds: 5));
_avatar = resp.bodyBytes;
if (mounted) {
setState(() => _avatar = resp.bodyBytes);
}
}
Future<void> _onLaunch() async {
await _setAvatarByImageURL();
await showFutureLoadingDialog(
context: context,
future: () async {
String? avatarUrl;
if (_avatar != null) {
final client = Matrix.of(context).client;
@ -253,12 +275,12 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
),
clipBehavior: Clip.hardEdge,
alignment: Alignment.center,
child: widget.activity.imageURL != null || _avatar != null
child: _imageURL != null || _avatar != null
? ClipRRect(
child: _avatar == null
? CachedNetworkImage(
fit: BoxFit.cover,
imageUrl: widget.activity.imageURL!,
imageUrl: _imageURL!,
placeholder: (context, url) {
return const Center(
child: CircularProgressIndicator(),
@ -279,14 +301,16 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
padding: EdgeInsets.all(28.0),
),
),
if (_isEditing)
Positioned(
top: 10.0,
right: 10.0,
child: IconButton(
icon: const Icon(Icons.upload_outlined),
onPressed: selectPhoto,
style:
IconButton.styleFrom(backgroundColor: Colors.black),
style: IconButton.styleFrom(
backgroundColor: Colors.black,
),
),
),
],

@ -6,11 +6,11 @@ import 'package:fluffychat/pangea/common/constants/model_keys.dart';
class ActivityPlanModel {
final String bookmarkId;
final ActivityPlanRequest req;
String title;
String learningObjective;
String instructions;
List<Vocab> vocab;
String? imageURL;
final String title;
final String learningObjective;
final String instructions;
final List<Vocab> vocab;
final String? imageURL;
ActivityPlanModel({
required this.req,
@ -19,7 +19,8 @@ class ActivityPlanModel {
required this.instructions,
required this.vocab,
this.imageURL,
}) : bookmarkId = req.hashCode.toString();
}) : bookmarkId =
"${title.hashCode ^ learningObjective.hashCode ^ instructions.hashCode ^ imageURL.hashCode ^ vocab.map((v) => v.hashCode).reduce((a, b) => a ^ b)}";
factory ActivityPlanModel.fromJson(Map<String, dynamic> json) {
return ActivityPlanModel(

@ -39,6 +39,7 @@ class BookmarkedActivitiesListState extends State<BookmarkedActivitiesList> {
double get cardWidth => _isColumnMode ? 225.0 : 150.0;
Future<void> _onEdit(
String activityId,
ActivityPlanModel activity,
Uint8List? avatar,
String? filename,
@ -48,10 +49,20 @@ class BookmarkedActivitiesListState extends State<BookmarkedActivitiesList> {
avatar,
filename: filename,
);
activity.imageURL = url.toString();
if (!mounted) return;
setState(() {
activity = ActivityPlanModel(
req: activity.req,
title: activity.title,
learningObjective: activity.learningObjective,
instructions: activity.instructions,
vocab: activity.vocab,
imageURL: url.toString(),
);
});
}
await BookmarkedActivitiesRepo.remove(activity.bookmarkId);
await BookmarkedActivitiesRepo.remove(activityId);
await BookmarkedActivitiesRepo.save(activity);
if (mounted) setState(() {});
}
@ -87,7 +98,7 @@ class BookmarkedActivitiesListState extends State<BookmarkedActivitiesList> {
context: context,
builder: (context) {
return ActivitySuggestionDialog(
activity: activity,
initialActivity: activity,
buttonText: L10n.of(context).inviteAndLaunch,
room: widget.room,
onEdit: _onEdit,

@ -60,6 +60,22 @@ class ActivitySuggestionCarouselState
_setActivityItems();
}
@override
void didUpdateWidget(covariant ActivitySuggestionCarousel oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedActivity != oldWidget.selectedActivity &&
_currentIndex != null &&
widget.selectedActivity != null) {
final prevIndex = _currentIndex!;
setState(
() {
_activityItems[prevIndex] = widget.selectedActivity!;
_currentActivity = widget.selectedActivity;
},
);
}
}
Future<void> _setActivityItems() async {
try {
final ActivityPlanRequest request = ActivityPlanRequest(
@ -138,7 +154,7 @@ class ActivitySuggestionCarouselState
context: context,
builder: (context) {
return ActivitySuggestionDialog(
activity: _currentActivity!,
initialActivity: _currentActivity!,
buttonText: L10n.of(context).selectActivity,
onLaunch: widget.onActivitySelected,
);

@ -26,7 +26,7 @@ import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
class ActivitySuggestionDialog extends StatefulWidget {
final ActivityPlanModel activity;
final ActivityPlanModel initialActivity;
final String buttonText;
final Room? room;
@ -37,13 +37,14 @@ class ActivitySuggestionDialog extends StatefulWidget {
)? onLaunch;
final Future<void> Function(
String,
ActivityPlanModel,
Uint8List?,
String?,
)? onEdit;
const ActivitySuggestionDialog({
required this.activity,
required this.initialActivity,
required this.buttonText,
this.onLaunch,
this.onEdit,
@ -59,6 +60,7 @@ class ActivitySuggestionDialog extends StatefulWidget {
class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
bool _isEditing = false;
Uint8List? _avatar;
String? _imageURL;
String? _filename;
final TextEditingController _titleController = TextEditingController();
@ -77,12 +79,14 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
@override
void initState() {
super.initState();
_titleController.text = widget.activity.title;
_learningObjectivesController.text = widget.activity.learningObjective;
_instructionsController.text = widget.activity.instructions;
_titleController.text = widget.initialActivity.title;
_learningObjectivesController.text =
widget.initialActivity.learningObjective;
_instructionsController.text = widget.initialActivity.instructions;
_participantsController.text =
widget.activity.req.numberOfParticipants.toString();
_vocab.addAll(widget.activity.vocab);
widget.initialActivity.req.numberOfParticipants.toString();
_vocab.addAll(widget.initialActivity.vocab);
_imageURL = widget.initialActivity.imageURL;
_setAvatarByURL();
}
@ -117,12 +121,12 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
}
Future<void> _setAvatarByURL() async {
if (widget.activity.imageURL == null) return;
if (widget.initialActivity.imageURL == null) return;
try {
if (_avatar == null) {
if (widget.activity.imageURL!.startsWith("mxc")) {
if (widget.initialActivity.imageURL!.startsWith("mxc")) {
final client = Matrix.of(context).client;
final mxcUri = Uri.parse(widget.activity.imageURL!);
final mxcUri = Uri.parse(widget.initialActivity.imageURL!);
final data = await client.downloadMxcCached(mxcUri);
_avatar = data;
_filename = Uri.encodeComponent(
@ -130,10 +134,10 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
);
} else {
final Response response =
await http.get(Uri.parse(widget.activity.imageURL!));
await http.get(Uri.parse(widget.initialActivity.imageURL!));
_avatar = response.bodyBytes;
_filename = Uri.encodeComponent(
Uri.parse(widget.activity.imageURL!).pathSegments.last,
Uri.parse(widget.initialActivity.imageURL!).pathSegments.last,
);
}
}
@ -142,7 +146,7 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
e: err,
s: s,
data: {
"imageURL": widget.activity.imageURL,
"imageURL": widget.initialActivity.imageURL,
},
);
}
@ -153,17 +157,29 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
_filename = null;
_setAvatarByURL();
_vocab.clear();
_vocab.addAll(widget.activity.vocab);
_vocab.addAll(widget.initialActivity.vocab);
if (mounted) setState(() {});
}
Future<void> _updateTextFields() async {
widget.activity.title = _titleController.text;
widget.activity.learningObjective = _learningObjectivesController.text;
widget.activity.instructions = _instructionsController.text;
widget.activity.req.numberOfParticipants =
int.tryParse(_participantsController.text) ?? 3;
widget.activity.vocab = _vocab;
ActivityPlanModel get _updatedActivity => ActivityPlanModel(
req: widget.initialActivity.req,
title: _titleController.text,
learningObjective: _learningObjectivesController.text,
instructions: _instructionsController.text,
vocab: _vocab,
imageURL: _imageURL,
);
Future<void> _updateImageURL() async {
if (_avatar == null) return;
final url = await Matrix.of(context).client.uploadContent(
_avatar!,
filename: _filename,
);
if (!mounted) return;
setState(() {
_imageURL = url.toString();
});
}
void _addVocab() {
@ -184,9 +200,11 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
}
Future<void> _launchActivity() async {
await _updateImageURL();
if (widget.room != null) {
await widget.room!.sendActivityPlan(
widget.activity,
_updatedActivity,
avatar: _avatar,
filename: _filename,
);
@ -194,27 +212,18 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
return;
}
String? avatarUrl;
if (_avatar != null) {
final url = await Matrix.of(context).client.uploadContent(
_avatar!,
filename: _filename,
);
avatarUrl = url.toString();
}
final client = Matrix.of(context).client;
final roomId = await client.createGroupChat(
preset: CreateRoomPreset.publicChat,
visibility: sdk.Visibility.private,
groupName: widget.activity.title,
groupName: _updatedActivity.title,
initialState: [
if (avatarUrl != null)
if (_updatedActivity.imageURL != null)
StateEvent(
type: EventTypes.RoomAvatar,
stateKey: '',
content: {
"url": avatarUrl,
"url": _updatedActivity.imageURL,
},
),
StateEvent(
@ -234,7 +243,7 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
}
await room.sendActivityPlan(
widget.activity,
_updatedActivity,
avatar: _avatar,
filename: _filename,
);
@ -244,11 +253,12 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
Future<void> _saveEdits() async {
if (!_formKey.currentState!.validate()) return;
await _updateTextFields();
await _updateImageURL();
_setEditing(false);
if (widget.onEdit != null) {
await widget.onEdit!(
widget.activity,
widget.initialActivity.bookmarkId,
_updatedActivity,
_avatar,
_filename,
);
@ -289,17 +299,19 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
borderRadius: BorderRadius.circular(24.0),
child: _avatar != null
? Image.memory(_avatar!, fit: BoxFit.cover)
: widget.activity.imageURL != null
? widget.activity.imageURL!.startsWith("mxc")
: _updatedActivity.imageURL != null
? _updatedActivity.imageURL!.startsWith("mxc")
? MxcImage(
uri: Uri.parse(widget.activity.imageURL!),
uri: Uri.parse(
_updatedActivity.imageURL!,
),
width: width,
height: 200,
cacheKey: widget.activity.bookmarkId,
cacheKey: _updatedActivity.bookmarkId,
fit: BoxFit.cover,
)
: CachedNetworkImage(
imageUrl: widget.activity.imageURL!,
imageUrl: _updatedActivity.imageURL!,
fit: BoxFit.cover,
placeholder: (context, url) =>
const Center(
@ -352,7 +364,7 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
ActivitySuggestionCardRow(
icon: Icons.event_note_outlined,
child: Text(
widget.activity.title,
_updatedActivity.title,
style: theme.textTheme.titleLarge
?.copyWith(fontWeight: FontWeight.bold),
maxLines: 6,
@ -376,7 +388,7 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
ActivitySuggestionCardRow(
icon: Symbols.target,
child: Text(
widget.activity.learningObjective,
_updatedActivity.learningObjective,
maxLines: 6,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyLarge,
@ -398,7 +410,7 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
ActivitySuggestionCardRow(
icon: Symbols.steps,
child: Text(
widget.activity.instructions,
_updatedActivity.instructions,
maxLines: 8,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyLarge,
@ -436,7 +448,7 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
icon: Icons.group_outlined,
child: Text(
L10n.of(context).countParticipants(
widget.activity.req.numberOfParticipants,
_updatedActivity.req.numberOfParticipants,
),
style: theme.textTheme.bodyLarge,
),
@ -598,13 +610,16 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
context: context,
future: () async {
if (widget.onLaunch != null) {
return widget.onLaunch?.call(
widget.activity,
await _updateImageURL();
widget.onLaunch!.call(
_updatedActivity,
_avatar,
_filename,
);
} else {
await _launchActivity();
}
return _launchActivity();
},
);

@ -136,7 +136,7 @@ class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
context: context,
builder: (context) {
return ActivitySuggestionDialog(
activity: activity,
initialActivity: activity,
buttonText: L10n.of(context).inviteAndLaunch,
room: widget.room,
);

Loading…
Cancel
Save