From ebcbe8143b23ff64b0028e88ff65b6884ec865af Mon Sep 17 00:00:00 2001 From: dom Date: Wed, 24 Jun 2026 10:47:27 +0800 Subject: [PATCH] refactor dyn page Signed-off-by: dom --- lib/http/dynamics.dart | 29 +- lib/http/init.dart | 19 +- .../dynamics/article_content_model.dart | 2 +- lib/models/dynamics/up.dart | 47 +-- lib/pages/dynamics/controller.dart | 155 +++------ lib/pages/dynamics/view.dart | 200 ++++++----- lib/pages/dynamics/widgets/up_panel.dart | 317 +++++++++--------- lib/pages/dynamics_tab/controller.dart | 46 +-- lib/pages/dynamics_tab/view.dart | 48 +-- lib/utils/request_utils.dart | 14 +- 10 files changed, 385 insertions(+), 492 deletions(-) diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index 0c1e9a7f4..86701730a 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -33,22 +33,19 @@ import 'package:dio/dio.dart'; abstract final class DynamicsHttp { @pragma('vm:notify-debugger-on-exception') static Future> followDynamic({ - DynamicsTabType type = .all, + int? hostMid, String? offset, - int? mid, Set? tempBannedList, + DynamicsTabType type = .all, }) async { - Map data = { - if (type == .up) - 'host_mid': mid - else ...{ - 'type': type.name, - 'timezone_offset': '-480', + final res = await Request.get( + Api.followDynamic, + queryParameters: { + if (type == .up) 'host_mid': hostMid else 'type': type.name, + 'offset': ?offset, + 'features': Constants.dynFeatures, }, - 'offset': offset, - 'features': Constants.dynFeatures, - }; - final res = await Request.get(Api.followDynamic, queryParameters: data); + ); final code = res.data['code']; if (code == 0) { try { @@ -61,7 +58,7 @@ abstract final class DynamicsHttp { return await followDynamic( type: type, offset: data.offset, - mid: mid, + hostMid: hostMid, tempBannedList: tempBannedList, ); } @@ -89,17 +86,17 @@ 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']); } diff --git a/lib/http/init.dart b/lib/http/init.dart index 13449ee19..52899ff8f 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -15,6 +15,7 @@ import 'package:brotli/brotli.dart'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:dio_http2_adapter/dio_http2_adapter.dart'; +import 'package:flutter/foundation.dart' show kDebugMode; abstract final class Request { static const _gzipDecoder = GZipDecoder(); @@ -163,15 +164,15 @@ abstract final class Request { } // 日志拦截器 输出请求、响应内容 - // if (kDebugMode) { - // dio.interceptors.add( - // LogInterceptor( - // request: false, - // requestHeader: false, - // responseHeader: false, - // ), - // ); - // } + if (kDebugMode) { + dio.interceptors.add( + LogInterceptor( + request: false, + requestHeader: false, + responseHeader: false, + ), + ); + } dio ..transformer = BackgroundTransformer() diff --git a/lib/models/dynamics/article_content_model.dart b/lib/models/dynamics/article_content_model.dart index b49420fad..99de20f16 100644 --- a/lib/models/dynamics/article_content_model.dart +++ b/lib/models/dynamics/article_content_model.dart @@ -134,7 +134,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..234eff089 100644 --- a/lib/models/dynamics/up.dart +++ b/lib/models/dynamics/up.dart @@ -1,41 +1,28 @@ 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)) - .toList(); - hasMore = json['has_more']; - offset = json['offset']; + 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']; + } } } diff --git a/lib/pages/dynamics/controller.dart b/lib/pages/dynamics/controller.dart index f08b1809c..71bb9fd58 100644 --- a/lib/pages/dynamics/controller.dart +++ b/lib/pages/dynamics/controller.dart @@ -1,151 +1,86 @@ import 'package:PiliPlus/http/dynamics.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/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/extension/scroll_controller_ext.dart'; import 'package:PiliPlus/utils/extension/string_ext.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; -import 'package:flutter/material.dart' show ScrollController, TabController; import 'package:get/get.dart'; -class DynamicsController extends GetxController - with GetSingleTickerProviderStateMixin, ScrollOrRefreshMixin, AccountMixin { - @override - final ScrollController scrollController = ScrollController(); - late final TabController tabController; +class DynamicsController + extends CommonDataController + with AccountMixin { + String? offset; - late final RxInt mid = (-1).obs; - late int currentMid = -1; - - Set tempBannedList = {}; - - final Rx> upState = - LoadingState.loading().obs; - late bool _upEnd = false; + bool isEnd = false; late bool showLiveUp = false; - + final Set tempBannedList = {}; final upPanelPosition = Pref.upPanelPosition; + late final innerController = Get.find(); + @override final AccountService accountService = Get.find(); - DynamicsTabController? get controller { - try { - return Get.find( - tag: DynamicsTabType.values[tabController.index].name, - ); - } catch (_) { - return null; - } - } - @override void onInit() { super.onInit(); - tabController = TabController( - length: DynamicsTabType.values.length, - vsync: this, - ); - queryFollowUp(); + queryData(); } - void onLoadMoreUp() { - queryUpList(); + @override + bool customHandleResponse(bool isRefresh, Success response) { + final res = response.response; + offset = res.offset; + if (!isRefresh) { + final lastData = loadingState.value.data; + if (res.upList case final upList?) { + lastData.upList!.addAll(upList); + } + res + ..liveUsers = lastData.liveUsers + ..upList = lastData.upList; + } + if (res.hasMore != true || offset.isNullOrEmpty) { + isEnd = true; + } + return false; } - 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(); - } + @override + Future> customGetData() { + if (offset == null) { + return DynamicsHttp.followUp(); } - - isQuerying = false; + return DynamicsHttp.dynUpList(offset); } - 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; - - final res = await DynamicsHttp.followUp(); - - if (res case final Success i) { - final data = i.response; - if (data.hasMore == false || data.offset.isNullOrEmpty) { - _upEnd = true; - } - upState.value = Success(data); - } else { - upState.value = const Error(null); - } - - isQuerying = false; - } - - void onSelectUp(int mid) { - if (this.mid.value == mid) { - tabController.index = (mid == -1 ? 0 : 4); - if (mid == -1) { - queryFollowUp(); - } - controller?.onReload(); - return; - } - - this.mid.value = mid; - tabController.index = (mid == -1 ? 0 : 4); + Future singleRefresh() { + offset = null; + isEnd = false; + return super.onRefresh(); } @override Future onRefresh() { - _refreshFollowUp(); - return controller!.onRefresh(); + singleRefresh(); + return innerController.onRefresh(); } - void _refreshFollowUp() { - queryFollowUp(); + @override + Future queryData([bool isRefresh = true]) { + if (!isRefresh && isEnd) return Future.value(); + return super.queryData(isRefresh); } @override void animateToTop() { - controller?.animateToTop(); - scrollController.animToTop(); + super.animateToTop(); + innerController.scrollController.animToTop(); } @override - void onClose() { - mid.close(); - tabController.dispose(); - scrollController.dispose(); - super.onClose(); - } - - @override - void onChangeAccount(bool isLogin) => _refreshFollowUp(); + void onChangeAccount(bool isLogin) => onReload(); } diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index ededd9ea7..07f99d652 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -1,13 +1,11 @@ -import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/dynamic/dynamics_type.dart'; -import 'package:PiliPlus/models/common/dynamic/up_panel_position.dart'; import 'package:PiliPlus/models/dynamics/up.dart'; import 'package:PiliPlus/pages/dynamics/controller.dart'; import 'package:PiliPlus/pages/dynamics/widgets/up_panel.dart'; import 'package:PiliPlus/pages/dynamics_create/view.dart'; +import 'package:PiliPlus/pages/dynamics_tab/controller.dart'; import 'package:PiliPlus/pages/dynamics_tab/view.dart'; -import 'package:PiliPlus/utils/extension/get_ext.dart'; import 'package:flutter/material.dart' hide DraggableScrollableSheet; import 'package:get/get.dart'; @@ -20,52 +18,59 @@ class DynamicsPage extends StatefulWidget { class _DynamicsPageState extends State with AutomaticKeepAliveClientMixin { - final _dynamicsController = Get.putOrFind(DynamicsController.new); - UpPanelPosition get upPanelPosition => _dynamicsController.upPanelPosition; + late ColorScheme colorScheme; + late final DynamicsController _outerController; + late final DynamicsTabController _innerController; @override - bool get wantKeepAlive => true; + void initState() { + super.initState(); + _outerController = Get.put(DynamicsController()); + _innerController = Get.put(DynamicsTabController()); + } - Widget _createDynamicBtn(ThemeData theme, {bool isRight = true}) => Center( - child: Container( - width: 34, - height: 34, - margin: EdgeInsets.only(left: !isRight ? 16 : 0, right: isRight ? 16 : 0), - child: IconButton( - tooltip: '发布动态', - style: ButtonStyle( - padding: const WidgetStatePropertyAll(EdgeInsets.zero), - backgroundColor: WidgetStatePropertyAll( - theme.colorScheme.secondaryContainer, - ), - ), - onPressed: () => CreateDynPanel.onCreateDyn(context), - icon: Icon( - Icons.add, - size: 18, - color: theme.colorScheme.onSecondaryContainer, + @override + void didChangeDependencies() { + super.didChangeDependencies(); + colorScheme = ColorScheme.of(context); + } + + Widget _createDynamicBtn() => SizedBox( + width: 34, + height: 34, + child: IconButton( + tooltip: '发布动态', + style: ButtonStyle( + padding: const WidgetStatePropertyAll(.zero), + backgroundColor: WidgetStatePropertyAll( + colorScheme.secondaryContainer, ), ), + onPressed: () => CreateDynPanel.onCreateDyn(context), + icon: Icon( + Icons.add, + size: 18, + color: colorScheme.onSecondaryContainer, + ), ), ); - Widget upPanelPart(ThemeData theme, {bool isTop = false}) { - final needBg = upPanelPosition.index > 2; + Widget upPanelPart({bool isTop = false}) { return Material( - type: needBg ? .canvas : .transparency, - color: needBg ? theme.colorScheme.surface : null, + type: .transparency, child: SizedBox( width: isTop ? null : 64, height: isTop ? 76 : null, child: NotificationListener( onNotification: (notification) { final metrics = notification.metrics; - if (metrics.pixels >= metrics.maxScrollExtent - 300) { - _dynamicsController.onLoadMoreUp(); + if (metrics.pixels >= metrics.maxScrollExtent - 300 && + !_outerController.isEnd) { + _outerController.onLoadMore(); } return false; }, - child: Obx(() => _buildUpPanel(_dynamicsController.upState.value)), + child: Obx(() => _buildUpPanel(_outerController.loadingState.value)), ), ), ); @@ -74,15 +79,14 @@ class _DynamicsPageState extends State Widget _buildUpPanel(LoadingState upState) { return switch (upState) { Loading() => const SizedBox.shrink(), - Success() => UpPanel( - dynamicsController: _dynamicsController, + Success(:final response) => UpPanel( + controller: _outerController, + upData: response, ), Error() => Center( child: IconButton( icon: const Icon(Icons.refresh), - onPressed: () => _dynamicsController - ..upState.value = LoadingState.loading() - ..queryFollowUp(), + onPressed: _outerController.onReload, ), ), }; @@ -91,79 +95,107 @@ class _DynamicsPageState extends State @override Widget build(BuildContext context) { super.build(context); - final theme = Theme.of(context); - Widget? leading; - List? actions; + final tab = _buildTab(); + Widget child = DynamicsTabPage(controller: _innerController); - Widget child = tabBarView( - controller: _dynamicsController.tabController, - children: DynamicsTabType.values - .map((e) => DynamicsTabPage(dynamicsType: e)) - .toList(), - ); - - switch (upPanelPosition) { + switch (_outerController.upPanelPosition) { case .top: - child = Column( + return Column( children: [ - upPanelPart(theme, isTop: true), + tab, + upPanelPart(isTop: true), Expanded(child: child), ], ); - actions = [_createDynamicBtn(theme)]; case .leftFixed: child = Row( children: [ - upPanelPart(theme), + upPanelPart(), Expanded(child: child), ], ); - actions = [_createDynamicBtn(theme)]; case .rightFixed: child = Row( children: [ Expanded(child: child), - upPanelPart(theme), + upPanelPart(), ], ); - actions = [_createDynamicBtn(theme)]; } return Column( children: [ - AppBar( - primary: false, - leading: leading, - leadingWidth: 50, - toolbarHeight: 50, - backgroundColor: Colors.transparent, - title: SizedBox( - height: 50, - child: TabBar( - dividerHeight: 0, - isScrollable: true, - tabAlignment: .center, - dividerColor: Colors.transparent, - labelColor: theme.colorScheme.primary, - indicatorColor: theme.colorScheme.primary, - controller: _dynamicsController.tabController, - unselectedLabelColor: theme.colorScheme.onSurface, - labelStyle: const TextStyle(fontSize: 13), - tabs: DynamicsTabType.values - .map((e) => Tab(text: e.label)) - .toList(), - onTap: (index) { - if (!_dynamicsController.tabController.indexIsChanging) { - _dynamicsController.animateToTop(); - } - }, - ), - ), - actions: actions, - ), + tab, Expanded(child: child), ], ); } + + Widget _buildTab() { + return Row( + children: [ + Expanded( + child: Obx( + () { + final dynamicsType = _innerController.dynamicsType.value; + return Row( + children: DynamicsTabType.values.map((e) { + final isCurr = e == dynamicsType; + return InkWell( + onTap: e == .up && !isCurr + ? null + : () { + if (isCurr) { + _outerController.animateToTop(); + return; + } + if (dynamicsType == .up) { + _innerController.hostMid = -1; + _outerController.loadingState.refresh(); + } + _innerController + ..dynamicsType.value = e + ..onReload(); + }, + child: DecoratedBox( + decoration: isCurr + ? BoxDecoration( + border: Border( + bottom: BorderSide( + width: 2.0, + color: colorScheme.primary, + ), + ), + ) + : const BoxDecoration(), + child: Container( + height: 46, + alignment: .center, + padding: const .symmetric(horizontal: 16), + child: Text( + e.label, + style: isCurr + ? TextStyle( + fontSize: 13, + color: colorScheme.primary, + ) + : const TextStyle(fontSize: 13), + ), + ), + ), + ); + }).toList(), + ); + }, + ), + ), + _createDynamicBtn(), + const SizedBox(width: 16), + ], + ); + } + + @override + bool get wantKeepAlive => true; } diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart index 2f2dc2929..e9d1c7ae7 100644 --- a/lib/pages/dynamics/widgets/up_panel.dart +++ b/lib/pages/dynamics/widgets/up_panel.dart @@ -10,65 +10,181 @@ import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -class UpPanel extends StatefulWidget { +class UpPanel extends StatelessWidget { const UpPanel({ - required this.dynamicsController, super.key, + required this.upData, + required this.controller, }); - final DynamicsController dynamicsController; - - @override - State createState() => _UpPanelState(); -} - -class _UpPanelState extends State { - late final controller = widget.dynamicsController; - late final isTop = controller.upPanelPosition == .top; - - void toFollowPage() => Get.to(const LiveFollowPage()); + final FollowUpModel upData; + final DynamicsController controller; @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 upList = upData.upList; + final liveList = upData.liveUsers?.items; + + final isTop = controller.upPanelPosition == .top; + void toFollowPage() => Get.to(const LiveFollowPage()); + + Widget buildItem(UpItem item) { + final hostMid = controller.innerController.hostMid; + final isLive = item is LiveUserItem; + final isCurrent = isLive || hostMid == -1 || hostMid == item.mid; + + final isAll = item.mid == -1; + void toMemberPage() => Get.toNamed('/member?mid=${item.mid}'); + + Widget avatar; + if (isAll) { + avatar = DecoratedBox( + decoration: const BoxDecoration( + shape: .circle, + color: Color(0xFF5CB67B), + ), + child: Image.asset( + width: 38, + height: 38, + cacheWidth: 38.cacheSize, + Assets.logo2, + color: Colors.white, + ), + ); + } else { + avatar = Padding( + padding: const .symmetric(horizontal: 4), + child: NetworkImgLayer( + width: 38, + height: 38, + src: item.face, + type: .avatar, + ), + ); + if (isLive) { + avatar = Stack( + clipBehavior: .none, + children: [ + avatar, + Positioned( + top: isLive && !isTop ? -5 : 0, + right: -6, + child: Badge( + label: const Text(' Live '), + textColor: theme.colorScheme.onSecondaryContainer, + backgroundColor: theme.colorScheme.secondaryContainer + .withValues(alpha: 0.75), + ), + ), + ], + ); + } else if (item.hasUpdate ?? false) { + avatar = Stack( + clipBehavior: .none, + children: [ + avatar, + Positioned( + top: 0, + right: 4, + child: Badge( + smallSize: 8, + backgroundColor: theme.colorScheme.primary, + ), + ), + ], + ); + } + } + + return SizedBox( + height: 76, + width: isTop ? 70 : null, + child: InkWell( + onTap: () { + if (isLive) { + PageUtils.toLiveRoom(item.roomId); + } else { + if (isAll) { + if (hostMid == -1) { + controller.singleRefresh(); + } + controller.innerController.dynamicsType.value = .all; + } else { + item.hasUpdate = false; + controller.innerController.dynamicsType.value = .up; + } + controller.innerController + ..hostMid = item.mid + ..onReload(); + (context as Element).markNeedsBuild(); + } + }, + // onDoubleTap: isLive ? () => _onSelect(data) : null, + onLongPress: !isAll ? toMemberPage : null, + onSecondaryTap: !isAll && !PlatformUtils.isMobile + ? toMemberPage + : null, + child: Opacity( + opacity: isCurrent ? 1 : 0.6, + child: Column( + spacing: 4, + mainAxisSize: .min, + mainAxisAlignment: .center, + children: [ + avatar, + Padding( + padding: const .symmetric(horizontal: 4), + child: Text( + isTop ? '${item.uname}\n' : item.uname!, + maxLines: 2, + textAlign: .center, + style: TextStyle( + color: hostMid == item.mid + ? theme.colorScheme.primary + : theme.colorScheme.outline, + height: 1.1, + fontSize: 12.5, + ), + ), + ), + ], + ), + ), + ), + ); + } + return CustomScrollView( - scrollDirection: isTop ? Axis.horizontal : Axis.vertical, - physics: const AlwaysScrollableScrollPhysics(), controller: controller.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + scrollDirection: isTop ? .horizontal : .vertical, slivers: [ SliverToBoxAdapter( child: InkWell( - onTap: () => setState(() { + onTap: () { controller.showLiveUp = !controller.showLiveUp; - }), + (context as Element).markNeedsBuild(); + }, 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, ), TextSpan( children: [ - TextSpan( - text: 'Live(${upData.liveUsers?.count ?? 0})', - ), + TextSpan(text: 'Live(${upData.liveUsers?.count ?? 0})'), if (!isTop) ...[ const TextSpan(text: '\n'), WidgetSpan( - alignment: PlaceholderAlignment.middle, + alignment: .middle, child: Icon( controller.showLiveUp ? Icons.expand_less @@ -79,7 +195,7 @@ class _UpPanelState extends State { ), ] else WidgetSpan( - alignment: PlaceholderAlignment.middle, + alignment: .middle, child: Icon( controller.showLiveUp ? Icons.keyboard_arrow_right @@ -98,157 +214,32 @@ class _UpPanelState extends State { SliverList.builder( itemCount: liveList.length, itemBuilder: (context, index) { - return upItemBuild(theme, liveList[index]); + return buildItem(liveList[index]); }, ), SliverToBoxAdapter( - child: upItemBuild(theme, UpItem(face: '', uname: '全部动态', mid: -1)), + child: buildItem(UpItem(face: '', uname: '全部动态', mid: -1)), ), SliverToBoxAdapter( child: Obx( - () => upItemBuild( - theme, + () => buildItem( 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) { - return upItemBuild(theme, upList[index]); + return buildItem(upList[index]); }, ), if (!isTop) const SliverToBoxAdapter(child: SizedBox(height: 200)), ], ); } - - void _onSelect(UpItem data) { - controller - ..currentMid = data.mid - ..onSelectUp(data.mid); - - data.hasUpdate = false; - - setState(() {}); - } - - Widget upItemBuild(ThemeData theme, UpItem data) { - final currentMid = controller.currentMid; - final isLive = data is LiveUserItem; - final isCurrent = isLive || currentMid == data.mid || currentMid == -1; - - final isAll = data.mid == -1; - void toMemberPage() => Get.toNamed('/member?mid=${data.mid}'); - - Widget avatar; - if (isAll) { - avatar = DecoratedBox( - decoration: BoxDecoration( - shape: .circle, - border: Border.all( - width: 5, - color: const Color(0xFF5CB67B), - ), - ), - child: Image.asset( - width: 38, - height: 38, - cacheWidth: 38.cacheSize, - Assets.logo, - ), - ); - } else { - avatar = Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: NetworkImgLayer( - width: 38, - height: 38, - src: data.face, - type: .avatar, - ), - ); - if (isLive) { - avatar = Stack( - clipBehavior: .none, - children: [ - avatar, - Positioned( - top: isLive && !isTop ? -5 : 0, - right: -6, - child: Badge( - label: const Text(' Live '), - textColor: theme.colorScheme.onSecondaryContainer, - backgroundColor: theme.colorScheme.secondaryContainer - .withValues(alpha: 0.75), - ), - ), - ], - ); - } else if (data.hasUpdate ?? false) { - avatar = Stack( - clipBehavior: .none, - children: [ - avatar, - Positioned( - top: 0, - right: 4, - child: Badge( - smallSize: 8, - backgroundColor: theme.colorScheme.primary, - ), - ), - ], - ); - } - } - - return SizedBox( - height: 76, - width: isTop ? 70 : null, - child: InkWell( - onTap: () { - if (isLive) { - PageUtils.toLiveRoom(data.roomId); - } else { - _onSelect(data); - } - }, - // onDoubleTap: isLive ? () => _onSelect(data) : null, - onLongPress: !isAll ? toMemberPage : null, - onSecondaryTap: !isAll && !PlatformUtils.isMobile ? toMemberPage : null, - child: Opacity( - opacity: isCurrent ? 1 : 0.6, - child: Column( - spacing: 4, - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - avatar, - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Text( - isTop ? '${data.uname}\n' : data.uname!, - maxLines: 2, - textAlign: TextAlign.center, - style: TextStyle( - color: currentMid == data.mid - ? theme.colorScheme.primary - : theme.colorScheme.outline, - height: 1.1, - fontSize: 12.5, - ), - ), - ), - ], - ), - ), - ), - ); - } } diff --git a/lib/pages/dynamics_tab/controller.dart b/lib/pages/dynamics_tab/controller.dart index 30631dfbd..c04224d3b 100644 --- a/lib/pages/dynamics_tab/controller.dart +++ b/lib/pages/dynamics_tab/controller.dart @@ -1,5 +1,3 @@ -import 'dart:async' show StreamSubscription; - import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/msg.dart'; @@ -17,53 +15,41 @@ import 'package:get/get.dart'; class DynamicsTabController extends CommonListController with AccountMixin, CommonReloadMixin { - DynamicsTabController({required this.dynamicsType}); - final DynamicsTabType dynamicsType; - String offset = ''; - int? mid; - late int flag = 0; + int hostMid = -1; + String? offset; + final Rx dynamicsType = Rx(.all); - late final MainController mainController = Get.find(); - final dynamicsController = Get.find(); - StreamSubscription? _listener; + late final outerController = Get.find(); + late final MainController _mainController = Get.find(); @override void onInit() { super.onInit(); queryData(); - if (dynamicsType == .up) { - _listener = dynamicsController.mid.listen((mid) { - if (mid != -1) { - flag++; - this.mid = mid; - onReload(); - } - }); - } } @override Future onRefresh() { - if (dynamicsType == .all) { - mainController.setDynCount(); + if (dynamicsType.value == .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, - tempBannedList: dynamicsController.tempBannedList, + hostMid: hostMid, + type: dynamicsType.value, + tempBannedList: outerController.tempBannedList, ); Future onRemove(int index, dynamic dynamicId) async { @@ -79,7 +65,7 @@ class DynamicsTabController } void onBlock(int index) { - if (dynamicsType != .up) { + if (dynamicsType.value != .up) { loadingState ..value.data!.removeAt(index) ..refresh(); @@ -101,10 +87,4 @@ class DynamicsTabController @override void onChangeAccount(bool isLogin) => onReload(); - - @override - void onClose() { - _listener?.cancel(); - super.onClose(); - } } diff --git a/lib/pages/dynamics_tab/view.dart b/lib/pages/dynamics_tab/view.dart index 3b7230d64..b4af02991 100644 --- a/lib/pages/dynamics_tab/view.dart +++ b/lib/pages/dynamics_tab/view.dart @@ -2,12 +2,9 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/models/common/dynamic/dynamics_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; -import 'package:PiliPlus/pages/dynamics/controller.dart'; import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart'; import 'package:PiliPlus/pages/dynamics_tab/controller.dart'; -import 'package:PiliPlus/utils/extension/get_ext.dart'; import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/waterfall.dart'; import 'package:flutter/material.dart'; @@ -15,53 +12,24 @@ import 'package:get/get.dart'; import 'package:waterfall_flow/waterfall_flow.dart' hide SliverWaterfallFlowDelegateWithMaxCrossAxisExtent; -class DynamicsTabPage extends StatefulWidget { - const DynamicsTabPage({super.key, required this.dynamicsType}); +class DynamicsTabPage extends StatelessWidget with DynMixin { + DynamicsTabPage({ + super.key, + required this.controller, + }); - final DynamicsTabType dynamicsType; - - @override - State createState() => _DynamicsTabPageState(); -} - -class _DynamicsTabPageState extends State - with AutomaticKeepAliveClientMixin, DynMixin { - DynamicsController dynamicsController = Get.putOrFind(DynamicsController.new); - late final DynamicsTabController controller; - - @override - bool get wantKeepAlive => widget.dynamicsType == .all; - - @override - void initState() { - super.initState(); - controller = Get.putOrFind( - () => - DynamicsTabController(dynamicsType: widget.dynamicsType) - ..mid = dynamicsController.mid.value, - tag: widget.dynamicsType.name, - ); - } + final DynamicsTabController controller; @override Widget build(BuildContext context) { - super.build(context); return refreshIndicator( - onRefresh: () { - dynamicsController.queryFollowUp(); - return controller.onRefresh(); - }, + onRefresh: controller.outerController.onRefresh, child: CustomScrollView( - key: switch (widget.dynamicsType) { - .all => null, - .up => PageStorageKey('${widget.dynamicsType}${controller.flag}'), - _ => PageStorageKey(widget.dynamicsType), - }, physics: ReloadScrollPhysics(controller: controller), controller: controller.scrollController, slivers: [ SliverPadding( - padding: const EdgeInsets.only(bottom: 100), + padding: const .only(bottom: 100), sliver: buildPage( Obx(() => _buildBody(controller.loadingState.value)), ), diff --git a/lib/utils/request_utils.dart b/lib/utils/request_utils.dart index 69109506d..819202efc 100644 --- a/lib/utils/request_utils.dart +++ b/lib/utils/request_utils.dart @@ -311,13 +311,15 @@ abstract final class RequestUtils { await Future.delayed(const Duration(milliseconds: 450)); final res = await DynamicsHttp.dynamicDetail(id: id); if (res case final Success e) { - final ctr = Get.find(tag: 'all'); - if (ctr.loadingState.value case Success(:final response?)) { - response.insert(0, e.response); - ctr.loadingState.refresh(); - return; + final ctr = Get.find(); + if (ctr.dynamicsType.value == .all) { + if (ctr.loadingState.value case Success(:final response?)) { + response.insert(0, e.response); + ctr.loadingState.refresh(); + return; + } + ctr.loadingState.value = Success([e.response]); } - ctr.loadingState.value = Success([e.response]); } } catch (e) { if (kDebugMode) debugPrint('create dyn $e');