main_topic_page.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. import 'package:cached_network_image/cached_network_image.dart';
  2. import 'package:easy_refresh/easy_refresh.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/services.dart';
  5. import 'package:flutter_riverpod/flutter_riverpod.dart';
  6. import 'package:flutter_screenutil/flutter_screenutil.dart';
  7. import 'package:go_router/go_router.dart';
  8. import 'package:news_app/constant/size_res.dart';
  9. import 'package:news_app/ui/topic/topic_item_widget.dart';
  10. import 'package:news_app/widget/load_image.dart';
  11. import 'package:shimmer/shimmer.dart';
  12. import '../../constant/color_res.dart';
  13. import '../../constant/config.dart';
  14. import '../../gen/assets.gen.dart';
  15. import '../../model/topic_item_model.dart';
  16. import '../../provider/topic_list_provider.dart';
  17. import '../../provider/topic_provider.dart';
  18. import '../../widget/list_animation_layout.dart';
  19. import '../../widget/my_txt.dart';
  20. import '../search/search_bar_widget.dart';
  21. /// @author: bo.zeng
  22. /// @email: cnhbwds@gmail.com
  23. /// @date: 2025 2025/4/9 16:00
  24. /// @description:
  25. class MainTopicPage extends ConsumerStatefulWidget {
  26. const MainTopicPage({super.key});
  27. @override
  28. ConsumerState<MainTopicPage> createState() => _MainTopicPageState();
  29. }
  30. final topicProvider = NotifierProvider<TopicProvider, TopicData>(
  31. () => TopicProvider(),
  32. );
  33. final topicListProvider = NotifierProvider<TopicListProvider, TopicItemModel>(
  34. () => TopicListProvider(),
  35. );
  36. class _MainTopicPageState extends ConsumerState<MainTopicPage>
  37. with AutomaticKeepAliveClientMixin {
  38. int _page = 1;
  39. int _currentTotal = 0;
  40. @override
  41. void initState() {
  42. super.initState();
  43. ref.read(topicProvider.notifier).fetchTopicHot();
  44. ref.read(topicProvider.notifier).fetchRankHot();
  45. ref.read(topicListProvider.notifier).fetchList(page: _page, tid: "");
  46. eventBus.on<String>().listen((event) {
  47. if (event == "topicCall") {
  48. _page = 1;
  49. ref.read(topicListProvider.notifier).fetchList(page: _page, tid: "");
  50. }
  51. });
  52. }
  53. void longTapAction(String contentId) {}
  54. @override
  55. Widget build(BuildContext context) {
  56. super.build(context);
  57. final hotData = ref.watch(topicProvider.select((p) => p.topicHotList));
  58. final rankData = ref.watch(topicProvider.select((p) => p.topicRankList));
  59. final itemData = ref.watch(topicListProvider);
  60. ref.listen(topicListProvider.select((p) => p.records), (previous, next) {
  61. _currentTotal = next?.length ?? 0;
  62. });
  63. return Scaffold(
  64. appBar: AppBar(
  65. elevation: 0,
  66. // 移除阴影
  67. scrolledUnderElevation: 0,
  68. // 禁用滚动时的阴影变化
  69. backgroundColor: color5F59F7,
  70. centerTitle: true,
  71. title: Row(
  72. spacing: 10.w,
  73. children: [
  74. Image.asset(Assets.images.logo.path, width: 70.w),
  75. Expanded(child: SearchBarWidget(tabIndex: 3)),
  76. ],
  77. ),
  78. systemOverlayStyle: SystemUiOverlayStyle(
  79. statusBarColor: color5F59F7,
  80. statusBarIconBrightness: Brightness.light, // 状态栏图标颜色
  81. ),
  82. ),
  83. body: EasyRefresh.builder(
  84. onRefresh: () async {
  85. _page = 1;
  86. await Future.wait([
  87. ref.read(topicProvider.notifier).fetchTopicHot(),
  88. ref.read(topicProvider.notifier).fetchRankHot(),
  89. ref
  90. .read(topicListProvider.notifier)
  91. .fetchList(page: _page, tid: ""),
  92. ]);
  93. return IndicatorResult.success;
  94. },
  95. onLoad: () async {
  96. // 重新读取最新的 itemData
  97. final newItemData = ref.read(topicListProvider);
  98. int total = newItemData.total ?? 0;
  99. if (_currentTotal >= total) {
  100. return IndicatorResult.noMore;
  101. } else {
  102. _page += 1;
  103. await ref
  104. .read(topicListProvider.notifier)
  105. .fetchList(page: _page, tid: "");
  106. return IndicatorResult.success;
  107. }
  108. },
  109. childBuilder: (context, physics) {
  110. return Stack(
  111. children: [
  112. Positioned(
  113. child: Container(
  114. height: 100.h,
  115. decoration: BoxDecoration(
  116. gradient: LinearGradient(
  117. colors: [color5F59F7, color6592FD, Colors.white],
  118. begin: Alignment.topCenter,
  119. end: Alignment.bottomCenter,
  120. ),
  121. ),
  122. ),
  123. ),
  124. CustomScrollView(
  125. slivers: [
  126. if (hotData.isNotEmpty)
  127. SliverPadding(
  128. padding: EdgeInsets.symmetric(
  129. vertical: horizontalPadding,
  130. horizontal: horizontalPadding,
  131. ),
  132. sliver: SliverToBoxAdapter(
  133. child: Row(
  134. spacing: 5.w,
  135. children: [
  136. Icon(
  137. Icons.fire_hydrant_alt_rounded,
  138. size: 20,
  139. color: Colors.white,
  140. ),
  141. myTxt(
  142. text: "热门话题",
  143. color: Colors.white,
  144. fontWeight: FontWeight.bold,
  145. fontSize: 18.sp,
  146. ),
  147. ],
  148. ),
  149. ),
  150. ),
  151. SliverPadding(
  152. padding: EdgeInsets.symmetric(
  153. horizontal: horizontalPadding,
  154. ),
  155. sliver: SliverToBoxAdapter(
  156. child: Builder(
  157. builder: (context) {
  158. if (hotData.isNotEmpty) {
  159. return Row(
  160. spacing: horizontalPadding,
  161. children: List<Widget>.generate(hotData.length, (
  162. index,
  163. ) {
  164. return Expanded(
  165. child: Stack(
  166. alignment: Alignment.bottomLeft,
  167. children: [
  168. AspectRatio(
  169. aspectRatio: 16 / 9,
  170. child: ClipRRect(
  171. borderRadius: BorderRadius.circular(
  172. 8.r,
  173. ),
  174. child: CachedNetworkImage(
  175. fit: BoxFit.cover,
  176. imageUrl:
  177. hotData[index].image ?? "",
  178. ),
  179. ),
  180. ),
  181. Positioned(
  182. bottom: 5.h,
  183. left: 5.h,
  184. right: 5.h,
  185. child: myTxt(
  186. text: hotData[index].title ?? "",
  187. fontSize: 14.sp,
  188. color: Colors.white,
  189. maxLines: 2, // 限制文本的最大行数
  190. ),
  191. ),
  192. ],
  193. ),
  194. );
  195. }),
  196. );
  197. } else {
  198. return const SizedBox.shrink();
  199. }
  200. },
  201. ),
  202. ),
  203. ),
  204. SliverPadding(
  205. padding: EdgeInsets.symmetric(
  206. vertical: horizontalPadding,
  207. horizontal: horizontalPadding,
  208. ),
  209. sliver: SliverToBoxAdapter(
  210. child: Row(
  211. spacing: 5.w,
  212. children: [
  213. // Image.asset(Assets.images.fire.path, width: 13.w),
  214. LoadAssetImage('topic_fire', width: 13.w),
  215. myTxt(
  216. text: "精选话题",
  217. color: Colors.black,
  218. fontWeight: FontWeight.bold,
  219. fontSize: 18.sp,
  220. ),
  221. ],
  222. ),
  223. ),
  224. ),
  225. SliverToBoxAdapter(
  226. child: Container(
  227. margin: EdgeInsets.symmetric(
  228. horizontal: horizontalPadding,
  229. ),
  230. padding: EdgeInsets.all(8.w),
  231. decoration: BoxDecoration(
  232. borderRadius: BorderRadius.circular(8.r),
  233. color: Colors.white,
  234. ),
  235. child: Builder(
  236. builder: (context) {
  237. return Column(
  238. spacing: 8.h,
  239. children: List<Widget>.generate(rankData.length, (
  240. index,
  241. ) {
  242. return GestureDetector(
  243. onTap: () {
  244. context.push(
  245. "/topic/list",
  246. extra: rankData[index].contentId,
  247. );
  248. },
  249. child: Row(
  250. spacing: 5.w,
  251. children: [
  252. SizedBox(width: 1.w),
  253. myTxt(
  254. text: "${index + 1}",
  255. fontWeight: FontWeight.w900,
  256. color:
  257. index <= 3
  258. ? Colors.red
  259. : Colors.black,
  260. fontSize: 15.sp,
  261. ),
  262. Flexible(
  263. child: myTxt(
  264. text: rankData[index].content ?? "",
  265. color: Colors.black,
  266. fontSize: 15.sp,
  267. maxLines: 1,
  268. ),
  269. ),
  270. ],
  271. ),
  272. );
  273. }),
  274. );
  275. },
  276. ),
  277. ),
  278. ),
  279. (itemData.records ?? []).isEmpty
  280. ? SliverList.separated(
  281. separatorBuilder: (context, index) {
  282. return Container(height: 10.h);
  283. },
  284. itemBuilder: (context, index) {
  285. return Shimmer.fromColors(
  286. baseColor: Color(0xffe5e5e5),
  287. highlightColor: Color(0xfff5f5f5),
  288. child: AnimationItem(),
  289. );
  290. },
  291. itemCount: 20,
  292. )
  293. : SliverList(
  294. delegate: SliverChildBuilderDelegate((context, index) {
  295. return GestureDetector(
  296. onTap: () {
  297. context.push(
  298. "/topic/detail",
  299. extra: itemData.records?[index].contentId,
  300. );
  301. },
  302. child: Padding(
  303. padding: EdgeInsets.only(
  304. left: horizontalPadding,
  305. right: horizontalPadding,
  306. top: horizontalPadding,
  307. ),
  308. child: TopicItemWidget(
  309. longTapAction,
  310. item: itemData.records?[index],
  311. ),
  312. ),
  313. );
  314. }, childCount: itemData.records?.length ?? 0),
  315. ),
  316. ],
  317. physics: physics,
  318. ),
  319. ],
  320. );
  321. },
  322. ),
  323. );
  324. }
  325. @override
  326. bool get wantKeepAlive => true;
  327. }