diff --git a/lib/models/mention/mention.dart b/lib/models/mention/mention.dart new file mode 100644 index 0000000..963e55c --- /dev/null +++ b/lib/models/mention/mention.dart @@ -0,0 +1,31 @@ +class MentionData { + final int id; + final String fullName; + final String text; + final String createdAt; + final List mentions; + + MentionData( + {required this.id, + required this.text, + required this.createdAt, + required this.fullName, + required this.mentions}); + + factory MentionData.fromJson(Map json, bool private) => + MentionData( + id: json['id'], + text: json['text'], + createdAt: json['createdAt'], + fullName: json['fullName'], + mentions: json['mentions'], + ); + + Map toJson() => { + 'id': id, + 'text': text, + 'createdAt': createdAt, + 'fullName': fullName, + 'mentions': mentions, + }; +} diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index 185fb63..148de1e 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -18,6 +18,8 @@ import 'package:didvan/views/home/new_statistic/statistics_details/stat_cats_gen import 'package:didvan/views/home/new_statistic/statistics_details/stat_cats_general_state.dart'; import 'package:didvan/views/home/new_statistic/stock/newStock_state.dart'; import 'package:didvan/views/home/new_statistic/stock/new_stock.dart'; +import 'package:didvan/views/mentions/mentions.dart'; +import 'package:didvan/views/mentions/mentions_state.dart'; import 'package:didvan/views/news/news.dart'; import 'package:didvan/views/news/news_details/news_details.dart'; import 'package:didvan/views/news/news_details/news_details_state.dart'; @@ -55,7 +57,6 @@ import '../views/customize_category/notification_status_step.dart'; import '../views/notification_time/notification_time.dart'; class RouteGenerator { - static Route generateRoute(RouteSettings settings) { HomeWidget.saveWidgetData("cRoute", settings.name!); switch (settings.name) { @@ -242,6 +243,15 @@ class RouteGenerator { ), ), ); + case Routes.mentions: + return _createRoute( + ChangeNotifierProvider( + create: (context) => MentionsState(), + child: Mentions( + pageData: settings.arguments as Map, + ), + ), + ); case Routes.bookmarks: return _createRoute( ChangeNotifierProvider( diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index d9fbe43..a837a47 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -19,6 +19,7 @@ class Routes { static const String directList = '/direct-list'; static const String direct = '/direct'; static const String comments = '/comments'; + static const String mentions = '/mentions'; static const String bookmarks = '/bookmarks'; static const String filteredBookmarks = '/filtered-bookmarks'; static const String imageCropper = '/image-cropper'; diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart index 3033e82..942bd01 100644 --- a/lib/services/network/request_helper.dart +++ b/lib/services/network/request_helper.dart @@ -186,16 +186,18 @@ class RequestHelper { static String mark(int id, String type) => '$baseUrl/$type/$id/mark'; static String tracking(int id, String type) => '$baseUrl/$type/$id/tracking'; - static String comments(int id, String type) => '$baseUrl/$type/$id/comments/v2'; + static String comments(int id, String type) => '$baseUrl/$type/$id/comments'; + static String mention(int id, String type) => '$baseUrl/$type/$id/mention'; static String favourites() => '$baseUrl/user/favorites'; static String notificationStatus() => '$baseUrl/user/notification/status'; static String notificationTime() => '$baseUrl/user/notification/time'; - static String usersMentions(String search) => '$baseUrl/comment/user?search=$search'; + static String usersMentions(String search) => + '$baseUrl/comment/user?search=$search'; static String feedback(int id, int commentId, String type) => - '$baseUrl/$type/$id/comments/$commentId/feedback/v2'; + '$baseUrl/$type/$id/comments/$commentId/feedback'; static String addComment(int id, String type) => '$baseUrl/$type/$id/comments/add'; - static String deleteComment(int id) => '$baseUrl/comment/$id/v2'; + static String deleteComment(int id) => '$baseUrl/comment/$id'; static String reportComment(int id) => '$baseUrl/comment/$id/report'; static String widgetNews() => '$baseUrl/user/widget'; diff --git a/lib/views/home/main/widgets/infography_item.dart b/lib/views/home/main/widgets/infography_item.dart index 0b5c09a..ffb0e3f 100644 --- a/lib/views/home/main/widgets/infography_item.dart +++ b/lib/views/home/main/widgets/infography_item.dart @@ -4,10 +4,12 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/tag.dart'; +import 'package:didvan/routes/routes.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/bookmark_button.dart'; import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/infography_tag.dart'; import 'package:didvan/views/widgets/ink_wrapper.dart'; @@ -108,7 +110,6 @@ class InfographyItem extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), - const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: GestureDetector( @@ -137,18 +138,38 @@ class InfographyItem extends StatelessWidget { ), const DidvanDivider(), Row( + crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ InfoCat( category: category, createdAt: createdAt, ), - BookmarkButton( - itemId: id, - type: 'infography', - gestureSize: 32, - value: marked, - onMarkChanged: (value) => onMarkChanged(id, value, true), + Row( + children: [ + DidvanIconButton( + gestureSize: 32, + onPressed: () => Navigator.of(context).pushNamed( + Routes.mentions, + arguments: { + 'id': id, + 'type': 'infography', + 'title': title, + }, + ), + icon: DidvanIcons.chats_light, + ), + const SizedBox( + width: 8.0, + ), + BookmarkButton( + itemId: id, + type: 'infography', + gestureSize: 32, + value: marked, + onMarkChanged: (value) => onMarkChanged(id, value, true), + ), + ], ), ], ) diff --git a/lib/views/mentions/mentions.dart b/lib/views/mentions/mentions.dart new file mode 100644 index 0000000..aac0615 --- /dev/null +++ b/lib/views/mentions/mentions.dart @@ -0,0 +1,555 @@ +import 'dart:ui'; + +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/constants/assets.dart'; +import 'package:didvan/models/users_mention.dart'; +import 'package:didvan/models/view/app_bar_data.dart'; +import 'package:didvan/views/comments/widgets/comment.dart'; +import 'package:didvan/views/mentions/mentions_state.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/checkbox.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/shimmer_placeholder.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; +import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../widgets/didvan/didvan_title_divider.dart'; +import '../widgets/user_mention.dart'; + +class Mentions extends StatefulWidget { + final Map pageData; + + const Mentions({ + Key? key, + required this.pageData, + }) : super(key: key); + + @override + State createState() => _MentionsState(); +} + +class _MentionsState extends State { + final _focusNode = FocusNode(); + double _bottomPadding = 0; + + @override + void initState() { + final state = context.read(); + state.itemId = widget.pageData['id']; + state.type = widget.pageData['type']; + + Future.delayed( + Duration.zero, + () => state.getComments(), + ); + + super.initState(); + } + + bool get _isPage => widget.pageData['isPage'] != false; + + @override + Widget build(BuildContext context) { + final commentsState = context.watch(); + + final bottomViewInset = MediaQuery.of(context).viewInsets.bottom; + if (bottomViewInset == 0) { + if (_bottomPadding != 0) { + FocusScope.of(context).unfocus(); + _bottomPadding = 0; + } + } + _bottomPadding = bottomViewInset; + return Material( + child: Stack( + children: [ + DidvanScaffold( + hidePlayer: true, + physics: const BouncingScrollPhysics(), + backgroundColor: Theme.of(context).colorScheme.surface, + appBarData: _isPage + ? AppBarData( + hasBack: true, + title: 'فراخوانی‌ها', + subtitle: widget.pageData['title'], + ) + : null, + padding: const EdgeInsets.only(left: 16, right: 16, bottom: 92), + showSliversFirst: false, + slivers: [ + Consumer( + builder: (context, state, child) => + SliverStateHandler( + onRetry: state.getComments, + state: state, + itemPadding: const EdgeInsets.symmetric(vertical: 16), + childCount: state.comments.length, + placeholder: const _CommentPlaceholder(), + centerEmptyState: _isPage, + enableEmptyState: + state.comments.isEmpty && state.privateComments.isEmpty, + emptyState: EmptyState( + asset: Assets.emptyChat, + title: 'دوستان خود را فراخوانی کنید'), + builder: (context, state, index) => Comment( + key: ValueKey( + state.comments[index].id.toString() + + state.comments[index].text, + ), + focusNode: _focusNode, + comment: state.comments[index], + ), + ), + ), + ], + children: commentsState.privateComments.isNotEmpty + ? [ + const SizedBox( + height: 6, + ), + DidvanTitleDivider( + title: "نظرات پنهان شده", + color: Theme.of(context).colorScheme.primary, + icon: Icon( + commentsState.showPrivates + ? DidvanIcons.angle_up_regular + : DidvanIcons.angle_down_regular, + color: Theme.of(context).colorScheme.primary, + ), + onClick: () { + commentsState.showPrivates = + !commentsState.showPrivates; + commentsState.update(); + }, + ), + const SizedBox( + height: 16, + ), + AnimatedVisibility( + duration: DesignConfig.lowAnimationDuration, + isVisible: commentsState.showPrivates, + child: _buildPrivateComments(commentsState), + ), + const SizedBox( + height: 16, + ), + const DidvanTitleDivider( + title: "سایر نظرات", + ), + ] + : null, + ), + AnimatedVisibility( + duration: DesignConfig.lowAnimationDuration, + isVisible: commentsState.showUsersForMentionsLayout, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0), + child: Container( + decoration: BoxDecoration( + color: + Theme.of(context).colorScheme.focused.withOpacity(0.5)), + child: DidvanScaffold( + appBarData: null, + padding: const EdgeInsets.only( + left: 16, right: 16, top: 16, bottom: 92), + backgroundColor: Colors.white.withOpacity(0.0), + slivers: [ + Consumer( + builder: (context, state, child) => + SliverStateHandler( + onRetry: state.getUsersMention, + state: state, + itemPadding: const EdgeInsets.symmetric(vertical: 8), + childCount: state.usersMention.length, + placeholder: const _UsersPlaceholder(), + centerEmptyState: _isPage, + enableEmptyState: state.usersMention.isEmpty, + emptyState: EmptyState( + asset: Assets.emptyBookmark, + title: 'لیست خالی است', + ), + builder: (context, state, index) { + return UserMention( + user: state.usersMention[index], + index: index, + ); + }, + ), + ), + ], + ), + ), + ), + ), + Positioned( + left: 0, + right: 0, + bottom: MediaQuery.of(context).viewInsets.bottom, + child: _MessageBox(focusNode: _focusNode), + ), + ], + ), + ); + } + + ListView _buildPrivateComments(MentionsState commentsState) { + return ListView.builder( + physics: const BouncingScrollPhysics(), + itemCount: commentsState.privateComments.length, + shrinkWrap: true, + itemBuilder: (context, index) { + return Comment( + key: ValueKey( + commentsState.privateComments[index].id.toString() + + commentsState.privateComments[index].text, + ), + focusNode: _focusNode, + comment: commentsState.privateComments[index], + ); + }); + } +} + +class _MessageBox extends StatefulWidget { + final FocusNode focusNode; + + const _MessageBox({Key? key, required this.focusNode}) : super(key: key); + + @override + State<_MessageBox> createState() => _MessageBoxState(); +} + +class _MessageBoxState extends State<_MessageBox> { + @override + Widget build(BuildContext context) { + final state = context.watch(); + + void onCheckBoxChange(bool b) { + state.hideMentionedUser = b; + state.update(); + } + + return Column( + children: [ + AnimatedVisibility( + duration: DesignConfig.lowAnimationDuration, + isVisible: state.showReplyBox, + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondCTA, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(16), + topLeft: Radius.circular(16)), + boxShadow: [ + BoxShadow( + color: Theme.of(context) + .colorScheme + .title + .withOpacity(0.2), + offset: const Offset( + 5.0, + 5.0, + ), + blurRadius: 10.0, + spreadRadius: 2.0, + ) + ] + + // border: Border( + // top: BorderSide( + // color: Theme.of(context).colorScheme.border, + // ), + // ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (state.replyingTo != null) + DidvanText( + 'پاسخ به ${state.replyingTo!.fullName}:', + color: Theme.of(context).colorScheme.primary, + style: Theme.of(context).textTheme.bodySmall, + ), + const Spacer(), + DidvanIconButton( + gestureSize: 24, + color: Theme.of(context).colorScheme.primary, + icon: DidvanIcons.close_regular, + onPressed: () { + state.commentId = null; + state.replyingTo = null; + state.showReplyBox = false; + state.update(); + }, + ), + ], + ), + ), + Container( + color: Theme.of(context).colorScheme.secondCTA, + padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), + child: Divider( + height: 2, + color: Theme.of(context).colorScheme.border, + ), + ) + ], + ), + ), + AnimatedVisibility( + duration: DesignConfig.lowAnimationDuration, + isVisible: state.usersMentioned.name != null, + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondCTA, + borderRadius: !state.showReplyBox + ? const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16)) + : null), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + color: Theme.of(context).colorScheme.primary, + width: 2, + height: 40, + ), + const SizedBox( + width: 8, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DidvanText( + "اشاره به", + color: Theme.of(context).colorScheme.text, + style: Theme.of(context).textTheme.labelSmall, + ), + DidvanText( + state.usersMentioned.name.toString(), + color: Theme.of(context).colorScheme.text, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ) + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + DidvanIconButton( + gestureSize: 24, + color: Theme.of(context).colorScheme.primary, + icon: DidvanIcons.close_regular, + onPressed: () { + state.usersMentioned = UsersMention(); + state.update(); + }, + ), + !state.showReplyBox + ? DidvanCheckbox( + title: "پنهان کردن فراخوانی", + value: state.hideMentionedUser, + color: Theme.of(context).colorScheme.primary, + onChanged: onCheckBoxChange, + size: 12, + ) + : const SizedBox(), + ], + ) + ], + ), + ), + ], + ), + ), + Container( + decoration: BoxDecoration( + boxShadow: state.showReplyBox && state.usersMentioned.name == null + ? null + : [ + BoxShadow( + color: + Theme.of(context).colorScheme.title.withOpacity(0.2), + offset: const Offset( + 5.0, + 5.0, + ), + blurRadius: 10.0, + spreadRadius: 2.0, + ) + ], + color: state.showReplyBox && state.usersMentioned.name == null + ? Theme.of(context).colorScheme.secondCTA + : Theme.of(context).colorScheme.surface, + ), + child: Row( + children: [ + DidvanIconButton( + onPressed: () => _onSend(state), + icon: DidvanIcons.send_solid, + size: 24, + color: Theme.of(context).colorScheme.focusedBorder, + ), + Expanded( + child: TextField( + focusNode: widget.focusNode, + controller: state.commentTextFieldController, + keyboardType: TextInputType.multiline, + textInputAction: TextInputAction.send, + style: Theme.of(context).textTheme.bodyMedium, + onEditingComplete: () {}, + onSubmitted: (value) => _onSend(state), + decoration: InputDecoration( + border: InputBorder.none, + hintText: 'پیام خود را ارسال کنید', + hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Theme.of(context).colorScheme.disabledText), + ), + onChanged: (value) => _onChange(state, value), + ), + ), + ], + ), + ), + ], + ); + } + + void _onSend(MentionsState state) { + if (state.commentTextFieldController.text.replaceAll(' ', '').isNotEmpty) { + state.addComment(); + state.commentTextFieldController.text = ''; + } + } + + void _onChange(MentionsState state, value) { + state.commentTextFieldController.text = value; + if (state.usersMentioned.name == null) { + if (state.commentTextFieldController.text.contains("@")) { + int index = state.commentTextFieldController.text.indexOf("@"); + if (state.commentTextFieldController.text.length > index + 1) { + if (state.commentTextFieldController.text + .substring(index) + .contains(" ")) { + state.showUsersForMentionsLayout = false; + } else { + state.mentionedText = + state.commentTextFieldController.text.substring(index); + state.getUsersMention(); + state.showUsersForMentionsLayout = true; + } + } + } else { + state.showUsersForMentionsLayout = false; + } + state.update(); + } + } +} + +class _CommentPlaceholder extends StatelessWidget { + const _CommentPlaceholder({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ShimmerPlaceholder( + height: 24, + width: 24, + borderRadius: DesignConfig.highBorderRadius, + ), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ShimmerPlaceholder( + height: 20, + width: 100, + ), + ShimmerPlaceholder( + height: 14, + width: 100, + ), + ], + ), + SizedBox(height: 12), + ShimmerPlaceholder( + height: 16, + width: double.infinity, + ), + SizedBox(height: 8), + ShimmerPlaceholder( + height: 16, + width: 200, + ), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ShimmerPlaceholder( + height: 24, + width: 48, + ), + SizedBox(width: 8), + ShimmerPlaceholder( + height: 24, + width: 48, + ), + ], + ), + ], + ), + ), + ], + ); + } +} + +class _UsersPlaceholder extends StatelessWidget { + const _UsersPlaceholder({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ShimmerPlaceholder( + height: 40, + width: 40, + borderRadius: DesignConfig.highBorderRadius, + ), + SizedBox(width: 16), + Expanded( + child: ShimmerPlaceholder( + borderRadius: DesignConfig.highBorderRadius, + height: 40, + ), + ), + ], + ); + } +} diff --git a/lib/views/mentions/mentions_state.dart b/lib/views/mentions/mentions_state.dart new file mode 100644 index 0000000..01fbf57 --- /dev/null +++ b/lib/views/mentions/mentions_state.dart @@ -0,0 +1,250 @@ +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/models/comment/comment.dart'; +import 'package:didvan/models/comment/feedback.dart'; +import 'package:didvan/models/comment/reply.dart'; +import 'package:didvan/models/comment/user.dart'; +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/users_mention.dart'; +import 'package:didvan/models/view/alert_data.dart'; +import 'package:didvan/providers/core.dart'; +import 'package:didvan/providers/user.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; +import 'package:didvan/utils/action_sheet.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; + +class MentionsState extends CoreProvier { + TextEditingController commentTextFieldController = TextEditingController(); + UsersMention usersMentioned = UsersMention(); + String mentionedText = ''; + int? commentId; + UserOverview? replyingTo; + bool showReplyBox = false; + bool showUsersForMentionsLayout = false; + bool showPrivates = false; + bool hideMentionedUser = false; + int _count = 0; + late String type; + + final List comments = []; + final List privateComments = []; + final List usersMention = []; + final Map> _feedbackQueue = {}; + + int itemId = 0; + + Future getComments() async { + final service = RequestService( + RequestHelper.comments(itemId, type), + ); + await service.httpGet(); + if (service.isSuccess) { + comments.clear(); + final messagesPublic = service.result['comments']['public']; + for (var i = 0; i < messagesPublic.length; i++) { + comments.add(CommentData.fromJson(messagesPublic[i], false)); + _count++; + for (var j = 0; j < messagesPublic[i]['replies'].length; j++) { + _count++; + } + } + + final messagesPrivate = service.result['comments']['private']; + for (var i = 0; i < messagesPrivate.length; i++) { + privateComments.add(CommentData.fromJson(messagesPrivate[i], true)); + _count++; + for (var j = 0; j < messagesPrivate[i]['replies'].length; j++) { + _count++; + } + } + appState = AppState.idle; + return; + } + appState = AppState.failed; + } + + Future getUsersMention() async { + final service = RequestService( + RequestHelper.usersMentions(mentionedText.replaceAll("@", "")), + ); + await service.httpGet(); + if (service.isSuccess) { + usersMention.clear(); + final List users = service.data('users'); + usersMention + .addAll(users.map((users) => UsersMention.fromJson(users)).toList()); + + appState = AppState.idle; + return; + } + appState = AppState.failed; + } + + Future feedback({ + required int id, + required bool like, + required bool dislike, + required int likeCount, + required int dislikeCount, + int? replyId, + }) async { + _feedbackQueue.addAll({id: MapEntry(like, dislike)}); + dynamic comment; + if (replyId == null) { + comment = comments.firstWhere((comment) => comment.id == id); + } else { + comment = comments + .firstWhere((comment) => comment.id == id) + .replies + .firstWhere((element) => element.id == replyId); + } + + if (comment != null) { + comment.feedback.like = likeCount; + comment.feedback.dislike = dislikeCount; + comment.disliked = dislike; + comment.liked = like; + } + Future.delayed(const Duration(milliseconds: 500), () async { + if (!_feedbackQueue.containsKey(id)) return; + final service = RequestService( + RequestHelper.feedback(itemId, id, type), + body: { + 'like': _feedbackQueue[id]!.key, + 'dislike': _feedbackQueue[id]!.value, + }, + ); + await service.put(); + _feedbackQueue.remove(id); + }); + } + + Future addComment() async { + late List cList = + hideMentionedUser ? privateComments : comments; + final user = DesignConfig.context!.read().user; + if (replyingTo != null) { + comments.firstWhere((comment) => comment.id == commentId).replies.add( + Reply( + id: 0, + text: commentTextFieldController.text, + createdAt: DateTime.now().toString(), + liked: false, + disliked: false, + feedback: FeedbackData(like: 0, dislike: 0), + toUser: replyingTo!, + user: UserOverview( + id: user.id, + fullName: user.fullName, + photo: user.photo, + ), + status: 2, + mention: usersMentioned.name, + ), + ); + } else { + cList.insert( + 0, + CommentData( + id: 0, + text: commentTextFieldController.text, + createdAt: DateTime.now().toString(), + liked: false, + disliked: false, + feedback: FeedbackData(like: 0, dislike: 0), + user: UserOverview( + id: user.id, + fullName: user.fullName, + photo: user.photo, + ), + replies: [], + status: 2, + private: hideMentionedUser, + mention: usersMentioned.name, + ), + ); + } + + final body = {}; + + if (commentId != null) { + body.addAll({'commentId': commentId}); + } + if (replyingTo != null) { + body.addAll({'replyUserId': replyingTo!.id}); + } + body.addAll({'status': 2}); + + showReplyBox = false; + // update(); + body.addAll({ + 'text': commentTextFieldController.text, + "mention": usersMentioned.id, + "private": hideMentionedUser + }); + final service = RequestService( + RequestHelper.addComment(itemId, type), + body: body, + ); + + await service.post(); + if (service.isSuccess) { + if (replyingTo != null) { + cList + .firstWhere((comment) => comment.id == commentId) + .replies + .firstWhere((reply) => reply.id == 0) + .id = service.result['comment']['id']; + } else { + cList.firstWhere((comment) => comment.id == 0).id = + service.result['comment']['id']; + } + commentId = null; + replyingTo = null; + usersMentioned = UsersMention(); + mentionedText = ''; + update(); + } + } + + void reportComment(int id) { + final service = RequestService(RequestHelper.reportComment(id)); + service.post(); + ActionSheetUtils.showAlert( + AlertData( + message: 'گزارش شما با موفقیت ثبت شد و به زودی بررسی میگردد.', + aLertType: ALertType.success, + ), + ); + } + + void deleteComment(int id, int status, int? rootId) async { + final service = RequestService(RequestHelper.deleteComment(id)); + service.delete(); + if (rootId == null) { + final comment = comments.firstWhere((element) => element.id == id); + if (comment.replies.isNotEmpty) { + _count = 0; + await getComments(); + } else { + comments.remove(comment); + + if (status != 2) { + _count--; + } + } + } else { + comments + .firstWhere((element) => element.id == rootId) + .replies + .removeWhere((element) => element.id == id); + + if (status != 2) { + _count--; + } + } + + notifyListeners(); + } +} diff --git a/lib/views/mentions/widgets/mention.dart b/lib/views/mentions/widgets/mention.dart new file mode 100644 index 0000000..7217093 --- /dev/null +++ b/lib/views/mentions/widgets/mention.dart @@ -0,0 +1,139 @@ +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/comment/comment.dart'; +import 'package:didvan/models/comment/reply.dart'; +import 'package:didvan/models/mention/mention.dart'; +import 'package:didvan/models/view/action_sheet_data.dart'; +import 'package:didvan/providers/user.dart'; +import 'package:didvan/utils/action_sheet.dart'; +import 'package:didvan/utils/date_time.dart'; +import 'package:didvan/views/comments/comments_state.dart'; +import 'package:didvan/views/mentions/mentions_state.dart'; +import 'package:didvan/views/widgets/menu_item.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class Mention extends StatefulWidget { + final FocusNode focusNode; + final MentionData comment; + + const Mention({ + Key? key, + required this.focusNode, + required this.comment, + }) : super(key: key); + + @override + State createState() => MentionState(); +} + +class MentionState extends State { + late final MentionsState state; + + MentionData get _comment => widget.comment; + + @override + void initState() { + state = context.read(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [_commentBuilder(comment: _comment)], + ); + } + + Widget _commentBuilder({required comment, bool isReply = false}) => Container( + decoration: BoxDecoration( + border: Border( + right: isReply + ? BorderSide(color: Theme.of(context).colorScheme.caption) + : BorderSide.none, + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isReply) const SizedBox(width: 12), + if (comment.user.photo == null) + const Icon(DidvanIcons.avatar_light), + if (comment.user.photo != null) + SkeletonImage( + imageUrl: comment.user.photo, + height: 24, + width: 24, + borderRadius: DesignConfig.highBorderRadius, + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + DidvanText( + comment.user.fullName, + style: Theme.of(context).textTheme.bodyLarge, + ), + const Spacer(), + DidvanText( + DateTimeUtils.momentGenerator(comment.createdAt), + style: Theme.of(context).textTheme.bodySmall, + color: Theme.of(context).colorScheme.caption, + ), + const SizedBox(width: 4), + DidvanIconButton( + size: 18, + gestureSize: 24, + icon: DidvanIcons.menu_light, + onPressed: () => _showCommentActions(comment), + ), + ], + ), + DidvanText( + comment.mention.toString(), + color: Theme.of(context).colorScheme.primary, + style: Theme.of(context).textTheme.titleSmall, + ), + ], + ), + ), + ], + ), + ); + + Future _showCommentActions(comment) async { + ActionSheetUtils.showBottomSheet( + data: ActionSheetData( + title: 'گزینه‌های نظر', + content: Column( + children: [ + if (comment.user.id == context.read().user.id) + MenuOption( + title: 'حذف نظر', + color: Theme.of(context).colorScheme.secondary, + onTap: () { + state.deleteComment( + comment.id, + comment.status, + comment.runtimeType == Reply ? _comment.id : null, + ); + ActionSheetUtils.pop(); + }, + icon: DidvanIcons.trash_solid, + ), + ], + ), + hasConfirmButton: false, + hasDismissButton: false, + ), + ); + } +} diff --git a/lib/views/profile/profile.dart b/lib/views/profile/profile.dart index ca2e503..c19a0e9 100644 --- a/lib/views/profile/profile.dart +++ b/lib/views/profile/profile.dart @@ -27,29 +27,6 @@ class ProfilePage extends StatelessWidget { DidvanCard( child: Column( children: [ - // Consumer( - // child: Icon( - // DidvanIcons.angle_left_regular, - // size: 18, - // color: Theme.of(context).colorScheme.title, - // ), - // builder: (context, state, child) => MenuOption( - // title: 'پیام‌ها', - // icon: DidvanIcons.message_regular, - // onTap: () => - // Navigator.of(context).pushNamed(Routes.directList), - // trailing: Row( - // children: [ - // if (state.unreadMessageCount != 0) - // DidvanBadge( - // text: state.unreadMessageCount.toString(), - // ), - // child!, - // ], - // ), - // ), - // ), - // const DidvanDivider(), MenuOption( title: 'ویرایش پروفایل', icon: DidvanIcons.user_edit_regular, @@ -65,12 +42,6 @@ class ProfilePage extends StatelessWidget { Navigator.of(context).pushNamed(Routes.generalSettings), ), const DidvanDivider(), - // MenuOption( - // title: 'نشان شده‌ها', - // icon: DidvanIcons.bookmark_regular, - // onTap: () => Navigator.of(context).pushNamed(Routes.bookmarks), - // ), - // const DidvanDivider(), MenuOption( title: 'خروج از حساب کاربری', icon: DidvanIcons.sign_out_regular, @@ -122,7 +93,7 @@ class ProfilePage extends StatelessWidget { ), const SizedBox(height: 16), DidvanText( - 'نسخه نرم‌افزار: 3.1.1', + 'نسخه نرم‌افزار: 3.2.0', style: Theme.of(context).textTheme.bodySmall, ), ], diff --git a/lib/views/widgets/floating_navigation_bar.dart b/lib/views/widgets/floating_navigation_bar.dart index dddfec0..1361a70 100644 --- a/lib/views/widgets/floating_navigation_bar.dart +++ b/lib/views/widgets/floating_navigation_bar.dart @@ -42,10 +42,12 @@ class FloatingNavigationBar extends StatefulWidget { class _FloatingNavigationBarState extends State { bool _isScrolled = false; int _comments = 0; + int _mentions = 0; @override void didUpdateWidget(covariant FloatingNavigationBar oldWidget) { _comments = widget.item.comments; + if (widget.openComments) { Future.delayed( const Duration(seconds: 1), @@ -71,6 +73,7 @@ class _FloatingNavigationBarState extends State { _handleScroll(); _isScrolled = false; _comments = widget.item.comments; + if (widget.openComments) { Future.delayed( const Duration(seconds: 1), @@ -85,6 +88,7 @@ class _FloatingNavigationBarState extends State { ), ); } + super.initState(); } @@ -134,6 +138,31 @@ class _FloatingNavigationBarState extends State { ), ), const Spacer(), + SizedBox( + width: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (_mentions != 0) + DidvanText( + _mentions.toString(), + color: foregroundColor, + ), + DidvanIconButton( + gestureSize: 32, + onPressed: () => Navigator.of(context).pushNamed( + Routes.mentions, + arguments: { + 'id': widget.item.id, + 'type': widget.isRadar ? 'radar' : 'news', + 'title': widget.item.title, + }, + ), + icon: DidvanIcons.chart_light, + ), + ], + ), + ), BookmarkButton( itemId: widget.item.id, type: widget.isRadar ? 'radar' : 'news', diff --git a/lib/views/widgets/overview/podcast.dart b/lib/views/widgets/overview/podcast.dart index 0b38d68..bbb4f88 100644 --- a/lib/views/widgets/overview/podcast.dart +++ b/lib/views/widgets/overview/podcast.dart @@ -4,6 +4,7 @@ import 'package:didvan/models/enums.dart'; import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/providers/media.dart'; +import 'package:didvan/routes/routes.dart'; import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart'; import 'package:didvan/views/widgets/bookmark_button.dart'; import 'package:didvan/views/widgets/duration_widget.dart'; @@ -109,8 +110,21 @@ class PodcastOverview extends StatelessWidget { strokeWidth: 2, ), ), - const SizedBox(width: 16), + const SizedBox(width: 8.0), ], + DidvanIconButton( + gestureSize: 32, + onPressed: () => Navigator.of(context).pushNamed( + Routes.mentions, + arguments: { + 'id': podcast.id, + 'type': 'studio', + 'title': podcast.title, + }, + ), + icon: DidvanIcons.chats_light, + ), + const SizedBox(width: 8.0), BookmarkButton( itemId: podcast.id, type: 'podcast', diff --git a/lib/views/widgets/overview/radar.dart b/lib/views/widgets/overview/radar.dart index 939d9e7..86ee5bc 100644 --- a/lib/views/widgets/overview/radar.dart +++ b/lib/views/widgets/overview/radar.dart @@ -118,10 +118,6 @@ class RadarOverview extends StatelessWidget { }, ), ), - // const SizedBox(width: 16), - // const DidvanText('10'), - // const SizedBox(width: 4), - // const Icon(DidvanIcons.evaluation_regular), const Spacer(), BookmarkButton( itemId: radar.id, diff --git a/lib/views/widgets/overview/video.dart b/lib/views/widgets/overview/video.dart index 73c61df..8297ba6 100644 --- a/lib/views/widgets/overview/video.dart +++ b/lib/views/widgets/overview/video.dart @@ -4,6 +4,7 @@ import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/views/widgets/bookmark_button.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/duration_widget.dart'; import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; @@ -94,13 +95,19 @@ class VideoOverview extends StatelessWidget { children: [ DurationWidget(duration: video.duration!), const Spacer(), - // DidvanIconButton( - // gestureSize: 28, - // icon: DidvanIcons.download_regular, - // onPressed: () => - // context.read().download(video.media!), - // ), - // const SizedBox(width: 16), + DidvanIconButton( + gestureSize: 32, + onPressed: () => Navigator.of(context).pushNamed( + Routes.mentions, + arguments: { + 'id': video.id, + 'type': 'studio', + 'title': video.title, + }, + ), + icon: DidvanIcons.chats_light, + ), + const SizedBox(width: 8.0), BookmarkButton( itemId: video.id, type: 'video',