home_page.dart 21 KB

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