11import 'package:easy_refresh/easy_refresh.dart' ;
2+ import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart' ;
23import 'package:flutter/material.dart' ;
34import 'package:flutter/rendering.dart' ;
45import 'package:flutter_bloc/flutter_bloc.dart' ;
@@ -57,9 +58,10 @@ class ForumPage extends StatefulWidget {
5758}
5859
5960class _ForumPageState extends State <ForumPage > with SingleTickerProviderStateMixin , LoggerMixin {
60- final _pinnedScrollController = ScrollController ();
61+ // final _pinnedScrollController = ScrollController();
6162 final _pinnedRefreshController = EasyRefreshController (controlFinishRefresh: true );
62- final _subredditScrollController = ScrollController ();
63+
64+ // final _subredditScrollController = ScrollController();
6365 final _subredditRefreshController = EasyRefreshController (controlFinishRefresh: true );
6466
6567 /// Controller of thread tab.
@@ -74,7 +76,7 @@ class _ForumPageState extends State<ForumPage> with SingleTickerProviderStateMix
7476 /// Visibility of FAB.
7577 bool _fabVisible = true ;
7678
77- void _updateFabVisibilityByTabIndex () {
79+ void _updateFabVisibilityByTabIndex () => WidgetsBinding .instance. addPostFrameCallback ((_) {
7880 if (tabController.index == _threadTabIndex) {
7981 setState (() {
8082 _fabVisible = true ;
@@ -84,39 +86,56 @@ class _ForumPageState extends State<ForumPage> with SingleTickerProviderStateMix
8486 _fabVisible = false ;
8587 });
8688 }
87- }
89+ _threadScrollController.animateTo (0 , duration: const Duration (milliseconds: 300 ), curve: Curves .ease);
90+ });
8891
8992 PreferredSizeWidget _buildListAppBar (BuildContext context, ForumState state) {
9093 return ListAppBar (
9194 title: widget.title ?? state.title,
9295 bottom: state.permissionDeniedMessage == null
93- ? TabBar (
94- controller: tabController,
95- tabs: [
96- Tab (child: Text (context.t.forumPage.stickThreadTab.title)),
97- Tab (child: Text (context.t.forumPage.threadTab.title)),
98- Tab (child: Text (context.t.forumPage.subredditTab.title)),
99- ],
100- onTap: (index) {
101- // Here we want to scroll the current tab to the top.
102- // Only scroll to top when user taps on the current
103- // tab, which means index is not changing.
104- if (tabController.indexIsChanging) {
105- // Do nothing because user tapped another index
106- // and want to switch to it.
107- return ;
108- }
109- const duration = Duration (milliseconds: 300 );
110- const curve = Curves .ease;
111- switch (tabController.index) {
112- case _pinnedTabIndex:
113- _pinnedScrollController.animateTo (0 , duration: duration, curve: curve);
114- case _threadTabIndex:
115- _threadScrollController.animateTo (0 , duration: duration, curve: curve);
116- case _subredditTabIndex:
117- _subredditScrollController.animateTo (0 , duration: duration, curve: curve);
118- }
119- },
96+ ? PreferredSize (
97+ // There the preferred size is manually calculated.
98+ // Not following material standard and need update when widgets in the column changed.
99+ preferredSize: const Size .fromHeight (kToolbarHeight + 30 ),
100+ child: Column (
101+ children: [
102+ TabBar (
103+ controller: tabController,
104+ tabs: [
105+ Tab (child: Text (context.t.forumPage.stickThreadTab.title)),
106+ Tab (child: Text (context.t.forumPage.threadTab.title)),
107+ Tab (child: Text (context.t.forumPage.subredditTab.title)),
108+ ],
109+ onTap: (index) {
110+ // Here we want to scroll the current tab to the top.
111+ // Only scroll to top when user taps on the current
112+ // tab, which means index is not changing.
113+ if (tabController.indexIsChanging) {
114+ // Do nothing because user tapped another index
115+ // and want to switch to it.
116+ return ;
117+ }
118+ // Now the controller in unique.
119+ _threadScrollController.animateTo (
120+ 0 ,
121+ duration: const Duration (milliseconds: 300 ),
122+ curve: Curves .ease,
123+ );
124+ // switch (tabController.index) {
125+ // case _pinnedTabIndex:
126+ // _pinnedScrollController.animateTo(0, duration: duration, curve: curve);
127+ // case _threadTabIndex:
128+ // _threadScrollController.animateTo(0, duration: duration, curve: curve);
129+ // case _subredditTabIndex:
130+ // _subredditScrollController.animateTo(0, duration: duration, curve: curve);
131+ // }
132+ },
133+ ),
134+ sizedBoxW4H4,
135+ _buildNormalThreadFilterRow (context, state),
136+ sizedBoxW4H4,
137+ ],
138+ ),
120139 )
121140 : null ,
122141 onSearch: () async {
@@ -166,22 +185,18 @@ class _ForumPageState extends State<ForumPage> with SingleTickerProviderStateMix
166185 return Row (
167186 children: [
168187 Expanded (
169- child: Container (
170- color: Theme .of (context).colorScheme.surface,
171- height: 40 ,
172- child: SingleChildScrollView (
173- scrollDirection: Axis .horizontal,
174- child: Row (
175- mainAxisSize: MainAxisSize .min,
176- children: < Widget > [
177- if (state.filterTypeList.isNotEmpty) const ThreadTypeChip (),
178- if (state.filterSpecialTypeList.isNotEmpty) const ThreadSpecialTypeChip (),
179- if (state.filterDatelineList.isNotEmpty) const ThreadDatelineChip (),
180- if (state.filterOrderList.isNotEmpty) const ThreadOrderChip (),
181- const ThreadDigestChip (),
182- const ThreadRecommendedChip (),
183- ].prepend (sizedBoxW4H4).insertBetween (sizedBoxW12H12),
184- ),
188+ child: SingleChildScrollView (
189+ scrollDirection: Axis .horizontal,
190+ child: Row (
191+ mainAxisSize: MainAxisSize .min,
192+ children: < Widget > [
193+ if (state.filterTypeList.isNotEmpty) const ThreadTypeChip (),
194+ if (state.filterSpecialTypeList.isNotEmpty) const ThreadSpecialTypeChip (),
195+ if (state.filterDatelineList.isNotEmpty) const ThreadDatelineChip (),
196+ if (state.filterOrderList.isNotEmpty) const ThreadOrderChip (),
197+ const ThreadDigestChip (),
198+ const ThreadRecommendedChip (),
199+ ].prepend (sizedBoxW4H4).insertBetween (sizedBoxW12H12),
185200 ),
186201 ),
187202 ),
@@ -196,15 +211,15 @@ class _ForumPageState extends State<ForumPage> with SingleTickerProviderStateMix
196211 late final Widget content;
197212 if (state.rulesElement == null ) {
198213 content = ListView .separated (
199- controller: _pinnedScrollController,
214+ // controller: _pinnedScrollController,
200215 padding: edgeInsetsL12T4R12,
201216 itemCount: state.stickThreadList.length,
202217 itemBuilder: (context, index) => NormalThreadCard (state.stickThreadList[index]),
203218 separatorBuilder: (context, index) => sizedBoxW4H4,
204219 );
205220 } else {
206221 content = ListView .separated (
207- controller: _pinnedScrollController,
222+ // controller: _pinnedScrollController,
208223 padding: edgeInsetsL12T4R12.add (context.safePadding ()),
209224 itemCount: state.stickThreadList.length + 1 ,
210225 itemBuilder: (context, index) {
@@ -227,7 +242,7 @@ class _ForumPageState extends State<ForumPage> with SingleTickerProviderStateMix
227242 scrollBehaviorBuilder: (physics) => ERScrollBehavior (physics).copyWith (physics: physics, scrollbars: false ),
228243 header: const MaterialHeader (),
229244 controller: _pinnedRefreshController,
230- scrollController: _pinnedScrollController,
245+ // scrollController: _pinnedScrollController,
231246 onRefresh: () async {
232247 if (! mounted) {
233248 return ;
@@ -248,10 +263,7 @@ class _ForumPageState extends State<ForumPage> with SingleTickerProviderStateMix
248263 if (state.filterState.isFiltering ()) {
249264 return Column (
250265 crossAxisAlignment: CrossAxisAlignment .start,
251- children: [
252- _buildNormalThreadFilterRow (context, state),
253- Expanded (child: emptyContentHint),
254- ],
266+ children: [Expanded (child: emptyContentHint)],
255267 );
256268 }
257269 return emptyContentHint;
@@ -264,7 +276,7 @@ class _ForumPageState extends State<ForumPage> with SingleTickerProviderStateMix
264276 header: const MaterialHeader (),
265277 footer: const MaterialFooter (),
266278 controller: _threadRefreshController,
267- scrollController: _threadScrollController,
279+ // scrollController: _threadScrollController,
268280 onRefresh: () async {
269281 if (! mounted) {
270282 return ;
@@ -286,10 +298,9 @@ class _ForumPageState extends State<ForumPage> with SingleTickerProviderStateMix
286298 // _refreshController.finishLoad();
287299 },
288300 childBuilder: (context, physics) => CustomScrollView (
289- controller: _threadScrollController,
301+ // controller: _threadScrollController,
290302 physics: physics,
291303 slivers: [
292- PinnedHeaderSliver (child: _buildNormalThreadFilterRow (context, state)),
293304 const SliverPadding (padding: edgeInsetsL12T4R12),
294305 SliverList .separated (
295306 itemCount: normalThreadList.length,
@@ -330,22 +341,6 @@ class _ForumPageState extends State<ForumPage> with SingleTickerProviderStateMix
330341 }
331342 }
332343
333- Widget _buildBody (BuildContext context, ForumState state) {
334- return switch (state.status) {
335- ForumStatus .initial || ForumStatus .loading => Column (
336- crossAxisAlignment: CrossAxisAlignment .start,
337- children: [
338- if (state.filterState.isFiltering ()) _buildNormalThreadFilterRow (context, state),
339- const Expanded (child: Center (child: CircularProgressIndicator ())),
340- ],
341- ),
342- ForumStatus .failure => buildRetryButton (context, () {
343- context.read <ForumBloc >().add (ForumLoadMoreRequested (state.currentPage));
344- }),
345- ForumStatus .success => _buildSuccessContent (context, state),
346- };
347- }
348-
349344 Widget _buildSubredditTab (BuildContext context, List <Forum > subredditList) {
350345 if (subredditList.isEmpty) {
351346 return Center (child: Text (context.t.forumPage.subredditTab.noSubreddit));
@@ -355,15 +350,15 @@ class _ForumPageState extends State<ForumPage> with SingleTickerProviderStateMix
355350 scrollBehaviorBuilder: (physics) => ERScrollBehavior (physics).copyWith (physics: physics, scrollbars: false ),
356351 header: const MaterialHeader (),
357352 controller: _subredditRefreshController,
358- scrollController: _subredditScrollController,
353+ // scrollController: _subredditScrollController,
359354 onRefresh: () async {
360355 if (! mounted) {
361356 return ;
362357 }
363358 context.read <ForumBloc >().add (ForumRefreshRequested ());
364359 },
365360 child: ListView .separated (
366- controller: _subredditScrollController,
361+ // controller: _subredditScrollController,
367362 padding: edgeInsetsL12T4R12.add (context.safePadding ()),
368363 itemCount: subredditList.length,
369364 itemBuilder: (context, index) => ForumCard (subredditList[index]),
@@ -414,11 +409,11 @@ class _ForumPageState extends State<ForumPage> with SingleTickerProviderStateMix
414409
415410 @override
416411 void dispose () {
417- _pinnedScrollController.dispose ();
412+ // _pinnedScrollController.dispose();
418413 _pinnedRefreshController.dispose ();
419414 _threadScrollController.dispose ();
420415 _threadRefreshController.dispose ();
421- _subredditScrollController.dispose ();
416+ // _subredditScrollController.dispose();
422417 _subredditRefreshController.dispose ();
423418 tabController.dispose ();
424419 super .dispose ();
@@ -472,12 +467,55 @@ class _ForumPageState extends State<ForumPage> with SingleTickerProviderStateMix
472467 }
473468
474469 return Scaffold (
475- appBar: _buildListAppBar (context, state),
476- body: NotificationListener <UserScrollNotification >(
477- onNotification: _onBodyScrollNotification,
478- child: SafeArea (bottom: false , child: _buildBody (context, state)),
470+ body: ExtendedNestedScrollView (
471+ controller: _threadScrollController,
472+ onlyOneScrollInBody: true ,
473+ headerSliverBuilder: (context, innerBoxIsScroller) => [_buildListAppBar (context, state)],
474+ body: NotificationListener (
475+ onNotification: _onBodyScrollNotification,
476+ child: switch (state.status) {
477+ ForumStatus .initial || ForumStatus .loading => const Center (child: CircularProgressIndicator ()),
478+ ForumStatus .failure => buildRetryButton (context, () {
479+ context.read <ForumBloc >().add (ForumLoadMoreRequested (state.currentPage));
480+ }),
481+ ForumStatus .success => _buildSuccessContent (context, state),
482+ },
483+ ),
479484 ),
485+ floatingActionButtonLocation: FloatingActionButtonLocation .endFloat,
480486 floatingActionButton: _buildFloatingActionButton (context, state),
487+ // bottomNavigationBar: AnimatedContainer(
488+ // curve: Curves.easeIn,
489+ // duration: duration200,
490+ // height: _fabVisible ? 100 : 0,
491+ // child: BottomAppBar(
492+ // padding: edgeInsetsL12R12,
493+ // child: Align(
494+ // alignment: Alignment.centerLeft,
495+ // child: SingleChildScrollView(
496+ // child: Wrap(
497+ // runAlignment: WrapAlignment.center,
498+ // children: [
499+ // const NoticeButton(),
500+ // const OpenInAppPageButton(),
501+ // IconButton(
502+ // icon: const Icon(Icons.search_outlined),
503+ // tooltip: context.t.searchPage.title,
504+ // onPressed: () async {
505+ // await context.pushNamed(ScreenPaths.search, queryParameters: {'fid': widget.fid});
506+ // },
507+ // ),
508+ // IconButton(
509+ // icon: const Icon(Icons.settings_outlined),
510+ // tooltip: context.t.general.openSettings,
511+ // onPressed: () => context.pushNamed(ScreenPaths.rootSettings),
512+ // ),
513+ // ],
514+ // ),
515+ // ),
516+ // ),
517+ // ),
518+ // ),
481519 );
482520 },
483521 ),
0 commit comments