main_activity_page.dart 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import 'package:easy_refresh/easy_refresh.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/services.dart';
  4. import 'package:flutter_riverpod/flutter_riverpod.dart';
  5. import 'package:flutter_screenutil/flutter_screenutil.dart';
  6. import 'package:flutter_swiper_view/flutter_swiper_view.dart';
  7. import 'package:go_router/go_router.dart';
  8. import 'package:news_app/constant/color_res.dart';
  9. import 'package:news_app/provider/activity_provider.dart';
  10. import 'package:news_app/widget/load_image.dart';
  11. import 'package:news_app/widget/my_txt.dart';
  12. import 'package:shimmer/shimmer.dart';
  13. import '../../constant/size_res.dart';
  14. import '../../gen/assets.gen.dart';
  15. import '../../model/activity_banner_model.dart';
  16. import '../../model/activity_model.dart';
  17. import '../../widget/list_animation_layout.dart';
  18. import '../search/search_bar_widget.dart';
  19. import '../video/video_detail_page.dart';
  20. import 'activity_card_widget.dart';
  21. /// @author: bo.zeng
  22. /// @email: cnhbwds@gmail.com
  23. /// @date: 2025 2025/4/9 16:00
  24. /// @description:
  25. class MainActivityPage extends ConsumerStatefulWidget {
  26. const MainActivityPage({super.key});
  27. @override
  28. ConsumerState<MainActivityPage> createState() => _MainActivityPageState();
  29. }
  30. final activityProvider = NotifierProvider<ActivityProvider, ActivityData>(
  31. () => ActivityProvider(),
  32. );
  33. class _MainActivityPageState extends ConsumerState<MainActivityPage>
  34. with AutomaticKeepAliveClientMixin {
  35. int _page = 1;
  36. int _currentTotal = 0;
  37. @override
  38. void initState() {
  39. super.initState();
  40. ref.read(activityProvider.notifier).fetchActivityBanner();
  41. ref.read(activityProvider.notifier).fetchActivityList(page: _page);
  42. }
  43. void bannerTap(ActivityBannerModel model){
  44. if (model.type == "activity") {
  45. if (model.contentType == "article") {
  46. context.push(
  47. "/activity/detail",
  48. extra: model.sourceId,
  49. );
  50. } else if (model.contentType == "video") {
  51. context.push(
  52. '/video/detail',
  53. extra: VideoParam(id: model.sourceId ?? ""),
  54. );
  55. }
  56. }
  57. }
  58. @override
  59. Widget build(BuildContext context) {
  60. super.build(context);
  61. final activityBanner = ref.watch(
  62. activityProvider.select((p) => p.activityBannerList),
  63. );
  64. final activityList = ref.watch(
  65. activityProvider.select((p) => p.activityList),
  66. );
  67. ref.listen(activityProvider.select((p) => p.activityList), (
  68. pre,
  69. next,
  70. ) {
  71. _currentTotal = next?.total ?? 0;
  72. });
  73. return Scaffold(
  74. appBar: AppBar(
  75. elevation: 0,
  76. // 移除阴影
  77. scrolledUnderElevation: 0,
  78. // 禁用滚动时的阴影变化
  79. backgroundColor: color5F59F7,
  80. centerTitle: true,
  81. title: Row(
  82. spacing: 10.w,
  83. children: [
  84. Image.asset(Assets.images.logo.path, width: 70.w),
  85. Expanded(
  86. child: SearchBarWidget(tabIndex: 1,),
  87. ),
  88. ],
  89. ),
  90. systemOverlayStyle: SystemUiOverlayStyle(
  91. statusBarColor: color5F59F7,
  92. statusBarIconBrightness: Brightness.light, // 状态栏图标颜色
  93. ),
  94. ),
  95. body: EasyRefresh.builder(
  96. onRefresh: () async {
  97. _page = 1;
  98. await Future.wait([
  99. ref.read(activityProvider.notifier).fetchActivityBanner(),
  100. // ref.read(activityProvider.notifier).fetchActivityVideo(),
  101. ref.read(activityProvider.notifier).fetchActivityList(page: _page),
  102. ]);
  103. return IndicatorResult.success;
  104. },
  105. onLoad: () async {
  106. // 重新读取最新的 itemData
  107. final newItemData = ref.read(activityProvider).activityList;
  108. int total = newItemData?.total ?? 0;
  109. if (_currentTotal >= total) {
  110. return IndicatorResult.noMore;
  111. } else {
  112. _page += 1;
  113. await ref
  114. .read(activityProvider.notifier)
  115. .fetchActivityList(page: _page);
  116. return IndicatorResult.success;
  117. }
  118. },
  119. childBuilder: (context, physics) {
  120. return Stack(
  121. children: [
  122. Positioned(
  123. child: Container(
  124. height: 100.h,
  125. decoration: BoxDecoration(
  126. gradient: LinearGradient(
  127. colors: [color5F59F7, color6592FD, Colors.white],
  128. begin: Alignment.topCenter,
  129. end: Alignment.bottomCenter,
  130. ),
  131. ),
  132. ),
  133. ),
  134. CustomScrollView(
  135. physics: physics,
  136. slivers: [
  137. if (activityBanner.isNotEmpty)
  138. SliverToBoxAdapter(
  139. child: Container(
  140. padding: EdgeInsets.symmetric(
  141. horizontal: horizontalPadding,
  142. vertical: 10.h,
  143. ),
  144. height: 190.h,
  145. child: ClipRRect(
  146. //切圆角加在这里防止滑动的时候圆角消失
  147. borderRadius: BorderRadius.circular(8),
  148. child: Swiper(
  149. autoplay: true,
  150. // 关键配置:关闭viewportFraction和pageSnapping
  151. viewportFraction: 1.0,
  152. onTap: (index){
  153. bannerTap(activityBanner[index]);
  154. },
  155. itemBuilder: (context, index) {
  156. return Stack(
  157. children: [
  158. LoadImage(
  159. activityBanner[index].image ?? '',
  160. width: screenWidth,
  161. height: 190.h,
  162. fit: BoxFit.cover,
  163. holderImg: 'bignone',
  164. ),
  165. Positioned(
  166. bottom: 20.h,
  167. left: 20.h,
  168. right: 20.h,
  169. child: Padding(
  170. padding: EdgeInsets.only(bottom: 5.h),
  171. child: myTxt(
  172. text: activityBanner[index].title ?? '',
  173. color: Colors.white,
  174. fontSize: 15.sp,
  175. ),
  176. ),
  177. ),
  178. ],
  179. );
  180. },
  181. itemCount: activityBanner.length,
  182. pagination: const SwiperPagination(),
  183. ),
  184. ),
  185. ),
  186. ),
  187. (activityList?.records ?? []).isEmpty ? SliverList.separated(
  188. separatorBuilder: (context, index) {
  189. return Container(height: 10.h);
  190. },
  191. itemBuilder: (context, index) {
  192. return Shimmer.fromColors(
  193. baseColor: Color(0xffe5e5e5),
  194. highlightColor: Color(0xfff5f5f5),
  195. child: AnimationItem(),
  196. );
  197. },
  198. itemCount: 20,
  199. ) :
  200. SliverPadding(
  201. padding: EdgeInsets.all(horizontalPadding),
  202. sliver: SliverList.separated(
  203. separatorBuilder: (context, index) {
  204. return SizedBox(height: 10.h);
  205. },
  206. itemCount: (activityList?.records ?? []).length,
  207. itemBuilder: (context, index) {
  208. ActivityModelRecord item =
  209. activityList!.records![index]!;
  210. return GestureDetector(
  211. onTap: () {
  212. context.push(
  213. "/activity/detail",
  214. extra: item.contentId,
  215. );
  216. },
  217. child: ActivityCardWidget(cellData: item),
  218. );
  219. },
  220. ),
  221. ),
  222. ],
  223. ),
  224. ],
  225. );
  226. },
  227. ),
  228. );
  229. }
  230. @override
  231. bool get wantKeepAlive => true;
  232. }