diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 40d0ce5df..f09185ff6 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -32,6 +32,7 @@ import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activ import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_page/analytics_page.dart'; import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart'; +import 'package:fluffychat/pangea/chat_settings/pages/edit_course.dart'; import 'package:fluffychat/pangea/chat_settings/pages/pangea_invitation_selection.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/course_creation/new_course_page.dart'; @@ -996,6 +997,15 @@ abstract class AppRoutes { ]; static List roomDetailsRoutes(String roomKey) => [ + GoRoute( + path: '/edit', + redirect: loggedOutRedirect, + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + EditCourse(roomId: state.pathParameters[roomKey]!), + ), + ), GoRoute( path: '/analytics', redirect: loggedOutRedirect, diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index a544cc634..72e1ba83e 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5232,5 +5232,7 @@ "results": "Results", "activityDone": "Activity Done!", "moreLabel": "more", - "promoCodeInfo": "Promo codes can be entered on the next page" + "promoCodeInfo": "Promo codes can be entered on the next page", + "editsComingSoon": "The ability to edit cities and activities is coming soon.", + "editing": "Editing" } diff --git a/lib/pangea/chat_settings/pages/edit_course.dart b/lib/pangea/chat_settings/pages/edit_course.dart new file mode 100644 index 000000000..2f221211f --- /dev/null +++ b/lib/pangea/chat_settings/pages/edit_course.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; + +import 'package:image_picker/image_picker.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pages/settings/settings.dart'; +import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart'; +import 'package:fluffychat/pangea/course_plans/map_clipper.dart'; +import 'package:fluffychat/utils/file_selector.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart'; +import 'package:fluffychat/widgets/future_loading_dialog.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class EditCourse extends StatefulWidget { + final String roomId; + const EditCourse({super.key, required this.roomId}); + + @override + EditCourseController createState() => EditCourseController(); +} + +class EditCourseController extends State { + final _titleController = TextEditingController(); + final _descController = TextEditingController(); + MatrixFile? _avatar; + + @override + void initState() { + super.initState(); + if (_room != null) { + _titleController.text = _room!.name; + _descController.text = _room!.topic; + } + } + + @override + void dispose() { + _titleController.dispose(); + _descController.dispose(); + super.dispose(); + } + + Room? get _room => Matrix.of(context).client.getRoomById(widget.roomId); + + Future _saveChanges() async { + if (_room == null) return; + final title = _titleController.text.trim(); + final desc = _descController.text.trim(); + + if (title.isNotEmpty && title != _room!.name) { + await _room!.setName(title); + } + if (desc.isNotEmpty && desc != _room!.topic) { + await _room!.setDescription(desc); + } + if (_avatar != null) { + await _room!.setAvatar(_avatar!); + } + } + + Future _setAvatarAction() async { + if (_room == null) return; + final actions = [ + if (PlatformInfos.isMobile) + AdaptiveModalAction( + value: AvatarAction.camera, + label: L10n.of(context).openCamera, + isDefaultAction: true, + icon: const Icon(Icons.camera_alt_outlined), + ), + AdaptiveModalAction( + value: AvatarAction.file, + label: L10n.of(context).openGallery, + icon: const Icon(Icons.photo_outlined), + ), + ]; + final action = actions.length == 1 + ? actions.single.value + : await showModalActionPopup( + context: context, + title: L10n.of(context).editRoomAvatar, + cancelLabel: L10n.of(context).cancel, + actions: actions, + ); + if (action == null) return; + if (PlatformInfos.isMobile) { + final result = await ImagePicker().pickImage( + source: action == AvatarAction.camera + ? ImageSource.camera + : ImageSource.gallery, + imageQuality: 50, + ); + if (result == null) return; + _avatar = MatrixFile( + bytes: await result.readAsBytes(), + name: result.path, + ); + } else { + final picked = await selectFiles( + context, + allowMultiple: false, + type: FileSelectorType.images, + ); + final pickedFile = picked.firstOrNull; + if (pickedFile == null) return; + _avatar = MatrixFile( + bytes: await pickedFile.readAsBytes(), + name: pickedFile.name, + ); + } + + if (mounted) setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: const Center(child: BackButton()), + title: Text(L10n.of(context).editing), + ), + body: StreamBuilder( + stream: Matrix.of(context).client.onRoomState.stream.where( + (u) => u.roomId == widget.roomId, + ), + builder: (context, snapshot) { + return SafeArea( + child: Container( + alignment: Alignment.topCenter, + padding: const EdgeInsets.all(16.0), + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 600, + ), + child: _room == null || !_room!.isSpace + ? Center(child: Text(L10n.of(context).noRoomsFound)) + : Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + spacing: 16.0, + children: [ + Stack( + children: [ + ClipPath( + clipper: MapClipper(), + child: _avatar != null + ? Image.memory( + _avatar!.bytes, + width: 200.0, + height: 200.0, + fit: BoxFit.cover, + ) + : ImageByUrl( + imageUrl: + _room?.avatar.toString(), + width: 200.0, + borderRadius: + BorderRadius.circular(0.0), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: FloatingActionButton.small( + onPressed: _setAvatarAction, + child: const Icon( + Icons.camera_alt_outlined, + ), + ), + ), + ], + ), + TextField( + controller: _titleController, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(4.0), + ), + ), + ), + TextField( + controller: _descController, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(4.0), + ), + ), + maxLines: null, + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 16.0, + ), + child: Text( + L10n.of(context).editsComingSoon, + style: const TextStyle( + fontStyle: FontStyle.italic, + ), + ), + ), + ], + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: ElevatedButton( + onPressed: () => showFutureLoadingDialog( + context: context, + future: _saveChanges, + ), + child: Row( + spacing: 8.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.save_outlined), + Text(L10n.of(context).saveChanges), + ], + ), + ), + ), + ], + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/pangea/chat_settings/pages/space_details_content.dart b/lib/pangea/chat_settings/pages/space_details_content.dart index bc69b4084..0518da4d4 100644 --- a/lib/pangea/chat_settings/pages/space_details_content.dart +++ b/lib/pangea/chat_settings/pages/space_details_content.dart @@ -19,7 +19,6 @@ import 'package:fluffychat/pangea/course_plans/course_plan_builder.dart'; import 'package:fluffychat/pangea/course_plans/course_plan_room_extension.dart'; import 'package:fluffychat/pangea/course_plans/map_clipper.dart'; import 'package:fluffychat/pangea/course_settings/course_settings.dart'; -import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/space_analytics/space_analytics.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; @@ -112,16 +111,15 @@ class SpaceDetailsContent extends StatelessWidget { '/rooms/spaces/${room.id}/details/invite?filter=$filter', ); }, - enabled: room.canInvite && !room.isDirectChat, + enabled: room.canInvite, showInMainView: false, ), ButtonDetails( title: l10n.editCourse, description: l10n.editCourseDesc, icon: const Icon(Icons.edit_outlined, size: 30.0), - onPressed: () {}, - visible: false, - enabled: room.canChangeStateEvent(PangeaEventTypes.coursePlan), + onPressed: () => context.go('/rooms/${room.id}/details/edit'), + enabled: room.isRoomAdmin, showInMainView: false, ), ButtonDetails( @@ -129,7 +127,7 @@ class SpaceDetailsContent extends StatelessWidget { description: l10n.permissionsDesc, icon: const Icon(Icons.edit_attributes_outlined, size: 30.0), onPressed: () => context.go('/rooms/${room.id}/details/permissions'), - enabled: room.isRoomAdmin && !room.isDirectChat, + enabled: room.isRoomAdmin, showInMainView: false, ), ButtonDetails( @@ -193,7 +191,7 @@ class SpaceDetailsContent extends StatelessWidget { context.go("/rooms"); } }, - enabled: room.isRoomAdmin && !room.isDirectChat, + enabled: room.isRoomAdmin, showInMainView: false, ), ];