home_page.dart 16 KB

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