diff --git a/.gitea/workflows/test-flight-cd.yaml b/.gitea/workflows/test-flight-cd.yaml index 2a4f5aec..1bb78c94 100644 --- a/.gitea/workflows/test-flight-cd.yaml +++ b/.gitea/workflows/test-flight-cd.yaml @@ -11,6 +11,9 @@ jobs: - name: Checkout uses: actions/checkout@v2 + - name: Remove cache + run: rm -rf /Users/gitea_runner_ci/Library/Developer/Xcode/DerivedData/* + - name: Download provisioning uses: mso7nueh/apple-provisioning-profiles-downloader@v1.0.8 with: diff --git a/assets/icons/edit_paint.svg b/assets/icons/edit_paint.svg new file mode 100644 index 00000000..e4a9eeef --- /dev/null +++ b/assets/icons/edit_paint.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/red_trash.svg b/assets/icons/red_trash.svg new file mode 100644 index 00000000..3864fb63 --- /dev/null +++ b/assets/icons/red_trash.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/remove_from_public.svg b/assets/icons/remove_from_public.svg new file mode 100644 index 00000000..36213c53 --- /dev/null +++ b/assets/icons/remove_from_public.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/core/widgets/custom_popup.dart b/lib/core/widgets/custom_popup.dart new file mode 100644 index 00000000..967f6fee --- /dev/null +++ b/lib/core/widgets/custom_popup.dart @@ -0,0 +1,175 @@ +import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/core/enums/complained_content.dart'; +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/utils/toasts.dart'; +import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; +import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart'; +import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart'; +import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; +import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:share_plus/share_plus.dart'; + +class CustomPopup extends StatelessWidget { + final bool isAuthor; + final ComplainedContent? complainedContentType; + final String path; + final String? slug; + final int? id; + final FeedBlogPostModel? feedBlogPostModel; + + const CustomPopup({ + super.key, + required this.isAuthor, + this.complainedContentType, + required this.path, + this.id, + this.slug, + this.feedBlogPostModel, + }); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + + return GestureDetector( + onTap: () async { + final RenderBox button = context.findRenderObject() as RenderBox; + final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; + final Offset position = button.localToGlobal(Offset.zero, ancestor: overlay); + + await showMenu( + context: context, + position: RelativeRect.fromLTRB( + position.dx, + position.dy + button.size.height, + position.dx + button.size.width, + position.dy, + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + color: Colors.white, + constraints: const BoxConstraints(minWidth: 260), + surfaceTintColor: Colors.transparent, + elevation: 2, + items: _buildMenuItems(s, context), + ); + }, + child: const Icon( + Icons.more_horiz_rounded, + ), + ); + } + + List _buildMenuItems(s, context) { + final entries = []; + + void addItem(String icon, String text, Function() onTap) { + entries.add(_buildCompactItem(icon: icon, text: text, onTap: onTap)); + entries.add(_buildDivider()); + } + + if (isAuthor) { + addItem('assets/icons/copy2.svg', 'Скопировать ссылку', () { + Clipboard.setData(ClipboardData(text: '$baseUrl/ru/$path')); + showInfoToast(s.link_was_copied); + }); + addItem('assets/icons/share2.svg', 'Поделиться', () { + final box = context.findRenderObject() as RenderBox?; + Share.shareUri( + Uri.parse( + '$baseUrl/ru/$path', + ), + sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + ); + }); + addItem('assets/icons/edit_paint.svg', 'Редактировать', () { + if (complainedContentType == ComplainedContent.posts) { + GoRouter.of(context).pushNamed('blogPostCreateScreen', extra: feedBlogPostModel); + } + }); + if (complainedContentType == ComplainedContent.plan || complainedContentType == ComplainedContent.diet) { + addItem('assets/icons/remove_from_public.svg', 'Снять с публикации', () {}); + } + addItem('assets/icons/red_trash.svg', 'Удалить', () async { + if (complainedContentType == ComplainedContent.posts) { + final result = await sl().deleteBlogPost(id!); + if (!context.mounted) return; + if (!result) { + showErrorDialog(context, title: S.current.could_not_delete_blog_post); + return; + } + context.read().loadFeedBlogPostList(); + } + }); + } else { + addItem('assets/icons/copy2.svg', 'Скопировать ссылку', () { + Clipboard.setData(ClipboardData(text: '$baseUrl/ru/$path')); + showInfoToast(s.link_was_copied); + }); + addItem('assets/icons/share2.svg', 'Поделиться', () { + final box = context.findRenderObject() as RenderBox?; + Share.shareUri( + Uri.parse( + '$baseUrl/ru/$path', + ), + sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + ); + }); + addItem('assets/icons/complain.svg', 'Пожаловаться', () { + if (complainedContentType != null) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => ComplainingBottomSheet( + complainedContentType: complainedContentType!, + id: id, + slug: slug, + ), + ); + } + }); + } + + if (entries.isNotEmpty && entries.last is PopupMenuItem) { + entries.removeLast(); + } + + return entries; + } + + PopupMenuItem _buildCompactItem({required String icon, required String text, required Function() onTap}) { + return PopupMenuItem( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + height: 36, + onTap: onTap, + child: SizedBox( + width: double.infinity, + child: Row( + children: [ + Text(text, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), + const Spacer(), + SvgPicture.asset(icon), + ], + ), + ), + ); + } + + PopupMenuItem _buildDivider() { + return PopupMenuItem( + enabled: false, + height: 1, + padding: EdgeInsets.zero, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 12), + height: 1, + color: appTheme.dividerColor, + ), + ); + } +} diff --git a/lib/features/blog_post/presentation/pages/blog_post_screen.dart b/lib/features/blog_post/presentation/pages/blog_post_screen.dart index 63f87233..e963a812 100644 --- a/lib/features/blog_post/presentation/pages/blog_post_screen.dart +++ b/lib/features/blog_post/presentation/pages/blog_post_screen.dart @@ -3,10 +3,8 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/enums/complained_content.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; -import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; -import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart'; +import 'package:eatvio_app/core/widgets/custom_popup.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; -import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart'; import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart'; @@ -14,7 +12,6 @@ import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_html/flutter_html.dart'; @@ -163,39 +160,14 @@ class _BlogPostScreenState extends State { appBar: AppBar( title: Text(widget.blogPost.title), actions: [ - PopupMenuButton( - onSelected: (value) async { - if (value == 'delete') { - final result = await sl().deleteBlogPost(widget.blogPost.id); - if (!context.mounted) return; - if (!result) { - showErrorDialog(context, title: S.current.could_not_delete_blog_post); - return; - } - context.read().loadFeedBlogPostList(); - Navigator.of(context).pop(); - } - if (value == 'edit') { - GoRouter.of(context).replaceNamed('blogPostCreateScreen', extra: widget.blogPost); - } - }, - itemBuilder: (context) => [ - if (widget.blogPost.author.authorIsMe) const PopupMenuItem(value: 'edit', child: Text('Редактировать')), - if (widget.blogPost.author.authorIsMe) const PopupMenuItem(value: 'delete', child: Text('Удалить')), - if (!widget.blogPost.author.authorIsMe) - PopupMenuItem( - value: 'report', - child: const Text('Пожаловаться'), - onTap: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => ComplainingBottomSheet(complainedContentType: ComplainedContent.posts, id: widget.blogPost.id), - ); - }, - ), - ], + CustomPopup( + isAuthor: widget.blogPost.author.authorIsMe, + complainedContentType: ComplainedContent.posts, + path: 'blog_post/${(widget.blogPost.id)}', + id: widget.blogPost.id, + feedBlogPostModel: widget.blogPost, ), + const SizedBox(width: 16), ], ), body: SingleChildScrollView( diff --git a/lib/features/feed/presentation/widgets/blog_post_widget.dart b/lib/features/feed/presentation/widgets/blog_post_widget.dart index 605df679..f5b8dd2d 100644 --- a/lib/features/feed/presentation/widgets/blog_post_widget.dart +++ b/lib/features/feed/presentation/widgets/blog_post_widget.dart @@ -4,9 +4,9 @@ import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/enums/complained_content.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; +import 'package:eatvio_app/core/widgets/custom_popup.dart'; import 'package:eatvio_app/core/widgets/feed_icon_button.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; -import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart'; @@ -14,7 +14,6 @@ import 'package:eatvio_app/features/feed/presentation/widgets/subscribe_button_w import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_html/flutter_html.dart'; -import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; @@ -119,30 +118,12 @@ class _BlogPostWidgetState extends State { slug: state.feedBlogPostList[widget.index].author.authorSlug, ), ), - PortalTarget( - visible: isMoreOptionsVisible, - portalFollower: MoreOptions( - complainedContentType: ComplainedContent.posts, - path: 'blog_post/${(state.feedBlogPostList[widget.index].id)}', - id: state.feedBlogPostList[widget.index].id, - ), - anchor: const Aligned(shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomRight), - child: InkWell( - onTap: () { - setState(() { - isMoreOptionsVisible = !isMoreOptionsVisible; - }); - }, - child: SizedBox( - width: 32, - height: 24, - child: Material( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - child: const Icon(Icons.more_horiz), - ), - ), - ), + CustomPopup( + isAuthor: state.feedBlogPostList[widget.index].author.authorIsMe, + complainedContentType: ComplainedContent.posts, + path: 'blog_post/${(state.feedBlogPostList[widget.index].id)}', + id: state.feedBlogPostList[widget.index].id, + feedBlogPostModel: state.feedBlogPostList[widget.index], ), ], ),