community_page.dart 14 KB

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