home_page.dart 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. import 'package:cpt_main/modules/home/home_state.dart';
  2. import 'package:cs_resources/generated/assets.dart';
  3. import 'package:cs_resources/generated/l10n.dart';
  4. import 'package:cs_resources/theme/app_colors_theme.dart';
  5. import 'package:domain/entity/home_list_entity.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:flutter/src/widgets/framework.dart';
  8. import 'package:flutter_hooks/flutter_hooks.dart';
  9. import 'package:hooks_riverpod/hooks_riverpod.dart';
  10. import 'package:auto_route/auto_route.dart';
  11. import 'package:plugin_platform/engine/toast/toast_engine.dart';
  12. import 'package:router/componentRouter/community_service.dart';
  13. import 'package:router/componentRouter/component_service_manager.dart';
  14. import 'package:widgets/ext/ex_widget.dart';
  15. import 'package:widgets/my_appbar.dart';
  16. import 'package:widgets/my_load_image.dart';
  17. import 'package:widgets/my_text_view.dart';
  18. import 'package:widgets/widget_export.dart';
  19. import 'item_home_category.dart';
  20. import 'home_view_model.dart';
  21. import 'item_home_last_news.dart';
  22. import 'item_home_last_trans.dart';
  23. import 'item_home_manage_guide.dart';
  24. import 'item_home_property_news.dart';
  25. import 'latest_news/info/latest_news_info_screen.dart';
  26. import 'latest_news/internal/latest_news_internal_screen.dart';
  27. import 'latest_news/property/latest_news_property_screen.dart';
  28. import 'latest_news/publish/latest_news_publish_screen.dart';
  29. @RoutePage()
  30. class HomePage extends HookConsumerWidget {
  31. const HomePage({super.key});
  32. @override
  33. Widget build(BuildContext context, WidgetRef ref) {
  34. final viewModel = ref.read(homeViewModelProvider.notifier);
  35. final state = ref.watch(homeViewModelProvider);
  36. final bannerIndex = useState(0);
  37. useEffect(() {
  38. // 组件挂载时执行 - 执行接口请求
  39. Future.microtask(() => viewModel.fetchHomeIndex());
  40. return () {
  41. // 组件卸载时执行
  42. };
  43. }, []);
  44. return Scaffold(
  45. appBar: MyAppBar.appBar(context, "Good Afternoon,Mike",
  46. showBackButton: false,
  47. backgroundColor: context.appColors.backgroundWhite,
  48. actions: [
  49. Center(
  50. child: Stack(
  51. clipBehavior: Clip.none, // 不裁剪超出边界的内容
  52. alignment: Alignment.topLeft,
  53. children: <Widget>[
  54. // 通知图标
  55. const MyAssetImage(Assets.mainHomeNotificationIcon, width: 19, height: 20),
  56. //未读消息
  57. Positioned(
  58. left: 0,
  59. top: 0,
  60. child: Transform.translate(
  61. offset: const Offset(-10, -5),
  62. child: MyTextView(
  63. "99",
  64. boxWidth: 20.0,
  65. textColor: Colors.white,
  66. fontSize: 10,
  67. isFontLight: true,
  68. backgroundColor: context.appColors.redDefault,
  69. cornerRadius: 8,
  70. paddingTop: 2,
  71. paddingBottom: 2,
  72. textAlign: TextAlign.center,
  73. ),
  74. ),
  75. ),
  76. ],
  77. ).onTap(viewModel.gotoNotificationPage))
  78. .marginOnly(right: 15),
  79. ],
  80. showBottomDivider: true),
  81. backgroundColor: context.appColors.backgroundDefault,
  82. body: EasyRefresh(
  83. controller: viewModel.refreshController,
  84. onRefresh: viewModel.onRefresh,
  85. child: CustomScrollView(
  86. scrollDirection: Axis.vertical,
  87. slivers: [
  88. //支付与奖励
  89. _buildPaymentAndRewardsWidget(context, ref),
  90. //间距
  91. _buildSliverSpace(20),
  92. //九宫选项组 (固定)
  93. _buildCategoryWidget(context, ref),
  94. //轮播图片 (动态)
  95. _buildBannerImage(ref, bannerIndex),
  96. //最新的新闻(动态)
  97. _buildLastNews(context, ref),
  98. //最新的交易
  99. SliverToBoxAdapter(
  100. child: MyTextView(
  101. marginTop: 14,
  102. marginLeft: 15,
  103. marginBottom: 14,
  104. S.current.latest_transactions,
  105. textColor: context.appColors.textPrimary,
  106. fontSize: 16,
  107. isFontMedium: true,
  108. ),
  109. ),
  110. //最新交易列表 (动态)
  111. _buildLastTransaction(context, state),
  112. //房产新闻
  113. SliverToBoxAdapter(
  114. child: MyTextView(
  115. marginTop: 14,
  116. marginLeft: 15,
  117. marginBottom: 14,
  118. onClick: viewModel.gotoPropertyNewsPage,
  119. S.current.property_news,
  120. textColor: context.appColors.textPrimary,
  121. fontSize: 16,
  122. isFontMedium: true,
  123. ),
  124. ),
  125. //房产新闻列表 (动态)
  126. _buildPropertyNews(context, ref),
  127. //管理员介绍 (固定)
  128. _buildManagementGuides(context, ref),
  129. //间距
  130. _buildSliverSpace(15),
  131. ],
  132. ),
  133. ),
  134. );
  135. }
  136. //顶部的支付与奖励的布局
  137. Widget _buildPaymentAndRewardsWidget(BuildContext context, WidgetRef ref) {
  138. final viewModel = ref.read(homeViewModelProvider.notifier);
  139. return SliverToBoxAdapter(
  140. child: Container(
  141. color: context.appColors.whiteBG,
  142. width: double.infinity,
  143. height: 45,
  144. child: Column(
  145. children: [
  146. Row(
  147. children: [
  148. Row(
  149. mainAxisSize: MainAxisSize.max,
  150. mainAxisAlignment: MainAxisAlignment.center,
  151. children: [
  152. const MyAssetImage(Assets.mainHomePaymentIcon, width: 16.5, height: 18).marginOnly(left: 20),
  153. MyTextView(
  154. S.current.payment,
  155. textColor: context.appColors.textBlack,
  156. fontSize: 15,
  157. isFontMedium: true,
  158. ).paddingOnly(left: 13, right: 13).expanded(),
  159. const MyAssetImage(Assets.mainHomeMoreIcon, width: 6, height: 8.5).marginOnly(right: 15),
  160. ],
  161. ).onTap(viewModel.gotoPaymentPage).expanded(),
  162. Container(color: context.appColors.dividerDefault, width: 0.5, height: double.infinity),
  163. Row(
  164. mainAxisSize: MainAxisSize.max,
  165. mainAxisAlignment: MainAxisAlignment.center,
  166. children: [
  167. const MyAssetImage(Assets.mainHomeRewardsIcon, width: 16.5, height: 17).marginOnly(left: 20),
  168. MyTextView(
  169. S.current.rewards,
  170. textColor: context.appColors.textBlack,
  171. fontSize: 15,
  172. isFontMedium: true,
  173. ).paddingOnly(left: 13, right: 4).expanded(),
  174. MyTextView(
  175. "9568",
  176. textColor: context.appColors.textBlack,
  177. fontSize: 15,
  178. isFontBold: true,
  179. ).paddingOnly(left: 1, right: 4),
  180. const MyAssetImage(Assets.mainHomeMoreIcon, width: 6, height: 8.5).marginOnly(right: 15),
  181. ],
  182. ).onTap(viewModel.gotoRewardsPage).expanded(),
  183. ],
  184. ).expanded(),
  185. //底部分割线
  186. Container(color: context.appColors.dividerDefault, height: 0.5, width: double.infinity),
  187. ],
  188. ),
  189. ),
  190. );
  191. }
  192. Widget _buildSliverSpace(double size) {
  193. return SliverToBoxAdapter(
  194. child: SizedBox(height: size),
  195. );
  196. }
  197. //九宫格选项组
  198. Widget _buildCategoryWidget(BuildContext context, WidgetRef ref) {
  199. final viewModel = ref.read(homeViewModelProvider.notifier);
  200. final state = ref.watch(homeViewModelProvider);
  201. return SliverGrid(
  202. gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
  203. crossAxisCount: 3, // 三列
  204. mainAxisSpacing: 10.0, // 主轴(上下)的间距
  205. crossAxisSpacing: 0.0, // 交叉轴(左右)的间距
  206. childAspectRatio: 9 / 8, // 子组件的宽高比
  207. ),
  208. delegate: SliverChildBuilderDelegate(
  209. (BuildContext context, int index) {
  210. return HomeCategoryItem(
  211. category: state.homeCategory[index],
  212. ).onTap(() {
  213. viewModel.switchCategory(index);
  214. }); // 生成每个网格项
  215. },
  216. childCount: state.homeCategory.length, // 总共的网格项数
  217. ),
  218. );
  219. }
  220. //Banner的布局
  221. Widget _buildBannerImage(WidgetRef ref, ValueNotifier<int> bannerIndex) {
  222. final viewModel = ref.read(homeViewModelProvider.notifier);
  223. final state = ref.watch(homeViewModelProvider);
  224. return SliverToBoxAdapter(
  225. child: Container(
  226. width: double.infinity,
  227. margin: const EdgeInsets.only(top: 21, left: 15, right: 15),
  228. child: state.homeIndex != null && state.homeIndex!.banners.isNotEmpty
  229. ? Stack(
  230. alignment: Alignment.bottomCenter, // 设置 Stack 的对齐方式为底部中心
  231. children: [
  232. CarouselSlider(
  233. options: CarouselOptions(
  234. aspectRatio: 345 / 152.5,
  235. viewportFraction: 1,
  236. initialPage: 0,
  237. enableInfiniteScroll: true,
  238. reverse: false,
  239. autoPlay: true,
  240. autoPlayInterval: const Duration(seconds: 5),
  241. autoPlayAnimationDuration: const Duration(milliseconds: 800),
  242. autoPlayCurve: Curves.fastOutSlowIn,
  243. enlargeCenterPage: true,
  244. scrollDirection: Axis.horizontal,
  245. onPageChanged: (index, reason) {
  246. bannerIndex.value = index;
  247. },
  248. ),
  249. items: state.homeIndex!.banners.map<Widget>((item) {
  250. return MyLoadImage(
  251. item.image,
  252. width: double.infinity,
  253. cornerRadius: 5,
  254. );
  255. }).toList(),
  256. ),
  257. Positioned(
  258. bottom: 10, // 距离底部 20 像素
  259. child: Row(
  260. mainAxisAlignment: MainAxisAlignment.center,
  261. children: state.homeIndex!.banners.map((item) {
  262. //难道就没有indexMap,要么就转换为Map的方式进行遍历Map
  263. int index = state.homeIndex!.banners.indexOf(item);
  264. return Container(
  265. width: 6.5,
  266. height: 6.5,
  267. margin: const EdgeInsets.symmetric(horizontal: 3.5),
  268. decoration: BoxDecoration(
  269. shape: BoxShape.circle,
  270. color: bannerIndex.value == index
  271. ? const Color(0x4D000000) // 选中状态颜色
  272. : const Color(0x1A000000), // 未选中状态颜色
  273. ),
  274. );
  275. }).toList(),
  276. ),
  277. ),
  278. ],
  279. )
  280. : AspectRatio(
  281. aspectRatio: 345 / 152.5,
  282. child: MyLoadImage(
  283. Assets.baseLibImageDefaultPlaceholder,
  284. width: double.infinity,
  285. cornerRadius: 5,
  286. ),
  287. ),
  288. ),
  289. );
  290. }
  291. //最新新闻
  292. Widget _buildLastNews(BuildContext context, WidgetRef ref) {
  293. final viewModel = ref.read(homeViewModelProvider.notifier);
  294. final state = ref.watch(homeViewModelProvider);
  295. return SliverToBoxAdapter(
  296. child: Column(
  297. crossAxisAlignment: CrossAxisAlignment.start,
  298. mainAxisAlignment: MainAxisAlignment.start,
  299. children: [
  300. MyTextView(
  301. S.current.latest_news,
  302. fontSize: 16,
  303. marginTop: 14,
  304. marginBottom: 14,
  305. isFontMedium: true,
  306. onClick: viewModel.gotoLastNewsPage,
  307. textColor: context.appColors.textPrimary,
  308. ),
  309. Row(
  310. mainAxisAlignment: MainAxisAlignment.spaceAround, // 均匀排布
  311. children: List.generate(state.lastNews.length, (index) {
  312. return Expanded(
  313. // 使用 Expanded 使每个子项占据相同空间
  314. child: LastNewsItem(
  315. lastNews: state.lastNews[index],
  316. onItemTap: () {
  317. //根据不同的索引跳转到不同的PageView指定页面
  318. if (index == 0) {
  319. LatestNewsPropertyScreen.startInstance(context: context);
  320. } else if (index == 1) {
  321. LatestNewsInternalScreen.startInstance(context: context);
  322. } else if (index == 2) {
  323. LatestNewsInfoScreen.startInstance(context: context);
  324. } else if (index == 3) {
  325. LatestNewsPublishScreen.startInstance(context: context);
  326. }
  327. },
  328. ),
  329. );
  330. }),
  331. ),
  332. ],
  333. ).paddingOnly(left: 15, right: 15));
  334. }
  335. //最新的交易列表
  336. Widget _buildLastTransaction(BuildContext context, HomeState state) {
  337. return SliverPadding(
  338. padding: const EdgeInsets.symmetric(horizontal: 15),
  339. sliver: DecoratedSliver(
  340. decoration: BoxDecoration(
  341. color: context.appColors.whiteBG,
  342. borderRadius: BorderRadius.circular(5.0),
  343. boxShadow: [
  344. BoxShadow(
  345. color: const Color(0xFF656565).withOpacity(0.1),
  346. offset: const Offset(0, 1.5),
  347. blurRadius: 2.5,
  348. spreadRadius: 1.5,
  349. ),
  350. ],
  351. ),
  352. sliver: SliverPadding(
  353. padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 20),
  354. sliver: SliverList(
  355. delegate: SliverChildBuilderDelegate(
  356. (BuildContext context, int index) {
  357. return Padding(
  358. padding: const EdgeInsets.symmetric(vertical: 10),
  359. child: LastTransItem(
  360. lastTrans: state.homeIndex!.latestTransactions[index],
  361. ),
  362. );
  363. },
  364. childCount: state.homeIndex?.latestTransactions.length ?? 0,
  365. ),
  366. ),
  367. ),
  368. ),
  369. );
  370. }
  371. //房产新闻的双列表
  372. Widget _buildPropertyNews(BuildContext context, WidgetRef ref) {
  373. final state = ref.watch(homeViewModelProvider);
  374. final propertyNewsList = state.homeIndex?.propertyNews ?? [];
  375. // 计算行数和每行的元素
  376. int totalItems = propertyNewsList.length;
  377. int firstRowCount = (totalItems + 1) ~/ 2; // 第1行的数量 (奇数时多一个)
  378. int secondRowCount = totalItems ~/ 2; // 第2行的数量
  379. return SliverList(
  380. delegate: SliverChildListDelegate(
  381. [
  382. // 第一个水平滑动列表
  383. _buildPropertyNewsHorizontalList(propertyNewsList.sublist(0, firstRowCount)),
  384. const SizedBox(height: 10),
  385. // 第二个水平滑动列表
  386. if (secondRowCount > 0) // 只有在有第二行内容时才显示
  387. _buildPropertyNewsHorizontalList(propertyNewsList.sublist(firstRowCount, firstRowCount + secondRowCount)),
  388. ],
  389. ),
  390. );
  391. }
  392. Widget _buildPropertyNewsHorizontalList(List<HomeListPropertyNews> propertyNews) {
  393. return SingleChildScrollView(
  394. scrollDirection: Axis.horizontal,
  395. physics: const BouncingScrollPhysics(),
  396. clipBehavior: Clip.none,
  397. child: Row(
  398. children: propertyNews.map((news) {
  399. return PropertyNews(news: news); // 假设 PropertyNews 需要传入一个 news 参数
  400. }).toList(),
  401. ).marginOnly(left: 15, right: 15),
  402. );
  403. }
  404. //管理员介绍
  405. Widget _buildManagementGuides(BuildContext context, WidgetRef ref) {
  406. final viewModel = ref.read(homeViewModelProvider.notifier);
  407. final state = ref.watch(homeViewModelProvider);
  408. return SliverToBoxAdapter(
  409. child: Column(
  410. crossAxisAlignment: CrossAxisAlignment.start,
  411. mainAxisAlignment: MainAxisAlignment.start,
  412. children: [
  413. MyTextView(
  414. S.current.strata_management_guides,
  415. fontSize: 16,
  416. marginTop: 14,
  417. marginBottom: 14,
  418. onClick: viewModel.gotoManageGuidePage,
  419. isFontMedium: true,
  420. textColor: context.appColors.textPrimary,
  421. ),
  422. SingleChildScrollView(
  423. scrollDirection: Axis.horizontal,
  424. physics: const BouncingScrollPhysics(),
  425. clipBehavior: Clip.none,
  426. child: Row(
  427. children: state.homeIndex?.strataManagementGuides == null
  428. ? [const SizedBox.shrink()]
  429. : List.generate(state.homeIndex!.strataManagementGuides.length, (index) {
  430. return ManageGuideItem(
  431. manageGuide: state.homeIndex!.strataManagementGuides[index],
  432. );
  433. }),
  434. ),
  435. )
  436. ],
  437. ).paddingOnly(left: 15, right: 15));
  438. }
  439. }