diff --git a/.changes/2390-space-object-share.md b/.changes/2390-space-object-share.md new file mode 100644 index 000000000000..93723eb1ea2b --- /dev/null +++ b/.changes/2390-space-object-share.md @@ -0,0 +1,2 @@ +- [New] : Now you can directly share space objects (Pin, Event and TaskList) to new Boost from their detail page. +- [New] : You can now share any object via its own unique QR code. \ No newline at end of file diff --git a/app/assets/icon/signal_logo.png b/app/assets/icon/signal_logo.png new file mode 100644 index 000000000000..eaf7a1ad46fc Binary files /dev/null and b/app/assets/icon/signal_logo.png differ diff --git a/app/lib/common/widgets/share/action/share_space_object_action.dart b/app/lib/common/widgets/share/action/share_space_object_action.dart new file mode 100644 index 000000000000..dafd8b7da0d1 --- /dev/null +++ b/app/lib/common/widgets/share/action/share_space_object_action.dart @@ -0,0 +1,170 @@ +import 'dart:io'; + +import 'package:acter/common/utils/routes.dart'; +import 'package:acter/common/widgets/share/widgets/attach_options.dart'; +import 'package:acter/common/widgets/share/widgets/external_share_options.dart'; +import 'package:acter/common/widgets/share/widgets/file_share_options.dart'; +import 'package:acter/features/deep_linking/actions/show_qr_code.dart'; +import 'package:acter/features/deep_linking/types.dart'; +import 'package:acter/features/files/actions/download_file.dart'; +import 'package:acter/features/news/model/news_references_model.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:open_filex/open_filex.dart'; +import 'package:share_plus/share_plus.dart'; + +typedef SpaceObjectDetails = ({ + String spaceId, + ObjectType objectType, + String objectId, +}); + +typedef FileDetails = ({ + File file, + String? mimeType, +}); + +Future openShareSpaceObjectDialog({ + required BuildContext context, + SpaceObjectDetails? spaceObjectDetails, + FileDetails? fileDetails, +}) async { + await showModalBottomSheet( + showDragHandle: true, + useSafeArea: true, + context: context, + isScrollControlled: true, + isDismissible: true, + builder: (context) => ShareSpaceObjectActionUI( + spaceObjectDetails: spaceObjectDetails, + fileDetails: fileDetails, + ), + ); +} + +class ShareSpaceObjectActionUI extends StatelessWidget { + final SpaceObjectDetails? spaceObjectDetails; + final FileDetails? fileDetails; + + const ShareSpaceObjectActionUI({ + super.key, + this.spaceObjectDetails, + this.fileDetails, + }); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + if (spaceObjectDetails != null) ...[ + attachmentOptionsUI(context, spaceObjectDetails!), + SizedBox(height: 16), + externalShareOptionsUI(context, spaceObjectDetails!), + SizedBox(height: 20), + ], + if (fileDetails != null) fileShareOptionsUI(context, fileDetails!), + ], + ), + ), + ); + } + + Widget attachmentOptionsUI( + BuildContext context, + SpaceObjectDetails spaceObjectDetails, + ) { + String spaceId = spaceObjectDetails.spaceId; + ObjectType objectType = spaceObjectDetails.objectType; + String objectId = spaceObjectDetails.objectId; + + final newsRefType = getNewsRefTypeFromObjType(objectType); + return AttachOptions( + onTapBoost: () { + Navigator.pop(context); + context.pushNamed( + Routes.actionAddUpdate.name, + queryParameters: {'spaceId': spaceId}, + extra: newsRefType != null + ? NewsReferencesModel(type: newsRefType, id: objectId) + : null, + ); + }, + ); + } + + Widget externalShareOptionsUI( + BuildContext context, + SpaceObjectDetails spaceObjectDetails, + ) { + String spaceId = spaceObjectDetails.spaceId; + ObjectType objectType = spaceObjectDetails.objectType; + String objectId = spaceObjectDetails.objectId; + + final internalLink = + 'acter:o/${spaceId.substring(1)}/${objectType.name}/${objectId.substring(1)}'; + + return ExternalShareOptions( + onTapQr: () { + Navigator.pop(context); + showQrCode( + context, + internalLink, + ); + }, + ); + } + + Widget fileShareOptionsUI( + BuildContext context, + FileDetails fileDetails, + ) { + File file = fileDetails.file; + String? mimeType = fileDetails.mimeType; + return FileShareOptions( + onTapOpen: () async { + final result = await OpenFilex.open(file.absolute.path); + if (result.type == ResultType.done) { + // done, close this dialog + if (context.mounted) { + Navigator.pop(context); + } + } + }, + onTapSave: !Platform.isAndroid + ? () async { + if (await downloadFile(context, file)) { + // done, close this dialog + if (context.mounted) { + Navigator.pop(context); + } + } + } + : null, + onTapShare: () async { + final result = await Share.shareXFiles( + [XFile(file.path, mimeType: mimeType)], + ); + if (result.status == ShareResultStatus.success) { + // done, close this dialog + if (context.mounted) { + Navigator.pop(context); + } + } + }, + ); + } + + NewsReferencesType? getNewsRefTypeFromObjType(ObjectType objectType) { + return switch (objectType) { + ObjectType.pin => NewsReferencesType.pin, + ObjectType.calendarEvent => NewsReferencesType.calendarEvent, + ObjectType.taskList => NewsReferencesType.taskList, + _ => null, + }; + } +} diff --git a/app/lib/common/widgets/share/widgets/attach_options.dart b/app/lib/common/widgets/share/widgets/attach_options.dart new file mode 100644 index 000000000000..53c6b75cf335 --- /dev/null +++ b/app/lib/common/widgets/share/widgets/attach_options.dart @@ -0,0 +1,124 @@ +import 'package:acter/common/themes/colors/color_scheme.dart'; +import 'package:atlas_icons/atlas_icons.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +class AttachOptions extends StatelessWidget { + final String? sectionTitle; + final GestureTapCallback? onTapBoost; + final GestureTapCallback? onTapPin; + final GestureTapCallback? onTapEvent; + final GestureTapCallback? onTapTaskList; + final GestureTapCallback? onTapTaskItem; + + const AttachOptions({ + super.key, + this.sectionTitle, + this.onTapBoost, + this.onTapPin, + this.onTapEvent, + this.onTapTaskList, + this.onTapTaskItem, + }); + + @override + Widget build(BuildContext context) { + final lang = L10n.of(context); + + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (sectionTitle != null) ...[ + Row( + children: [ + Divider(indent: 0), + Text( + sectionTitle!, + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.labelLarge, + ), + Expanded(child: Divider(indent: 20)), + ], + ), + SizedBox(height: 12), + ], + Wrap( + children: [ + if (onTapBoost != null) + attachToItemUI( + name: lang.newBoost, + iconData: Atlas.megaphone_thin, + color: boastFeatureColor, + onTap: onTapBoost, + ), + if (onTapPin != null) + attachToItemUI( + name: lang.pin, + iconData: Atlas.pin, + color: pinFeatureColor, + onTap: onTapPin, + ), + if (onTapEvent != null) + attachToItemUI( + name: lang.event, + iconData: Atlas.calendar, + color: eventFeatureColor, + onTap: onTapEvent, + ), + if (onTapTaskList != null) + attachToItemUI( + name: lang.taskList, + iconData: Atlas.list, + color: taskFeatureColor, + onTap: onTapTaskList, + ), + if (onTapTaskItem != null) + attachToItemUI( + name: lang.task, + iconData: Atlas.list, + color: taskFeatureColor, + onTap: onTapTaskItem, + ), + ], + ), + ], + ); + } + + Widget attachToItemUI({ + required String name, + required IconData iconData, + required Color color, + GestureTapCallback? onTap, + }) { + return InkWell( + borderRadius: BorderRadius.circular(16), + onTap: onTap, + child: Container( + margin: const EdgeInsets.all(10), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: color.withOpacity(0.3), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: color, + style: BorderStyle.solid, + width: 1.0, + ), + ), + child: Icon(iconData), + ), + SizedBox(height: 6), + Text(name, textAlign: TextAlign.center), + ], + ), + ), + ); + } +} diff --git a/app/lib/common/widgets/share/widgets/external_share_options.dart b/app/lib/common/widgets/share/widgets/external_share_options.dart new file mode 100644 index 000000000000..b1090f0281d1 --- /dev/null +++ b/app/lib/common/widgets/share/widgets/external_share_options.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +class ExternalShareOptions extends StatelessWidget { + final String? sectionTitle; + final GestureTapCallback? onTapCopy; + final GestureTapCallback? onTapQr; + final GestureTapCallback? onTapSignal; + final GestureTapCallback? onTapWhatsApp; + final GestureTapCallback? onTapTelegram; + final GestureTapCallback? onTapMore; + + const ExternalShareOptions({ + super.key, + this.sectionTitle, + this.onTapCopy, + this.onTapQr, + this.onTapSignal, + this.onTapWhatsApp, + this.onTapTelegram, + this.onTapMore, + }); + + @override + Widget build(BuildContext context) { + final lang = L10n.of(context); + + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (sectionTitle != null) ...[ + Row( + children: [ + Divider(indent: 0), + Text( + sectionTitle!, + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.labelLarge, + ), + Expanded(child: Divider(indent: 20)), + ], + ), + SizedBox(height: 12), + ], + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + if (onTapQr != null) + shareToItemUI( + name: lang.qr, + iconWidget: Icon(PhosphorIcons.qrCode()), + color: Colors.grey.shade600, + onTap: onTapQr, + ), + if (onTapCopy != null) + shareToItemUI( + name: lang.copyLink, + iconWidget: Icon(PhosphorIcons.link()), + color: Colors.blueGrey, + onTap: onTapCopy, + ), + if (onTapSignal != null) + shareToItemUI( + name: lang.signal, + iconWidget: Image.asset( + 'assets/icon/signal_logo.png', + height: 25, + width: 25, + ), + color: Colors.blue, + onTap: onTapSignal, + ), + if (onTapWhatsApp != null) + shareToItemUI( + name: lang.whatsApp, + iconWidget: Icon(PhosphorIcons.whatsappLogo()), + color: Colors.green, + onTap: onTapWhatsApp, + ), + if (onTapTelegram != null) + shareToItemUI( + name: lang.telegram, + iconWidget: Icon(PhosphorIcons.telegramLogo()), + color: Colors.blue, + onTap: onTapTelegram, + ), + if (onTapMore != null) + shareToItemUI( + name: lang.more, + iconWidget: Icon(PhosphorIcons.dotsThree()), + color: Colors.grey.shade800, + onTap: onTapMore, + ), + ], + ), + ), + ], + ); + } + + Widget shareToItemUI({ + required String name, + required Widget iconWidget, + required Color color, + GestureTapCallback? onTap, + }) { + return InkWell( + borderRadius: BorderRadius.circular(100), + onTap: onTap, + child: Container( + margin: const EdgeInsets.all(10), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(100), + border: Border.all( + color: color, + style: BorderStyle.solid, + width: 1.0, + ), + ), + child: iconWidget, + ), + SizedBox(height: 6), + Text(name), + ], + ), + ), + ); + } +} diff --git a/app/lib/common/widgets/share/widgets/file_share_options.dart b/app/lib/common/widgets/share/widgets/file_share_options.dart new file mode 100644 index 000000000000..4cc84f69fcb3 --- /dev/null +++ b/app/lib/common/widgets/share/widgets/file_share_options.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +class FileShareOptions extends StatelessWidget { + final String? sectionTitle; + final GestureTapCallback? onTapOpen; + final GestureTapCallback? onTapSave; + final GestureTapCallback? onTapShare; + + const FileShareOptions({ + super.key, + this.sectionTitle, + this.onTapOpen, + this.onTapSave, + this.onTapShare, + }); + + @override + Widget build(BuildContext context) { + final lang = L10n.of(context); + + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (sectionTitle != null) ...[ + Row( + children: [ + Divider(indent: 0), + Text( + sectionTitle!, + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.labelLarge, + ), + Expanded(child: Divider(indent: 20)), + ], + ), + SizedBox(height: 12), + ], + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + if (onTapOpen != null) + shareToItemUI( + name: lang.openFile, + iconData: PhosphorIcons.fileArrowUp(), + onTap: onTapOpen, + ), + if (onTapSave != null) + shareToItemUI( + name: lang.save, + iconData: PhosphorIcons.downloadSimple(), + onTap: onTapSave, + ), + if (onTapShare != null) + shareToItemUI( + name: lang.share, + iconData: PhosphorIcons.shareNetwork(), + onTap: onTapShare, + ), + ], + ), + ), + ], + ); + } + + Widget shareToItemUI({ + required String name, + required IconData iconData, + GestureTapCallback? onTap, + }) { + return InkWell( + borderRadius: BorderRadius.circular(100), + onTap: onTap, + child: Container( + margin: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(iconData), + SizedBox(height: 6), + Text(name), + ], + ), + ), + ); + } +} diff --git a/app/lib/features/events/pages/event_details_page.dart b/app/lib/features/events/pages/event_details_page.dart index 26b65340340a..e36babcde940 100644 --- a/app/lib/features/events/pages/event_details_page.dart +++ b/app/lib/features/events/pages/event_details_page.dart @@ -9,21 +9,22 @@ import 'package:acter/common/utils/routes.dart'; import 'package:acter/common/widgets/edit_html_description_sheet.dart'; import 'package:acter/common/widgets/edit_title_sheet.dart'; import 'package:acter/common/widgets/render_html.dart'; -import 'package:acter/features/attachments/widgets/attachment_section.dart'; +import 'package:acter/common/widgets/share/action/share_space_object_action.dart'; import 'package:acter/features/attachments/types.dart'; +import 'package:acter/features/attachments/widgets/attachment_section.dart'; import 'package:acter/features/bookmarks/types.dart'; import 'package:acter/features/bookmarks/widgets/bookmark_action.dart'; import 'package:acter/features/comments/types.dart'; import 'package:acter/features/comments/widgets/comments_section_widget.dart'; -import 'package:acter/features/events/providers/event_type_provider.dart'; +import 'package:acter/features/deep_linking/types.dart'; import 'package:acter/features/events/model/keys.dart'; import 'package:acter/features/events/providers/event_providers.dart'; +import 'package:acter/features/events/providers/event_type_provider.dart'; import 'package:acter/features/events/utils/events_utils.dart'; import 'package:acter/features/events/widgets/change_date_sheet.dart'; import 'package:acter/features/events/widgets/event_date_widget.dart'; import 'package:acter/features/events/widgets/participants_list.dart'; import 'package:acter/features/events/widgets/skeletons/event_details_skeleton_widget.dart'; -import 'package:acter/features/files/actions/file_share.dart'; import 'package:acter/features/home/providers/client_providers.dart'; import 'package:acter/features/home/widgets/space_chip.dart'; import 'package:acter/features/space/widgets/member_avatar.dart'; @@ -95,7 +96,10 @@ class _EventDetailPageConsumerState extends ConsumerState { pinned: true, actions: calendarEvent != null ? [ - _buildShareAction(calendarEvent), + IconButton( + icon: PhosphorIcon(PhosphorIcons.shareFat()), + onPressed: () => onShareEvent(context, calendarEvent), + ), BookmarkAction( bookmarker: BookmarkType.forEvent(widget.calendarId), ), @@ -437,14 +441,7 @@ class _EventDetailPageConsumerState extends ConsumerState { ); } - Widget _buildShareAction(CalendarEvent calendarEvent) { - return IconButton( - icon: PhosphorIcon(PhosphorIcons.shareFat()), - onPressed: () => onShareEvent(calendarEvent), - ); - } - - Future onShareEvent(CalendarEvent event) async { + Future onShareEvent(BuildContext context, CalendarEvent event) async { final lang = L10n.of(context); try { final filename = event.title().replaceAll(RegExp(r'[^A-Za-z0-9_-]'), '_'); @@ -453,17 +450,21 @@ class _EventDetailPageConsumerState extends ConsumerState { event.icalForSharing(icalPath); if (context.mounted) { - await openFileShareDialog( - // ignore: use_build_context_synchronously + openShareSpaceObjectDialog( context: context, - // ignore: use_build_context_synchronously - header: Text(lang.shareIcal), - file: File(icalPath), - mimeType: 'text/calendar', + spaceObjectDetails: ( + spaceId: event.roomIdStr(), + objectType: ObjectType.calendarEvent, + objectId: widget.calendarId, + ), + fileDetails: ( + file: File(icalPath), + mimeType: 'text/calendar', + ), ); } } catch (e, s) { - _log.severe('Creating iCal Share Event failed', e, s); + _log.severe('Creating iCal share Event failed', e, s); if (!mounted) return; EasyLoading.showError( lang.shareFailed(e), diff --git a/app/lib/features/news/news_utils/news_utils.dart b/app/lib/features/news/news_utils/news_utils.dart index 8a0666fd69e2..d55f9952ad7a 100644 --- a/app/lib/features/news/news_utils/news_utils.dart +++ b/app/lib/features/news/news_utils/news_utils.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:acter/common/utils/utils.dart'; import 'package:acter/features/news/model/news_post_color_data.dart'; +import 'package:acter/features/news/model/news_references_model.dart'; import 'package:acter/features/news/model/news_slide_model.dart'; import 'package:acter/features/news/providers/news_post_editor_providers.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk.dart'; @@ -51,18 +52,25 @@ class NewsUtils { } //Add text slide - static void addTextSlide(WidgetRef ref) { + static void addTextSlide({ + required WidgetRef ref, + NewsReferencesModel? newsReferencesModel, + }) { final clr = getRandomElement(newsPostColors); NewsSlideItem textSlide = NewsSlideItem( type: NewsSlideType.text, text: '', backgroundColor: clr, + newsReferencesModel: newsReferencesModel, ); ref.read(newsStateProvider.notifier).addSlide(textSlide); } //Add image slide - static Future addImageSlide(WidgetRef ref) async { + static Future addImageSlide({ + required WidgetRef ref, + NewsReferencesModel? newsReferencesModel, + }) async { final clr = getRandomElement(newsPostColors); XFile? imageFile = await imagePicker.pickImage( source: ImageSource.gallery, @@ -72,13 +80,17 @@ class NewsUtils { type: NewsSlideType.image, mediaFile: imageFile, backgroundColor: clr, + newsReferencesModel: newsReferencesModel, ); ref.read(newsStateProvider.notifier).addSlide(slide); } } //Add video slide - static Future addVideoSlide(WidgetRef ref) async { + static Future addVideoSlide({ + required WidgetRef ref, + NewsReferencesModel? newsReferencesModel, + }) async { final clr = getRandomElement(newsPostColors); XFile? videoFile = await imagePicker.pickVideo( source: ImageSource.gallery, @@ -88,6 +100,7 @@ class NewsUtils { type: NewsSlideType.video, mediaFile: videoFile, backgroundColor: clr, + newsReferencesModel: newsReferencesModel, ); ref.read(newsStateProvider.notifier).addSlide(slide); } diff --git a/app/lib/features/news/pages/add_news_page.dart b/app/lib/features/news/pages/add_news_page.dart index 8cdd9c10704f..f93fdacc06b7 100644 --- a/app/lib/features/news/pages/add_news_page.dart +++ b/app/lib/features/news/pages/add_news_page.dart @@ -6,6 +6,7 @@ import 'package:acter/common/widgets/acter_video_player.dart'; import 'package:acter/common/widgets/html_editor.dart'; import 'package:acter/features/news/actions/submit_news.dart'; import 'package:acter/features/news/model/keys.dart'; +import 'package:acter/features/news/model/news_references_model.dart'; import 'package:acter/features/news/model/news_slide_model.dart'; import 'package:acter/features/news/news_utils/news_utils.dart'; import 'package:acter/features/news/providers/news_post_editor_providers.dart'; @@ -24,10 +25,12 @@ const addNewsKey = Key('add-news'); class AddNewsPage extends ConsumerStatefulWidget { final String? initialSelectedSpace; + final NewsReferencesModel? newsReferencesModel; const AddNewsPage({ super.key = addNewsKey, this.initialSelectedSpace, + this.newsReferencesModel, }); @override @@ -289,19 +292,30 @@ class AddNewsState extends ConsumerState { const SizedBox(height: 40), OutlinedButton( key: NewsUpdateKeys.addTextSlide, - onPressed: () => NewsUtils.addTextSlide(ref), + onPressed: () { + NewsUtils.addTextSlide( + ref: ref, + newsReferencesModel: widget.newsReferencesModel, + ); + }, child: Text(lang.addTextSlide), ), const SizedBox(height: 20), OutlinedButton( key: NewsUpdateKeys.addImageSlide, - onPressed: () async => await NewsUtils.addImageSlide(ref), + onPressed: () async => await NewsUtils.addImageSlide( + ref: ref, + newsReferencesModel: widget.newsReferencesModel, + ), child: Text(lang.addImageSlide), ), const SizedBox(height: 20), OutlinedButton( key: NewsUpdateKeys.addVideoSlide, - onPressed: () async => await NewsUtils.addVideoSlide(ref), + onPressed: () async => await NewsUtils.addVideoSlide( + ref: ref, + newsReferencesModel: widget.newsReferencesModel, + ), child: Text(lang.addVideoSlide), ), ], diff --git a/app/lib/features/news/widgets/news_post_editor/news_slide_options.dart b/app/lib/features/news/widgets/news_post_editor/news_slide_options.dart index ca33ea24e253..bc8186e13dde 100644 --- a/app/lib/features/news/widgets/news_post_editor/news_slide_options.dart +++ b/app/lib/features/news/widgets/news_post_editor/news_slide_options.dart @@ -186,9 +186,9 @@ class _NewsSlideOptionsState extends ConsumerState { ), ), builder: (context) => PostAttachmentOptions( - onTapAddText: () => NewsUtils.addTextSlide(ref), - onTapImage: () async => await NewsUtils.addImageSlide(ref), - onTapVideo: () async => await NewsUtils.addVideoSlide(ref), + onTapAddText: () => NewsUtils.addTextSlide(ref: ref), + onTapImage: () async => await NewsUtils.addImageSlide(ref: ref), + onTapVideo: () async => await NewsUtils.addVideoSlide(ref: ref), ), ); } diff --git a/app/lib/features/pins/pages/pin_details_page.dart b/app/lib/features/pins/pages/pin_details_page.dart index 271e0594f159..3b229e53e090 100644 --- a/app/lib/features/pins/pages/pin_details_page.dart +++ b/app/lib/features/pins/pages/pin_details_page.dart @@ -7,13 +7,15 @@ import 'package:acter/common/widgets/acter_icon_picker/model/color_data.dart'; import 'package:acter/common/widgets/edit_html_description_sheet.dart'; import 'package:acter/common/widgets/edit_title_sheet.dart'; import 'package:acter/common/widgets/render_html.dart'; -import 'package:acter/features/attachments/widgets/attachment_section.dart'; +import 'package:acter/common/widgets/share/action/share_space_object_action.dart'; import 'package:acter/features/attachments/types.dart'; -import 'package:acter/features/comments/types.dart'; -import 'package:acter/features/comments/widgets/skeletons/comment_list_skeleton_widget.dart'; -import 'package:acter/features/comments/widgets/comments_section_widget.dart'; +import 'package:acter/features/attachments/widgets/attachment_section.dart'; import 'package:acter/features/bookmarks/types.dart'; import 'package:acter/features/bookmarks/widgets/bookmark_action.dart'; +import 'package:acter/features/comments/types.dart'; +import 'package:acter/features/comments/widgets/comments_section_widget.dart'; +import 'package:acter/features/comments/widgets/skeletons/comment_list_skeleton_widget.dart'; +import 'package:acter/features/deep_linking/types.dart'; import 'package:acter/features/home/widgets/space_chip.dart'; import 'package:acter/features/pins/actions/edit_pin_actions.dart'; import 'package:acter/features/pins/actions/pin_update_actions.dart'; @@ -28,6 +30,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; import 'package:skeletonizer/skeletonizer.dart'; final _log = Logger('a3::pin::details_page'); @@ -61,8 +64,21 @@ class _PinDetailsPageState extends ConsumerState { } AppBar _buildAppBar() { + final pinData = ref.watch(pinProvider(widget.pinId)).valueOrNull; return AppBar( actions: [ + if (pinData != null) + IconButton( + icon: PhosphorIcon(PhosphorIcons.shareFat()), + onPressed: () => openShareSpaceObjectDialog( + context: context, + spaceObjectDetails: ( + spaceId: pinData.roomIdStr(), + objectType: ObjectType.pin, + objectId: widget.pinId, + ), + ), + ), BookmarkAction(bookmarker: BookmarkType.forPins(widget.pinId)), _buildActionMenu(), ], diff --git a/app/lib/features/tasks/pages/task_list_details_page.dart b/app/lib/features/tasks/pages/task_list_details_page.dart index c597874d140f..f209702a3564 100644 --- a/app/lib/features/tasks/pages/task_list_details_page.dart +++ b/app/lib/features/tasks/pages/task_list_details_page.dart @@ -8,16 +8,18 @@ import 'package:acter/common/widgets/acter_icon_picker/model/color_data.dart'; import 'package:acter/common/widgets/edit_html_description_sheet.dart'; import 'package:acter/common/widgets/edit_title_sheet.dart'; import 'package:acter/common/widgets/render_html.dart'; +import 'package:acter/common/widgets/share/action/share_space_object_action.dart'; +import 'package:acter/features/attachments/types.dart'; import 'package:acter/features/attachments/widgets/attachment_section.dart'; import 'package:acter/features/bookmarks/types.dart'; import 'package:acter/features/bookmarks/widgets/bookmark_action.dart'; +import 'package:acter/features/comments/types.dart'; import 'package:acter/features/comments/widgets/comments_section_widget.dart'; +import 'package:acter/features/deep_linking/types.dart'; import 'package:acter/features/home/widgets/space_chip.dart'; import 'package:acter/features/tasks/actions/update_tasklist.dart'; import 'package:acter/features/tasks/providers/tasklists_providers.dart'; import 'package:acter/features/tasks/widgets/task_items_list_widget.dart'; -import 'package:acter/features/attachments/types.dart'; -import 'package:acter/features/comments/types.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; @@ -25,6 +27,7 @@ import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; import 'package:skeletonizer/skeletonizer.dart'; final _log = Logger('a3::tasks::tasklist_details'); @@ -60,6 +63,18 @@ class _TaskListPageState extends ConsumerState { final textTheme = Theme.of(context).textTheme; final tasklist = ref.watch(taskListProvider(widget.taskListId)).valueOrNull; final List actions = [ + if (tasklist != null) + IconButton( + icon: PhosphorIcon(PhosphorIcons.shareFat()), + onPressed: () => openShareSpaceObjectDialog( + context: context, + spaceObjectDetails: ( + spaceId: tasklist.spaceIdStr(), + objectType: ObjectType.taskList, + objectId: widget.taskListId, + ), + ), + ), BookmarkAction(bookmarker: BookmarkType.forTaskList(widget.taskListId)), ]; if (tasklist != null) { diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 4854eb508932..5a5ffba107ac 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -2295,6 +2295,14 @@ "@addBoost": {}, "addTaskList": "Add TaskList", "@addTaskList": {}, + "task": "Task", + "signal": "Signal", + "whatsApp": "WhatsApp", + "telegram": "Telegram", + "copy": "copy", + "copyLink": "Copy Link", + "qr": "QR", + "newBoost": "New\nBoost", "addComment": "Add Comment", "@addComment": {} } diff --git a/app/lib/router/general_router.dart b/app/lib/router/general_router.dart index b0d8a11adb08..82d67bcf9d71 100644 --- a/app/lib/router/general_router.dart +++ b/app/lib/router/general_router.dart @@ -15,6 +15,7 @@ import 'package:acter/features/deep_linking/pages/scan_qr_code.dart'; import 'package:acter/features/intro/pages/intro_page.dart'; import 'package:acter/features/intro/pages/intro_profile.dart'; import 'package:acter/features/link_room/types.dart'; +import 'package:acter/features/news/model/news_references_model.dart'; import 'package:acter/features/news/pages/add_news_page.dart'; import 'package:acter/features/onboarding/pages/analytics_opt_in_page.dart'; import 'package:acter/features/onboarding/pages/link_email_page.dart'; @@ -223,10 +224,12 @@ final generalRoutes = [ redirect: authGuardRedirect, pageBuilder: (context, state) { final spaceId = state.uri.queryParameters['spaceId']; + final newsReferencesModel = state.extra as NewsReferencesModel?; return NoTransitionPage( key: state.pageKey, child: AddNewsPage( initialSelectedSpace: spaceId?.isNotEmpty == true ? spaceId : null, + newsReferencesModel: newsReferencesModel, ), ); },