home_page.dart 21 KB

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