From aa03eed92d12dd987d1bf0ba395abfd8e99fe959 Mon Sep 17 00:00:00 2001 From: dom Date: Mon, 6 Jul 2026 12:22:27 +0800 Subject: [PATCH] dyn topic fold Signed-off-by: dom --- lib/http/api.dart | 2 + lib/http/dynamics.dart | 33 +++++++- lib/models/dynamics/result.dart | 3 +- .../dyn_topic_feed/fold_card_item.dart | 11 +++ .../dynamic/dyn_topic_feed/item.dart | 9 ++- lib/pages/dynamics/widgets/action_panel.dart | 20 +++-- lib/pages/dynamics_tab/view.dart | 42 ++++------ lib/pages/dynamics_topic/controller.dart | 12 +++ lib/pages/dynamics_topic/view.dart | 78 +++++++++++++------ lib/pages/member_dynamics/view.dart | 34 ++++---- lib/pages/member_search/child/view.dart | 22 +++--- lib/utils/parse_bool.dart | 9 +++ lib/utils/parse_int.dart | 6 +- 13 files changed, 184 insertions(+), 97 deletions(-) create mode 100644 lib/models_new/dynamic/dyn_topic_feed/fold_card_item.dart create mode 100644 lib/utils/parse_bool.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index 27821f7aa..3f75a555c 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -810,6 +810,8 @@ abstract final class Api { static const String topicFeed = '/x/polymer/web-dynamic/v1/feed/topic'; + static const String topicFold = '/x/topic/web/details/fold'; + static const String spaceOpus = '/x/polymer/web-dynamic/v1/opus/feed/space'; static const String articleList = '/x/article/list/web/articles'; diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index 6553bb9b7..c3be4b5f6 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -471,10 +471,35 @@ abstract final class DynamicsHttp { }, ); if (res.data['code'] == 0) { - TopicCardList? data = res.data['data']?['topic_card_list'] == null - ? null - : TopicCardList.fromJson(res.data['data']['topic_card_list']); - return Success(data); + final list = res.data['data']?['topic_card_list']; + if (list == null) { + return const Success(null); + } else { + return Success(TopicCardList.fromJson(list)); + } + } else { + return Error(res.data['message']); + } + } + + static Future> topicFold({ + required Object topicId, + required int sortBy, + }) async { + final res = await Request().get( + Api.topicFold, + queryParameters: { + 'topic_id': topicId, + 'sort_by': sortBy, + }, + ); + if (res.data['code'] == 0) { + final list = res.data['data']?['topic_card_list']; + if (list == null) { + return const Success(null); + } else { + return Success(TopicCardList.fromJson(list)); + } } else { return Error(res.data['message']); } diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart index 18132d01d..110ed9d25 100644 --- a/lib/models/dynamics/result.dart +++ b/lib/models/dynamics/result.dart @@ -7,6 +7,7 @@ import 'package:PiliPlus/models/model_avatar.dart'; import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models_new/live/live_feed_index/watched_show.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; +import 'package:PiliPlus/utils/parse_bool.dart'; import 'package:PiliPlus/utils/parse_int.dart'; import 'package:PiliPlus/utils/parse_string.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; @@ -1345,7 +1346,7 @@ class DynamicStat { if (safeToInt(json['count']) case final count? when count > 0) { this.count = count; } - status = json['status']; + status = safeToBool(json['status'], () => 'STATE_LIKE'); } } diff --git a/lib/models_new/dynamic/dyn_topic_feed/fold_card_item.dart b/lib/models_new/dynamic/dyn_topic_feed/fold_card_item.dart new file mode 100644 index 000000000..76cb42762 --- /dev/null +++ b/lib/models_new/dynamic/dyn_topic_feed/fold_card_item.dart @@ -0,0 +1,11 @@ +class FoldCardItem { + int? foldCount; + String? foldDesc; + + FoldCardItem({this.foldCount, this.foldDesc}); + + factory FoldCardItem.fromJson(Map json) => FoldCardItem( + foldCount: json['fold_count'] as int?, + foldDesc: json['fold_desc'] as String?, + ); +} diff --git a/lib/models_new/dynamic/dyn_topic_feed/item.dart b/lib/models_new/dynamic/dyn_topic_feed/item.dart index 78ce31057..112dd769d 100644 --- a/lib/models_new/dynamic/dyn_topic_feed/item.dart +++ b/lib/models_new/dynamic/dyn_topic_feed/item.dart @@ -1,10 +1,12 @@ import 'package:PiliPlus/models/dynamics/result.dart'; +import 'package:PiliPlus/models_new/dynamic/dyn_topic_feed/fold_card_item.dart'; class TopicCardItem { + FoldCardItem? foldCardItem; DynamicItemModel? dynamicCardItem; String? topicType; - TopicCardItem({this.dynamicCardItem, this.topicType}); + TopicCardItem({this.dynamicCardItem, this.foldCardItem, this.topicType}); factory TopicCardItem.fromJson(Map json) => TopicCardItem( dynamicCardItem: json['dynamic_card_item'] == null @@ -12,6 +14,11 @@ class TopicCardItem { : DynamicItemModel.fromJson( json['dynamic_card_item'] as Map, ), + foldCardItem: json['fold_card_item'] == null + ? null + : FoldCardItem.fromJson( + json['fold_card_item'] as Map, + ), topicType: json['topic_type'] as String?, ); } diff --git a/lib/pages/dynamics/widgets/action_panel.dart b/lib/pages/dynamics/widgets/action_panel.dart index 12332a0e3..941a9ed7c 100644 --- a/lib/pages/dynamics/widgets/action_panel.dart +++ b/lib/pages/dynamics/widgets/action_panel.dart @@ -87,13 +87,23 @@ class ActionPanel extends StatelessWidget { Expanded( child: Builder( builder: (context) { + final IconData icon; + final Color color; + final String label; + if (like.status ?? false) { + icon = FontAwesomeIcons.solidThumbsUp; + color = primary; + label = '已赞'; + } else { + icon = FontAwesomeIcons.thumbsUp; + color = outline; + label = '点赞'; + } final likeIcon = Icon( - like.status! - ? FontAwesomeIcons.solidThumbsUp - : FontAwesomeIcons.thumbsUp, + icon, size: 16, - color: like.status! ? primary : outline, - semanticLabel: like.status! ? "已赞" : "点赞", + color: color, + semanticLabel: label, ); return TextButton.icon( onPressed: () => RequestUtils.onLikeDynamic( diff --git a/lib/pages/dynamics_tab/view.dart b/lib/pages/dynamics_tab/view.dart index 370407755..300131160 100644 --- a/lib/pages/dynamics_tab/view.dart +++ b/lib/pages/dynamics_tab/view.dart @@ -76,36 +76,13 @@ class _DynamicsTabPageState extends State ? SliverWaterfallFlow( gridDelegate: dynGridDelegate, delegate: SliverChildBuilderDelegate( - (_, index) { - if (index == response.length - 1) { - controller.onLoadMore(); - } - final item = response[index]; - return DynamicPanel( - item: item, - onRemove: (idStr) => - controller.onRemove(index, idStr), - onBlock: () => controller.onBlock(index), - onUnfold: () => controller.onUnfold(item, index), - ); - }, + (_, index) => _itemBuilder(response, index), childCount: response.length, ), ) : SliverList.builder( - itemBuilder: (context, index) { - if (index == response.length - 1) { - controller.onLoadMore(); - } - final item = response[index]; - return DynamicPanel( - item: item, - onRemove: (idStr) => - controller.onRemove(index, idStr), - onBlock: () => controller.onBlock(index), - onUnfold: () => controller.onUnfold(item, index), - ); - }, + itemBuilder: (context, index) => + _itemBuilder(response, index), itemCount: response.length, ) : HttpError(onReload: controller.onReload), @@ -115,4 +92,17 @@ class _DynamicsTabPageState extends State ), }; } + + Widget _itemBuilder(List list, int index) { + if (index == list.length - 1) { + controller.onLoadMore(); + } + final item = list[index]; + return DynamicPanel( + item: item, + onRemove: (idStr) => controller.onRemove(index, idStr), + onBlock: () => controller.onBlock(index), + onUnfold: () => controller.onUnfold(item, index), + ); + } } diff --git a/lib/pages/dynamics_topic/controller.dart b/lib/pages/dynamics_topic/controller.dart index 17e8b9641..3150ccdf6 100644 --- a/lib/pages/dynamics_topic/controller.dart +++ b/lib/pages/dynamics_topic/controller.dart @@ -130,4 +130,16 @@ class DynTopicController res.toast(); } } + + Future topicFold() async { + final res = await DynamicsHttp.topicFold(topicId: topicId, sortBy: sortBy); + if (res case Success(:final response)) { + if (response?.items case final items? when items.isNotEmpty) { + loadingState.value.data! + ..removeLast() + ..addAll(items); + loadingState.refresh(); + } + } + } } diff --git a/lib/pages/dynamics_topic/view.dart b/lib/pages/dynamics_topic/view.dart index f292e74ae..36449ecbb 100644 --- a/lib/pages/dynamics_topic/view.dart +++ b/lib/pages/dynamics_topic/view.dart @@ -9,6 +9,7 @@ import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/image_type.dart'; +import 'package:PiliPlus/models_new/dynamic/dyn_topic_feed/fold_card_item.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_feed/item.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/top_details.dart'; import 'package:PiliPlus/pages/common/fab_mixin.dart'; @@ -41,15 +42,22 @@ class DynTopicPage extends StatefulWidget { class _DynTopicPageState extends State with DynMixin, SingleTickerProviderStateMixin, BaseFabMixin, FabMixin { + late EdgeInsets padding; + late ColorScheme colorScheme; final DynTopicController _controller = Get.put( DynTopicController(), tag: Utils.generateRandomString(8), ); + @override + void didChangeDependencies() { + super.didChangeDependencies(); + colorScheme = ColorScheme.of(context); + padding = MediaQuery.viewPaddingOf(context); + } + @override Widget build(BuildContext context) { - final colorScheme = ColorScheme.of(context); - final padding = MediaQuery.viewPaddingOf(context); return Material( child: Stack( clipBehavior: .none, @@ -372,33 +380,13 @@ class _DynTopicPageState extends State ? SliverWaterfallFlow( gridDelegate: dynGridDelegate, delegate: SliverChildBuilderDelegate( - (_, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - - final item = response[index]; - if (item.dynamicCardItem != null) { - return DynamicPanel(item: item.dynamicCardItem!); - } - - return Text(item.topicType ?? 'err'); - }, + (_, index) => _itemBuilder(response, index), childCount: response.length, ), ) : SliverList.builder( - itemBuilder: (context, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - final item = response[index]; - if (item.dynamicCardItem != null) { - return DynamicPanel(item: item.dynamicCardItem!); - } else { - return Text(item.topicType ?? 'err'); - } - }, + itemBuilder: (context, index) => + _itemBuilder(response, index), itemCount: response.length, ) : HttpError(onReload: _controller.onReload), @@ -408,4 +396,44 @@ class _DynTopicPageState extends State ), }; } + + Widget _itemBuilder(List list, int index) { + if (index == list.length - 1) { + _controller.onLoadMore(); + } + + final item = list[index]; + + if (item.dynamicCardItem case final dynamicCardItem?) { + return DynamicPanel(item: dynamicCardItem); + } + + if (item.foldCardItem case final foldCardItem?) { + return _buildFoldItem(foldCardItem); + } + + return Text(item.topicType ?? 'err'); + } + + Widget _buildFoldItem(FoldCardItem item) { + return Padding( + padding: const .only(top: 12), + child: InkWell( + onTap: _controller.topicFold, + child: Ink( + padding: const .symmetric(vertical: 10), + color: colorScheme.outline.withValues(alpha: .05), + child: Center( + child: Row( + mainAxisSize: .min, + children: [ + Text(item.foldDesc!), + const Icon(Icons.keyboard_arrow_right, size: 22), + ], + ), + ), + ), + ), + ); + } } diff --git a/lib/pages/member_dynamics/view.dart b/lib/pages/member_dynamics/view.dart index a24619ddd..801abc637 100644 --- a/lib/pages/member_dynamics/view.dart +++ b/lib/pages/member_dynamics/view.dart @@ -85,30 +85,13 @@ class _MemberDynamicsPageState extends State ? SliverWaterfallFlow( gridDelegate: dynGridDelegate, delegate: SliverChildBuilderDelegate( - (_, index) { - if (index == response.length - 1) { - _memberDynamicController.onLoadMore(); - } - return DynamicPanel( - item: response[index], - onRemove: _memberDynamicController.onRemove, - onSetTop: _memberDynamicController.onSetTop, - ); - }, + (_, index) => _itemBuilder(response, index), childCount: response.length, ), ) : SliverList.builder( - itemBuilder: (context, index) { - if (index == response.length - 1) { - _memberDynamicController.onLoadMore(); - } - return DynamicPanel( - item: response[index], - onRemove: _memberDynamicController.onRemove, - onSetTop: _memberDynamicController.onSetTop, - ); - }, + itemBuilder: (context, index) => + _itemBuilder(response, index), itemCount: response.length, ) : HttpError(onReload: _memberDynamicController.onReload), @@ -118,4 +101,15 @@ class _MemberDynamicsPageState extends State ), }; } + + Widget _itemBuilder(List list, int index) { + if (index == list.length - 1) { + _memberDynamicController.onLoadMore(); + } + return DynamicPanel( + item: list[index], + onRemove: _memberDynamicController.onRemove, + onSetTop: _memberDynamicController.onSetTop, + ); + } } diff --git a/lib/pages/member_search/child/view.dart b/lib/pages/member_search/child/view.dart index ec4259884..3667a3e0e 100644 --- a/lib/pages/member_search/child/view.dart +++ b/lib/pages/member_search/child/view.dart @@ -87,22 +87,13 @@ class _MemberSearchChildPageState extends State ? SliverWaterfallFlow( gridDelegate: dynGridDelegate, delegate: SliverChildBuilderDelegate( - (_, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - return DynamicPanel(item: response[index]); - }, + (_, index) => _itemBuilder(response, index), childCount: response.length, ), ) : SliverList.builder( - itemBuilder: (context, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - return DynamicPanel(item: response[index]); - }, + itemBuilder: (context, index) => + _itemBuilder(response, index), itemCount: response.length, ), } @@ -114,6 +105,13 @@ class _MemberSearchChildPageState extends State }; } + Widget _itemBuilder(List list, int index) { + if (index == list.length - 1) { + _controller.onLoadMore(); + } + return DynamicPanel(item: list[index]); + } + @override bool get wantKeepAlive => true; } diff --git a/lib/utils/parse_bool.dart b/lib/utils/parse_bool.dart new file mode 100644 index 000000000..cdb85b656 --- /dev/null +++ b/lib/utils/parse_bool.dart @@ -0,0 +1,9 @@ +import 'package:flutter/foundation.dart' show ValueGetter; + +bool? safeToBool(dynamic value, [ValueGetter? orString]) => + switch (value) { + bool _ => value, + int _ => value == 1, + String _ => orString != null && value == orString(), + _ => null, + }; diff --git a/lib/utils/parse_int.dart b/lib/utils/parse_int.dart index 95c163063..0883cb5db 100644 --- a/lib/utils/parse_int.dart +++ b/lib/utils/parse_int.dart @@ -1,6 +1,6 @@ int? safeToInt(dynamic value) => switch (value) { - int() => value, - String() => int.tryParse(value), - num() => value.toInt(), + int _ => value, + String _ => int.tryParse(value), + num _ => value.toInt(), _ => null, };