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.

394 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 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(116),
  101. child: Column(
  102. children: [
  103. SizedBox(height: context.height * 26 / AppConstants.instance.appHeight),
  104. Padding(
  105. padding: EdgeInsets.symmetric(
  106. horizontal: context.width * 26 / AppConstants.instance.appWidth,
  107. ),
  108. child: _searchMode
  109. ? Row(
  110. children: [
  111. Expanded(
  112. child: SearchWidget(
  113. search: (query) {
  114. _cubit.search(query: query);
  115. },
  116. ),
  117. ),
  118. SizedBox(width: context.width * 12 / AppConstants.instance.appWidth),
  119. GestureDetector(
  120. onTap: () {
  121. _cubit.clearSearch();
  122. setState(() {
  123. _searchMode = !_searchMode;
  124. });
  125. },
  126. child: SvgPicture.asset(
  127. 'ic_back'.svgPath,
  128. ),
  129. ),
  130. ],
  131. )
  132. : Stack(
  133. children: [
  134. Align(
  135. alignment: AlignmentDirectional.centerStart,
  136. child: GestureDetector(
  137. onTap: _showOptions,
  138. child: SvgPicture.asset(
  139. 'ic_more'.svgPath,
  140. ),
  141. ),
  142. ),
  143. PositionedDirectional(
  144. start: context.width * 44 / AppConstants.instance.appWidth,
  145. child: GestureDetector(
  146. onTap: () {
  147. setState(() {
  148. _searchMode = !_searchMode;
  149. });
  150. },
  151. child: SvgPicture.asset('ic_rounded_search'.svgPath),
  152. ),
  153. ),
  154. Align(
  155. alignment: AlignmentDirectional.center,
  156. child: Padding(
  157. padding: const EdgeInsets.only(top: 8),
  158. child: Text(
  159. widget.title,
  160. style: const TextStyle(
  161. color: Color(0xff404966),
  162. fontSize: 22,
  163. ),
  164. ),
  165. ),
  166. ),
  167. Align(
  168. alignment: AlignmentDirectional.centerEnd,
  169. child: GestureDetector(
  170. onTap: () {
  171. Navigator.pop(context);
  172. },
  173. child: RotatedBox(
  174. quarterTurns:
  175. Initializer.instance.getTextDirection() == TextDirection.ltr ? 90 : 0,
  176. child: SvgPicture.asset('ic_back'.svgPath),
  177. ),
  178. ),
  179. ),
  180. ],
  181. ),
  182. ),
  183. if (_searchMode)
  184. Padding(
  185. padding: EdgeInsets.only(
  186. top: context.height * 17 / AppConstants.instance.appHeight,
  187. left: context.width * 26 / AppConstants.instance.appWidth,
  188. right: context.width * 26 / AppConstants.instance.appWidth,
  189. ),
  190. child: Row(
  191. children: [
  192. SvgPicture.asset(
  193. 'ic_search'.svgPath,
  194. colorFilter: const ColorFilter.mode(
  195. Color(0xff26237A),
  196. BlendMode.srcIn,
  197. ),
  198. ),
  199. const SizedBox(width: 8),
  200. Text(
  201. '${Translator.translate('search')}:',
  202. style: const TextStyle(
  203. color: Color(0xff26237A),
  204. fontSize: 16,
  205. ),
  206. ),
  207. const SizedBox(width: 8),
  208. Expanded(
  209. child: Text(
  210. _cubit.query,
  211. maxLines: 1,
  212. overflow: TextOverflow.ellipsis,
  213. style: const TextStyle(
  214. color: Color(0xff26237A),
  215. fontSize: 16,
  216. ),
  217. ),
  218. ),
  219. const Spacer(),
  220. Text(
  221. _loading
  222. ? 'در حال جستجو'
  223. : _cubit.query.isEmpty
  224. ? ''
  225. : _cubit.searchedList.isEmpty
  226. ? 'موردی یافت نشد'
  227. : '${_cubit.searchedList.length} مورد یافت شد',
  228. style: const TextStyle(
  229. color: Color(0xff636E88),
  230. fontSize: 12,
  231. ),
  232. )
  233. ],
  234. ),
  235. ),
  236. SizedBox(height: context.height * 35 / AppConstants.instance.appHeight),
  237. SizedBox(
  238. height: context.height * 31 / AppConstants.instance.appHeight,
  239. child: ListView.builder(
  240. padding: EdgeInsetsDirectional.only(
  241. start: context.width * 26 / AppConstants.instance.appWidth,
  242. ),
  243. itemBuilder: (context, index) {
  244. return GestureDetector(
  245. onTap: () {
  246. _cubit.changeFilter(index);
  247. },
  248. child: FilterItemWidget(
  249. title: _filterList[index].title,
  250. selected: index == _selectedFilterIndex,
  251. ),
  252. );
  253. },
  254. itemCount: _filterList.length,
  255. scrollDirection: Axis.horizontal,
  256. ),
  257. ),
  258. SizedBox(height: context.height * 26 / AppConstants.instance.appHeight),
  259. ],
  260. ),
  261. ),
  262. ),
  263. SliverList(
  264. delegate: SliverChildBuilderDelegate(
  265. (context, index) {
  266. if (_loading) {
  267. return const LoadingListWidget();
  268. }
  269. return ListView.builder(
  270. shrinkWrap: true,
  271. physics: const NeverScrollableScrollPhysics(),
  272. itemBuilder: (context, index) {
  273. if (_searchMode) {
  274. return GestureDetector(
  275. onTap: () => _clickOnPost(_cubit.searchedList[index]),
  276. child: PostItemWidget(post: _cubit.searchedList[index]),
  277. );
  278. }
  279. return GestureDetector(
  280. onTap: () => _clickOnPost(_cubit.postList[index]),
  281. child: PostItemWidget(post: _cubit.postList[index]),
  282. );
  283. },
  284. itemCount: _searchMode ? _cubit.searchedList.length : _cubit.postList.length,
  285. );
  286. },
  287. childCount: 1,
  288. ),
  289. ),
  290. ],
  291. );
  292. },
  293. ),
  294. ),
  295. );
  296. }
  297. void _clickOnPost(Post post) {
  298. Navigator.push(
  299. context,
  300. MaterialPageRoute(
  301. builder: (context) {
  302. return BlocProvider(
  303. child: SinglePostScreen(post: post),
  304. create: (context) => SinglePostCubit(),
  305. );
  306. },
  307. ),
  308. );
  309. }
  310. void _showOptions() {
  311. showModalBottomSheet(
  312. context: context,
  313. backgroundColor: Colors.transparent,
  314. builder: (context) {
  315. return Container(
  316. height: 120,
  317. width: context.width,
  318. decoration: BoxDecoration(
  319. color: Colors.white,
  320. border: Border.all(color: Colors.white, width: 0.2),
  321. borderRadius: const BorderRadius.only(
  322. topLeft: Radius.circular(16),
  323. topRight: Radius.circular(16),
  324. ),
  325. ),
  326. padding: const EdgeInsets.only(
  327. top: 16,
  328. left: 4,
  329. right: 4,
  330. ),
  331. child: Column(
  332. children: [
  333. MoreOptionsWidget(
  334. title: Translator.translate('about_us'),
  335. onTap: () {
  336. Navigator.pop(context);
  337. Navigator.push(
  338. context,
  339. MaterialPageRoute(
  340. builder: (context) {
  341. return const AboutUsScreen();
  342. },
  343. ),
  344. );
  345. },
  346. ),
  347. const SizedBox(height: 8),
  348. Container(
  349. width: context.width,
  350. height: 1,
  351. color: const Color(0xffD3D8E9),
  352. ),
  353. const SizedBox(height: 8),
  354. MoreOptionsWidget(
  355. title: Translator.translate('select_language'),
  356. onTap: () {
  357. Navigator.pop(context);
  358. Navigator.push(
  359. context,
  360. MaterialPageRoute(
  361. builder: (context) {
  362. return BlocProvider(
  363. child: const SelectLanguageScreen(),
  364. create: (context) => SelectLanguageCubit(),
  365. );
  366. },
  367. ),
  368. );
  369. },
  370. ),
  371. ],
  372. ),
  373. );
  374. },
  375. );
  376. }
  377. }
  378. class FilterItem {
  379. final bool selected;
  380. final String title;
  381. const FilterItem({required this.selected, required this.title});
  382. }