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.

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