Sonnat Project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

392 lines
16 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_bloc/flutter_bloc.dart';
  3. import 'package:flutter_svg/flutter_svg.dart';
  4. import 'package:sonnat/core/extensions/context_extension.dart';
  5. import 'package:sonnat/core/extensions/string_extension.dart';
  6. import 'package:sonnat/core/language/translator.dart';
  7. import 'package:sonnat/core/select_language/cubit/select_language_cubit.dart';
  8. import 'package:sonnat/core/select_language/screen/select_language_screen.dart';
  9. import 'package:sonnat/core/utils/app_constants.dart';
  10. import 'package:sonnat/core/utils/base_cubit_type.dart';
  11. import 'package:sonnat/core/utils/initializer.dart';
  12. import 'package:sonnat/core/widgets/loading_list_widget.dart';
  13. import 'package:sonnat/core/widgets/more_options_widget.dart';
  14. import 'package:sonnat/features/about_us/about_us_screen.dart';
  15. import 'package:sonnat/features/posts/cubit/posts_cubit.dart';
  16. import 'package:sonnat/features/posts/widgets/filter_item_widget.dart';
  17. import 'package:sonnat/features/posts/widgets/post_item_widget.dart';
  18. import 'package:sonnat/features/posts/widgets/search_widget.dart';
  19. import 'package:sonnat/features/single_post/cubit/single_post_cubit.dart';
  20. import 'package:sonnat/features/single_post/screen/single_post_screen.dart';
  21. import 'package:sonnat/features/single_post/view_models/post.dart';
  22. class PostsScreen extends StatefulWidget {
  23. final String title;
  24. final bool searchMode;
  25. const PostsScreen({super.key, required this.title, this.searchMode = false});
  26. @override
  27. State<PostsScreen> createState() => _PostsScreenState();
  28. }
  29. class _PostsScreenState extends State<PostsScreen> {
  30. late final PostsCubit _cubit;
  31. bool _loading = true;
  32. bool _getListFlag = false;
  33. late bool _searchMode;
  34. final ScrollController _controller = ScrollController();
  35. int _selectedFilterIndex = 1;
  36. final List<FilterItem> _filterList = [
  37. const FilterItem(selected: false, title: 'پر تکرارترین‌ها'),
  38. const FilterItem(selected: true, title: 'مشابه‌ها'),
  39. const FilterItem(selected: false, title: 'محبوبترین‌ها'),
  40. const FilterItem(selected: false, title: 'برای‌ شما'),
  41. const FilterItem(selected: false, title: 'محبوبترین‌ها'),
  42. const FilterItem(selected: false, title: 'پر تکرارترین‌ها'),
  43. const FilterItem(selected: true, title: 'مشابه‌ها'),
  44. const FilterItem(selected: false, title: 'محبوبترین‌ها'),
  45. const FilterItem(selected: false, title: 'برای‌ شما'),
  46. const FilterItem(selected: false, title: 'محبوبترین‌ها'),
  47. ];
  48. @override
  49. void initState() {
  50. _searchMode = widget.searchMode;
  51. _cubit = BlocProvider.of<PostsCubit>(context);
  52. _cubit.getData();
  53. _controller.addListener(_onScroll);
  54. super.initState();
  55. }
  56. void _onScroll() {
  57. final double maxScroll = _controller.position.maxScrollExtent;
  58. final double currentScroll = _controller.position.pixels;
  59. if (maxScroll - currentScroll <= 400) {
  60. if (_getListFlag) {
  61. _getListFlag = false;
  62. _cubit.getData();
  63. }
  64. }
  65. }
  66. @override
  67. Widget build(BuildContext context) {
  68. return Scaffold(
  69. body: SafeArea(
  70. child: BlocBuilder<PostsCubit, BaseCubitType<PostsState>>(
  71. builder: (context, state) {
  72. switch (state.eventName!) {
  73. case PostsState.empty:
  74. break;
  75. case PostsState.data:
  76. _loading = false;
  77. _getListFlag = true;
  78. break;
  79. case PostsState.loading:
  80. _loading = true;
  81. break;
  82. case PostsState.loadingPagination:
  83. break;
  84. case PostsState.changeFilterIndex:
  85. _selectedFilterIndex = state.data;
  86. _loading = true;
  87. break;
  88. }
  89. return CustomScrollView(
  90. controller: _controller,
  91. slivers: [
  92. SliverAppBar(
  93. floating: true,
  94. elevation: 0,
  95. backgroundColor: const Color(0xffE7E7F5),
  96. automaticallyImplyLeading: false,
  97. forceElevated: false,
  98. shadowColor: Colors.transparent,
  99. bottom: PreferredSize(
  100. preferredSize: const Size.fromHeight(95),
  101. child: Column(
  102. children: [
  103. SizedBox(height: context.height * 26 / AppConstants.instance.appHeight),
  104. Padding(
  105. padding:
  106. EdgeInsets.symmetric(horizontal: context.width * 26 / AppConstants.instance.appWidth),
  107. child: _searchMode
  108. ? Row(
  109. children: [
  110. Expanded(
  111. child: SearchWidget(
  112. search: (query) {
  113. _cubit.search(query: query);
  114. },
  115. ),
  116. ),
  117. SizedBox(width: context.width * 12 / AppConstants.instance.appWidth),
  118. GestureDetector(
  119. onTap: () {
  120. _cubit.clearSearch();
  121. setState(() {
  122. _searchMode = !_searchMode;
  123. });
  124. },
  125. child: SvgPicture.asset(
  126. 'ic_back'.svgPath,
  127. ),
  128. ),
  129. ],
  130. )
  131. : Stack(
  132. children: [
  133. Align(
  134. alignment: AlignmentDirectional.centerStart,
  135. child: GestureDetector(
  136. onTap: _showOptions,
  137. child: SvgPicture.asset(
  138. 'ic_more'.svgPath,
  139. ),
  140. ),
  141. ),
  142. PositionedDirectional(
  143. start: context.width * 44 / AppConstants.instance.appWidth,
  144. child: GestureDetector(
  145. onTap: () {
  146. setState(() {
  147. _searchMode = !_searchMode;
  148. });
  149. },
  150. child: SvgPicture.asset('ic_rounded_search'.svgPath),
  151. ),
  152. ),
  153. Align(
  154. alignment: AlignmentDirectional.center,
  155. child: Padding(
  156. padding: const EdgeInsets.only(top: 8),
  157. child: Text(
  158. widget.title,
  159. style: const TextStyle(
  160. color: Color(0xff404966),
  161. fontSize: 22,
  162. ),
  163. ),
  164. ),
  165. ),
  166. Align(
  167. alignment: AlignmentDirectional.centerEnd,
  168. child: GestureDetector(
  169. onTap: () {
  170. Navigator.pop(context);
  171. },
  172. child: RotatedBox(
  173. quarterTurns:
  174. Initializer.instance.getTextDirection() == TextDirection.ltr ? 90 : 0,
  175. child: SvgPicture.asset('ic_back'.svgPath),
  176. ),
  177. ),
  178. ),
  179. ],
  180. ),
  181. ),
  182. if (_searchMode)
  183. Padding(
  184. padding: EdgeInsets.only(
  185. top: context.height * 17 / AppConstants.instance.appHeight,
  186. left: context.width * 26 / AppConstants.instance.appWidth,
  187. right: context.width * 26 / AppConstants.instance.appWidth,
  188. ),
  189. child: Row(
  190. children: [
  191. SvgPicture.asset(
  192. 'ic_search'.svgPath,
  193. colorFilter: const ColorFilter.mode(
  194. Color(0xff26237A),
  195. BlendMode.srcIn,
  196. ),
  197. ),
  198. const SizedBox(width: 8),
  199. Text(
  200. '${Translator.translate('search')}:',
  201. style: const TextStyle(
  202. color: Color(0xff26237A),
  203. fontSize: 16,
  204. ),
  205. ),
  206. const SizedBox(width: 8),
  207. Expanded(
  208. child: Text(
  209. _cubit.query,
  210. maxLines: 1,
  211. overflow: TextOverflow.ellipsis,
  212. style: const TextStyle(
  213. color: Color(0xff26237A),
  214. fontSize: 16,
  215. ),
  216. ),
  217. ),
  218. const Spacer(),
  219. Text(
  220. _loading
  221. ? 'در حال جستجو'
  222. : _cubit.query.isEmpty
  223. ? ''
  224. : _cubit.searchedList.isEmpty
  225. ? 'موردی یافت نشد'
  226. : '${_cubit.searchedList.length} مورد یافت شد',
  227. style: const TextStyle(
  228. color: Color(0xff636E88),
  229. fontSize: 12,
  230. ),
  231. )
  232. ],
  233. ),
  234. ),
  235. SizedBox(height: context.height * 35 / AppConstants.instance.appHeight),
  236. SizedBox(
  237. height: context.height * 31 / AppConstants.instance.appHeight,
  238. child: ListView.builder(
  239. padding:
  240. EdgeInsetsDirectional.only(start: context.width * 26 / AppConstants.instance.appWidth),
  241. itemBuilder: (context, index) {
  242. return GestureDetector(
  243. onTap: () {
  244. _cubit.changeFilter(index);
  245. },
  246. child: FilterItemWidget(
  247. title: _filterList[index].title,
  248. selected: index == _selectedFilterIndex,
  249. ),
  250. );
  251. },
  252. itemCount: _filterList.length,
  253. scrollDirection: Axis.horizontal,
  254. ),
  255. ),
  256. SizedBox(height: context.height * 26 / AppConstants.instance.appHeight),
  257. ],
  258. ),
  259. ),
  260. ),
  261. SliverList(
  262. delegate: SliverChildBuilderDelegate(
  263. (context, index) {
  264. if (_loading) {
  265. return const Expanded(child: LoadingListWidget());
  266. }
  267. return ListView.builder(
  268. shrinkWrap: true,
  269. physics: NeverScrollableScrollPhysics(),
  270. itemBuilder: (context, index) {
  271. if (_searchMode) {
  272. return GestureDetector(
  273. onTap: () => _clickOnPost(_cubit.searchedList[index]),
  274. child: PostItemWidget(post: _cubit.searchedList[index]),
  275. );
  276. }
  277. return GestureDetector(
  278. onTap: () => _clickOnPost(_cubit.postList[index]),
  279. child: PostItemWidget(post: _cubit.postList[index]),
  280. );
  281. },
  282. itemCount: _searchMode ? _cubit.searchedList.length : _cubit.postList.length,
  283. );
  284. },
  285. childCount: 1,
  286. ),
  287. ),
  288. ],
  289. );
  290. },
  291. ),
  292. ),
  293. );
  294. }
  295. void _clickOnPost(Post post) {
  296. Navigator.push(
  297. context,
  298. MaterialPageRoute(
  299. builder: (context) {
  300. return BlocProvider(
  301. child: SinglePostScreen(post: post),
  302. create: (context) => SinglePostCubit(),
  303. );
  304. },
  305. ),
  306. );
  307. }
  308. void _showOptions() {
  309. showModalBottomSheet(
  310. context: context,
  311. backgroundColor: Colors.transparent,
  312. builder: (context) {
  313. return Container(
  314. height: 120,
  315. width: context.width,
  316. decoration: BoxDecoration(
  317. color: Colors.white,
  318. border: Border.all(color: Colors.white, width: 0.2),
  319. borderRadius: const BorderRadius.only(
  320. topLeft: Radius.circular(16),
  321. topRight: Radius.circular(16),
  322. ),
  323. ),
  324. padding: const EdgeInsets.only(
  325. top: 16,
  326. left: 4,
  327. right: 4,
  328. ),
  329. child: Column(
  330. children: [
  331. MoreOptionsWidget(
  332. title: Translator.translate('about_us'),
  333. onTap: () {
  334. Navigator.pop(context);
  335. Navigator.push(
  336. context,
  337. MaterialPageRoute(
  338. builder: (context) {
  339. return const AboutUsScreen();
  340. },
  341. ),
  342. );
  343. },
  344. ),
  345. const SizedBox(height: 8),
  346. Container(
  347. width: context.width,
  348. height: 1,
  349. color: const Color(0xffD3D8E9),
  350. ),
  351. const SizedBox(height: 8),
  352. MoreOptionsWidget(
  353. title: Translator.translate('select_language'),
  354. onTap: () {
  355. Navigator.pop(context);
  356. Navigator.push(
  357. context,
  358. MaterialPageRoute(
  359. builder: (context) {
  360. return BlocProvider(
  361. child: const SelectLanguageScreen(),
  362. create: (context) => SelectLanguageCubit(),
  363. );
  364. },
  365. ),
  366. );
  367. },
  368. ),
  369. ],
  370. ),
  371. );
  372. },
  373. );
  374. }
  375. }
  376. class FilterItem {
  377. final bool selected;
  378. final String title;
  379. const FilterItem({required this.selected, required this.title});
  380. }