@@ -7,9 +7,7 @@ import 'package:miru_alpha/ui/features/detail/desktop_loaded_page.dart';
77import 'package:miru_alpha/ui/features/detail/mobile_loaded_page.dart' ;
88import 'package:miru_alpha/provider/detial_provider.dart' ;
99import 'package:miru_alpha/provider/extension_provider.dart' ;
10- import 'package:miru_alpha/utils/core/log.dart' ;
1110import 'package:miru_alpha/utils/router/page_entry.dart' ;
12-
1311import 'package:miru_alpha/ui/core/index.dart' ;
1412import 'package:smooth_sheets/smooth_sheets.dart' ;
1513import './widget/index.dart' ;
@@ -28,6 +26,20 @@ class DetailLoadingPage extends StatefulHookConsumerWidget {
2826}
2927
3028class _DetailLoadPageState extends ConsumerState <DetailLoadingPage > {
29+ /// Mirrors the detail page's scroll offset. The body (`MobileLoadedPage` )
30+ /// scrolls inside its own [CustomScrollView] , so the outer sliver header
31+ /// never receives a changing `shrinkOffset` . We feed the real scroll offset
32+ /// here (via [MiruScaffold.onScrollChange] ) and drive the header from it
33+ /// instead, so the header's title animation (and any logging) updates on
34+ /// scroll.
35+ final ValueNotifier <double > _scrollNotifier = ValueNotifier (0.0 );
36+
37+ @override
38+ void dispose () {
39+ _scrollNotifier.dispose ();
40+ super .dispose ();
41+ }
42+
3143 @override
3244 Widget build (BuildContext context) {
3345 final detailPr = detialProvider (widget.detailUrl, meta: widget.meta);
@@ -37,6 +49,7 @@ class _DetailLoadPageState extends ConsumerState<DetailLoadingPage> {
3749 final favorite = ref.watch (detailPr.select ((value) => value.favorite));
3850 return detial.when (
3951 data: (detial) => MiruScaffold .mobile (
52+ onScrollChange: (offset, progress) => _scrollNotifier.value = offset,
4053 snappingOffsets: const [
4154 AbsoluteSheetOffset (150 ),
4255 ProportionalToViewportSheetOffset (0.5 ),
@@ -52,107 +65,117 @@ class _DetailLoadPageState extends ConsumerState<DetailLoadingPage> {
5265 ],
5366 sliverHeaders: [
5467 CustomSliverHeaderDelegate (
55- maxExtent: 120 ,
56- minExtent: 60 ,
68+ maxExtent: 50 ,
69+ minExtent: 50 ,
70+ scrollPosition: _scrollNotifier,
5771 builder: (context, offset, progress) {
58- logger.info ('progress: $progress ' , 'offset: $offset ' );
59- final titleVisible = progress > 0.3 ;
60- return Padding (
61- padding: EdgeInsetsGeometry .only (right: 10 , left: 5 , bottom: 0 ),
62- child: Row (
63- children: [
64- GestureDetector (
65- onTap: () {
66- Navigator .of (context).pop ();
67- },
68- child: Padding (
69- padding: const EdgeInsets .only (
70- right: 12.0 ,
71- top: 4 ,
72- left: 10 ,
73- ),
74- child: Icon (
75- FLucideIcons .chevronLeft,
76- size: 28 ,
77- color: context.theme.colors.primary,
78- ),
72+ final titleVisible = offset > 285 ;
73+ return Row (
74+ children: [
75+ GestureDetector (
76+ onTap: () {
77+ Navigator .of (context).pop ();
78+ },
79+ child: Padding (
80+ padding: const EdgeInsets .only (
81+ right: 12.0 ,
82+ top: 4 ,
83+ left: 10 ,
84+ ),
85+ child: Icon (
86+ FLucideIcons .chevronLeft,
87+ size: 28 ,
88+ color: context.theme.colors.primary,
7989 ),
8090 ),
81- Expanded (
82- child: AnimatedOpacity (
83- opacity: titleVisible ? 1.0 : 0.0 ,
91+ ),
92+ Expanded (
93+ child: AnimatedOpacity (
94+ opacity: titleVisible ? 1.0 : 0.0 ,
95+ duration: const Duration (milliseconds: 150 ),
96+ curve: Curves .easeInOut,
97+ child: AnimatedSlide (
98+ offset: titleVisible
99+ ? Offset .zero
100+ : const Offset (0 , - 0.3 ),
84101 duration: const Duration (milliseconds: 150 ),
85102 curve: Curves .easeInOut,
86- child: AnimatedSlide (
87- offset: titleVisible
88- ? Offset .zero
89- : const Offset (0 , - 0.3 ),
90- duration: const Duration (milliseconds: 150 ),
91- curve: Curves .easeInOut,
92- child: Padding (
93- padding: const EdgeInsets .only (
94- top: 8.0 ,
95- bottom: 8.0 ,
96- ),
97- child: Text (
103+ child: Column (
104+ crossAxisAlignment: .start,
105+ children: [
106+ Text (
98107 detial.title,
108+ maxLines: 1 ,
99109 style: TextStyle (
100- height: 1.2 ,
101110 fontWeight: FontWeight .bold,
102- fontSize: 20 ,
111+ fontSize: 18 ,
103112 color: context.theme.colors.primary,
104113 ),
114+ // Let the title fill the full width of the Expanded
115+ // and wrap onto multiple lines instead of being
116+ // clamped to a single ellipsized line.
117+ softWrap: true ,
118+ ),
119+ Text (
120+ widget.meta.name,
105121 maxLines: 1 ,
106- overflow: TextOverflow .ellipsis,
122+ style: TextStyle (
123+ fontWeight: FontWeight .bold,
124+ fontSize: 11 ,
125+ color: context.theme.colors.mutedForeground,
126+ ),
127+ // Let the title fill the full width of the Expanded
128+ // and wrap onto multiple lines instead of being
129+ // clamped to a single ellipsized line.
130+ softWrap: true ,
107131 ),
108- ) ,
132+ ] ,
109133 ),
110134 ),
111135 ),
112- Spacer (),
113- FButton .icon (
114- variant: .ghost,
115- onPress: () {
116- context.push (
117- '/mobileWebView' ,
118- extra: WebviewParam (
119- meta: widget.meta,
120- url: widget.detailUrl,
121- ),
122- );
123- },
124- child: Icon (
125- FLucideIcons .globe,
126- size: 28 ,
127- color: context.theme.colors.primary,
128- ),
136+ ),
137+ FButton .icon (
138+ variant: .ghost,
139+ onPress: () {
140+ context.push (
141+ '/mobileWebView' ,
142+ extra: WebviewParam (
143+ meta: widget.meta,
144+ url: widget.detailUrl,
145+ ),
146+ );
147+ },
148+ child: Icon (
149+ FLucideIcons .globe,
150+ size: 24 ,
151+ color: context.theme.colors.primary,
129152 ),
130- FButton . icon (
131- variant : .ghost,
132- onPress : () {
133- if (favorite != null ) {
134- ref. read (detailPr.notifier). removeFavorite (favorite);
135- return ;
136- }
137- showDialog (
138- context : context,
139- builder : ( context) => FavoriteDialog (
140- meta : widget.meta,
141- detailUrl : widget.detailUrl ,
142- detail : detial ,
143- detailPr : detailPr ,
144- ) ,
145- );
146- },
147- child : HeartButton (
148- size : 28 ,
149- activeColor : context.theme.colors.primary ,
150- inactiveColor : context.theme.colors.primary,
151- isLiked : favorite != null ,
152- ) ,
153+ ),
154+ FButton . icon (
155+ variant : .ghost,
156+ onPress : ( ) {
157+ if (favorite != null ) {
158+ ref. read (detailPr.notifier). removeFavorite (favorite) ;
159+ return ;
160+ }
161+ showDialog (
162+ context : context,
163+ builder : (context) => FavoriteDialog (
164+ meta : widget.meta ,
165+ detailUrl : widget.detailUrl ,
166+ detail : detial ,
167+ detailPr : detailPr ,
168+ ),
169+ );
170+ },
171+ child : HeartButton (
172+ size : 24 ,
173+ activeColor : context.theme.colors.primary,
174+ inactiveColor : context.theme.colors.primary ,
175+ isLiked : favorite != null ,
153176 ),
154- ] ,
155- ) ,
177+ ) ,
178+ ] ,
156179 );
157180 },
158181 ),
0 commit comments