home_page.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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:cs_resources/theme/theme_config.dart';
  6. import 'package:domain/entity/home_list_entity.dart';
  7. import 'package:flutter/material.dart';
  8. import 'package:flutter/services.dart';
  9. import 'package:flutter/src/widgets/framework.dart';
  10. import 'package:flutter_hooks/flutter_hooks.dart';
  11. import 'package:hooks_riverpod/hooks_riverpod.dart';
  12. import 'package:auto_route/auto_route.dart';
  13. import 'package:plugin_basic/basic_export.dart';
  14. import 'package:plugin_basic/constants/app_constant.dart';
  15. import 'package:plugin_basic/provider/user_config/user_config.dart';
  16. import 'package:plugin_basic/provider/user_config/user_config_service.dart';
  17. import 'package:plugin_platform/engine/toast/toast_engine.dart';
  18. import 'package:router/componentRouter/community_service.dart';
  19. import 'package:router/componentRouter/component_service_manager.dart';
  20. import 'package:shared/utils/event_bus.dart';
  21. import 'package:shared/utils/log_utils.dart';
  22. import 'package:widgets/ext/ex_widget.dart';
  23. import 'package:widgets/my_appbar.dart';
  24. import 'package:widgets/my_load_image.dart';
  25. import 'package:widgets/my_text_view.dart';
  26. import 'package:widgets/utils/dark_theme_util.dart';
  27. import 'package:widgets/widget_export.dart';
  28. import 'item_home_category.dart';
  29. import 'home_view_model.dart';
  30. import 'item_home_last_news.dart';
  31. import 'item_home_last_trans.dart';
  32. import 'item_home_manage_guide.dart';
  33. import 'item_home_property_news.dart';
  34. import 'latest_news/info/latest_news_info_screen.dart';
  35. import 'latest_news/internal/latest_news_internal_screen.dart';
  36. import 'latest_news/property/latest_news_property_screen.dart';
  37. import 'latest_news/publish/latest_news_publish_screen.dart';
  38. @RoutePage()
  39. class HomePage extends HookConsumerWidget {
  40. const HomePage({super.key});
  41. @override
  42. Widget build(BuildContext context, WidgetRef ref) {
  43. final viewModel = ref.read(homeViewModelProvider.notifier);
  44. final state = ref.watch(homeViewModelProvider);
  45. String? notificationCount = UserConfigService.getState(ref: ref).user?.unreadNotificationsCount;
  46. final bannerIndex = useState(0);
  47. useEffect(() {
  48. // 组件挂载时执行 - 执行接口请求
  49. Future.microtask(() => viewModel.fetchHomeIndex());
  50. return () {
  51. // 组件卸载时执行
  52. };
  53. }, []);
  54. return AnnotatedRegion<SystemUiOverlayStyle>(
  55. value: MediaQuery.of(context).platformBrightness == Brightness.dark
  56. ? ThemeConfig.systemUiOverlayStyleDarkTheme
  57. : ThemeConfig.systemUiOverlayStyleLightThemeWhite,
  58. child: VisibilityDetector(
  59. key: const Key('unique-key'),
  60. onVisibilityChanged: (VisibilityInfo info) {
  61. if (info.visibleFraction == 1) {
  62. Log.d("Home Page 全部显示");
  63. //发送通知刷新用户信息
  64. bus.emit(AppConstant.eventProfileRefresh, true);
  65. } else {
  66. Log.d("Home Page 隐藏了");
  67. }
  68. },
  69. child: Scaffold(
  70. backgroundColor: context.appColors.backgroundDefault,
  71. body: Stack(
  72. children: [
  73. // 固定顶部的背景图片
  74. const Positioned(
  75. top: 0,
  76. left: 0,
  77. right: 0,
  78. child: MyAssetImage(
  79. Assets.mainHomeTopImgBg,
  80. width: double.infinity,
  81. fit: BoxFit.cover,
  82. height: 325,
  83. ), // 替换为你的图片路径
  84. ),
  85. Positioned(
  86. top: 58,
  87. left: 0,
  88. right: 0,
  89. child: Row(
  90. crossAxisAlignment: CrossAxisAlignment.center,
  91. children: [
  92. MyTextView(
  93. _formatNowGreetingText(ref),
  94. marginLeft: 17,
  95. textColor: context.appColors.textBlack,
  96. isFontBold: true,
  97. fontSize: 20,
  98. ).expanded(),
  99. //通知角标
  100. Center(
  101. child: Stack(
  102. clipBehavior: Clip.none, // 不裁剪超出边界的内容
  103. alignment: Alignment.topLeft,
  104. children: <Widget>[
  105. // 通知图标
  106. MyAssetImage(
  107. Assets.mainHomeNotificationIcon,
  108. width: 17,
  109. height: 19,
  110. color: context.appColors.imageDarkModelWhite,
  111. ),
  112. //未读消息
  113. Positioned(
  114. left: 0,
  115. top: 0,
  116. child: Visibility(
  117. visible: notificationCount != null && notificationCount != "0",
  118. child: Transform.translate(
  119. offset: const Offset(-10, -5),
  120. child: MyTextView(
  121. notificationCount ?? "0",
  122. boxWidth: 20.0,
  123. textColor: Colors.white,
  124. fontSize: 10,
  125. isFontLight: true,
  126. backgroundColor: context.appColors.redDefault,
  127. cornerRadius: 8,
  128. paddingTop: 2,
  129. paddingBottom: 2,
  130. textAlign: TextAlign.center,
  131. ),
  132. ),
  133. ),
  134. ),
  135. ],
  136. ).onTap(viewModel.gotoNotificationPage))
  137. .marginOnly(right: 17),
  138. ],
  139. ), // 替换为你的图片路径
  140. ),
  141. Positioned(
  142. top: 100,
  143. left: 0,
  144. right: 0,
  145. bottom: 0,
  146. child: EasyRefresh(
  147. header: const MaterialHeader(),
  148. controller: viewModel.refreshController,
  149. onRefresh: viewModel.onRefresh,
  150. child: CustomScrollView(
  151. scrollDirection: Axis.vertical,
  152. slivers: [
  153. //支付与奖励
  154. _buildPaymentAndRewardsWidget(context, ref),
  155. //间距
  156. _buildSliverSpace(20),
  157. //九宫选项组 (固定)
  158. _buildCategoryWidget(context, ref),
  159. //轮播图片 (动态)
  160. if (state.homeIndex?.banners != null && state.homeIndex?.banners.isNotEmpty == true)
  161. _buildBannerImage(ref, bannerIndex),
  162. //最新的新闻(动态)
  163. _buildLastNews(context, ref),
  164. //最新的交易
  165. SliverToBoxAdapter(
  166. child: MyTextView(
  167. marginTop: 32,
  168. marginLeft: 15,
  169. marginBottom: 14,
  170. S.current.latest_transactions,
  171. textColor: context.appColors.textBlack,
  172. fontSize: 16,
  173. isFontMedium: true,
  174. ),
  175. ),
  176. //最新交易列表 (动态)
  177. if (state.homeIndex?.latestTransactions != null && state.homeIndex?.latestTransactions.isNotEmpty == true)
  178. _buildLastTransaction(context, state),
  179. //房产新闻
  180. if (state.homeIndex?.propertyNews != null && state.homeIndex?.propertyNews.isNotEmpty == true)
  181. SliverToBoxAdapter(
  182. child: Row(
  183. children: [
  184. MyTextView(
  185. marginTop: 30,
  186. marginLeft: 15,
  187. marginBottom: 14,
  188. S.current.property_news,
  189. textColor: context.appColors.textBlack,
  190. fontSize: 16,
  191. isFontMedium: true,
  192. ).expanded(),
  193. MyTextView(
  194. marginTop: 30,
  195. marginLeft: 15,
  196. marginRight: 15,
  197. marginBottom: 14,
  198. onClick: viewModel.gotoPropertyNewsPage,
  199. S.current.all,
  200. textColor: context.appColors.textBlack,
  201. fontSize: 15,
  202. isFontMedium: true,
  203. )
  204. ],
  205. ),
  206. ),
  207. //房产新闻列表 (动态)
  208. if (state.homeIndex?.propertyNews != null && state.homeIndex?.propertyNews.isNotEmpty == true)
  209. _buildPropertyNews(context, ref),
  210. //管理员介绍 (固定)
  211. if (state.homeIndex?.strataManagementGuides != null && state.homeIndex?.strataManagementGuides.isNotEmpty == true)
  212. _buildManagementGuides(context, ref),
  213. //间距
  214. _buildSliverSpace(15),
  215. ],
  216. ),
  217. ),
  218. ),
  219. ],
  220. ),
  221. ),
  222. ),
  223. );
  224. }
  225. //顶部的支付与奖励的布局
  226. Widget _buildPaymentAndRewardsWidget(BuildContext context, WidgetRef ref) {
  227. final viewModel = ref.read(homeViewModelProvider.notifier);
  228. return SliverToBoxAdapter(
  229. child: Row(
  230. children: [
  231. Row(
  232. mainAxisSize: MainAxisSize.max,
  233. mainAxisAlignment: MainAxisAlignment.center,
  234. children: [
  235. const MyAssetImage(
  236. Assets.mainHomePaymentIcon,
  237. width: 24.5,
  238. height: 24.5,
  239. ).marginOnly(left: 10),
  240. MyTextView(
  241. S.current.payment,
  242. textColor: context.appColors.textBlack,
  243. fontSize: 14,
  244. isFontRegular: true,
  245. ).paddingOnly(left: 8, right: 8).expanded(),
  246. MyAssetImage(
  247. Assets.mainHomeMoreIcon,
  248. width: 6,
  249. height: 8.5,
  250. color: context.appColors.textBlack,
  251. ).marginOnly(right: 15),
  252. ],
  253. )
  254. .constrained(height: 46)
  255. .decorated(
  256. color: const Color(0xFFE0E3FF).withOpacity(0.5),
  257. borderRadius: BorderRadius.circular(9.0),
  258. )
  259. .onTap(viewModel.gotoPaymentPage)
  260. .expanded(),
  261. const SizedBox(width: 14.5),
  262. Row(
  263. mainAxisSize: MainAxisSize.max,
  264. mainAxisAlignment: MainAxisAlignment.center,
  265. children: [
  266. const MyAssetImage(
  267. Assets.mainHomeRewardsIcon,
  268. width: 24.5,
  269. height: 24.5,
  270. ).marginOnly(left: 10),
  271. MyTextView(
  272. S.current.rewards,
  273. textColor: context.appColors.textBlack,
  274. fontSize: 14,
  275. isFontRegular: true,
  276. ).paddingOnly(left: 8, right: 4).expanded(),
  277. MyTextView(
  278. UserConfigService.getState(ref: ref).user?.rewardPoints ?? "0",
  279. textColor: context.appColors.textBlack,
  280. fontSize: 14,
  281. isFontBold: true,
  282. ).paddingOnly(left: 1, right: 4),
  283. MyAssetImage(
  284. Assets.mainHomeMoreIcon,
  285. width: 6,
  286. height: 8.5,
  287. color: context.appColors.textBlack,
  288. ).marginOnly(right: 8),
  289. ],
  290. )
  291. .constrained(height: 46)
  292. .decorated(
  293. color: const Color(0xFFE0E3FF).withOpacity(0.5),
  294. borderRadius: BorderRadius.circular(9.0),
  295. )
  296. .onTap(viewModel.gotoRewardsPage)
  297. .expanded(),
  298. ],
  299. ).paddingOnly(left: 15, right: 15, top: 10),
  300. );
  301. }
  302. Widget _buildSliverSpace(double size) {
  303. return SliverToBoxAdapter(
  304. child: SizedBox(height: size),
  305. );
  306. }
  307. //九宫格选项组
  308. Widget _buildCategoryWidget(BuildContext context, WidgetRef ref) {
  309. final viewModel = ref.read(homeViewModelProvider.notifier);
  310. final state = ref.watch(homeViewModelProvider);
  311. return SliverGrid(
  312. gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
  313. crossAxisCount: 3, // 三列
  314. mainAxisSpacing: 0.0, // 主轴(上下)的间距
  315. crossAxisSpacing: 0.0, // 交叉轴(左右)的间距
  316. ),
  317. delegate: SliverChildBuilderDelegate(
  318. (BuildContext context, int index) {
  319. return HomeCategoryItem(
  320. category: state.homeCategory[index],
  321. ).onTap(() {
  322. viewModel.switchCategory(index);
  323. }); // 生成每个网格项
  324. },
  325. childCount: state.homeCategory.length, // 总共的网格项数
  326. ),
  327. );
  328. }
  329. //Banner的布局
  330. Widget _buildBannerImage(WidgetRef ref, ValueNotifier<int> bannerIndex) {
  331. final viewModel = ref.read(homeViewModelProvider.notifier);
  332. final state = ref.watch(homeViewModelProvider);
  333. return SliverToBoxAdapter(
  334. child: Container(
  335. width: double.infinity,
  336. margin: const EdgeInsets.only(top: 30, left: 15, right: 15),
  337. child: state.homeIndex != null && state.homeIndex!.banners.isNotEmpty
  338. ? Stack(
  339. alignment: Alignment.bottomCenter, // 设置 Stack 的对齐方式为底部中心
  340. children: [
  341. CarouselSlider(
  342. options: CarouselOptions(
  343. aspectRatio: 345 / 152.5,
  344. viewportFraction: 1,
  345. initialPage: 0,
  346. enableInfiniteScroll: true,
  347. reverse: false,
  348. autoPlay: true,
  349. autoPlayInterval: const Duration(seconds: 5),
  350. autoPlayAnimationDuration: const Duration(milliseconds: 800),
  351. autoPlayCurve: Curves.fastOutSlowIn,
  352. enlargeCenterPage: true,
  353. scrollDirection: Axis.horizontal,
  354. onPageChanged: (index, reason) {
  355. bannerIndex.value = index;
  356. },
  357. ),
  358. items: state.homeIndex!.banners.map<Widget>((item) {
  359. return MyLoadImage(
  360. item.image,
  361. width: double.infinity,
  362. cornerRadius: 5,
  363. );
  364. }).toList(),
  365. ),
  366. Positioned(
  367. bottom: 10, // 距离底部 20 像素
  368. child: Row(
  369. mainAxisAlignment: MainAxisAlignment.center,
  370. children: state.homeIndex!.banners.map((item) {
  371. //难道就没有indexMap,要么就转换为Map的方式进行遍历Map
  372. int index = state.homeIndex!.banners.indexOf(item);
  373. return Container(
  374. width: 6.5,
  375. height: 6.5,
  376. margin: const EdgeInsets.symmetric(horizontal: 3.5),
  377. decoration: BoxDecoration(
  378. shape: BoxShape.circle,
  379. color: bannerIndex.value == index
  380. ? const Color(0x4D000000) // 选中状态颜色
  381. : const Color(0x1A000000), // 未选中状态颜色
  382. ),
  383. );
  384. }).toList(),
  385. ),
  386. ),
  387. ],
  388. )
  389. : AspectRatio(
  390. aspectRatio: 345 / 152.5,
  391. child: MyLoadImage(
  392. Assets.baseLibImageDefaultPlaceholder,
  393. width: double.infinity,
  394. cornerRadius: 5,
  395. ),
  396. ),
  397. ),
  398. );
  399. }
  400. //最新新闻
  401. Widget _buildLastNews(BuildContext context, WidgetRef ref) {
  402. final viewModel = ref.read(homeViewModelProvider.notifier);
  403. final state = ref.watch(homeViewModelProvider);
  404. return SliverToBoxAdapter(
  405. child: Column(
  406. crossAxisAlignment: CrossAxisAlignment.start,
  407. mainAxisAlignment: MainAxisAlignment.start,
  408. children: [
  409. MyTextView(
  410. S.current.latest_news,
  411. fontSize: 16,
  412. marginTop: 30,
  413. marginBottom: 14,
  414. isFontMedium: true,
  415. onClick: viewModel.gotoLastNewsPage,
  416. textColor: context.appColors.textBlack,
  417. ),
  418. Row(
  419. mainAxisAlignment: MainAxisAlignment.spaceAround, // 均匀排布
  420. children: List.generate(state.lastNews.length, (index) {
  421. return Expanded(
  422. // 使用 Expanded 使每个子项占据相同空间
  423. child: LastNewsItem(
  424. lastNews: state.lastNews[index],
  425. onItemTap: () {
  426. //根据不同的索引跳转到不同的PageView指定页面
  427. if (index == 0) {
  428. LatestNewsPropertyScreen.startInstance(context: context);
  429. } else if (index == 1) {
  430. LatestNewsInternalScreen.startInstance(context: context);
  431. } else if (index == 2) {
  432. LatestNewsInfoScreen.startInstance(context: context);
  433. } else if (index == 3) {
  434. LatestNewsPublishScreen.startInstance(context: context);
  435. }
  436. },
  437. ),
  438. );
  439. }),
  440. ),
  441. ],
  442. ).paddingOnly(left: 15, right: 15));
  443. }
  444. //最新的交易列表
  445. Widget _buildLastTransaction(BuildContext context, HomeState state) {
  446. return SliverPadding(
  447. padding: const EdgeInsets.symmetric(horizontal: 15),
  448. sliver: DecoratedSliver(
  449. decoration: BoxDecoration(
  450. color: context.appColors.whiteBG,
  451. borderRadius: BorderRadius.circular(5.0),
  452. boxShadow: [
  453. BoxShadow(
  454. color: const Color(0xFF656565).withOpacity(0.1),
  455. offset: const Offset(0, 1.5),
  456. blurRadius: 2.5,
  457. spreadRadius: 1.5,
  458. ),
  459. ],
  460. ),
  461. sliver: SliverPadding(
  462. padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 20),
  463. sliver: SliverList(
  464. delegate: SliverChildBuilderDelegate(
  465. (BuildContext context, int index) {
  466. return Padding(
  467. padding: const EdgeInsets.symmetric(vertical: 10),
  468. child: LastTransItem(
  469. lastTrans: state.homeIndex!.latestTransactions[index],
  470. ),
  471. );
  472. },
  473. childCount: state.homeIndex?.latestTransactions.length ?? 0,
  474. ),
  475. ),
  476. ),
  477. ),
  478. );
  479. }
  480. //房产新闻的列表
  481. Widget _buildPropertyNews(BuildContext context, WidgetRef ref) {
  482. final state = ref.watch(homeViewModelProvider);
  483. final propertyNewsList = state.homeIndex?.propertyNews ?? [];
  484. return SliverList(
  485. delegate: SliverChildListDelegate(
  486. [
  487. // 第一个水平滑动列表
  488. _buildPropertyNewsHorizontalList(propertyNewsList),
  489. ],
  490. ),
  491. );
  492. }
  493. Widget _buildPropertyNewsHorizontalList(List<HomeListPropertyNews> propertyNews) {
  494. return SingleChildScrollView(
  495. scrollDirection: Axis.horizontal,
  496. physics: const BouncingScrollPhysics(),
  497. clipBehavior: Clip.none,
  498. child: Row(
  499. children: propertyNews.map((news) {
  500. return PropertyNews(news: news).onTap(() {
  501. //路由跳转到Property模块的新闻详情页面
  502. ComponentServiceManager().propertyService.startPropertyNewsDetailPage(int.parse(news.id ?? "0"));
  503. }); // 假设 PropertyNews 需要传入一个 news 参数
  504. }).toList(),
  505. ).marginOnly(left: 15, right: 15),
  506. );
  507. }
  508. //管理员介绍
  509. Widget _buildManagementGuides(BuildContext context, WidgetRef ref) {
  510. final viewModel = ref.read(homeViewModelProvider.notifier);
  511. final state = ref.watch(homeViewModelProvider);
  512. return SliverToBoxAdapter(
  513. child: Column(
  514. crossAxisAlignment: CrossAxisAlignment.start,
  515. mainAxisAlignment: MainAxisAlignment.start,
  516. children: [
  517. MyTextView(
  518. S.current.strata_management_guides,
  519. fontSize: 16,
  520. marginTop: 30,
  521. marginBottom: 14,
  522. onClick: viewModel.gotoManageGuidePage,
  523. isFontMedium: true,
  524. textColor: context.appColors.textBlack,
  525. ),
  526. SingleChildScrollView(
  527. scrollDirection: Axis.horizontal,
  528. physics: const BouncingScrollPhysics(),
  529. clipBehavior: Clip.none,
  530. child: Row(
  531. children: state.homeIndex?.strataManagementGuides == null
  532. ? [const SizedBox.shrink()]
  533. : List.generate(state.homeIndex!.strataManagementGuides.length, (index) {
  534. return ManageGuideItem(
  535. manageGuide: state.homeIndex!.strataManagementGuides[index],
  536. );
  537. }),
  538. ),
  539. )
  540. ],
  541. ).paddingOnly(left: 15, right: 15));
  542. }
  543. /// 根据现在的小时数,返回 早上好,中午好,下午好,晚上好 这四个字符串
  544. String _formatNowGreetingText(WidgetRef ref) {
  545. final now = DateTime.now();
  546. final hour = now.hour;
  547. String greeting;
  548. if (hour < 12) {
  549. greeting = S.current.good_morning(UserConfigService.getState(ref: ref).userName ?? "-");
  550. } else if (hour < 18) {
  551. greeting = S.current.good_afternoon(UserConfigService.getState(ref: ref).userName ?? "-");
  552. } else {
  553. greeting = S.current.good_evening(UserConfigService.getState(ref: ref).userName ?? "-");
  554. }
  555. return greeting;
  556. }
  557. }