03-02-1403 / Rhmn / edit mention section.

This commit is contained in:
OkaykOrhmn 2024-05-23 12:34:38 +03:30
parent 4c369c8588
commit f4689f3391
10 changed files with 265 additions and 759 deletions

View File

@ -6,12 +6,9 @@ class CommentData {
int id;
final String text;
final String createdAt;
String? type;
bool liked;
bool disliked;
bool private;
int status;
dynamic mention;
final FeedbackData feedback;
final UserOverview user;
final List<Reply> replies;
@ -22,21 +19,17 @@ class CommentData {
required this.createdAt,
required this.liked,
required this.disliked,
required this.private,
required this.feedback,
required this.user,
required this.replies,
required this.status,
this.type,
required this.mention,
});
factory CommentData.fromJson(Map<String, dynamic> json, bool private) => CommentData(
factory CommentData.fromJson(Map<String, dynamic> json) => CommentData(
id: json['id'],
text: json['text'],
createdAt: json['createdAt'],
liked: json['liked'],
private: private,
disliked: json['disliked'],
feedback: FeedbackData.fromJson(json['feedback']),
user: UserOverview.fromJson(json['user']),
@ -46,19 +39,14 @@ class CommentData {
),
),
status: json['status'],
mention: json['mention'],
);
Map<String, dynamic> toJson() => {
'id': id,
'text': text,
'createdAt': createdAt,
'liked': liked,
'disliked': disliked,
'private': private,
'mention': mention,
'type': type,
'feedback': feedback.toJson(),
'user': user.toJson(),
'replies': replies.map((e) => e.toJson()).toList(),

View File

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

View File

@ -1,17 +1,16 @@
class UsersMention {
int? id;
String? name;
int id;
String name;
String? type;
String? photo;
UsersMention({this.id, this.name, this.type, this.photo});
UsersMention({required this.id, required this.name, this.type, this.photo});
UsersMention.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
type = json['type'];
photo = json['photo'];
}
factory UsersMention.fromJson(Map<String, dynamic> json) => UsersMention(
id: json['id'],
name: json['name'],
type: json['type'],
photo: json['photo']);
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
@ -21,4 +20,4 @@ class UsersMention {
data['photo'] = photo;
return data;
}
}
}

View File

@ -1,15 +1,11 @@
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/comments_state.dart';
import 'package:didvan/views/comments/widgets/comment.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';
@ -19,12 +15,8 @@ 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 Comments extends StatefulWidget {
final Map<String, dynamic> pageData;
const Comments({
Key? key,
required this.pageData,
@ -55,8 +47,6 @@ class _CommentsState extends State<Comments> {
@override
Widget build(BuildContext context) {
final commentsState = context.watch<CommentsState>();
final bottomViewInset = MediaQuery.of(context).viewInsets.bottom;
if (bottomViewInset == 0) {
if (_bottomPadding != 0) {
@ -80,7 +70,6 @@ class _CommentsState extends State<Comments> {
)
: null,
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 92),
showSliversFirst: false,
slivers: [
Consumer<CommentsState>(
builder: (context, state, child) =>
@ -91,8 +80,7 @@ class _CommentsState extends State<Comments> {
childCount: state.comments.length,
placeholder: const _CommentPlaceholder(),
centerEmptyState: _isPage,
enableEmptyState:
state.comments.isEmpty && state.privateComments.isEmpty,
enableEmptyState: state.comments.isEmpty,
emptyState: EmptyState(
asset: Assets.emptyChat,
title: 'اولین نظر را بنویسید...',
@ -108,84 +96,6 @@ class _CommentsState extends State<Comments> {
),
),
],
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<CommentsState>(
builder: (context, state, child) =>
SliverStateHandler<CommentsState>(
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,
@ -197,28 +107,10 @@ class _CommentsState extends State<Comments> {
),
);
}
ListView _buildPrivateComments(CommentsState 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
@ -226,179 +118,59 @@ class _MessageBox extends StatefulWidget {
}
class _MessageBoxState extends State<_MessageBox> {
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
final state = context.watch<CommentsState>();
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,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border: Border(
top: BorderSide(
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(),
],
)
],
),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (state.replyingTo != null)
DidvanText(
'پاسخ به ${state.replyingTo!.fullName}:',
color: Theme.of(context).colorScheme.caption,
style: Theme.of(context).textTheme.bodySmall,
),
const Spacer(),
DidvanIconButton(
gestureSize: 24,
color: Theme.of(context).colorScheme.caption,
icon: DidvanIcons.close_regular,
onPressed: () {
state.commentId = null;
state.replyingTo = null;
state.showReplyBox = false;
state.update();
},
),
],
),
),
),
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,
color: Theme.of(context).colorScheme.surface,
border: Border(
top: BorderSide(
color: Theme.of(context).colorScheme.border,
),
),
),
child: Row(
children: [
@ -411,7 +183,7 @@ class _MessageBoxState extends State<_MessageBox> {
Expanded(
child: TextField(
focusNode: widget.focusNode,
controller: state.commentTextFieldController,
controller: _controller,
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.send,
style: Theme.of(context).textTheme.bodyMedium,
@ -423,7 +195,7 @@ class _MessageBoxState extends State<_MessageBox> {
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).colorScheme.disabledText),
),
onChanged: (value) => _onChange(state, value),
onChanged: (value) => state.text = value,
),
),
],
@ -434,33 +206,9 @@ class _MessageBoxState extends State<_MessageBox> {
}
void _onSend(CommentsState state) {
if (state.commentTextFieldController.text.replaceAll(' ', '').isNotEmpty) {
if (state.text.replaceAll(' ', '').isNotEmpty) {
state.addComment();
state.commentTextFieldController.text = '';
}
}
void _onChange(CommentsState 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();
_controller.text = '';
}
}
}
@ -528,28 +276,3 @@ class _CommentPlaceholder extends StatelessWidget {
);
}
}
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

@ -4,33 +4,24 @@ 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 CommentsState extends CoreProvier {
TextEditingController commentTextFieldController = TextEditingController();
UsersMention usersMentioned = UsersMention();
String mentionedText = '';
String text = '';
int? commentId;
UserOverview? replyingTo;
bool showReplyBox = false;
bool showUsersForMentionsLayout = false;
bool showPrivates = false;
bool hideMentionedUser = false;
late void Function(int count) onCommentsChanged;
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;
@ -42,40 +33,14 @@ class CommentsState extends CoreProvier {
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));
final messages = service.result['comments'];
for (var i = 0; i < messages.length; i++) {
comments.add(CommentData.fromJson(messages[i]));
_count++;
for (var j = 0; j < messagesPublic[i]['replies'].length; j++) {
for (var j = 0; j < messages[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;
}
@ -122,14 +87,12 @@ class CommentsState extends CoreProvier {
}
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,
text: text,
createdAt: DateTime.now().toString(),
liked: false,
disliked: false,
@ -141,15 +104,14 @@ class CommentsState extends CoreProvier {
photo: user.photo,
),
status: 2,
mention: usersMentioned.name,
),
);
} else {
cList.insert(
comments.insert(
0,
CommentData(
id: 0,
text: commentTextFieldController.text,
text: text,
createdAt: DateTime.now().toString(),
liked: false,
disliked: false,
@ -161,8 +123,6 @@ class CommentsState extends CoreProvier {
),
replies: [],
status: 2,
private: hideMentionedUser,
mention: usersMentioned.name,
),
);
}
@ -178,12 +138,8 @@ class CommentsState extends CoreProvier {
body.addAll({'status': 2});
showReplyBox = false;
// update();
body.addAll({
'text': commentTextFieldController.text,
"mention": usersMentioned.id,
"private": hideMentionedUser
});
update();
body.addAll({'text': text});
final service = RequestService(
RequestHelper.addComment(itemId, type),
body: body,
@ -192,20 +148,17 @@ class CommentsState extends CoreProvier {
await service.post();
if (service.isSuccess) {
if (replyingTo != null) {
cList
comments
.firstWhere((comment) => comment.id == commentId)
.replies
.firstWhere((reply) => reply.id == 0)
.id = service.result['comment']['id'];
} else {
cList.firstWhere((comment) => comment.id == 0).id =
comments.firstWhere((comment) => comment.id == 0).id =
service.result['comment']['id'];
}
commentId = null;
replyingTo = null;
usersMentioned = UsersMention();
mentionedText = '';
update();
}
}

View File

@ -20,7 +20,6 @@ import 'package:provider/provider.dart';
class Comment extends StatefulWidget {
final FocusNode focusNode;
final CommentData comment;
const Comment({
Key? key,
required this.focusNode,
@ -110,13 +109,6 @@ class CommentState extends State<Comment> {
),
],
),
comment.mention != null
? DidvanText(
comment.mention.toString(),
color: Theme.of(context).colorScheme.primary,
style: Theme.of(context).textTheme.titleSmall,
)
: const SizedBox(),
const SizedBox(height: 8),
if (isReply)
DidvanText(
@ -132,22 +124,19 @@ class CommentState extends State<Comment> {
children: [
Icon(
Icons.circle,
color:
Theme.of(context).colorScheme.secondaryDisabled,
color: Theme.of(context)
.colorScheme
.secondary
.withOpacity(0.3),
size: 18,
),
const SizedBox(width: 4),
DidvanText(
'در انتظار تایید',
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(
color: Theme.of(context)
.colorScheme
.secondaryDisabled),
color:
Theme.of(context).colorScheme.secondaryDisabled,
color: Theme.of(context)
.colorScheme
.secondary
.withOpacity(0.3),
),
],
),
@ -265,7 +254,6 @@ class _FeedbackButtons extends StatefulWidget {
final bool dislikeValue;
final void Function(bool like, bool dislike, int likeCount, int dislikeCount)
onFeedback;
const _FeedbackButtons({
Key? key,
required this.onFeedback,

View File

@ -4,10 +4,9 @@ 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/mentions/widgets/mention.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';
@ -18,8 +17,7 @@ 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 '../comments/comments_state.dart';
import '../widgets/user_mention.dart';
class Mentions extends StatefulWidget {
@ -54,6 +52,12 @@ class _MentionsState extends State<Mentions> {
bool get _isPage => widget.pageData['isPage'] != false;
void _onChange(MentionsState state, value) {
state.searchUsers = value;
state.getUsersMention();
state.update();
}
@override
Widget build(BuildContext context) {
final commentsState = context.watch<MentionsState>();
@ -92,12 +96,11 @@ class _MentionsState extends State<Mentions> {
childCount: state.comments.length,
placeholder: const _CommentPlaceholder(),
centerEmptyState: _isPage,
enableEmptyState:
state.comments.isEmpty && state.privateComments.isEmpty,
enableEmptyState: state.comments.isEmpty,
emptyState: EmptyState(
asset: Assets.emptyChat,
title: 'دوستان خود را فراخوانی کنید'),
builder: (context, state, index) => Comment(
builder: (context, state, index) => Mention(
key: ValueKey(
state.comments[index].id.toString() +
state.comments[index].text,
@ -108,42 +111,6 @@ class _MentionsState extends State<Mentions> {
),
),
],
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,
@ -159,30 +126,73 @@ class _MentionsState extends State<Mentions> {
padding: const EdgeInsets.only(
left: 16, right: 16, top: 16, bottom: 92),
backgroundColor: Colors.white.withOpacity(0.0),
showSliversFirst: false,
slivers: [
Consumer<MentionsState>(
builder: (context, state, child) =>
SliverStateHandler<MentionsState>(
onRetry: state.getUsersMention,
state: state,
itemPadding: const EdgeInsets.symmetric(vertical: 8),
childCount: state.usersMention.length,
childCount: state.users.length,
placeholder: const _UsersPlaceholder(),
centerEmptyState: _isPage,
enableEmptyState: state.usersMention.isEmpty,
enableEmptyState: state.users.isEmpty,
emptyState: EmptyState(
asset: Assets.emptyBookmark,
title: 'لیست خالی است',
),
builder: (context, state, index) {
return UserMention(
user: state.usersMention[index],
user: state.users[index],
index: index,
);
},
),
),
],
children: [
Row(
children: [
Expanded(
child: TextField(
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.send,
style: Theme.of(context).textTheme.bodyMedium,
onEditingComplete: () {},
onChanged: (val) => _onChange(commentsState, val),
decoration: InputDecoration(
icon: const Icon(DidvanIcons.search_regular),
border: InputBorder.none,
hintText: 'جستجو کنید...',
hintStyle: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: Theme.of(context)
.colorScheme
.disabledText),
),
),
),
DidvanIconButton(
icon: DidvanIcons.close_regular,
onPressed: () {
commentsState.showUsersForMentionsLayout = false;
commentsState.update();
}),
],
),
const SizedBox(
height: 4,
),
Divider(
height: 1,
color: Theme.of(context).colorScheme.splash,
),
const SizedBox(
height: 12,
)
],
),
),
),
@ -197,23 +207,6 @@ class _MentionsState extends State<Mentions> {
),
);
}
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 {
@ -245,31 +238,23 @@ class _MessageBoxState extends State<_MessageBox> {
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,
// ),
// ),
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,
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -307,7 +292,7 @@ class _MessageBoxState extends State<_MessageBox> {
),
AnimatedVisibility(
duration: DesignConfig.lowAnimationDuration,
isVisible: state.usersMentioned.name != null,
isVisible: state.mentionedUsers.isNotEmpty,
child: Column(
children: [
Container(
@ -343,7 +328,9 @@ class _MessageBoxState extends State<_MessageBox> {
style: Theme.of(context).textTheme.labelSmall,
),
DidvanText(
state.usersMentioned.name.toString(),
state.mentionedUsers
.map((user) => user.name)
.join("، "),
color: Theme.of(context).colorScheme.text,
style: Theme.of(context).textTheme.bodySmall,
),
@ -359,19 +346,10 @@ class _MessageBoxState extends State<_MessageBox> {
color: Theme.of(context).colorScheme.primary,
icon: DidvanIcons.close_regular,
onPressed: () {
state.usersMentioned = UsersMention();
state.mentionedUsers = [];
state.update();
},
),
!state.showReplyBox
? DidvanCheckbox(
title: "پنهان کردن فراخوانی",
value: state.hideMentionedUser,
color: Theme.of(context).colorScheme.primary,
onChanged: onCheckBoxChange,
size: 12,
)
: const SizedBox(),
],
)
],
@ -382,23 +360,12 @@ class _MessageBoxState extends State<_MessageBox> {
),
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,
color: Theme.of(context).colorScheme.surface,
border: Border(
top: BorderSide(
color: Theme.of(context).colorScheme.border,
),
),
),
child: Row(
children: [
@ -423,9 +390,19 @@ class _MessageBoxState extends State<_MessageBox> {
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).colorScheme.disabledText),
),
onChanged: (value) => _onChange(state, value),
),
),
const Spacer(),
if (!state.showUsersForMentionsLayout)
DidvanIconButton(
onPressed: () {
state.showUsersForMentionsLayout = true;
state.update();
},
icon: DidvanIcons.user_shield_regular,
size: 24,
color: Theme.of(context).colorScheme.focusedBorder,
),
],
),
),
@ -437,30 +414,8 @@ class _MessageBoxState extends State<_MessageBox> {
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();
state.searchUsers = '';
state.mentionedUsers.clear();
}
}
}

View File

@ -1,23 +1,19 @@
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/mention/mention.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 = '';
List<UsersMention> mentionedUsers = [];
String searchUsers = '';
int? commentId;
UserOverview? replyingTo;
bool showReplyBox = false;
@ -27,38 +23,27 @@ class MentionsState extends CoreProvier {
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 = {};
final List<MentionData> comments = [];
final List<UsersMention> users = [];
int itemId = 0;
Future<void> getComments() async {
final service = RequestService(
RequestHelper.comments(itemId, type),
RequestHelper.mention(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));
final messages = service.result['comments'];
for (var i = 0; i < messages.length; i++) {
comments.add(MentionData.fromJson(messages[i]));
_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;
@ -66,14 +51,17 @@ class MentionsState extends CoreProvier {
Future<void> getUsersMention() async {
final service = RequestService(
RequestHelper.usersMentions(mentionedText.replaceAll("@", "")),
RequestHelper.usersMentions(searchUsers),
);
await service.httpGet();
if (service.isSuccess) {
usersMention.clear();
final List<dynamic> users = service.data('users');
usersMention
.addAll(users.map((users) => UsersMention.fromJson(users)).toList());
users.clear();
final List<dynamic> resUsers = service.data('users');
users
.addAll(resUsers.map((user) => UsersMention.fromJson(user)).toList());
appState = AppState.idle;
return;
@ -81,169 +69,49 @@ class MentionsState extends CoreProvier {
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;
late List<MentionData> cList = 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,
),
);
}
cList.insert(
0,
MentionData(
id: 0,
fullName: user.fullName,
text: commentTextFieldController.text,
createdAt: DateTime.now().toString(),
mentions: mentionedUsers.map((user) => user.name).toList(),
),
);
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
"mentions": mentionedUsers.map((user) => user.id.toString()).toList(),
});
final service = RequestService(
RequestHelper.addComment(itemId, type),
RequestHelper.mention(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 = '';
cList.firstWhere((comment) => comment.id == 0).id =
service.result['comment']['id'];
mentionedUsers = [];
searchUsers = '';
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);
await service.delete();
if (status != 2) {
_count--;
}
}
} else {
comments
.firstWhere((element) => element.id == rootId)
.replies
.removeWhere((element) => element.id == id);
if (status != 2) {
_count--;
}
}
if (service.isSuccess) await getComments();
notifyListeners();
}

View File

@ -1,17 +1,14 @@
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';

View File

@ -1,4 +1,7 @@
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/views/mentions/mentions_state.dart';
import 'package:didvan/views/widgets/animated_visibility.dart';
import 'package:didvan/views/widgets/ink_wrapper.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
@ -6,7 +9,6 @@ import 'package:provider/provider.dart';
import '../../constants/app_icons.dart';
import '../../models/users_mention.dart';
import '../comments/comments_state.dart';
import 'didvan/text.dart';
class UserMention extends StatefulWidget {
@ -26,28 +28,62 @@ class UserMention extends StatefulWidget {
class _UserMentionState extends State<UserMention> {
@override
Widget build(BuildContext context) {
final state = context.watch<CommentsState>();
final state = context.watch<MentionsState>();
final userFound = state.mentionedUsers.contains(widget.user);
return InkWrapper(
onPressed: () {
state.usersMentioned = widget.user;
state.commentTextFieldController.text = state
.commentTextFieldController.text
.replaceAll(state.mentionedText.toString(), "")
.replaceAll("@", "");
state.showUsersForMentionsLayout = false;
if (userFound) {
state.mentionedUsers.remove(widget.user);
} else {
state.mentionedUsers.add(widget.user);
}
state.update();
},
child: Column(
children: [
const SizedBox(
height: 8,
),
Row(
children: [
Container(
width: 18,
height: 18,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(6),
border: Border.all(
width: 1.0,
color: userFound
? Theme.of(context).colorScheme.checkFav
: Theme.of(context).colorScheme.text),
),
margin: const EdgeInsets.all(8),
child: AnimatedVisibility(
isVisible: userFound,
duration: DesignConfig.mediumAnimationDuration,
child: Center(
child: Icon(
Icons.check,
size: 12,
color: userFound
? Theme.of(context).colorScheme.checkFav
: Theme.of(context).colorScheme.text,
),
),
),
),
widget.user.photo == null
? const Icon(DidvanIcons.avatar_light,size: 40,)
? const Icon(
DidvanIcons.avatar_light,
size: 40,
)
: SkeletonImage(
imageUrl: widget.user.photo.toString(),
height: 40,
width: 40,
height: 36,
width: 36,
borderRadius: BorderRadius.circular(360),
),
const SizedBox(
@ -62,7 +98,7 @@ class _UserMentionState extends State<UserMention> {
const SizedBox(
height: 8,
),
widget.index == state.usersMention.length - 1
widget.index == state.users.length - 1
? const SizedBox()
: Divider(
height: 2,