home_page.dart 18 KB

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