chore: fixes for editting / bookmarking of activities (#2436)
This commit is contained in:
parent
f6b8008fe7
commit
d111b11783
8 changed files with 159 additions and 86 deletions
|
|
@ -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> _setAvatarByImageURL() async {
|
||||
if (_avatar != null || _imageURL == null) return;
|
||||
final resp = await http
|
||||
.get(Uri.parse(_imageURL!))
|
||||
.timeout(const Duration(seconds: 5));
|
||||
if (mounted) {
|
||||
setState(() => _avatar = resp.bodyBytes);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLaunch() async {
|
||||
await _setAvatarByImageURL();
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
if (_avatar == null && widget.activity.imageURL != null) {
|
||||
final resp = await http
|
||||
.get(Uri.parse(widget.activity.imageURL!))
|
||||
.timeout(const Duration(seconds: 5));
|
||||
_avatar = resp.bodyBytes;
|
||||
}
|
||||
|
||||
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,16 +301,18 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
padding: EdgeInsets.all(28.0),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 10.0,
|
||||
right: 10.0,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.upload_outlined),
|
||||
onPressed: selectPhoto,
|
||||
style:
|
||||
IconButton.styleFrom(backgroundColor: Colors.black),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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…
Add table
Reference in a new issue