Add mention view

This commit is contained in:
Amir Hossein Mousavi 2024-05-23 08:16:30 +03:30
parent c1edb1c019
commit 4c369c8588
13 changed files with 1080 additions and 54 deletions

View File

@ -0,0 +1,31 @@
class MentionData {
final int id;
final String fullName;
final String text;
final String createdAt;
final List<String> mentions;
MentionData(
{required this.id,
required this.text,
required this.createdAt,
required this.fullName,
required this.mentions});
factory MentionData.fromJson(Map<String, dynamic> json, bool private) =>
MentionData(
id: json['id'],
text: json['text'],
createdAt: json['createdAt'],
fullName: json['fullName'],
mentions: json['mentions'],
);
Map<String, dynamic> toJson() => {
'id': id,
'text': text,
'createdAt': createdAt,
'fullName': fullName,
'mentions': mentions,
};
}

View File

@ -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/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/newStock_state.dart';
import 'package:didvan/views/home/new_statistic/stock/new_stock.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.dart';
import 'package:didvan/views/news/news_details/news_details.dart'; import 'package:didvan/views/news/news_details/news_details.dart';
import 'package:didvan/views/news/news_details/news_details_state.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'; import '../views/notification_time/notification_time.dart';
class RouteGenerator { class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) { static Route<dynamic> generateRoute(RouteSettings settings) {
HomeWidget.saveWidgetData("cRoute", settings.name!); HomeWidget.saveWidgetData("cRoute", settings.name!);
switch (settings.name) { switch (settings.name) {
@ -242,6 +243,15 @@ class RouteGenerator {
), ),
), ),
); );
case Routes.mentions:
return _createRoute(
ChangeNotifierProvider<MentionsState>(
create: (context) => MentionsState(),
child: Mentions(
pageData: settings.arguments as Map<String, dynamic>,
),
),
);
case Routes.bookmarks: case Routes.bookmarks:
return _createRoute( return _createRoute(
ChangeNotifierProvider<BookmarksState>( ChangeNotifierProvider<BookmarksState>(

View File

@ -19,6 +19,7 @@ class Routes {
static const String directList = '/direct-list'; static const String directList = '/direct-list';
static const String direct = '/direct'; static const String direct = '/direct';
static const String comments = '/comments'; static const String comments = '/comments';
static const String mentions = '/mentions';
static const String bookmarks = '/bookmarks'; static const String bookmarks = '/bookmarks';
static const String filteredBookmarks = '/filtered-bookmarks'; static const String filteredBookmarks = '/filtered-bookmarks';
static const String imageCropper = '/image-cropper'; static const String imageCropper = '/image-cropper';

View File

@ -186,16 +186,18 @@ class RequestHelper {
static String mark(int id, String type) => '$baseUrl/$type/$id/mark'; static String mark(int id, String type) => '$baseUrl/$type/$id/mark';
static String tracking(int id, String type) => '$baseUrl/$type/$id/tracking'; 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 favourites() => '$baseUrl/user/favorites';
static String notificationStatus() => '$baseUrl/user/notification/status'; static String notificationStatus() => '$baseUrl/user/notification/status';
static String notificationTime() => '$baseUrl/user/notification/time'; 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) => 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) => static String addComment(int id, String type) =>
'$baseUrl/$type/$id/comments/add'; '$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 reportComment(int id) => '$baseUrl/comment/$id/report';
static String widgetNews() => '$baseUrl/user/widget'; static String widgetNews() => '$baseUrl/user/widget';

View File

@ -4,10 +4,12 @@ import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/tag.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/animated_visibility.dart';
import 'package:didvan/views/widgets/bookmark_button.dart'; import 'package:didvan/views/widgets/bookmark_button.dart';
import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/card.dart';
import 'package:didvan/views/widgets/didvan/divider.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/didvan/text.dart';
import 'package:didvan/views/widgets/infography_tag.dart'; import 'package:didvan/views/widgets/infography_tag.dart';
import 'package:didvan/views/widgets/ink_wrapper.dart'; import 'package:didvan/views/widgets/ink_wrapper.dart';
@ -108,7 +110,6 @@ class InfographyItem extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
const SizedBox(height: 8),
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
child: GestureDetector( child: GestureDetector(
@ -137,12 +138,30 @@ class InfographyItem extends StatelessWidget {
), ),
const DidvanDivider(), const DidvanDivider(),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
InfoCat( InfoCat(
category: category, category: category,
createdAt: createdAt, createdAt: createdAt,
), ),
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( BookmarkButton(
itemId: id, itemId: id,
type: 'infography', type: 'infography',
@ -151,6 +170,8 @@ class InfographyItem extends StatelessWidget {
onMarkChanged: (value) => onMarkChanged(id, value, true), onMarkChanged: (value) => onMarkChanged(id, value, true),
), ),
], ],
),
],
) )
], ],
), ),

View File

@ -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<String, dynamic> pageData;
const Mentions({
Key? key,
required this.pageData,
}) : super(key: key);
@override
State<Mentions> createState() => _MentionsState();
}
class _MentionsState extends State<Mentions> {
final _focusNode = FocusNode();
double _bottomPadding = 0;
@override
void initState() {
final state = context.read<MentionsState>();
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<MentionsState>();
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<MentionsState>(
builder: (context, state, child) =>
SliverStateHandler<MentionsState>(
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<MentionsState>(
builder: (context, state, child) =>
SliverStateHandler<MentionsState>(
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<MentionsState>();
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,
),
),
],
);
}
}

View File

@ -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<CommentData> comments = [];
final List<CommentData> privateComments = [];
final List<UsersMention> usersMention = [];
final Map<int, MapEntry<bool, bool>> _feedbackQueue = {};
int itemId = 0;
Future<void> 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<void> getUsersMention() async {
final service = RequestService(
RequestHelper.usersMentions(mentionedText.replaceAll("@", "")),
);
await service.httpGet();
if (service.isSuccess) {
usersMention.clear();
final List<dynamic> users = service.data('users');
usersMention
.addAll(users.map((users) => UsersMention.fromJson(users)).toList());
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
Future<void> 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<void> addComment() async {
late List<CommentData> cList =
hideMentionedUser ? privateComments : comments;
final user = DesignConfig.context!.read<UserProvider>().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();
}
}

View File

@ -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<Mention> createState() => MentionState();
}
class MentionState extends State<Mention> {
late final MentionsState state;
MentionData get _comment => widget.comment;
@override
void initState() {
state = context.read<MentionsState>();
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<void> _showCommentActions(comment) async {
ActionSheetUtils.showBottomSheet(
data: ActionSheetData(
title: 'گزینه‌های نظر',
content: Column(
children: [
if (comment.user.id == context.read<UserProvider>().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,
),
);
}
}

View File

@ -27,29 +27,6 @@ class ProfilePage extends StatelessWidget {
DidvanCard( DidvanCard(
child: Column( child: Column(
children: [ children: [
// Consumer<UserProvider>(
// 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( MenuOption(
title: 'ویرایش پروفایل', title: 'ویرایش پروفایل',
icon: DidvanIcons.user_edit_regular, icon: DidvanIcons.user_edit_regular,
@ -65,12 +42,6 @@ class ProfilePage extends StatelessWidget {
Navigator.of(context).pushNamed(Routes.generalSettings), Navigator.of(context).pushNamed(Routes.generalSettings),
), ),
const DidvanDivider(), const DidvanDivider(),
// MenuOption(
// title: 'نشان شده‌ها',
// icon: DidvanIcons.bookmark_regular,
// onTap: () => Navigator.of(context).pushNamed(Routes.bookmarks),
// ),
// const DidvanDivider(),
MenuOption( MenuOption(
title: 'خروج از حساب کاربری', title: 'خروج از حساب کاربری',
icon: DidvanIcons.sign_out_regular, icon: DidvanIcons.sign_out_regular,
@ -122,7 +93,7 @@ class ProfilePage extends StatelessWidget {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
DidvanText( DidvanText(
'نسخه نرم‌افزار: 3.1.1', 'نسخه نرم‌افزار: 3.2.0',
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall,
), ),
], ],

View File

@ -42,10 +42,12 @@ class FloatingNavigationBar extends StatefulWidget {
class _FloatingNavigationBarState extends State<FloatingNavigationBar> { class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
bool _isScrolled = false; bool _isScrolled = false;
int _comments = 0; int _comments = 0;
int _mentions = 0;
@override @override
void didUpdateWidget(covariant FloatingNavigationBar oldWidget) { void didUpdateWidget(covariant FloatingNavigationBar oldWidget) {
_comments = widget.item.comments; _comments = widget.item.comments;
if (widget.openComments) { if (widget.openComments) {
Future.delayed( Future.delayed(
const Duration(seconds: 1), const Duration(seconds: 1),
@ -71,6 +73,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
_handleScroll(); _handleScroll();
_isScrolled = false; _isScrolled = false;
_comments = widget.item.comments; _comments = widget.item.comments;
if (widget.openComments) { if (widget.openComments) {
Future.delayed( Future.delayed(
const Duration(seconds: 1), const Duration(seconds: 1),
@ -85,6 +88,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
), ),
); );
} }
super.initState(); super.initState();
} }
@ -134,6 +138,31 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
), ),
), ),
const Spacer(), 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( BookmarkButton(
itemId: widget.item.id, itemId: widget.item.id,
type: widget.isRadar ? 'radar' : 'news', type: widget.isRadar ? 'radar' : 'news',

View File

@ -4,6 +4,7 @@ import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/providers/media.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/podcasts/studio_details/studio_details_state.dart';
import 'package:didvan/views/widgets/bookmark_button.dart'; import 'package:didvan/views/widgets/bookmark_button.dart';
import 'package:didvan/views/widgets/duration_widget.dart'; import 'package:didvan/views/widgets/duration_widget.dart';
@ -109,8 +110,21 @@ class PodcastOverview extends StatelessWidget {
strokeWidth: 2, 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( BookmarkButton(
itemId: podcast.id, itemId: podcast.id,
type: 'podcast', type: 'podcast',

View File

@ -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(), const Spacer(),
BookmarkButton( BookmarkButton(
itemId: radar.id, itemId: radar.id,

View File

@ -4,6 +4,7 @@ import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/widgets/bookmark_button.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/duration_widget.dart';
import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/card.dart';
import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/divider.dart';
@ -94,13 +95,19 @@ class VideoOverview extends StatelessWidget {
children: [ children: [
DurationWidget(duration: video.duration!), DurationWidget(duration: video.duration!),
const Spacer(), const Spacer(),
// DidvanIconButton( DidvanIconButton(
// gestureSize: 28, gestureSize: 32,
// icon: DidvanIcons.download_regular, onPressed: () => Navigator.of(context).pushNamed(
// onPressed: () => Routes.mentions,
// context.read<StudioState>().download(video.media!), arguments: {
// ), 'id': video.id,
// const SizedBox(width: 16), 'type': 'studio',
'title': video.title,
},
),
icon: DidvanIcons.chats_light,
),
const SizedBox(width: 8.0),
BookmarkButton( BookmarkButton(
itemId: video.id, itemId: video.id,
type: 'video', type: 'video',