diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 2e6c825a1..5b0fd503c 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4826,7 +4826,6 @@ } } }, - "startChatting": "Start chatting", "referFriends": "Refer friends", "referFriendDialogTitle": "Invite a friend to your conversation", "referFriendDialogDesc": "Do you have a friend who is excited to learn a new language with you? Then copy and send this invitation link to join and start chatting with you today.", @@ -4835,12 +4834,14 @@ "resetInstructionTooltipsDesc": "Click to show instruction tooltips like for a brand new user.", "selectForGrammar": "Select a grammar icon for activities and details.", "newChatActivityTitle": "Add a fun activity", - "newChatActivityDesc": "Make every group chat an adventure with Activity Planner! Set captivating topics and objectives for the group, and bring conversations to life with stunning images. Spark imaginative discussions and keep the fun flowing effortlessly!", + "newChatActivityDesc": "Choose one of the activities below to add to your chat or skip this step and create an activity later.", "exploreMore": "Explore more", "randomize": "Randomize", "clear": "Clear", "makeYourOwnActivity": "Make your own activity", - "makeYourOwn": "Make your own", "featuredActivities": "Featured activities", - "yourBookmarks": "Your bookmarks" + "yourBookmarks": "Your bookmarks", + "goToChat": "Go to chat", + "save": "Save", + "selectActivity": "Select activity" } \ No newline at end of file diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 514384024..388217fc9 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -574,6 +574,19 @@ abstract class AppRoutes { ), ), redirect: loggedOutRedirect, + routes: [ + GoRoute( + path: '/generator', + redirect: loggedOutRedirect, + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + ActivityGenerator( + roomID: state.pathParameters['roomid']!, + ), + ), + ), + ], ), // GoRoute( // path: 'encryption', diff --git a/lib/pages/invitation_selection/invitation_selection_view.dart b/lib/pages/invitation_selection/invitation_selection_view.dart index ca0d14237..89466b179 100644 --- a/lib/pages/invitation_selection/invitation_selection_view.dart +++ b/lib/pages/invitation_selection/invitation_selection_view.dart @@ -281,7 +281,7 @@ class InvitationSelectionView extends StatelessWidget { color: DefaultTextStyle.of(context).style.color, ), Text( - L10n.of(context).startChatting, + L10n.of(context).goToChat, style: const TextStyle( fontWeight: FontWeight.bold, ), diff --git a/lib/pages/new_group/new_group.dart b/lib/pages/new_group/new_group.dart index 288611718..24607d28c 100644 --- a/lib/pages/new_group/new_group.dart +++ b/lib/pages/new_group/new_group.dart @@ -37,7 +37,10 @@ class NewGroupController extends State { TextEditingController nameController = TextEditingController(); // #Pangea - ActivityPlanModel? _selectedActivity; + ActivityPlanModel? selectedActivity; + Uint8List? selectedActivityImage; + String? selectedActivityImageFilename; + bool requiredCodeToJoin = false; // bool publicGroup = false; // Pangea# @@ -64,8 +67,23 @@ class NewGroupController extends State { // setState(() => publicGroup = groupCanBeFound = b); void setRequireCode(bool b) => setState(() => requiredCodeToJoin = b); - void setSelectedActivity(ActivityPlanModel? activity) => - setState(() => _selectedActivity = activity); + void setSelectedActivity( + ActivityPlanModel? activity, + Uint8List? image, + String? imageFilename, + ) { + setState(() { + selectedActivity = activity; + selectedActivityImage = image; + selectedActivityImageFilename = imageFilename; + }); + } + + @override + void initState() { + super.initState(); + nameController.addListener(() => setState(() {})); + } // Pangea# void setGroupCanBeFound(bool b) => setState(() => groupCanBeFound = b); @@ -118,14 +136,18 @@ class NewGroupController extends State { ); if (!mounted) return; // #Pangea - if (_selectedActivity != null) { + if (selectedActivity != null) { Room? room = Matrix.of(context).client.getRoomById(roomId); if (room == null) { await Matrix.of(context).client.waitForRoomInSync(roomId); room = Matrix.of(context).client.getRoomById(roomId); } if (room == null) return; - await room.sendActivityPlan(_selectedActivity!); + await room.sendActivityPlan( + selectedActivity!, + avatar: selectedActivityImage, + filename: selectedActivityImageFilename, + ); } // if a timeout happened, don't redirect to the chat if (error != null) return; @@ -230,8 +252,11 @@ class NewGroupController extends State { final client = Matrix.of(context).client; try { - if (nameController.text.trim().isEmpty && - createGroupType == CreateGroupType.space) { + // #Pangea + // if (nameController.text.trim().isEmpty && + // createGroupType == CreateGroupType.space) { + if (nameController.text.trim().isEmpty) { + // Pangea# setState(() => error = L10n.of(context).pleaseFillOut); return; } diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index e2faec7ed..9c10e12f9 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -39,6 +39,11 @@ class NewGroupView extends StatelessWidget { body: MaxWidthBody( // #Pangea showBorder: false, + padding: const EdgeInsets.only( + left: 32.0, + right: 32.0, + bottom: 32.0, + ), // Pangea# child: Column( mainAxisSize: MainAxisSize.min, @@ -180,6 +185,9 @@ class NewGroupView extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 24.0), child: ActivitySuggestionCarousel( onActivitySelected: controller.setSelectedActivity, + enabled: controller.nameController.text.isNotEmpty, + selectedActivity: controller.selectedActivity, + selectedActivityImage: controller.selectedActivityImage, ), ), // Pangea# diff --git a/lib/pangea/activity_generator/activity_generator.dart b/lib/pangea/activity_generator/activity_generator.dart index 9366ee01c..40e4fdd82 100644 --- a/lib/pangea/activity_generator/activity_generator.dart +++ b/lib/pangea/activity_generator/activity_generator.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/activity_generator/activity_generator_view.dart'; @@ -21,7 +22,11 @@ import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_en import 'package:fluffychat/widgets/matrix.dart'; class ActivityGenerator extends StatefulWidget { - const ActivityGenerator({super.key}); + final String? roomID; + const ActivityGenerator({ + this.roomID, + super.key, + }); @override ActivityGeneratorState createState() => ActivityGeneratorState(); @@ -93,6 +98,10 @@ class ActivityGeneratorState extends State { Future> get objectiveItems => LearningObjectiveListRepo.get(req); + Room? get room => widget.roomID != null + ? Matrix.of(context).client.getRoomById(widget.roomID!) + : null; + String? validateNotNull(String? value) { if (value == null || value.isEmpty) { return L10n.of(context).interactiveTranslatorRequired; diff --git a/lib/pangea/activity_generator/activity_generator_view.dart b/lib/pangea/activity_generator/activity_generator_view.dart index b10ad743a..3464f1e10 100644 --- a/lib/pangea/activity_generator/activity_generator_view.dart +++ b/lib/pangea/activity_generator/activity_generator_view.dart @@ -55,7 +55,7 @@ class ActivityGeneratorView extends StatelessWidget { itemBuilder: (context, index) { return ActivityPlanCard( activity: controller.activities![index], - room: null, + room: controller.room, onEdit: (updatedActivity) => controller.onEdit(index, updatedActivity), onChange: controller.update, diff --git a/lib/pangea/activity_planner/activity_plan_card.dart b/lib/pangea/activity_planner/activity_plan_card.dart index a96bba4e8..95af173e9 100644 --- a/lib/pangea/activity_planner/activity_plan_card.dart +++ b/lib/pangea/activity_planner/activity_plan_card.dart @@ -165,7 +165,7 @@ class ActivityPlanCardState extends State { filename: _filename, ); - Navigator.of(context).pop(); + context.go("/rooms/${widget.room?.id}"); return; } @@ -199,7 +199,7 @@ class ActivityPlanCardState extends State { filename: _filename, ); - context.go("/rooms/$roomId"); + context.go("/rooms/$roomId/invite"); }, ); } diff --git a/lib/pangea/activity_planner/activity_planner_page.dart b/lib/pangea/activity_planner/activity_planner_page.dart index 946c58551..a7a4cbdbe 100644 --- a/lib/pangea/activity_planner/activity_planner_page.dart +++ b/lib/pangea/activity_planner/activity_planner_page.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/activity_planner/activity_planner_page_appbar.dart'; import 'package:fluffychat/pangea/activity_planner/bookmarked_activity_list.dart'; import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_area.dart'; @@ -43,11 +41,12 @@ class ActivityPlannerPageState extends State { ); break; case PageMode.featuredActivities: - body = const Expanded( + body = Expanded( child: SingleChildScrollView( child: ActivitySuggestionsArea( scrollDirection: Axis.vertical, - includeCustomCards: false, + showCreateChatCard: false, + room: room, ), ), ); @@ -90,28 +89,6 @@ class ActivityPlannerPageState extends State { ], ), body, - if (!FluffyThemes.isColumnMode(context)) - Padding( - padding: const EdgeInsets.all(16.0), - child: ElevatedButton( - onPressed: () => context.go("/rooms/planner"), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 0.0, - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - L10n.of(context).makeYourOwn, - style: Theme.of(context).textTheme.bodyMedium, - ), - ], - ), - ), - ), ], ), ), diff --git a/lib/pangea/activity_planner/activity_planner_page_appbar.dart b/lib/pangea/activity_planner/activity_planner_page_appbar.dart index 7543fe9b7..c91003415 100644 --- a/lib/pangea/activity_planner/activity_planner_page_appbar.dart +++ b/lib/pangea/activity_planner/activity_planner_page_appbar.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart'; class ActivityPlannerPageAppBar extends StatelessWidget @@ -23,14 +21,9 @@ class ActivityPlannerPageAppBar extends StatelessWidget Widget build(BuildContext context) { final l10n = L10n.of(context); return AppBar( - leadingWidth: FluffyThemes.isColumnMode(context) ? 150.0 : 50.0, - leading: Container( - padding: const EdgeInsets.only(left: 16.0), - alignment: Alignment.centerLeft, - child: IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.of(context).pop(), - ), + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), ), title: pageMode == PageMode.savedActivities ? Center( @@ -60,27 +53,6 @@ class ActivityPlannerPageAppBar extends StatelessWidget ], ), ), - actions: [ - FluffyThemes.isColumnMode(context) - ? Container( - width: 150.0, - alignment: Alignment.centerRight, - child: ElevatedButton( - onPressed: () => context.go("/rooms/planner"), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 0.0, - ), - ), - child: Text( - L10n.of(context).makeYourOwn, - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - ) - : const SizedBox(width: 50.0), - ], ); } } diff --git a/lib/pangea/activity_planner/bookmarked_activity_list.dart b/lib/pangea/activity_planner/bookmarked_activity_list.dart index 6acb26c39..fee714005 100644 --- a/lib/pangea/activity_planner/bookmarked_activity_list.dart +++ b/lib/pangea/activity_planner/bookmarked_activity_list.dart @@ -3,10 +3,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart'; import 'package:fluffychat/pangea/activity_planner/bookmarked_activities_repo.dart'; -import 'activity_plan_card.dart'; +import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card.dart'; +import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog.dart'; class BookmarkedActivitiesList extends StatefulWidget { final Room? room; @@ -28,6 +30,12 @@ class BookmarkedActivitiesListState extends State { List get _bookmarkedActivities => BookmarkedActivitiesRepo.get(); + bool get _isColumnMode => FluffyThemes.isColumnMode(context); + + final double cardHeight = 250.0; + double get cardPadding => _isColumnMode ? 8.0 : 0.0; + double get cardWidth => _isColumnMode ? 225.0 : 150.0; + @override Widget build(BuildContext context) { final l10n = L10n.of(context); @@ -44,22 +52,36 @@ class BookmarkedActivitiesListState extends State { } return Expanded( - child: ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: _bookmarkedActivities.length, - itemBuilder: (context, index) { - final activity = _bookmarkedActivities[index]; - return ActivityPlanCard( - activity: activity, - room: widget.room, - onEdit: (updatedActivity) async { - await BookmarkedActivitiesRepo.remove(activity.bookmarkId); - await BookmarkedActivitiesRepo.save(updatedActivity); - setState(() {}); - }, - onChange: () => setState(() {}), - ); - }, + child: SingleChildScrollView( + child: SizedBox( + width: 800.0, + child: Wrap( + alignment: WrapAlignment.spaceEvenly, + runSpacing: 16.0, + spacing: 4.0, + children: _bookmarkedActivities.map((activity) { + return ActivitySuggestionCard( + activity: activity, + onPressed: () { + showDialog( + context: context, + builder: (context) { + return ActivitySuggestionDialog( + activity: activity, + buttonText: L10n.of(context).inviteAndLaunch, + room: widget.room, + ); + }, + ); + }, + width: cardWidth, + height: cardHeight, + padding: cardPadding, + onChange: () => setState(() {}), + ); + }).toList(), + ), + ), ), ); } diff --git a/lib/pangea/activity_suggestions/activity_suggestion_card.dart b/lib/pangea/activity_suggestions/activity_suggestion_card.dart index 4b5f5c2ca..8136997e7 100644 --- a/lib/pangea/activity_suggestions/activity_suggestion_card.dart +++ b/lib/pangea/activity_suggestions/activity_suggestion_card.dart @@ -1,5 +1,8 @@ +import 'dart:typed_data'; + import 'package:flutter/material.dart'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; @@ -9,11 +12,13 @@ import 'package:fluffychat/pangea/common/widgets/pressable_button.dart'; class ActivitySuggestionCard extends StatelessWidget { final ActivityPlanModel activity; - final VoidCallback onPressed; + final Uint8List? image; + final VoidCallback? onPressed; final double width; final double height; final double padding; + final bool selected; final VoidCallback onChange; @@ -25,6 +30,8 @@ class ActivitySuggestionCard extends StatelessWidget { required this.height, required this.padding, required this.onChange, + this.selected = false, + this.image, }); @override @@ -35,10 +42,19 @@ class ActivitySuggestionCard extends StatelessWidget { return Padding( padding: EdgeInsets.all(padding), child: PressableButton( + depressed: selected || onPressed == null, onPressed: onPressed, borderRadius: BorderRadius.circular(24.0), color: theme.colorScheme.primary, - child: SizedBox( + child: Container( + decoration: BoxDecoration( + border: selected + ? Border.all( + color: theme.colorScheme.primary, + ) + : null, + borderRadius: BorderRadius.circular(24.0), + ), height: height, width: width, child: Stack( @@ -58,13 +74,25 @@ class ActivitySuggestionCard extends StatelessWidget { height: 100, width: width, decoration: BoxDecoration( - image: activity.imageURL != null - ? DecorationImage( - image: NetworkImage(activity.imageURL!), - ) - : null, borderRadius: BorderRadius.circular(24.0), ), + child: ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: image != null + ? Image.memory(image!) + : activity.imageURL != null + ? CachedNetworkImage( + imageUrl: activity.imageURL!, + placeholder: (context, url) => const Center( + child: CircularProgressIndicator(), + ), + errorWidget: (context, url, error) => Icon( + Icons.error, + color: theme.colorScheme.error, + ), + ) + : null, + ), ), Expanded( child: Padding( @@ -144,11 +172,13 @@ class ActivitySuggestionCard extends StatelessWidget { icon: Icon( isBookmarked ? Icons.bookmark : Icons.bookmark_border, ), - onPressed: () => isBookmarked - ? BookmarkedActivitiesRepo.remove(activity.bookmarkId) - .then((_) => onChange()) - : BookmarkedActivitiesRepo.save(activity) - .then((_) => onChange()), + onPressed: onPressed != null + ? () => isBookmarked + ? BookmarkedActivitiesRepo.remove(activity.bookmarkId) + .then((_) => onChange()) + : BookmarkedActivitiesRepo.save(activity) + .then((_) => onChange()) + : null, iconSize: 24.0, ), ), diff --git a/lib/pangea/activity_suggestions/activity_suggestion_carousel.dart b/lib/pangea/activity_suggestions/activity_suggestion_carousel.dart index 35f37d22a..7568d8f37 100644 --- a/lib/pangea/activity_suggestions/activity_suggestion_carousel.dart +++ b/lib/pangea/activity_suggestions/activity_suggestion_carousel.dart @@ -1,3 +1,4 @@ +import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/material.dart'; @@ -5,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:collection/collection.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; import 'package:shimmer/shimmer.dart'; import 'package:fluffychat/config/themes.dart'; @@ -20,9 +20,20 @@ import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_en import 'package:fluffychat/widgets/matrix.dart'; class ActivitySuggestionCarousel extends StatefulWidget { - final Function(ActivityPlanModel?) onActivitySelected; + final Function( + ActivityPlanModel?, + Uint8List? avatar, + String? filename, + ) onActivitySelected; + final ActivityPlanModel? selectedActivity; + final Uint8List? selectedActivityImage; + final bool enabled; + const ActivitySuggestionCarousel({ required this.onActivitySelected, + required this.selectedActivity, + required this.selectedActivityImage, + this.enabled = true, super.key, }); @@ -84,10 +95,13 @@ class ActivitySuggestionCarouselState return index == -1 ? null : index; } - bool get _canMoveLeft => _currentIndex != null && _currentIndex! > 0; + bool get _canMoveLeft => + widget.enabled && _currentIndex != null && _currentIndex! > 0; bool get _canMoveRight => - _currentIndex != null && _currentIndex! < _activityItems.length - 1; + widget.enabled && + _currentIndex != null && + _currentIndex! < _activityItems.length - 1; void _moveLeft() { if (!_canMoveLeft) return; @@ -101,17 +115,37 @@ class ActivitySuggestionCarouselState void _setActivityByIndex(int index) { if (index < 0 || index >= _activityItems.length) return; - widget.onActivitySelected(_activityItems[index]); setState(() { _currentActivity = _activityItems[index]; }); } void _close() { - widget.onActivitySelected(null); + widget.onActivitySelected(null, null, null); setState(() => _isOpen = false); } + void _onClickCard() { + if (widget.selectedActivity == _currentActivity) { + widget.onActivitySelected( + null, + null, + null, + ); + return; + } + showDialog( + context: context, + builder: (context) { + return ActivitySuggestionDialog( + activity: _currentActivity!, + buttonText: L10n.of(context).selectActivity, + launch: widget.onActivitySelected, + ); + }, + ); + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -119,168 +153,169 @@ class ActivitySuggestionCarouselState duration: FluffyThemes.animationDuration, child: !_isOpen ? const SizedBox.shrink() - : Container( - decoration: BoxDecoration( - border: Border.all(color: theme.dividerColor), - borderRadius: BorderRadius.circular(24.0), - ), - padding: const EdgeInsets.all(16.0), - child: Column( - spacing: 16.0, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - L10n.of(context).newChatActivityTitle, - style: theme.textTheme.titleLarge, - ), - IconButton( - icon: const Icon(Icons.close), - onPressed: _close, - ), - ], - ), - Text(L10n.of(context).newChatActivityDesc), - Row( - spacing: _isColumnMode ? 16.0 : 4.0, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MouseRegion( - cursor: _canMoveLeft - ? SystemMouseCursors.click - : SystemMouseCursors.basic, - child: GestureDetector( - onTap: _canMoveLeft ? _moveLeft : null, - child: Icon( - Icons.chevron_left_outlined, - size: 32.0, - color: _canMoveLeft ? null : theme.disabledColor, - ), + : AnimatedOpacity( + duration: FluffyThemes.animationDuration, + opacity: widget.enabled ? 1.0 : 0.5, + child: Container( + decoration: BoxDecoration( + border: Border.all(color: theme.dividerColor), + borderRadius: BorderRadius.circular(24.0), + ), + padding: const EdgeInsets.all(16.0), + child: Column( + spacing: 16.0, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + L10n.of(context).newChatActivityTitle, + style: theme.textTheme.titleLarge, ), - ), - Container( - constraints: - BoxConstraints(maxHeight: _cardHeight + 12.0), - child: _error != null || - (_currentActivity == null && !_loading) - ? const SizedBox.shrink() - : _loading - ? Shimmer.fromColors( - baseColor: - theme.colorScheme.primary.withAlpha(50), - highlightColor: theme.colorScheme.primary - .withAlpha(150), - child: Container( - height: _cardHeight, - width: _cardWidth, - decoration: BoxDecoration( - color: - theme.colorScheme.surfaceContainer, - borderRadius: - BorderRadius.circular(24.0), - ), - ), - ) - : ActivitySuggestionCard( - activity: _currentActivity!, - onPressed: () { - showDialog( - context: context, - builder: (context) { - return ActivitySuggestionDialog( - activity: _currentActivity!, - ); - }, - ); - }, - width: _cardWidth, - height: _cardHeight, - padding: 0.0, - onChange: () { - if (mounted) setState(() {}); - }, - ), - ), - MouseRegion( - cursor: _canMoveRight - ? SystemMouseCursors.click - : SystemMouseCursors.basic, - child: GestureDetector( - onTap: _canMoveRight ? _moveRight : null, - child: Icon( - Icons.chevron_right_outlined, - size: 32.0, - color: _canMoveRight ? null : theme.disabledColor, - ), + IconButton( + icon: const Icon(Icons.close), + onPressed: widget.enabled ? _close : null, ), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 16.0, - children: _activityItems.mapIndexed((i, activity) { - final selected = activity == _currentActivity; - return InkWell( - borderRadius: BorderRadius.circular(12.0), - onTap: () => _setActivityByIndex(i), - child: ImageFiltered( - imageFilter: ImageFilter.blur( - sigmaX: selected ? 0.0 : 0.5, - sigmaY: selected ? 0.0 : 0.5, - ), - child: Opacity( - opacity: selected ? 1.0 : 0.5, - child: ClipOval( - child: SizedBox.fromSize( - size: const Size.fromRadius(12.0), - child: activity.imageURL != null - ? CachedNetworkImage( - imageUrl: activity.imageURL!, - errorWidget: (context, url, error) { - return CircleAvatar( - backgroundColor: - theme.colorScheme.secondary, - radius: 12.0, - ); - }, - progressIndicatorBuilder: - (context, url, progress) { - return CircularProgressIndicator( - value: progress.progress, - ); - }, - ) - : CircleAvatar( - backgroundColor: - theme.colorScheme.secondary, - radius: 12.0, - ), - ), + ], + ), + Text(L10n.of(context).newChatActivityDesc), + Row( + spacing: _isColumnMode ? 16.0 : 4.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MouseRegion( + cursor: _canMoveLeft + ? SystemMouseCursors.click + : SystemMouseCursors.basic, + child: GestureDetector( + onTap: _canMoveLeft ? _moveLeft : null, + child: Icon( + Icons.chevron_left_outlined, + size: 32.0, + color: _canMoveLeft ? null : theme.disabledColor, ), ), ), - ); - }).toList(), - ), - ElevatedButton( - onPressed: () => _isColumnMode - ? context.go("/rooms") - : context.go("/rooms/homepage"), - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.primaryContainer, - ), - child: _loading - ? const LinearProgressIndicator() - : Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(L10n.of(context).exploreMore), - ], + Container( + constraints: + BoxConstraints(maxHeight: _cardHeight + 12.0), + child: _error != null || + (_currentActivity == null && !_loading) + ? const SizedBox.shrink() + : _loading + ? Shimmer.fromColors( + baseColor: theme.colorScheme.primary + .withAlpha(50), + highlightColor: theme.colorScheme.primary + .withAlpha(150), + child: Container( + height: _cardHeight, + width: _cardWidth, + decoration: BoxDecoration( + color: theme + .colorScheme.surfaceContainer, + borderRadius: + BorderRadius.circular(24.0), + ), + ), + ) + : ActivitySuggestionCard( + selected: widget.selectedActivity == + _currentActivity, + activity: _currentActivity!, + onPressed: + widget.enabled ? _onClickCard : null, + width: _cardWidth, + height: _cardHeight, + padding: 0.0, + image: _currentActivity == + widget.selectedActivity + ? widget.selectedActivityImage + : null, + onChange: () { + if (mounted) setState(() {}); + }, + ), + ), + MouseRegion( + cursor: _canMoveRight + ? SystemMouseCursors.click + : SystemMouseCursors.basic, + child: GestureDetector( + onTap: _canMoveRight ? _moveRight : null, + child: Icon( + Icons.chevron_right_outlined, + size: 32.0, + color: _canMoveRight ? null : theme.disabledColor, + ), ), - ), - ], + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 16.0, + children: _activityItems.mapIndexed((i, activity) { + final selected = activity == _currentActivity; + return InkWell( + enableFeedback: widget.enabled, + borderRadius: BorderRadius.circular(12.0), + onTap: widget.enabled + ? () => _setActivityByIndex(i) + : null, + child: ImageFiltered( + imageFilter: ImageFilter.blur( + sigmaX: selected ? 0.0 : 0.5, + sigmaY: selected ? 0.0 : 0.5, + ), + child: Opacity( + opacity: selected ? 1.0 : 0.5, + child: ClipOval( + child: SizedBox.fromSize( + size: const Size.fromRadius(12.0), + child: activity.imageURL != null + ? CachedNetworkImage( + imageUrl: activity.imageURL!, + errorWidget: (context, url, error) { + return CircleAvatar( + backgroundColor: + theme.colorScheme.secondary, + radius: 12.0, + ); + }, + progressIndicatorBuilder: + (context, url, progress) { + return CircularProgressIndicator( + value: progress.progress, + ); + }, + ) + : CircleAvatar( + backgroundColor: + theme.colorScheme.secondary, + radius: 12.0, + ), + ), + ), + ), + ), + ); + }).toList(), + ), + ElevatedButton( + onPressed: widget.enabled ? _close : null, + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.primaryContainer, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(L10n.of(context).skip), + ], + ), + ), + ], + ), ), ), ); diff --git a/lib/pangea/activity_suggestions/activity_suggestion_dialog.dart b/lib/pangea/activity_suggestions/activity_suggestion_dialog.dart index b727aecaa..eea250fa1 100644 --- a/lib/pangea/activity_suggestions/activity_suggestion_dialog.dart +++ b/lib/pangea/activity_suggestions/activity_suggestion_dialog.dart @@ -9,8 +9,8 @@ import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import 'package:http/http.dart'; import 'package:material_symbols_icons/symbols.dart'; +import 'package:matrix/matrix.dart' as sdk; import 'package:matrix/matrix.dart'; -import 'package:matrix/matrix_api_lite/generated/model.dart' as sdk; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; @@ -24,8 +24,20 @@ import 'package:fluffychat/widgets/matrix.dart'; class ActivitySuggestionDialog extends StatefulWidget { final ActivityPlanModel activity; + final String buttonText; + final Room? room; + + final Function( + ActivityPlanModel, + Uint8List? avatar, + String? filename, + )? launch; + const ActivitySuggestionDialog({ required this.activity, + required this.buttonText, + this.launch, + this.room, super.key, }); @@ -36,9 +48,8 @@ class ActivitySuggestionDialog extends StatefulWidget { class ActivitySuggestionDialogState extends State { bool _isEditing = false; - Uint8List? _avatar; - String? _avatarURL; + String? _filename; final TextEditingController _titleController = TextEditingController(); final TextEditingController _instructionsController = TextEditingController(); @@ -62,6 +73,7 @@ class ActivitySuggestionDialogState extends State { _participantsController.text = widget.activity.req.numberOfParticipants.toString(); _vocab.addAll(widget.activity.vocab); + _setAvatarByURL(); } @override @@ -86,20 +98,25 @@ class ActivitySuggestionDialogState extends State { allowMultiple: false, ); final bytes = await photo.singleOrNull?.readAsBytes(); - if (mounted) setState(() => _avatar = bytes); + if (mounted) { + setState(() { + _avatar = bytes; + _filename = photo.singleOrNull?.name; + }); + } } - Future _setAvatarURL() async { - if (widget.activity.imageURL == null && _avatar == null) return; + Future _setAvatarByURL() async { + if (widget.activity.imageURL == null) return; try { if (_avatar == null) { final Response response = await http.get(Uri.parse(widget.activity.imageURL!)); _avatar = response.bodyBytes; + _filename = Uri.encodeComponent( + Uri.parse(widget.activity.imageURL!).pathSegments.last, + ); } - final resp = await Matrix.of(context).client.uploadContent(_avatar!); - if (mounted) setState(() => _avatarURL = resp.toString()); - widget.activity.imageURL = _avatarURL; } catch (err, s) { ErrorHandler.logError( e: err, @@ -113,7 +130,8 @@ class ActivitySuggestionDialogState extends State { void _clearEdits() { _avatar = null; - _avatarURL = null; + _filename = null; + _setAvatarByURL(); _vocab.clear(); _vocab.addAll(widget.activity.vocab); if (mounted) setState(() {}); @@ -145,47 +163,45 @@ class ActivitySuggestionDialogState extends State { if (mounted) setState(() {}); } - Future _launch() async { + Future _launchActivity() async { + if (widget.room != null) { + await widget.room!.sendActivityPlan( + widget.activity, + avatar: _avatar, + filename: _filename, + ); + context.go("/rooms/${widget.room!.id}/invite"); + return; + } + final client = Matrix.of(context).client; - - final resp = await showFutureLoadingDialog( - context: context, - future: () async { - await _setAvatarURL(); - final roomId = await client.createGroupChat( - preset: CreateRoomPreset.publicChat, - visibility: sdk.Visibility.private, - groupName: widget.activity.title, - initialState: [ - if (_avatarURL != null) - StateEvent( - type: EventTypes.RoomAvatar, - content: {'url': _avatarURL.toString()}, - ), - StateEvent( - type: EventTypes.RoomPowerLevels, - stateKey: '', - content: defaultPowerLevels(client.userID!), - ), - ], - enableEncryption: false, - ); - - Room? room = Matrix.of(context).client.getRoomById(roomId); - if (room == null) { - await client.waitForRoomInSync(roomId); - room = Matrix.of(context).client.getRoomById(roomId); - if (room == null) return; - } - - await room.sendActivityPlan(widget.activity); - context.go("/rooms/$roomId/invite"); - }, + final roomId = await client.createGroupChat( + preset: CreateRoomPreset.publicChat, + visibility: sdk.Visibility.private, + groupName: widget.activity.title, + initialState: [ + StateEvent( + type: EventTypes.RoomPowerLevels, + stateKey: '', + content: defaultPowerLevels(client.userID!), + ), + ], + enableEncryption: false, ); - if (!resp.isError) { - Navigator.of(context).pop(); + Room? room = Matrix.of(context).client.getRoomById(roomId); + if (room == null) { + await client.waitForRoomInSync(roomId); + room = Matrix.of(context).client.getRoomById(roomId); + if (room == null) return; } + + await room.sendActivityPlan( + widget.activity, + avatar: _avatar, + filename: _filename, + ); + context.go("/rooms/$roomId/invite"); } @override @@ -445,17 +461,6 @@ class ActivitySuggestionDialogState extends State { child: Row( spacing: 6.0, children: [ - if (_isEditing) - GestureDetector( - child: const Icon(Icons.save_outlined, size: 16.0), - onTap: () { - if (!_formKey.currentState!.validate()) { - return; - } - _updateTextFields(); - _setEditing(false); - }, - ), if (_isEditing) GestureDetector( child: const Icon(Icons.close_outlined, size: 16.0), @@ -464,31 +469,72 @@ class ActivitySuggestionDialogState extends State { _setEditing(false); }, ), - Expanded( - child: ElevatedButton( - onPressed: () { - if (!_formKey.currentState!.validate()) { - return; - } - _updateTextFields(); - _launch(); - }, - style: ElevatedButton.styleFrom( - minimumSize: Size.zero, - padding: const EdgeInsets.all(6.0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), + if (_isEditing) + Expanded( + child: ElevatedButton( + onPressed: () async { + if (!_formKey.currentState!.validate()) { + return; + } + await _updateTextFields(); + _setEditing(false); + }, + 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), + ), + ), + ), + if (!_isEditing) + Expanded( + child: ElevatedButton( + onPressed: () async { + if (!_formKey.currentState!.validate()) { + return; + } + final resp = await showFutureLoadingDialog( + context: context, + future: () async { + if (widget.launch != null) { + return widget.launch?.call( + widget.activity, + _avatar, + _filename, + ); + } + return _launchActivity(); + }, + ); + + if (resp.isError) return; + Navigator.of(context).pop(); + }, + 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( + widget.buttonText, + style: theme.textTheme.bodyLarge + ?.copyWith(color: theme.colorScheme.onPrimary), ), - backgroundColor: theme.colorScheme.primary, - foregroundColor: theme.colorScheme.onPrimary, - ), - child: Text( - L10n.of(context).inviteAndLaunch, - style: theme.textTheme.bodyLarge - ?.copyWith(color: theme.colorScheme.onPrimary), ), ), - ), if (!_isEditing) IconButton.filled( style: IconButton.styleFrom( diff --git a/lib/pangea/activity_suggestions/activity_suggestions_area.dart b/lib/pangea/activity_suggestions/activity_suggestions_area.dart index 759bbea93..a6543bacf 100644 --- a/lib/pangea/activity_suggestions/activity_suggestions_area.dart +++ b/lib/pangea/activity_suggestions/activity_suggestions_area.dart @@ -3,6 +3,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart'; @@ -18,11 +21,17 @@ import 'package:fluffychat/widgets/matrix.dart'; class ActivitySuggestionsArea extends StatefulWidget { final Axis? scrollDirection; - final bool includeCustomCards; + final bool showCreateChatCard; + final bool showMakeActivityCard; + + final Room? room; + const ActivitySuggestionsArea({ super.key, this.scrollDirection, - this.includeCustomCards = true, + this.showCreateChatCard = true, + this.showMakeActivityCard = true, + this.room, }); @override ActivitySuggestionsAreaState createState() => ActivitySuggestionsAreaState(); @@ -80,6 +89,8 @@ class ActivitySuggestionsAreaState extends State { builder: (context) { return ActivitySuggestionDialog( activity: activity, + buttonText: L10n.of(context).inviteAndLaunch, + room: widget.room, ); }, ); @@ -95,19 +106,22 @@ class ActivitySuggestionsAreaState extends State { .cast() .toList(); - if (widget.includeCustomCards) { + if (widget.showMakeActivityCard) { cards.insert( 0, - CreateChatCard( + MakeActivityCard( width: cardWidth, height: cardHeight, padding: cardPadding, + roomID: widget.room?.id, ), ); + } + if (widget.showCreateChatCard) { cards.insert( - 1, - MakeActivityCard( + 0, + CreateChatCard( width: cardWidth, height: cardHeight, padding: cardPadding, diff --git a/lib/pangea/activity_suggestions/make_activity_card.dart b/lib/pangea/activity_suggestions/make_activity_card.dart index 17eea5ed3..5c1235327 100644 --- a/lib/pangea/activity_suggestions/make_activity_card.dart +++ b/lib/pangea/activity_suggestions/make_activity_card.dart @@ -12,11 +12,13 @@ class MakeActivityCard extends StatelessWidget { final double width; final double height; final double padding; + final String? roomID; const MakeActivityCard({ required this.width, required this.height, required this.padding, + this.roomID, super.key, }); @@ -26,7 +28,11 @@ class MakeActivityCard extends StatelessWidget { return Padding( padding: EdgeInsets.all(padding), child: PressableButton( - onPressed: () => context.go('/rooms/planner'), + onPressed: () { + roomID == null + ? context.go('/rooms/planner') + : context.go('/rooms/${roomID!}/planner/generator'); + }, borderRadius: BorderRadius.circular(24.0), color: theme.colorScheme.primary, child: Container( @@ -55,7 +61,7 @@ class MakeActivityCard extends StatelessWidget { Padding( padding: const EdgeInsets.all(16.0), child: Text( - L10n.of(context).makeYourOwn, + L10n.of(context).makeYourOwnActivity, style: theme.textTheme.bodyLarge ?.copyWith(color: theme.colorScheme.secondary), textAlign: TextAlign.center, diff --git a/lib/widgets/layouts/max_width_body.dart b/lib/widgets/layouts/max_width_body.dart index 8d9d2b84c..534dfc46d 100644 --- a/lib/widgets/layouts/max_width_body.dart +++ b/lib/widgets/layouts/max_width_body.dart @@ -10,6 +10,7 @@ class MaxWidthBody extends StatelessWidget { final EdgeInsets? innerPadding; // #Pangea final bool showBorder; + final EdgeInsets? padding; // Pangea# const MaxWidthBody({ @@ -19,6 +20,7 @@ class MaxWidthBody extends StatelessWidget { this.innerPadding, // #Pangea this.showBorder = true, + this.padding, // Pangea# super.key, }); @@ -34,7 +36,10 @@ class MaxWidthBody extends StatelessWidget { ? child : Container( alignment: Alignment.topCenter, - padding: const EdgeInsets.all(32), + // #Pangea + // padding: const EdgeInsets.all(32), + padding: padding ?? const EdgeInsets.all(32), + // Pangea# child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: FluffyThemes.columnWidth * 1.5,