Houshan-Basa/lib/ui/screens/main/forum/forum_screen.dart

1087 lines
50 KiB
Dart

// ignore_for_file: deprecated_member_use_from_same_package, use_build_context_synchronously
import 'package:cross_file/cross_file.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:go_router/go_router.dart';
import 'package:hoshan/core/gen/assets.gen.dart';
import 'package:hoshan/core/services/api/dio_service.dart';
import 'package:hoshan/core/utils/date_time.dart';
import 'package:hoshan/core/utils/strings.dart';
import 'package:hoshan/data/model/empty_states_enum.dart';
import 'package:hoshan/data/model/forum_model.dart';
import 'package:hoshan/data/model/sort_by_model.dart';
import 'package:hoshan/data/model/tools_categories_model.dart';
import 'package:hoshan/data/repository/forum_repository.dart';
import 'package:hoshan/ui/screens/main/forum/cubit/category_cubit.dart';
import 'package:hoshan/ui/screens/main/forum/cubit/comments_cubit.dart';
import 'package:hoshan/ui/screens/main/forum/cubit/replies_cubit.dart';
import 'package:hoshan/ui/screens/splash/cubit/user_info_cubit.dart';
import 'package:hoshan/ui/theme/colors.dart';
import 'package:hoshan/ui/theme/cubit/theme_mode_cubit.dart';
import 'package:hoshan/ui/theme/text.dart';
import 'package:hoshan/ui/widgets/components/dialog/bottom_sheets.dart';
import 'package:hoshan/ui/widgets/components/image/network_image.dart';
import 'package:hoshan/ui/widgets/sections/empty/empty_states.dart';
import 'package:hoshan/ui/widgets/sections/loading/default_placeholder.dart';
class ForumScreen extends StatefulWidget {
const ForumScreen({super.key});
@override
State<ForumScreen> createState() => _ForumScreenState();
}
class _ForumScreenState extends State<ForumScreen>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
final ScrollController scrollController = ScrollController();
void loadCommentsFromCategory(int id) {
if (id == CommentsCubit.categoriesId) return;
if (context.read<CommentsCubit>().state is CommentsInitial) return;
context.read<CommentsCubit>().loadComments(cId: id, retry: true);
}
void loadCommentsFromSortBy(SortByModel sortBy) {
context.read<CommentsCubit>().loadComments(
cId: CommentsCubit.categoriesId, retry: true, orderBy: sortBy);
}
Future sendComment({required String message, XFile? file}) async {
try {
final comment = await ForumRepository.sendForum(
text: message,
categoryId: CommentsCubit.categoriesId,
image: file,
);
if (mounted) {
final userInfo = UserInfoCubit.userInfoModel;
final user = User(
id: userInfo.id,
image: userInfo.image,
name: userInfo.name,
username: userInfo.username);
context
.read<CommentsCubit>()
.addComment(comment: comment.copyWith(user: user));
context.pop();
}
} on DioException catch (e) {
if (kDebugMode) {
print('Dio Error is: $e');
}
}
}
Future sendReply(
{required BuildContext context,
required String message,
required int parentId,
required String repliedUserId,
XFile? file}) async {}
@override
void initState() {
super.initState();
scrollController.addListener(
() {
if (scrollController.position.pixels ==
scrollController.position.maxScrollExtent &&
context.read<CommentsCubit>().state is CommentsSuccess) {
context
.read<CommentsCubit>()
.loadComments(cId: CommentsCubit.categoriesId);
}
},
);
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
floatingActionButton: FloatingActionButton.small(
heroTag: 'add-comment-fb',
onPressed: () async {
await BottomSheetHandler(context).showAddComment(
onSend: (message, file) async =>
sendComment(message: message, file: file),
);
},
shape: const CircleBorder(),
backgroundColor: AppColors.primaryColor.defaultShade,
child: const Icon(
CupertinoIcons.add,
color: Colors.white,
),
),
body: RefreshIndicator(
backgroundColor: Theme.of(context).colorScheme.surface,
color: Theme.of(context).colorScheme.primary,
onRefresh: () async {
// context.read<CategoryCubit>().getAllCategorie();
context
.read<CommentsCubit>()
.loadComments(cId: CommentsCubit.categoriesId, retry: true);
scrollController.jumpTo(0);
},
child: SingleChildScrollView(
controller: scrollController,
physics: context.watch<CommentsCubit>().state is CommentsInitial
? const NeverScrollableScrollPhysics()
: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
child: Column(
children: [
BlocBuilder<CategoryCubit, CategoryState>(
builder: (context, state) {
if (state is CategoryLoading) {
return categoriesPlaceholder();
}
if (state is CategorySuccess) {
final categories = state.categories;
List<Categories> oddCategories = [];
List<Categories> evenCategories = [];
for (int i = 0; i < categories.length; i++) {
if (i % 2 == 0) {
evenCategories.add(categories[i]);
} else {
oddCategories.add(categories[i]);
}
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Directionality(
textDirection: TextDirection.rtl,
child: Stack(
children: [
SizedBox(
width: MediaQuery.sizeOf(context).width,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding:
const EdgeInsets.fromLTRB(120, 0, 16, 0),
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (evenCategories.isNotEmpty)
SizedBox(
height: 36,
child: Row(
children: [
...List.generate(
evenCategories.length,
(index) {
final cat =
evenCategories[index];
return categoryContainer(cat);
},
)
],
),
),
const SizedBox(
height: 8,
),
if (oddCategories.isNotEmpty)
SizedBox(
height: 36,
child: Row(
children: [
const SizedBox(
width: 16,
),
...List.generate(
oddCategories.length,
(index) {
final cat =
oddCategories[index];
return categoryContainer(cat);
},
)
],
),
),
],
),
),
),
if (context.watch<CommentsCubit>().state
is CommentsSuccess ||
context.watch<CommentsCubit>().state
is CommentsFail)
Positioned(
left: 0,
bottom: 0,
top: 0,
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
blurRadius: 16,
color: Theme.of(context)
.scaffoldBackgroundColor,
spreadRadius: 10)
],
color: Theme.of(context)
.scaffoldBackgroundColor,
gradient: RadialGradient(colors: [
Theme.of(context)
.scaffoldBackgroundColor,
Theme.of(context)
.scaffoldBackgroundColor
.withValues(alpha: 0.7),
Theme.of(context)
.scaffoldBackgroundColor
.withValues(alpha: 0.6),
])),
child: GestureDetector(
onTap: () =>
BottomSheetHandler(context).showSortBy(
initailValue: CommentsCubit.sortByModel,
items: [
SortByModel(
text: 'جدیدترین', value: 'date'),
SortByModel(
text: 'پربحث‌ترین‌ها',
value: 'replies'),
],
onSelected: (item) =>
loadCommentsFromSortBy(item),
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16),
child: Icon(
CupertinoIcons.sort_down,
color: Theme.of(context)
.colorScheme
.onSurface,
)),
),
),
)
],
),
),
);
}
return const SizedBox.shrink();
},
),
const SizedBox(
height: 12,
),
BlocBuilder<CommentsCubit, CommentsState>(
builder: (context, state) {
if (state is CommentsInitial) {
return ListView.builder(
itemCount: 20,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return commentContainerPlaceholder();
},
);
}
if (state.comments.isEmpty) {
return Center(
child: EmptyStates.getEmptyState(
status: EmptyStatesEnum.messages));
}
return Column(
children: [
ListView.builder(
itemCount: state.comments.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final comment = state.comments[index];
return BlocProvider<RepliesCubit>(
create: (context) => RepliesCubit(),
child: Builder(builder: (context) {
return Column(
children: [
commentContainer(context, comment),
Padding(
padding: const EdgeInsets.only(right: 24),
child:
BlocBuilder<RepliesCubit, RepliesState>(
builder: (context, state) {
return Column(
children: [
if (state.replies.isNotEmpty)
ListView.builder(
physics:
const NeverScrollableScrollPhysics(),
itemCount: state.replies.length,
shrinkWrap: true,
itemBuilder: (context, index) {
final reply =
state.replies[index];
return commentContainer(
context,
reply,
parrentComment: comment,
);
},
),
BlocBuilder<RepliesCubit,
RepliesState>(
builder: (context, state) {
return Column(
children: [
if (state.lastPage !=
null &&
state.page >
state.lastPage!)
Padding(
padding:
const EdgeInsets
.symmetric(
vertical: 12.0),
child: GestureDetector(
onTap: () {
if (comment.replies !=
null &&
comment.replies! >
0) {
if (state.lastPage !=
null &&
state.page >
state
.lastPage!) {
final lastPosition =
scrollController
.position;
context
.read<
RepliesCubit>()
.clear();
scrollController.jumpTo(scrollController
.position
.pixels -
lastPosition
.pixels);
} else {
context
.read<
RepliesCubit>()
.loadReplies(
comment:
comment);
}
}
},
child: Row(
children: [
const Expanded(
flex: 6,
child:
Divider()),
if (comment.replies !=
null &&
comment.replies! >
0)
Padding(
padding: const EdgeInsets
.symmetric(
horizontal:
8.0),
child: Text(
'پنهان کردن پاسخ‌ها',
style: AppTextStyles.body4.copyWith(
color: AppColors
.gray[context
.read<ThemeModeCubit>()
.isDark()
? 600
: 900]),
),
),
const Expanded(
child:
Divider()),
],
),
),
),
],
);
},
),
],
);
},
),
)
],
);
}),
);
},
),
if (state is CommentsLoading)
Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: LinearProgressIndicator(
color: AppColors.primaryColor.defaultShade,
borderRadius: BorderRadius.circular(8),
),
),
const SizedBox(
height: 36,
)
],
);
},
)
],
),
),
),
);
}
GestureDetector categoryContainer(Categories cat) {
return GestureDetector(
onTap: () => loadCommentsFromCategory(cat.id!),
child: Container(
padding: const EdgeInsets.all(8),
margin: const EdgeInsets.symmetric(horizontal: 4),
constraints: const BoxConstraints(minWidth: 100),
alignment: Alignment.center,
decoration: BoxDecoration(
color: CommentsCubit.categoriesId == cat.id
? AppColors.primaryColor[
context.read<ThemeModeCubit>().isDark() ? 500 : 50]
: AppColors
.gray[context.read<ThemeModeCubit>().isDark() ? 900 : 400],
borderRadius: BorderRadius.circular(360)),
child: Text(
cat.name ?? '',
style: AppTextStyles.body4.copyWith(
color: context.read<ThemeModeCubit>().isDark()
? Colors.white
: CommentsCubit.categoriesId == cat.id
? AppColors.primaryColor.defaultShade
: AppColors.black[300]),
),
),
);
}
Widget commentContainer(BuildContext context, Comment comment,
{final Comment? parrentComment}) {
final daysAgo = DateTimeUtils.getDaysBetweenNowAnd(comment.createdAt!);
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: parrentComment != null
? Theme.of(context).colorScheme.surface
: context.read<ThemeModeCubit>().isDark()
? AppColors.black[900]
: AppColors.primaryColor[50],
borderRadius: BorderRadius.circular(16)),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
comment.createdAt != null
? Row(
children: [
Assets.icon.outline.clock.svg(
width: 20,
height: 20,
color: AppColors.gray[context
.read<ThemeModeCubit>()
.isDark()
? 600
: 900]),
const SizedBox(
width: 8,
),
Text(
daysAgo == 0
? 'امروز'
: ('$daysAgo روز پیش'),
style: AppTextStyles.body5.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface),
textDirection: TextDirection.rtl,
)
],
)
: const SizedBox.shrink(),
Row(
children: [
Text(
comment.user?.username ?? '',
style: AppTextStyles.body4.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context)
.colorScheme
.onSurface),
),
const SizedBox(
width: 8,
),
],
)
],
),
const SizedBox(
height: 8,
),
SizedBox(
width: MediaQuery.sizeOf(context).width,
child: Text(
comment.text ?? '',
style: AppTextStyles.body3.copyWith(
color: Theme.of(context).colorScheme.onSurface),
textDirection: comment.text != null &&
comment.text!.startsWithEnglish()
? TextDirection.ltr
: TextDirection.rtl,
),
),
const SizedBox(
height: 8,
),
if (comment.image != null)
Column(
children: [
Container(
margin: const EdgeInsets.symmetric(vertical: 12),
constraints: BoxConstraints(
maxHeight:
MediaQuery.sizeOf(context).height * 0.2),
alignment: Alignment.centerRight,
child: AspectRatio(
aspectRatio: 16 / 9,
child: ImageNetwork(
url: DioService.baseURL + comment.image!,
radius: 16,
showHero: true,
),
),
),
const SizedBox(
height: 8,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
GestureDetector(
onTap: () {
BottomSheetHandler(context)
.showReportOptions();
},
child: commentBtn(
icon: Assets.icon.outline.flag2,
text: 'گزارش')),
const SizedBox(
width: 12,
),
// BlocProvider<CommentLikeCubit>(
// create: (context) =>
// CommentLikeCubit()..getLike(comment.userFeedback),
// child: BlocConsumer<CommentLikeCubit, CommentLikeState>(
// listener: (context, state) {
// int likes = comment.likes!;
// int disLikes = comment.dislikes!;
// if (state.like != null) {
// likes = likes + state.like!;
// }
// if (state.disLike != null) {
// disLikes = disLikes + state.disLike!;
// }
// context.read<CommentsCubit>().changeComment(
// newComment: comment.copyWith(
// likes: likes,
// dislikes: disLikes,
// userFeedback: state is CommentLiked
// ? 1
// : state is CommentDisLiked
// ? 0
// : -1));
// },
// builder: (context, state) {
// return Row(
// children: [
// DefaultPlaceHolder(
// enabled: state is CommentLikeLoading,
// child: GestureDetector(
// onTap: () => context
// .read<CommentLikeCubit>()
// .setLiked(
// id: comment.id!,
// status:
// state is CommentLiked ? -1 : 1),
// child: commentBtn(
// color: state is CommentLiked
// ? AppColors.green.defaultShade
// : null,
// icon: state is CommentLiked
// ? Assets.icon.bold.like
// : Assets.icon.outline.like,
// text: comment.likes?.toString() ?? '0'),
// ),
// ),
// const SizedBox(
// width: 12,
// ),
// DefaultPlaceHolder(
// enabled: state is CommentLikeLoading,
// child: GestureDetector(
// onTap: () => context
// .read<CommentLikeCubit>()
// .setLiked(
// id: comment.id!,
// status: state is CommentDisLiked
// ? -1
// : 0),
// child: commentBtn(
// color: state is CommentDisLiked
// ? AppColors.red.defaultShade
// : null,
// icon: state is CommentDisLiked
// ? Assets.icon.bold.dislike
// : Assets.icon.outline.dislike,
// text:
// comment.dislikes?.toString() ?? '0'),
// ),
// ),
// ],
// );
// },
// ),
// ),
// const SizedBox(
// width: 12,
// ),
if (comment.replies != null &&
parrentComment == null &&
comment.replies != 0)
commentBtn(
icon: Assets.icon.outline.messageText,
text: comment.replies!.toString()),
],
),
GestureDetector(
onTap: () async {
await BottomSheetHandler(context).showAddComment(
comment: comment,
onSend: (message, file) async {
try {
final commentRes =
await ForumRepository.sendForum(
text: message,
categoryId:
CommentsCubit.categoriesId,
image: file,
parentId: parrentComment?.id ??
comment.id,
repliedUserId:
parrentComment?.user!.id ??
comment.user!.id);
if (mounted) {
final userInfo =
UserInfoCubit.userInfoModel;
final user = User(
id: userInfo.id,
image: userInfo.image,
name: userInfo.name,
username: userInfo.username);
context.read<RepliesCubit>().addReply(
parent: comment,
comment:
commentRes.copyWith(user: user));
context.read<CommentsCubit>().addReplies(
commentId: parrentComment?.id! ??
comment.id!);
context.pop();
}
} on DioException catch (e) {
if (kDebugMode) {
print('Dio Error is: $e');
}
}
},
);
},
child: Text('پاسخ دادن',
style: AppTextStyles.body4.copyWith(
color: Theme.of(context).colorScheme.primary,
)),
)
],
),
],
),
),
const SizedBox(
width: 12,
),
ImageNetwork(
url: comment.user != null && comment.user!.image != null
? DioService.baseURL + comment.user!.image!
: 'https://placehold.co/600x400',
width: 40,
height: 40,
radius: 360,
)
],
),
),
if (comment.replies != null && comment.replies! > 0)
BlocBuilder<RepliesCubit, RepliesState>(
builder: (context, state) {
if (state.lastPage != null && state.page > state.lastPage!) {
return const SizedBox();
}
return Column(
children: [
state is RepliesLoading
? Padding(
padding: const EdgeInsets.only(top: 24.0),
child: SpinKitThreeBounce(
color: AppColors.primaryColor.defaultShade,
size: 32,
),
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: GestureDetector(
onTap: () {
if (comment.replies != null &&
comment.replies! > 0) {
if (state.lastPage != null &&
state.page > state.lastPage!) {
final lastPosition =
scrollController.position;
context.read<RepliesCubit>().clear();
scrollController.jumpTo(
scrollController.position.pixels -
lastPosition.pixels);
} else {
context
.read<RepliesCubit>()
.loadReplies(comment: comment);
}
}
},
child: Row(
children: [
const Expanded(flex: 6, child: Divider()),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0),
child: Text(
'مشاهده پاسخ‌ها',
style: AppTextStyles.body4.copyWith(
color: AppColors.gray[context
.read<ThemeModeCubit>()
.isDark()
? 600
: 900]),
),
),
const Expanded(child: Divider()),
],
),
),
),
],
);
},
),
],
),
);
}
Padding categoriesPlaceholder() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Directionality(
textDirection: TextDirection.rtl,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
child: Column(
children: [
SizedBox(
height: 36,
child: Row(
children: [
...List.generate(
20,
(index) {
return DefaultPlaceHolder(
child: Container(
padding: const EdgeInsets.all(8),
margin: const EdgeInsets.symmetric(horizontal: 4),
constraints: const BoxConstraints(minWidth: 100),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(360)),
child: Text('$index'),
),
);
},
)
],
),
),
const SizedBox(
height: 8,
),
SizedBox(
height: 36,
child: Row(
children: [
const SizedBox(
width: 24,
),
...List.generate(
20,
(index) {
return DefaultPlaceHolder(
child: Container(
padding: const EdgeInsets.all(8),
margin: const EdgeInsets.symmetric(horizontal: 4),
constraints: const BoxConstraints(minWidth: 100),
alignment: Alignment.center,
decoration: BoxDecoration(
color: AppColors.gray[400],
borderRadius: BorderRadius.circular(360)),
child: Text('$index'),
),
);
},
)
],
),
),
],
),
),
),
);
}
Container commentContainerPlaceholder() {
return Container(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
DefaultPlaceHolder(
child: Assets.icon.outline.clock.svg(
width: 20,
height: 20,
color: AppColors.gray[900]),
),
const SizedBox(
width: 8,
),
DefaultPlaceHolder(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.white,
),
child: Text(
'time ago',
style: AppTextStyles.body5,
),
),
)
],
),
Row(
children: [
DefaultPlaceHolder(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.white,
),
child: Text(
'this is a username',
style: AppTextStyles.body4
.copyWith(fontWeight: FontWeight.bold),
),
),
),
const SizedBox(
width: 8,
),
],
)
],
),
const SizedBox(
height: 8,
),
DefaultPlaceHolder(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.white,
),
child: Text(
'بات‌های تولید عکس می‌تونه تصاویری با سبک‌های مختلف مثل نقاشی، سه‌بعدی یا رئال تولید کنه؟🎨🎨🎨',
style: AppTextStyles.body3,
textDirection: TextDirection.rtl,
),
),
),
const SizedBox(
height: 8,
),
Row(
children: [
Row(
children: [
DefaultPlaceHolder(
child: commentBtn(
icon: Assets.icon.outline.flag2,
text: 'گزارش'),
),
const SizedBox(
width: 12,
),
DefaultPlaceHolder(
child: commentBtn(
icon: Assets.icon.outline.like, text: '---'),
),
const SizedBox(
width: 12,
),
DefaultPlaceHolder(
child: commentBtn(
icon: Assets.icon.outline.dislike,
text: '---'),
),
const SizedBox(
width: 12,
),
DefaultPlaceHolder(
child: commentBtn(
icon: Assets.icon.outline.messageText,
text: '---'),
),
],
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
DefaultPlaceHolder(
child: Text('پاسخ دادن',
style: AppTextStyles.body4.copyWith(
color: AppColors.primaryColor.defaultShade,
)),
)
],
)
],
),
),
const SizedBox(
width: 12,
),
const DefaultPlaceHolder(
child: ImageNetwork(
url: 'https://placehold.co/600x400',
width: 40,
height: 40,
radius: 360,
),
)
],
),
],
),
);
}
Row commentBtn(
{required final SvgGenImage icon,
final String? text,
final Color? color}) {
return Row(
children: [
icon.svg(
width: 18,
height: 18,
color: color ??
AppColors
.gray[context.read<ThemeModeCubit>().isDark() ? 600 : 900]),
if (text != null)
Row(
children: [
const SizedBox(
width: 4,
),
Text(
text,
style: AppTextStyles.body4.copyWith(
color: AppColors.gray[
context.read<ThemeModeCubit>().isDark() ? 600 : 900]),
),
],
)
],
);
}
}