home_page.dart 18 KB

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