community_page.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  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. useEffect(() {
  84. // 监听窗口
  85. WidgetsBinding.instance.addObserver(this);
  86. }, []);
  87. useEffect((){
  88. Log.d("CommunityPage initState");
  89. // 延迟监听
  90. WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
  91. if(tabsRouterKey.currentState?.controller != null){
  92. tabsRouterKey.currentState?.controller?.addListener((){
  93. vm.tabsRouterChange();
  94. });
  95. }
  96. });
  97. return (){
  98. Log.d("CommunityPage dispose");
  99. WidgetsBinding.instance.removeObserver(this);
  100. tabsRouterKey.currentState?.controller?.removeListener(vm.tabsRouterChange);
  101. };
  102. },[]);
  103. return Scaffold(
  104. appBar: MyAppBar.searchAppBar(
  105. context,
  106. value: vm.getCurrentQueryParams('keyword'),
  107. actions: [
  108. const MyAssetImage(
  109. Assets.communityLikeActive,
  110. width: 21.5,
  111. height: 21.5,
  112. ).onTap((){
  113. vm.handlerClickNavbarLikeBtn(context);
  114. }),
  115. SizedBox(width: state.currentCategoryIdx ==0 ? 15:20),
  116. state.currentCategoryIdx ==1 ?
  117. const MyAssetImage(
  118. Assets.communityFillterIcon,
  119. width: 21,
  120. height: 21,
  121. ).onTap((){
  122. vm.handlerClickNavbarFilterBtn(context);
  123. }) : const SizedBox.shrink(),
  124. const SizedBox(width: 15),
  125. ],
  126. backgroundColor: context.appColors.backgroundWhite,
  127. onSearch: (value) {
  128. vm.handlerSearch(value);
  129. }
  130. ),
  131. backgroundColor: context.appColors.backgroundDefault,
  132. body: ExtendedNestedScrollView(
  133. key: extendedNestedScrollViewKey,
  134. onlyOneScrollInBody: true,
  135. headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
  136. return [
  137. // SliverPersistentHeader(
  138. // delegate: CustomSliverPersistentHeaderDelegate(
  139. // maxHeight: 180,
  140. // minHeight: 180,
  141. // child: _buildTopSection(context, ref, vm, state),
  142. // ),
  143. // pinned: false,
  144. // ),
  145. // top 组件,转换为 Sliver 组件
  146. SliverToBoxAdapter(
  147. child: _buildTopSection(context, ref, vm, state),
  148. ),
  149. ];
  150. },
  151. body: Column(
  152. mainAxisSize: MainAxisSize.max,
  153. children: [
  154. Expanded(
  155. child: AutoTabsRouter.pageView(
  156. key: tabsRouterKey,
  157. routes: const [
  158. NewsPageRoute(),
  159. FollowingPageRoute(),
  160. ForyouPageRoute(),
  161. ForsalePageRoute(),
  162. ForrentPageRoute(),
  163. ],
  164. builder: (context, child, pageController) {
  165. final tabsRouter = AutoTabsRouter.of(context);
  166. return Column(
  167. children: [
  168. _buildTabsSection(context, ref, tabsRouter, vm, state),
  169. _buildPostSection(context, ref, vm, state),
  170. Expanded(
  171. child: child
  172. ),
  173. ],
  174. );
  175. },
  176. )
  177. )
  178. ]
  179. )
  180. )
  181. );
  182. }
  183. Widget _buildTopSection(BuildContext context, WidgetRef ref, vm, state) {
  184. final topSectionsData = vm.topSectionsData;
  185. // final currentPageIdx = tabsRouterKey.currentState?.controller?.activeIndex ?? 0;
  186. int curTagIdx = 0;
  187. int currentPageIdx = state.currentPageViewIdx;
  188. int newsfeedTabCount = state.newsFeedTabsList.length;
  189. if(currentPageIdx >= newsfeedTabCount){
  190. curTagIdx = 1;
  191. }else {
  192. curTagIdx = 0;
  193. }
  194. return Container(
  195. color: context.appColors.whiteBG,
  196. padding: const EdgeInsets.only(top: 0, bottom: 10),
  197. child: Center(
  198. child: Row(
  199. mainAxisSize: MainAxisSize.max,
  200. mainAxisAlignment: MainAxisAlignment.center,
  201. crossAxisAlignment: CrossAxisAlignment.center,
  202. children: List.generate(topSectionsData.length, (index) {
  203. final item = topSectionsData[index];
  204. return Column(
  205. children: [
  206. Container(
  207. width: MediaQuery.of(context).size.width / topSectionsData.length - 36,
  208. height: 70,
  209. decoration: BoxDecoration(
  210. shape: BoxShape.circle, // 设置为圆形
  211. color: ColorUtils.string2Color("#F0F8FF"),
  212. boxShadow: index == curTagIdx ? [
  213. BoxShadow(
  214. color: DarkThemeUtil.multiColors(context, context.appColors.tabLightBlueShadow, darkColor: AppColorsTheme.colorPrimary,)??context.appColors.tabLightBlueShadow, // 设置阴影颜色
  215. blurRadius: 5, // 设置模糊半径
  216. spreadRadius: 0.05, // 控制阴影扩散
  217. offset: const Offset(0, 2), // 设置阴影偏移量
  218. ),] : [],// 未选中时无阴影,
  219. ),
  220. child: MyAssetImage(
  221. item['icon'],
  222. width: MediaQuery.of(context).size.width / topSectionsData.length - 36,
  223. // width: 70,
  224. height: 70,
  225. ).onTap(() {
  226. vm.handlerSwitchNewsfeedOrGaragesale(index, context, null);
  227. },
  228. type: ClickType.throttle,
  229. ),
  230. ),
  231. SizedBox.fromSize(size: const Size(0, 9)),
  232. MyTextView(
  233. item['title'],
  234. fontSize: 15,
  235. textColor: DarkThemeUtil.multiColors(context, index == curTagIdx ? context.appColors.textPrimary: context.appColors.textBlack, darkColor: index == curTagIdx? AppColorsTheme.colorPrimary: Colors.white),
  236. textAlign: TextAlign.center,
  237. isFontMedium: true,
  238. ),
  239. ],
  240. ).marginOnly(left: 18, right: 18, top: 10, bottom: 10);
  241. }),
  242. ),
  243. ),
  244. );
  245. }
  246. Widget _buildTabsSection(BuildContext context, WidgetRef ref, tabsRouter, vm, state){
  247. int currentPageIndex = tabsRouter!.activeIndex ?? 0;
  248. int newsfeedTabCount = state.newsFeedTabsList.length;
  249. List<String> tabsList;
  250. if(currentPageIndex < newsfeedTabCount){
  251. // news feed
  252. tabsList = state.newsFeedTabsList ?? [];
  253. }else {
  254. tabsList = state.garageSaleTabsList ?? [];
  255. }
  256. return Container(
  257. width: double.infinity,
  258. color: DarkThemeUtil.multiColors(context, ColorUtils.string2Color('#F2F3F6'), darkColor: Colors.white30),
  259. child: Center(
  260. child: NewsfeedTabs(
  261. key: UniqueKey(),
  262. tabsList: tabsList,
  263. tabsRouter: tabsRouter,
  264. margin: EdgeInsets.only(top: 14,bottom: 12, left: 20, right: 20),
  265. onClickAction:(Map<String, dynamic>? params){
  266. if (params != null) {
  267. // 解构 params
  268. final int? currentCatgoryIdx = params['currentCatgoryIdx'] as int?;
  269. final int? tabIdx = params['tabIdx'] as int?;
  270. vm.handlerChangeTab(tabIdx, tabsRouter, currentCatgoryIdx);
  271. }
  272. }
  273. ),
  274. ),
  275. );
  276. }
  277. Widget _buildPostSection(BuildContext context, WidgetRef ref, vm, state){
  278. int currentPageIndex = state.currentPageViewIdx;
  279. int newsfeedTabCount = state.newsFeedTabsList.length;
  280. if(currentPageIndex < newsfeedTabCount){
  281. // news feed
  282. return _buildNewsFeedPost(context, ref, vm, state);
  283. }else {
  284. return _buildGarageSalePost(context, ref, vm, state);
  285. }
  286. }
  287. Widget _buildNewsFeedPost(BuildContext context, WidgetRef ref, vm, state){
  288. final userConfig = UserConfigService.getState(ref: ref);
  289. return Container(
  290. height: 65.5,
  291. width: double.infinity,
  292. padding: const EdgeInsets.only(left: 20, right: 20),
  293. color: DarkThemeUtil.multiColors(context, context.appColors.whiteBG, darkColor: Colors.black),
  294. child: Row(
  295. children: [
  296. // const MyAssetImage(Assets.communityNewsFeed, width: 45,height: 45,),
  297. MyLoadImage(
  298. userConfig.user?.avatar,
  299. width: 45,
  300. height: 45,
  301. isCircle: true,
  302. fit: BoxFit.fill,
  303. ),
  304. Expanded(
  305. child: MyTextView(
  306. S.current.what_on_your_mind,
  307. textColor: DarkThemeUtil.multiColors(context, context.appColors.textBlack, darkColor: Colors.white),
  308. fontSize: 15,
  309. marginLeft: 15,
  310. alignment: Alignment.centerLeft,
  311. textAlign: TextAlign.left,
  312. backgroundColor: DarkThemeUtil.multiColors(context, context.appColors.textWhite, darkColor: Colors.black),
  313. maxLines: 1,
  314. isFontMedium: true,
  315. ),
  316. ),
  317. const MyAssetImage(
  318. Assets.communityCamera,
  319. width: 21,
  320. height: 16.5,
  321. ),
  322. ],
  323. ).onTap((){
  324. vm.handlerGotoNewsfeedPost(context);
  325. }),
  326. );
  327. }
  328. Widget _buildGarageSalePost(BuildContext context, WidgetRef ref, vm, state){
  329. final userConfig = UserConfigService.getState(ref: ref);
  330. String title = S.current.sell_item;
  331. if(state.currentCategoryIdx == 1 ){
  332. // grage sale
  333. if(state.currentPageViewIdx == 3){
  334. title = S.current.sell_item;
  335. }else if(state.currentPageViewIdx == 4){
  336. title = S.current.rent_item;
  337. }
  338. }
  339. return Container(
  340. height: 65.5,
  341. width: double.infinity,
  342. padding: const EdgeInsets.only(left: 20, right: 20),
  343. color: DarkThemeUtil.multiColors(context, context.appColors.whiteBG, darkColor: Colors.black),
  344. child: Row(
  345. children: [
  346. MyLoadImage(
  347. userConfig.user?.avatar,
  348. width: 45,
  349. height: 45,
  350. isCircle: true,
  351. fit: BoxFit.fill,
  352. ),
  353. Expanded(
  354. child: MyTextView(
  355. title,
  356. textColor: DarkThemeUtil.multiColors(context, context.appColors.textBlack, darkColor: Colors.white),
  357. fontSize: 15,
  358. marginLeft: 15,
  359. alignment: Alignment.centerLeft,
  360. textAlign: TextAlign.left,
  361. backgroundColor: DarkThemeUtil.multiColors(context, context.appColors.textWhite, darkColor: Colors.black),
  362. maxLines: 1,
  363. isFontMedium: true,
  364. ),
  365. ),
  366. const MyAssetImage(
  367. Assets.communityCamera,
  368. width: 21,
  369. height: 16.5,
  370. ),
  371. ],
  372. ).onTap((){
  373. vm.handlerGotoGaragePost(context);
  374. }),
  375. );
  376. }
  377. }