From f615a43259b8d9f962a95d30ae88c4e0d0907856 Mon Sep 17 00:00:00 2001 From: dom Date: Tue, 30 Jun 2026 19:23:41 +0800 Subject: [PATCH] opt trending header Signed-off-by: dom --- .../widgets/sliver/trending_header.dart | 55 +++++++++++++++++++ lib/http/search.dart | 5 +- .../search/search_trending/data.dart | 40 ++++++++------ lib/pages/search_trending/controller.dart | 9 +-- lib/pages/search_trending/view.dart | 50 ++++------------- 5 files changed, 95 insertions(+), 64 deletions(-) create mode 100644 lib/common/widgets/sliver/trending_header.dart diff --git a/lib/common/widgets/sliver/trending_header.dart b/lib/common/widgets/sliver/trending_header.dart new file mode 100644 index 000000000..3640182ce --- /dev/null +++ b/lib/common/widgets/sliver/trending_header.dart @@ -0,0 +1,55 @@ +import 'package:flutter/foundation.dart' show clampDouble; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart' show RenderSliverToBoxAdapter; + +class TrendingHeader extends SliverToBoxAdapter { + const TrendingHeader({ + super.key, + required this.offset, + required this.onScrollRatioChanged, + required super.child, + }); + + final double offset; + final ValueChanged onScrollRatioChanged; + + @override + RenderSliverToBoxAdapter createRenderObject(BuildContext context) { + return RenderTrendingHeader( + offset: offset, + onScrollRatioChanged: onScrollRatioChanged, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderTrendingHeader renderObject, + ) { + renderObject.offset = offset; + } +} + +class RenderTrendingHeader extends RenderSliverToBoxAdapter { + RenderTrendingHeader({ + required this.offset, + required this.onScrollRatioChanged, + }); + + double offset; + double? _scrollRatio; + final ValueChanged onScrollRatioChanged; + + @override + void performLayout() { + super.performLayout(); + final scrollOffset = constraints.scrollOffset; + final scrollRatio = clampDouble(scrollOffset / offset, 0.0, 1.0); + if (_scrollRatio != scrollRatio) { + _scrollRatio = scrollRatio; + WidgetsBinding.instance.addPostFrameCallback((_) { + onScrollRatioChanged(scrollRatio); + }); + } + } +} diff --git a/lib/http/search.dart b/lib/http/search.dart index d2040f755..646322465 100644 --- a/lib/http/search.dart +++ b/lib/http/search.dart @@ -262,6 +262,7 @@ abstract final class SearchHttp { static Future> searchTrending({ int limit = 30, + bool needsTop = false, }) async { final res = await Request().get( Api.searchTrending, @@ -270,7 +271,9 @@ abstract final class SearchHttp { }, ); if (res.data['code'] == 0) { - return Success(SearchTrendingData.fromJson(res.data['data'])); + return Success( + SearchTrendingData.fromJson(res.data['data'], needsTop: needsTop), + ); } else { return Error(res.data['message']); } diff --git a/lib/models_new/search/search_trending/data.dart b/lib/models_new/search/search_trending/data.dart index 8fc2fb2c0..f11c204e0 100644 --- a/lib/models_new/search/search_trending/data.dart +++ b/lib/models_new/search/search_trending/data.dart @@ -2,23 +2,27 @@ import 'package:PiliPlus/models_new/search/search_rcmd/data.dart'; import 'package:PiliPlus/models_new/search/search_trending/list.dart'; class SearchTrendingData extends SearchRcmdData { - List? topList; + late int topCount; - SearchTrendingData({super.list, this.topList}); - - factory SearchTrendingData.fromJson(Map json) => - SearchTrendingData( - list: (json['list'] as List?) - ?.map( - (e) => - SearchTrendingItemModel.fromJson(e as Map), - ) - .toList(), - topList: (json['top_list'] as List?) - ?.map( - (e) => - SearchTrendingItemModel.fromJson(e as Map), - ) - .toList(), - ); + SearchTrendingData.fromJson( + Map json, { + bool needsTop = false, + }) { + list = (json['list'] as List?) + ?.map((e) => SearchTrendingItemModel.fromJson(e)) + .toList(); + if (needsTop) { + final topList = (json['top_list'] as List?) + ?.map((e) => SearchTrendingItemModel.fromJson(e)) + .toList(); + topCount = topList?.length ?? 0; + if (topList != null && topList.isNotEmpty) { + if (list != null) { + list!.insertAll(0, topList); + } else { + list = topList; + } + } + } + } } diff --git a/lib/pages/search_trending/controller.dart b/lib/pages/search_trending/controller.dart index cf12341ea..4540bb317 100644 --- a/lib/pages/search_trending/controller.dart +++ b/lib/pages/search_trending/controller.dart @@ -16,14 +16,11 @@ class SearchTrendingController @override List? getDataList(SearchTrendingData response) { - List topList = - response.topList ?? []; - topCount = topList.length; - return response.list == null ? topList : topList - ..addAll(response.list ?? []); + topCount = response.topCount; + return response.list; } @override Future> customGetData() => - SearchHttp.searchTrending(); + SearchHttp.searchTrending(needsTop: true); } diff --git a/lib/pages/search_trending/view.dart b/lib/pages/search_trending/view.dart index 0d33f60fe..cc96759d8 100644 --- a/lib/pages/search_trending/view.dart +++ b/lib/pages/search_trending/view.dart @@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/sliver/trending_header.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/search/search_trending/list.dart'; import 'package:PiliPlus/pages/search_trending/controller.dart'; @@ -15,7 +16,6 @@ import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/extension/size_ext.dart'; import 'package:PiliPlus/utils/image_utils.dart'; import 'package:cached_network_image_ce/cached_network_image.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' hide ListTile; import 'package:flutter/services.dart' show SystemUiOverlayStyle; import 'package:get/get.dart'; @@ -33,28 +33,6 @@ class _SearchTrendingPageState extends State { late double _offset; final RxDouble _scrollRatio = 0.0.obs; - @override - void initState() { - super.initState(); - _controller.scrollController.addListener(listener); - } - - @override - void dispose() { - _controller.scrollController.removeListener(listener); - super.dispose(); - } - - void listener() { - if (_controller.scrollController.hasClients) { - _scrollRatio.value = clampDouble( - _controller.scrollController.position.pixels / _offset, - 0.0, - 1.0, - ); - } - } - @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -63,14 +41,13 @@ class _SearchTrendingPageState extends State { final maxWidth = size.width - padding.horizontal; final width = size.isPortrait ? maxWidth : min(640.0, maxWidth * 0.6); final height = width * 528 / 1125; - _offset = height - 56 - padding.top; - listener(); + _offset = height - kToolbarHeight - padding.top; return Scaffold( extendBody: true, extendBodyBehindAppBar: true, resizeToAvoidBottomInset: false, appBar: PreferredSize( - preferredSize: const Size.fromHeight(56), + preferredSize: const Size.fromHeight(kToolbarHeight), child: Obx( () { final scrollRatio = _scrollRatio.value; @@ -80,9 +57,7 @@ class _SearchTrendingPageState extends State { opacity: scrollRatio, child: Text( 'bilibili热搜', - style: TextStyle( - color: flag ? null : Colors.white, - ), + style: TextStyle(color: flag ? null : Colors.white), ), ), backgroundColor: theme.colorScheme.surface.withValues( @@ -92,15 +67,13 @@ class _SearchTrendingPageState extends State { systemOverlayStyle: flag ? null : const SystemUiOverlayStyle( - statusBarBrightness: Brightness.dark, - statusBarIconBrightness: Brightness.light, + statusBarBrightness: .dark, + statusBarIconBrightness: .light, ), shape: scrollRatio == 1 ? Border( bottom: BorderSide( - color: theme.colorScheme.outline.withValues( - alpha: 0.1, - ), + color: theme.colorScheme.outline.withValues(alpha: 0.1), ), ) : null, @@ -109,10 +82,7 @@ class _SearchTrendingPageState extends State { ), ), body: Padding( - padding: EdgeInsets.only( - left: padding.left, - right: padding.right, - ), + padding: .only(left: padding.left, right: padding.right), child: Center( child: SizedBox( width: width, @@ -122,7 +92,9 @@ class _SearchTrendingPageState extends State { controller: _controller.scrollController, physics: const AlwaysScrollableScrollPhysics(), slivers: [ - SliverToBoxAdapter( + TrendingHeader( + offset: _offset, + onScrollRatioChanged: _scrollRatio.call, child: Image.asset( width: width, height: height,