community_page.dart 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. import 'package:cs_resources/generated/assets.dart';
  2. import 'package:cs_resources/generated/l10n.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:auto_route/auto_route.dart';
  5. import 'package:flutter/rendering.dart';
  6. import 'package:flutter_hooks/flutter_hooks.dart';
  7. import 'package:hooks_riverpod/hooks_riverpod.dart';
  8. import 'package:plugin_basic/provider/user_config/user_config_service.dart';
  9. import 'package:router/ext/auto_router_extensions.dart';
  10. import 'package:shared/utils/color_utils.dart';
  11. import 'package:shared/utils/log_utils.dart';
  12. import 'package:widgets/my_load_image.dart';
  13. import 'package:widgets/ext/ex_widget.dart';
  14. import 'package:widgets/my_text_view.dart';
  15. import 'package:widgets/my_appbar.dart';
  16. import 'package:cs_resources/theme/app_colors_theme.dart';
  17. import 'package:widgets/utils/dark_theme_util.dart';
  18. import 'package:widgets/widget_export.dart';
  19. import '../../router/page/community_page_router.dart';
  20. import '../my_posts/my_posts_page.dart';
  21. import 'newsfeed_tabs.dart';
  22. import 'community_vm.dart';
  23. import 'customSilverHeaderTabs.dart';
  24. final tabsRouterKey = GlobalKey<AutoTabsRouterState>();
  25. final GlobalKey<ExtendedNestedScrollViewState> extendedNestedScrollViewKey =
  26. GlobalKey<ExtendedNestedScrollViewState>();
  27. @RoutePage()
  28. class CommunityPage extends HookConsumerWidget with WidgetsBindingObserver {
  29. CommunityPage({Key? key}) : super(key: key);
  30. //启动当前页面
  31. static void startInstance({BuildContext? context}) {
  32. if (context != null) {
  33. context.router.push(const CommunityPageRoute());
  34. } else {
  35. appRouter.push(const CommunityPageRoute());
  36. }
  37. }
  38. bool _isKeyboardVisible = false;
  39. void handlerNestedScrollViewScroll({double? outerOffset, double? innerOffset, bool? isOuterScrollAnimated=false, bool? isInnerScrollAnimated=false}){
  40. if(outerOffset !=null){
  41. if(isOuterScrollAnimated!){
  42. extendedNestedScrollViewKey.currentState?.outerController.animateTo(
  43. outerOffset,
  44. duration: const Duration(seconds: 1),
  45. curve: Curves.easeIn,
  46. );
  47. }else {
  48. extendedNestedScrollViewKey.currentState?.outerController.jumpTo(
  49. outerOffset,
  50. );
  51. }
  52. }
  53. if(innerOffset !=null){
  54. extendedNestedScrollViewKey.currentState?.innerPositions.forEach((position) {
  55. if(isInnerScrollAnimated!){
  56. position.animateTo(innerOffset,
  57. duration: Duration(seconds: 1), curve: Curves.easeIn);
  58. }else {
  59. position.jumpTo(innerOffset);
  60. }
  61. });
  62. }
  63. }
  64. @override
  65. void didChangeMetrics() {
  66. final bottomInset = WidgetsBinding.instance.window.viewInsets.bottom;
  67. final newValue = bottomInset > 0.0;
  68. if (_isKeyboardVisible != newValue) {
  69. _isKeyboardVisible = newValue;
  70. if (_isKeyboardVisible) {
  71. handlerNestedScrollViewScroll(innerOffset: 0.0,);
  72. print("键盘已显示");
  73. } else {
  74. WidgetsBinding.instance.removeObserver(this);
  75. print("键盘已隐藏");
  76. }
  77. }
  78. }
  79. @override
  80. Widget build(BuildContext context, WidgetRef ref) {
  81. final vm = ref.read(communityVmProvider.notifier);
  82. final state = ref.watch(communityVmProvider);
  83. final newsIsCollection = useState<int?>(null);
  84. final followIsCollection = useState<int?>(null);
  85. final foryouIsCollection = useState<int?>(null);
  86. final saleIsCollection = useState<int?>(null);
  87. final rentIsCollection = useState<int?>(null);
  88. useEffect(() {
  89. // 监听窗口
  90. WidgetsBinding.instance.addObserver(this);
  91. }, []);
  92. useEffect((){
  93. Log.d("CommunityPage initState");
  94. // 延迟监听
  95. WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
  96. if(tabsRouterKey.currentState?.controller != null){
  97. tabsRouterKey.currentState?.controller?.addListener((){
  98. vm.tabsRouterChange();
  99. });
  100. }
  101. });
  102. return (){
  103. Log.d("CommunityPage dispose");
  104. WidgetsBinding.instance.removeObserver(this);
  105. tabsRouterKey.currentState?.controller?.removeListener(vm.tabsRouterChange);
  106. };
  107. },[]);
  108. return Scaffold(
  109. appBar: state.currentCategoryIdx == 0 ?MyAppBar.appBar(
  110. context,
  111. S.current.community,
  112. backgroundColor: context.appColors.backgroundWhite,
  113. actions: [
  114. if(state.currentPageViewIdx == 0)
  115. MyAssetImage(
  116. newsIsCollection.value ==1? Assets.communityLikeActive : Assets.communityLike,
  117. width: 21.5,
  118. height: 21.5,
  119. ).onTap((){
  120. newsIsCollection.value = newsIsCollection.value == 1 ? null:1;
  121. vm.handlerClickNavbarLikeBtn(context, newsIsCollection.value);
  122. }),
  123. if(state.currentPageViewIdx == 1)
  124. MyAssetImage(
  125. followIsCollection.value ==1? Assets.communityLikeActive : Assets.communityLike,
  126. width: 21.5,
  127. height: 21.5,
  128. ).onTap((){
  129. followIsCollection.value = followIsCollection.value == 1 ? null:1;
  130. vm.handlerClickNavbarLikeBtn(context, followIsCollection.value);
  131. }),
  132. if(state.currentPageViewIdx == 2)
  133. MyAssetImage(
  134. foryouIsCollection.value ==1? Assets.communityLikeActive : Assets.communityLike,
  135. width: 21.5,
  136. height: 21.5,
  137. ).onTap((){
  138. foryouIsCollection.value = foryouIsCollection.value == 1 ? null:1;
  139. vm.handlerClickNavbarLikeBtn(context, foryouIsCollection.value);
  140. }),
  141. const SizedBox(width:15),
  142. ],
  143. ):MyAppBar.searchAppBar(
  144. context,
  145. value: vm.getCurrentQueryParams('keyword'),
  146. actions: [
  147. state.currentPageViewIdx == 3 ? MyAssetImage(
  148. saleIsCollection.value ==1? Assets.communityLikeActive : Assets.communityLike,
  149. width: 21.5,
  150. height: 21.5,
  151. ).onTap((){
  152. saleIsCollection.value = saleIsCollection.value == 1 ? null:1;
  153. vm.handlerClickNavbarLikeBtn(context, saleIsCollection.value);
  154. }): MyAssetImage(
  155. rentIsCollection.value ==1? Assets.communityLikeActive : Assets.communityLike,
  156. width: 21.5,
  157. height: 21.5,
  158. ).onTap((){
  159. rentIsCollection.value = rentIsCollection.value == 1 ? null:1;
  160. vm.handlerClickNavbarLikeBtn(context, rentIsCollection.value);
  161. }),
  162. const SizedBox(width:15),
  163. const MyAssetImage(
  164. Assets.communityFillterIcon,
  165. width: 21,
  166. height: 21,
  167. ).onTap((){
  168. vm.handlerClickNavbarFilterBtn(context);
  169. }),
  170. SizedBox(width:15),
  171. ],
  172. backgroundColor: context.appColors.backgroundWhite,
  173. onSearch: (value) {
  174. vm.handlerSearch(value);
  175. }
  176. ),
  177. backgroundColor: context.appColors.backgroundDefault,
  178. body: ExtendedNestedScrollView(
  179. key: extendedNestedScrollViewKey,
  180. onlyOneScrollInBody: true,
  181. headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
  182. return [
  183. // SliverPersistentHeader(
  184. // delegate: CustomSliverPersistentHeaderDelegate(
  185. // maxHeight: 180,
  186. // minHeight: 180,
  187. // child: _buildTopSection(context, ref, vm, state),
  188. // ),
  189. // pinned: false,
  190. // ),
  191. // top 组件,转换为 Sliver 组件
  192. SliverToBoxAdapter(
  193. child: _buildTopSection(context, ref, vm, state),
  194. ),
  195. ];
  196. },
  197. body: Column(
  198. mainAxisSize: MainAxisSize.max,
  199. children: [
  200. Expanded(
  201. child: AutoTabsRouter.pageView(
  202. key: tabsRouterKey,
  203. routes: const [
  204. NewsPageRoute(),
  205. FollowingPageRoute(),
  206. ForyouPageRoute(),
  207. ForsalePageRoute(),
  208. ForrentPageRoute(),
  209. ],
  210. builder: (context, child, pageController) {
  211. final tabsRouter = AutoTabsRouter.of(context);
  212. return Column(
  213. children: [
  214. _buildTabsSection(context, ref, tabsRouter, vm, state),
  215. _buildPostSection(context, ref, vm, state),
  216. Expanded(
  217. child: child
  218. ),
  219. ],
  220. );
  221. },
  222. )
  223. )
  224. ]
  225. )
  226. )
  227. );
  228. }
  229. Widget _buildTopSection(BuildContext context, WidgetRef ref, vm, state) {
  230. final topSectionsData = vm.topSectionsData;
  231. // final currentPageIdx = tabsRouterKey.currentState?.controller?.activeIndex ?? 0;
  232. int curTagIdx = 0;
  233. int currentPageIdx = state.currentPageViewIdx;
  234. int newsfeedTabCount = state.newsFeedTabsList.length;
  235. if(currentPageIdx >= newsfeedTabCount){
  236. curTagIdx = 1;
  237. }else {
  238. curTagIdx = 0;
  239. }
  240. return Container(
  241. color: context.appColors.whiteBG,
  242. padding: const EdgeInsets.only(top: 0, bottom: 10),
  243. child: Center(
  244. child: Row(
  245. mainAxisSize: MainAxisSize.max,
  246. mainAxisAlignment: MainAxisAlignment.center,
  247. crossAxisAlignment: CrossAxisAlignment.center,
  248. children: List.generate(topSectionsData.length, (index) {
  249. final item = topSectionsData[index];
  250. return Column(
  251. children: [
  252. Container(
  253. width: MediaQuery.of(context).size.width / topSectionsData.length - 36,
  254. height: 70,
  255. decoration: BoxDecoration(
  256. shape: BoxShape.circle, // 设置为圆形
  257. color: ColorUtils.string2Color("#F0F8FF"),
  258. boxShadow: index == curTagIdx ? [
  259. BoxShadow(
  260. color: DarkThemeUtil.multiColors(context, context.appColors.tabLightBlueShadow, darkColor: AppColorsTheme.colorPrimary,)??context.appColors.tabLightBlueShadow, // 设置阴影颜色
  261. blurRadius: 5, // 设置模糊半径
  262. spreadRadius: 0.05, // 控制阴影扩散
  263. offset: const Offset(0, 2), // 设置阴影偏移量
  264. ),] : [],// 未选中时无阴影,
  265. ),
  266. child: MyAssetImage(
  267. item['icon'],
  268. width: MediaQuery.of(context).size.width / topSectionsData.length - 36,
  269. // width: 70,
  270. height: 70,
  271. ).onTap(() {
  272. vm.handlerSwitchNewsfeedOrGaragesale(index, context, null);
  273. },
  274. type: ClickType.throttle,
  275. ),
  276. ),
  277. SizedBox.fromSize(size: const Size(0, 9)),
  278. MyTextView(
  279. item['title'],
  280. fontSize: 15,
  281. textColor: DarkThemeUtil.multiColors(context, index == curTagIdx ? context.appColors.textPrimary: context.appColors.textBlack, darkColor: index == curTagIdx? AppColorsTheme.colorPrimary: Colors.white),
  282. textAlign: TextAlign.center,
  283. isFontMedium: true,
  284. ),
  285. ],
  286. ).marginOnly(left: 18, right: 18, top: 10, bottom: 10);
  287. }),
  288. ),
  289. ),
  290. );
  291. }
  292. Widget _buildTabsSection(BuildContext context, WidgetRef ref, tabsRouter, vm, state){
  293. int currentPageIndex = tabsRouter!.activeIndex ?? 0;
  294. int newsfeedTabCount = state.newsFeedTabsList.length;
  295. List<String> tabsList;
  296. if(currentPageIndex < newsfeedTabCount){
  297. // news feed
  298. tabsList = state.newsFeedTabsList ?? [];
  299. }else {
  300. tabsList = state.garageSaleTabsList ?? [];
  301. }
  302. return Container(
  303. width: double.infinity,
  304. color: DarkThemeUtil.multiColors(context, ColorUtils.string2Color('#F2F3F6'), darkColor: Colors.white30),
  305. child: Center(
  306. child: NewsfeedTabs(
  307. key: UniqueKey(),
  308. tabsList: tabsList,
  309. tabsRouter: tabsRouter,
  310. margin: EdgeInsets.only(top: 14,bottom: 12, left: 20, right: 20),
  311. onClickAction:(Map<String, dynamic>? params){
  312. if (params != null) {
  313. // 解构 params
  314. final int? currentCatgoryIdx = params['currentCatgoryIdx'] as int?;
  315. final int? tabIdx = params['tabIdx'] as int?;
  316. vm.handlerChangeTab(tabIdx, tabsRouter, currentCatgoryIdx);
  317. }
  318. }
  319. ),
  320. ),
  321. );
  322. }
  323. Widget _buildPostSection(BuildContext context, WidgetRef ref, vm, state){
  324. int currentPageIndex = state.currentPageViewIdx;
  325. int newsfeedTabCount = state.newsFeedTabsList.length;
  326. if(currentPageIndex < newsfeedTabCount){
  327. // news feed
  328. return _buildNewsFeedPost(context, ref, vm, state);
  329. }else {
  330. return _buildGarageSalePost(context, ref, vm, state);
  331. }
  332. }
  333. Widget _buildNewsFeedPost(BuildContext context, WidgetRef ref, vm, state){
  334. final userConfig = UserConfigService.getState(ref: ref);
  335. return Container(
  336. height: 65.5,
  337. width: double.infinity,
  338. padding: const EdgeInsets.only(left: 20, right: 20),
  339. color: DarkThemeUtil.multiColors(context, context.appColors.whiteBG, darkColor: Colors.black),
  340. child: Row(
  341. children: [
  342. // const MyAssetImage(Assets.communityNewsFeed, width: 45,height: 45,),
  343. MyLoadImage(
  344. userConfig.user?.avatar,
  345. width: 45,
  346. height: 45,
  347. isCircle: true,
  348. fit: BoxFit.fill,
  349. ),
  350. Expanded(
  351. child: MyTextView(
  352. S.current.what_on_your_mind,
  353. textColor: DarkThemeUtil.multiColors(context, context.appColors.textBlack, darkColor: Colors.white),
  354. fontSize: 15,
  355. marginLeft: 15,
  356. alignment: Alignment.centerLeft,
  357. textAlign: TextAlign.left,
  358. backgroundColor: DarkThemeUtil.multiColors(context, context.appColors.textWhite, darkColor: Colors.black),
  359. maxLines: 1,
  360. isFontMedium: true,
  361. ),
  362. ),
  363. const MyAssetImage(
  364. Assets.communityCamera,
  365. width: 21,
  366. height: 16.5,
  367. ),
  368. ],
  369. ).onTap((){
  370. vm.handlerGotoNewsfeedPost(context);
  371. }),
  372. );
  373. }
  374. Widget _buildGarageSalePost(BuildContext context, WidgetRef ref, vm, state){
  375. final userConfig = UserConfigService.getState(ref: ref);
  376. String title = S.current.sell_item;
  377. if(state.currentCategoryIdx == 1 ){
  378. // grage sale
  379. if(state.currentPageViewIdx == 3){
  380. title = S.current.sell_item;
  381. }else if(state.currentPageViewIdx == 4){
  382. title = S.current.rent_item;
  383. }
  384. }
  385. return Container(
  386. height: 65.5,
  387. width: double.infinity,
  388. padding: const EdgeInsets.only(left: 20, right: 20),
  389. color: DarkThemeUtil.multiColors(context, context.appColors.whiteBG, darkColor: Colors.black),
  390. child: Row(
  391. children: [
  392. MyLoadImage(
  393. userConfig.user?.avatar,
  394. width: 45,
  395. height: 45,
  396. isCircle: true,
  397. fit: BoxFit.fill,
  398. ),
  399. Expanded(
  400. child: MyTextView(
  401. title,
  402. textColor: DarkThemeUtil.multiColors(context, context.appColors.textBlack, darkColor: Colors.white),
  403. fontSize: 15,
  404. marginLeft: 15,
  405. alignment: Alignment.centerLeft,
  406. textAlign: TextAlign.left,
  407. backgroundColor: DarkThemeUtil.multiColors(context, context.appColors.textWhite, darkColor: Colors.black),
  408. maxLines: 1,
  409. isFontMedium: true,
  410. ),
  411. ),
  412. const MyAssetImage(
  413. Assets.communityCamera,
  414. width: 21,
  415. height: 16.5,
  416. ),
  417. ],
  418. ).onTap((){
  419. vm.handlerGotoGaragePost(context);
  420. }),
  421. );
  422. }
  423. }