From 5d15bf5e59b5790c12631640e5e914d456035657 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 27 Jun 2026 09:45:18 +0800 Subject: [PATCH] refactor dyn uplist Signed-off-by: dom --- lib/http/dynamics.dart | 47 +++- .../dynamics/article_content_model.dart | 2 +- lib/models/dynamics/up.dart | 60 +++-- lib/models_new/follow/data.dart | 10 +- lib/pages/dynamics/controller.dart | 247 ++++++++---------- lib/pages/dynamics/view.dart | 13 +- lib/pages/dynamics/widgets/up_panel.dart | 75 +++--- lib/pages/dynamics_tab/controller.dart | 19 +- lib/pages/dynamics_tab/view.dart | 30 +-- 9 files changed, 228 insertions(+), 275 deletions(-) diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index 3ff0e3509..6553bb9b7 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/constants.dart'; +import 'package:PiliPlus/http/error_msg.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/reply.dart'; @@ -33,19 +34,14 @@ import 'package:dio/dio.dart'; abstract final class DynamicsHttp { @pragma('vm:notify-debugger-on-exception') static Future> followDynamic({ - DynamicsTabType type = DynamicsTabType.all, + int? hostMid, String? offset, - int? mid, Set? tempBannedList, + DynamicsTabType type = .all, }) async { Map data = { - if (type == DynamicsTabType.up) - 'host_mid': mid - else ...{ - 'type': type.name, - 'timezone_offset': '-480', - }, - 'offset': offset, + if (type == .up) 'host_mid': hostMid else 'type': type.name, + 'offset': ?offset, 'features': Constants.dynFeatures, }; final res = await Request().get(Api.followDynamic, queryParameters: data); @@ -61,7 +57,7 @@ abstract final class DynamicsHttp { return await followDynamic( type: type, offset: data.offset, - mid: mid, + hostMid: hostMid, tempBannedList: tempBannedList, ); } @@ -89,22 +85,45 @@ abstract final class DynamicsHttp { } } - static Future> dynUpList(String? offset) async { + static Future> dynUpList(String? offset) async { final res = await Request().get( Api.dynUplist, queryParameters: { - 'offset': offset, + 'offset': ?offset, 'platform': 'web', 'web_location': 333.1365, }, ); if (res.data['code'] == 0) { - return Success(DynUpList.fromJson(res.data['data'])); + return Success(FollowUpModel.fromUpList(res.data['data'])); } else { return Error(res.data['message']); } } + static Future> followings({ + int? vmid, + int? pn, + int ps = 20, + String orderType = '', // ''=>最近关注,'attention'=>最常访问 + }) async { + final res = await Request().get( + Api.followings, + queryParameters: { + 'vmid': vmid, + 'pn': pn, + 'ps': ps, + 'order': 'desc', + 'order_type': orderType, + }, + ); + if (res.data['code'] == 0) { + return Success(FollowUpModel.fromFollowList(res.data['data'])); + } else { + return Error(errorMsg[res.data['code']] ?? res.data['message']); + } + } + // 动态点赞 // static Future likeDynamic({ // required String? dynamicId, @@ -817,7 +836,7 @@ abstract final class DynamicsHttp { Api.dynReaction, queryParameters: { 'id': id, - 'offset': offset, + 'offset': ?offset, 'web_location': 333.1369, }, ); diff --git a/lib/models/dynamics/article_content_model.dart b/lib/models/dynamics/article_content_model.dart index d328c0b47..6f5e6c28e 100644 --- a/lib/models/dynamics/article_content_model.dart +++ b/lib/models/dynamics/article_content_model.dart @@ -132,7 +132,7 @@ class Word { style = json['style'] == null ? null : Style.fromJson(json['style']); if (json['color'] case final String rawColor when rawColor.startsWith('#')) { - color = ColourUtils.parse2Int(json['color']); + color = ColourUtils.parse2Int(rawColor); } fontLevel = json['font_level']; } diff --git a/lib/models/dynamics/up.dart b/lib/models/dynamics/up.dart index beca77eda..2aba3faff 100644 --- a/lib/models/dynamics/up.dart +++ b/lib/models/dynamics/up.dart @@ -1,41 +1,43 @@ +import 'package:PiliPlus/models_new/follow/list.dart'; import 'package:PiliPlus/utils/parse_int.dart'; class FollowUpModel { - FollowUpModel({ - this.liveUsers, - required this.upList, - }); - LiveUsers? liveUsers; - late List upList; - bool? hasMore; - String? offset; - - FollowUpModel.fromJson(Map json) { - liveUsers = json['live_users'] != null - ? LiveUsers.fromJson(json['live_users']) - : null; - upList = - (json['up_list']?['items'] as List?) - ?.map((e) => UpItem.fromJson(e)) - .toList() ?? - []; - hasMore = json['up_list']?['has_more']; - offset = json['up_list']?['offset']; - } -} - -class DynUpList { List? upList; bool? hasMore; String? offset; - DynUpList.fromJson(Map json) { - upList = (json['items'] as List?) - ?.map((e) => UpItem.fromJson(e)) + void addAllUpList(List newList) { + if (upList != null) { + upList!.addAll(newList); + } else { + upList = newList; + } + } + + factory FollowUpModel.fromJson(Map json) { + final model = FollowUpModel.fromUpList(json['up_list']); + final liveUsers = json['live_users']; + if (liveUsers != null) { + model.liveUsers = LiveUsers.fromJson(liveUsers); + } + return model; + } + + FollowUpModel.fromUpList(Map? json) { + if (json != null) { + upList = (json['items'] as List?) + ?.map((e) => UpItem.fromJson(e)) + .toList(); + hasMore = json['has_more']; + offset = json['offset']; + } + } + + FollowUpModel.fromFollowList(Map json) { + upList = (json['list'] as List?) + ?.map((e) => FollowItemModel.fromJson(e)) .toList(); - hasMore = json['has_more']; - offset = json['offset']; } } diff --git a/lib/models_new/follow/data.dart b/lib/models_new/follow/data.dart index 7efe4ff42..9dedeee52 100644 --- a/lib/models_new/follow/data.dart +++ b/lib/models_new/follow/data.dart @@ -1,17 +1,15 @@ import 'package:PiliPlus/models_new/follow/list.dart'; class FollowData { - late List list; + List? list; int? total; FollowData({required this.list, this.total}); factory FollowData.fromJson(Map json) => FollowData( - list: - (json['list'] as List?) - ?.map((e) => FollowItemModel.fromJson(e as Map)) - .toList() ?? - [], + list: (json['list'] as List?) + ?.map((e) => FollowItemModel.fromJson(e as Map)) + .toList(), total: json['total'] as int?, ); } diff --git a/lib/pages/dynamics/controller.dart b/lib/pages/dynamics/controller.dart index b75f8a71c..539bd2709 100644 --- a/lib/pages/dynamics/controller.dart +++ b/lib/pages/dynamics/controller.dart @@ -1,12 +1,10 @@ import 'dart:async'; import 'package:PiliPlus/http/dynamics.dart'; -import 'package:PiliPlus/http/follow.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/dynamic/dynamics_type.dart'; import 'package:PiliPlus/models/dynamics/up.dart'; -import 'package:PiliPlus/models_new/follow/data.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_data_controller.dart'; import 'package:PiliPlus/pages/dynamics_tab/controller.dart'; import 'package:PiliPlus/services/account_service.dart'; import 'package:PiliPlus/utils/accounts.dart'; @@ -17,24 +15,20 @@ import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -class DynamicsController extends GetxController - with GetSingleTickerProviderStateMixin, ScrollOrRefreshMixin, AccountMixin { - @override - final ScrollController scrollController = ScrollController(); +class DynamicsController + extends CommonDataController + with GetSingleTickerProviderStateMixin, AccountMixin { late final TabController tabController; - late final RxInt mid = (-1).obs; - late int currentMid = -1; + final Set tempBannedList = {}; - Set tempBannedList = {}; - - final Rx> upState = - LoadingState.loading().obs; - late int _upPage = 1; - late bool _upEnd = false; + String? _offset; + late int _page = 1; + late bool _isEnd = false; Set? _cacheUpList; - late final _showAllUp = Pref.dynamicsShowAllFollowedUp; + late int hostMid = -1, currentMid = -1; late bool showLiveUp = Pref.expandDynLivePanel; + late final _showAllUp = Pref.dynamicsShowAllFollowedUp; final upPanelPosition = Pref.upPanelPosition; @@ -55,154 +49,56 @@ class DynamicsController extends GetxController void onInit() { super.onInit(); tabController = TabController( - length: DynamicsTabType.values.length, vsync: this, + length: DynamicsTabType.values.length, initialIndex: Pref.defaultDynamicTypeIndex, ); - queryFollowUp(); + queryData(); } - void onLoadMoreUp() { - if (_showAllUp) { - queryAllUp(); - } else { - queryUpList(); - } - } - - Future queryUpList() async { - if (isQuerying || _upEnd) return; - isQuerying = true; - - final res = await DynamicsHttp.dynUpList(upState.value.data.offset); - - if (res case Success(:final response)) { - if (response.hasMore == false || response.offset.isNullOrEmpty) { - _upEnd = true; - } - final upData = upState.value.data - ..hasMore = response.hasMore - ..offset = response.offset; - final list = response.upList; - if (list != null && list.isNotEmpty) { - upData.upList.addAll(list); - upState.refresh(); - } - } - - isQuerying = false; - } - - Future queryAllUp() async { - if (isQuerying || _upEnd) return; - isQuerying = true; - - final res = await FollowHttp.followings( - vmid: Accounts.main.mid, - pn: _upPage, - orderType: 'attention', - ps: 50, - ); - - if (res case Success(:final response)) { - _upPage++; - final list = response.list; - if (list.isEmpty) { - _upEnd = true; - } - upState - ..value.data.upList.addAll( - list..removeWhere((e) => _cacheUpList?.contains(e) == true), - ) - ..refresh(); - } - - isQuerying = false; - } - - late bool isQuerying = false; - Future queryFollowUp() async { - if (isQuerying) return; - isQuerying = true; - - if (!accountService.isLogin.value) { - upState.value = const Error(null); - isQuerying = false; - return; - } - - // reset - _upEnd = false; - if (_showAllUp) _upPage = 1; - - final res = await Future.wait([ - DynamicsHttp.followUp(), - if (_showAllUp) - FollowHttp.followings( - vmid: Accounts.main.mid, - pn: _upPage, - orderType: 'attention', - ps: 50, - ), - ]); - - final first = res.first; - if (first case final Success i) { - final data = i.response; - final second = res.elementAtOrNull(1); - if (second case final Success j) { - final data1 = j.response; - final list1 = data1.list; - - _upPage++; - if (list1.isEmpty || list1.length >= (data1.total ?? 0)) { - _upEnd = true; - } - - final list = data.upList; - list.addAll(list1..removeWhere((_cacheUpList = list.toSet()).contains)); - } - if (!_showAllUp) { - if (data.hasMore == false || data.offset.isNullOrEmpty) { - _upEnd = true; - } - } - upState.value = Success(data); - } else { - upState.value = const Error(null); - } - - isQuerying = false; + void _jumpToTab(int mid) { + tabController.index = mid == -1 ? 0 : 4; } void onSelectUp(int mid) { - if (this.mid.value == mid) { - tabController.index = (mid == -1 ? 0 : 4); + if (currentMid == mid) { + _jumpToTab(mid); if (mid == -1) { - queryFollowUp(); + singleRefresh(); } controller?.onReload(); return; } - this.mid.value = mid; - tabController.index = (mid == -1 ? 0 : 4); + if (mid != -1) { + hostMid = mid; + try { + Get.find( + tag: DynamicsTabType.up.name, + ).onReload(); + } catch (_) {} + } + + currentMid = mid; + _jumpToTab(mid); + } + + Future singleRefresh() { + if (_showAllUp) { + _page = 1; + _cacheUpList = null; + } + _offset = null; + _isEnd = false; + return super.onRefresh(); } @override Future onRefresh() { - _refreshFollowUp(); + singleRefresh(); return controller!.onRefresh(); } - void _refreshFollowUp() { - if (_showAllUp) { - _upPage = 1; - _cacheUpList = null; - } - queryFollowUp(); - } - @override void animateToTop() { controller?.animateToTop(); @@ -234,10 +130,71 @@ class DynamicsController extends GetxController @override void onClose() { tabController.dispose(); - scrollController.dispose(); super.onClose(); } @override - void onChangeAccount(bool isLogin) => _refreshFollowUp(); + void onChangeAccount(bool isLogin) => onReload(); + + @override + Future> customGetData() { + if (_offset == null) { + return DynamicsHttp.followUp(); + } + if (_showAllUp) { + return DynamicsHttp.followings( + vmid: Accounts.main.mid, + pn: _page, + orderType: 'attention', + ps: 50, + ); + } else { + return DynamicsHttp.dynUpList(_offset); + } + } + + @override + Future queryData([bool isRefresh = true]) { + if (!isRefresh && _isEnd) return Future.value(); + return super.queryData(isRefresh); + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { + final res = response.response; + + if (_showAllUp) { + if (res.upList?.isNotEmpty != true) { + _isEnd = true; + } + } else { + _offset = res.offset; + if (res.hasMore != true || _offset.isNullOrEmpty) { + _isEnd = true; + } + } + + if (isRefresh) { + if (_showAllUp) { + _offset = ''; + _cacheUpList = res.upList?.toSet(); + } + loadingState.value = response; + } else { + if (_showAllUp) { + _page++; + } + + if (res.upList case final upList? when upList.isNotEmpty) { + if (_showAllUp && _cacheUpList != null) { + upList.removeWhere(_cacheUpList!.contains); + } + loadingState + ..value.data.addAllUpList(upList) + ..refresh(); + } + } + + return true; + } } diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index 9d3d1b446..1b06398e1 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -65,11 +65,13 @@ class _DynamicsPageState extends CommonPageState onNotification: (notification) { final metrics = notification.metrics; if (metrics.pixels >= metrics.maxScrollExtent - 300) { - _dynamicsController.onLoadMoreUp(); + _dynamicsController.onLoadMore(); } return false; }, - child: Obx(() => _buildUpPanel(_dynamicsController.upState.value)), + child: Obx( + () => _buildUpPanel(_dynamicsController.loadingState.value), + ), ), ), ); @@ -78,15 +80,14 @@ class _DynamicsPageState extends CommonPageState Widget _buildUpPanel(LoadingState upState) { return switch (upState) { Loading() => const SizedBox.shrink(), - Success() => UpPanel( + Success(:final response) => UpPanel( + upData: response, dynamicsController: _dynamicsController, ), Error() => Center( child: IconButton( icon: const Icon(Icons.refresh), - onPressed: () => _dynamicsController - ..upState.value = LoadingState.loading() - ..queryFollowUp(), + onPressed: _dynamicsController.onReload, ), ), }; diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart index 0036bb07a..a798324b6 100644 --- a/lib/pages/dynamics/widgets/up_panel.dart +++ b/lib/pages/dynamics/widgets/up_panel.dart @@ -1,7 +1,6 @@ import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/dynamic/up_panel_position.dart'; -import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models/dynamics/up.dart'; import 'package:PiliPlus/pages/dynamics/controller.dart'; import 'package:PiliPlus/pages/live_follow/view.dart'; @@ -15,10 +14,12 @@ import 'package:get/get.dart'; class UpPanel extends StatefulWidget { const UpPanel({ - required this.dynamicsController, super.key, + required this.upData, + required this.dynamicsController, }); + final FollowUpModel upData; final DynamicsController dynamicsController; @override @@ -33,16 +34,12 @@ class _UpPanelState extends State { @override Widget build(BuildContext context) { - final accountService = controller.accountService; - if (!accountService.isLogin.value) { - return const SizedBox.shrink(); - } final theme = Theme.of(context); - final upData = controller.upState.value.data; - final List upList = upData.upList; - final List? liveList = upData.liveUsers?.items; + final upData = widget.upData; + final upList = upData.upList; + final liveList = upData.liveUsers?.items; return CustomScrollView( - scrollDirection: isTop ? Axis.horizontal : Axis.vertical, + scrollDirection: isTop ? .horizontal : .vertical, physics: const AlwaysScrollableScrollPhysics(), controller: controller.scrollController, slivers: [ @@ -54,11 +51,11 @@ class _UpPanelState extends State { onLongPress: toFollowPage, onSecondaryTap: PlatformUtils.isMobile ? null : toFollowPage, child: Container( - alignment: Alignment.center, + alignment: .center, height: isTop ? 76 : 60, - padding: isTop ? const EdgeInsets.only(left: 12, right: 6) : null, + padding: isTop ? const .only(left: 12, right: 6) : null, child: Text.rich( - textAlign: TextAlign.center, + textAlign: .center, style: TextStyle( fontSize: 13, color: theme.colorScheme.primary, @@ -71,7 +68,7 @@ class _UpPanelState extends State { if (!isTop) ...[ const TextSpan(text: '\n'), WidgetSpan( - alignment: PlaceholderAlignment.middle, + alignment: .middle, child: Icon( controller.showLiveUp ? Icons.expand_less @@ -82,7 +79,7 @@ class _UpPanelState extends State { ), ] else WidgetSpan( - alignment: PlaceholderAlignment.middle, + alignment: .middle, child: Icon( controller.showLiveUp ? Icons.keyboard_arrow_right @@ -113,13 +110,13 @@ class _UpPanelState extends State { theme, UpItem( uname: '我', - face: accountService.face.value, + face: controller.accountService.face.value, mid: Accounts.main.mid, ), ), ), ), - if (upList.isNotEmpty) + if (upList != null && upList.isNotEmpty) SliverList.builder( itemCount: upList.length, itemBuilder: (context, index) { @@ -131,23 +128,19 @@ class _UpPanelState extends State { ); } - void _onSelect(UpItem data) { - controller - ..currentMid = data.mid - ..onSelectUp(data.mid); - - data.hasUpdate = false; - + void _onSelect(UpItem item) { + item.hasUpdate = false; + controller.onSelectUp(item.mid); setState(() {}); } - Widget upItemBuild(ThemeData theme, UpItem data) { + Widget upItemBuild(ThemeData theme, UpItem item) { final currentMid = controller.currentMid; - final isLive = data is LiveUserItem; - final isCurrent = isLive || currentMid == data.mid || currentMid == -1; + final isLive = item is LiveUserItem; + final isCurrent = isLive || currentMid == item.mid || currentMid == -1; - final isAll = data.mid == -1; - void toMemberPage() => Get.toNamed('/member?mid=${data.mid}'); + final isAll = item.mid == -1; + void toMemberPage() => Get.toNamed('/member?mid=${item.mid}'); Widget avatar; if (isAll) { @@ -166,12 +159,12 @@ class _UpPanelState extends State { ); } else { avatar = Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), + padding: const .symmetric(horizontal: 4), child: NetworkImgLayer( width: 38, height: 38, - src: data.face, - type: ImageType.avatar, + src: item.face, + type: .avatar, ), ); if (isLive) { @@ -191,7 +184,7 @@ class _UpPanelState extends State { ), ], ); - } else if (data.hasUpdate ?? false) { + } else if (item.hasUpdate ?? false) { avatar = Stack( clipBehavior: .none, children: [ @@ -216,9 +209,9 @@ class _UpPanelState extends State { onTap: () { feedBack(); if (isLive) { - PageUtils.toLiveRoom(data.roomId); + PageUtils.toLiveRoom(item.roomId); } else { - _onSelect(data); + _onSelect(item); } }, // onDoubleTap: isLive ? () => _onSelect(data) : null, @@ -228,18 +221,18 @@ class _UpPanelState extends State { opacity: isCurrent ? 1 : 0.6, child: Column( spacing: 4, - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: .min, + mainAxisAlignment: .center, children: [ avatar, Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), + padding: const .symmetric(horizontal: 4), child: Text( - isTop ? '${data.uname}\n' : data.uname!, + isTop ? '${item.uname}\n' : item.uname!, maxLines: 2, - textAlign: TextAlign.center, + textAlign: .center, style: TextStyle( - color: currentMid == data.mid + color: currentMid == item.mid ? theme.colorScheme.primary : theme.colorScheme.outline, height: 1.1, diff --git a/lib/pages/dynamics_tab/controller.dart b/lib/pages/dynamics_tab/controller.dart index e3f6d6b0a..14e21587a 100644 --- a/lib/pages/dynamics_tab/controller.dart +++ b/lib/pages/dynamics_tab/controller.dart @@ -16,9 +16,10 @@ class DynamicsTabController with AccountMixin { DynamicsTabController({required this.dynamicsType}); final DynamicsTabType dynamicsType; - String offset = ''; - int? mid; - late final MainController mainController = Get.find(); + + String? offset; + + late final mainController = Get.find(); final dynamicsController = Get.find(); @override @@ -29,25 +30,25 @@ class DynamicsTabController @override Future onRefresh() { - if (dynamicsType == DynamicsTabType.all) { + if (dynamicsType == .all) { mainController.setDynCount(); } - offset = ''; + offset = null; return super.onRefresh(); } @override List? getDataList(DynamicsDataModel response) { - offset = response.offset ?? ''; + offset = response.offset; return response.items; } @override Future> customGetData() => DynamicsHttp.followDynamic( - type: dynamicsType, offset: offset, - mid: mid, + type: dynamicsType, + hostMid: dynamicsController.hostMid, tempBannedList: dynamicsController.tempBannedList, ); @@ -70,7 +71,7 @@ class DynamicsTabController } void onBlock(int index) { - if (dynamicsType != DynamicsTabType.up) { + if (dynamicsType != .up) { loadingState ..value.data!.removeAt(index) ..refresh(); diff --git a/lib/pages/dynamics_tab/view.dart b/lib/pages/dynamics_tab/view.dart index c14c2e08b..370407755 100644 --- a/lib/pages/dynamics_tab/view.dart +++ b/lib/pages/dynamics_tab/view.dart @@ -27,9 +27,7 @@ class DynamicsTabPage extends StatefulWidget { class _DynamicsTabPageState extends State with AutomaticKeepAliveClientMixin, DynMixin { - StreamSubscription? _listener; - - DynamicsController dynamicsController = Get.putOrFind(DynamicsController.new); + final dynamicsController = Get.putOrFind(DynamicsController.new); late final DynamicsTabController controller; @override @@ -38,38 +36,22 @@ class _DynamicsTabPageState extends State @override void initState() { controller = Get.putOrFind( - () => - DynamicsTabController(dynamicsType: widget.dynamicsType) - ..mid = dynamicsController.mid.value, + () => DynamicsTabController(dynamicsType: widget.dynamicsType), tag: widget.dynamicsType.name, ); super.initState(); - if (widget.dynamicsType == DynamicsTabType.up) { - _listener = dynamicsController.mid.listen((mid) { - if (mid != -1) { - controller - ..mid = mid - ..onReload(); - } - }); - } } - @override - void dispose() { - _listener?.cancel(); - dynamicsController.mid.close(); - super.dispose(); + Future onRefresh() { + dynamicsController.singleRefresh(); + return controller.onRefresh(); } @override Widget build(BuildContext context) { super.build(context); return refreshIndicator( - onRefresh: () { - dynamicsController.queryFollowUp(); - return controller.onRefresh(); - }, + onRefresh: onRefresh, child: CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), controller: controller.scrollController,