home_page.dart 20 KB

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