diff --git a/lib/components/appbar.dart b/lib/components/appbar.dart index 0550e60..825dc14 100644 --- a/lib/components/appbar.dart +++ b/lib/components/appbar.dart @@ -281,7 +281,9 @@ class _FilledTabBarState extends State { _IndicatorPainter? painter; - var scrollController = ScrollController(); + var scrollController = ScrollController( + keepScrollOffset: false + ); var tabBarKey = GlobalKey(); @@ -298,12 +300,20 @@ class _FilledTabBarState extends State { super.dispose(); } + PageStorageBucket get bucket => PageStorage.of(context); + @override void didChangeDependencies() { _controller = widget.controller ?? DefaultTabController.of(context); _controller.animation!.addListener(onTabChanged); initPainter(); super.didChangeDependencies(); + var prevIndex = bucket.readState(context) as int?; + if (prevIndex != null && prevIndex != _controller.index) { + Future.microtask(() { + _controller.index = prevIndex; + }); + } } @override @@ -387,6 +397,7 @@ class _FilledTabBarState extends State { } updateScrollOffset(i); previousIndex = i; + bucket.writeState(context, i); } void updateScrollOffset(int i) { diff --git a/lib/components/comic.dart b/lib/components/comic.dart index b8e5061..2c6b457 100644 --- a/lib/components/comic.dart +++ b/lib/components/comic.dart @@ -865,6 +865,39 @@ class ComicListState extends State { String? _nextUrl; + Map get state => { + 'maxPage': _maxPage, + 'data': _data, + 'page': _page, + 'error': _error, + 'loading': _loading, + 'nextUrl': _nextUrl, + }; + + void restoreState(Map? state) { + if (state == null) { + return; + } + _maxPage = state['maxPage']; + _data.clear(); + _data.addAll(state['data']); + _page = state['page']; + _error = state['error']; + _loading.clear(); + _loading.addAll(state['loading']); + _nextUrl = state['nextUrl']; + } + + void storeState() { + PageStorage.of(context).writeState(context, state); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + restoreState(PageStorage.of(context).readState(context)); + } + void remove(Comic c) { if (_data[_page] == null || !_data[_page]!.remove(c)) { for (var page in _data.values) { @@ -1025,6 +1058,7 @@ class ComicListState extends State { } } finally { _loading[page] = false; + storeState(); } } @@ -1073,6 +1107,7 @@ class ComicListState extends State { ); } return SmoothCustomScrollView( + key: const PageStorageKey('scroll'), controller: widget.controller, slivers: [ if (widget.leadingSliver != null) widget.leadingSliver!, diff --git a/lib/pages/categories_page.dart b/lib/pages/categories_page.dart index fe2bb45..6ea4e93 100644 --- a/lib/pages/categories_page.dart +++ b/lib/pages/categories_page.dart @@ -53,6 +53,7 @@ class CategoriesPage extends StatelessWidget { child: Column( children: [ FilledTabBar( + key: PageStorageKey(categories.toString()), tabs: categories.map((e) { String title = e; try { diff --git a/lib/pages/explore_page.dart b/lib/pages/explore_page.dart index abd7618..5528aa3 100644 --- a/lib/pages/explore_page.dart +++ b/lib/pages/explore_page.dart @@ -110,7 +110,7 @@ class _ExplorePageState extends State return Tab(text: i.ts(comicSource.key), key: Key(i)); } - Widget buildBody(String i) => _SingleExplorePage(i, key: Key(i)); + Widget buildBody(String i) => _SingleExplorePage(i, key: PageStorageKey(i)); Widget buildEmpty() { var msg = "No Explore Pages".tl; @@ -147,7 +147,7 @@ class _ExplorePageState extends State Widget tabBar = Material( child: FilledTabBar( - key: Key(pages.toString()), + key: PageStorageKey(pages.toString()), tabs: pages.map((e) => buildTab(e)).toList(), controller: controller, ), @@ -240,12 +240,6 @@ class _SingleExplorePageState extends StateWithController<_SingleExplorePage> with AutomaticKeepAliveClientMixin<_SingleExplorePage> { late final ExplorePageData data; - bool loading = true; - - String? message; - - List? parts; - late final String comicSourceKey; int key = 0; @@ -288,9 +282,19 @@ class _SingleExplorePageState extends StateWithController<_SingleExplorePage> Widget build(BuildContext context) { super.build(context); if (data.loadMultiPart != null) { - return buildMultiPart(); + return _MultiPartExplorePage( + key: PageStorageKey(key), + data: data, + controller: scrollController, + comicSourceKey: comicSourceKey, + ); } else if (data.loadPage != null || data.loadNext != null) { - return buildComicList(); + return ComicList( + loadPage: data.loadPage, + loadNext: data.loadNext, + key: PageStorageKey(key), + controller: scrollController, + ); } else if (data.loadMixed != null) { return _MixedExplorePage( data, @@ -305,74 +309,14 @@ class _SingleExplorePageState extends StateWithController<_SingleExplorePage> } } - Widget buildComicList() { - return ComicList( - loadPage: data.loadPage, - loadNext: data.loadNext, - key: ValueKey(key), - controller: scrollController, - ); - } - - void load() async { - var res = await data.loadMultiPart!(); - loading = false; - if (mounted) { - setState(() { - if (res.error) { - message = res.errorMessage; - } else { - parts = res.data; - } - }); - } - } - - Widget buildMultiPart() { - if (loading) { - load(); - return const Center( - child: CircularProgressIndicator(), - ); - } else if (message != null) { - return NetworkError( - message: message!, - retry: refresh, - withAppbar: false, - ); - } else { - return buildPage(); - } - } - - Widget buildPage() { - return SmoothCustomScrollView( - controller: scrollController, - slivers: _buildPage().toList(), - ); - } - - Iterable _buildPage() sync* { - for (var part in parts!) { - yield* _buildExplorePagePart(part, comicSourceKey); - } - } - @override Object? get tag => widget.title; @override void refresh() { - message = null; - if (data.loadMultiPart != null) { - setState(() { - loading = true; - }); - } else { - setState(() { - key++; - }); - } + setState(() { + key++; + }); } @override @@ -393,7 +337,8 @@ class _SingleExplorePageState extends StateWithController<_SingleExplorePage> } class _MixedExplorePage extends StatefulWidget { - const _MixedExplorePage(this.data, this.sourceKey, {super.key, this.controller}); + const _MixedExplorePage(this.data, this.sourceKey, + {super.key, this.controller}); final ExplorePageData data; @@ -518,3 +463,112 @@ Iterable _buildExplorePagePart( yield buildTitle(part); yield buildComics(part); } + +class _MultiPartExplorePage extends StatefulWidget { + const _MultiPartExplorePage({ + super.key, + required this.data, + required this.controller, + required this.comicSourceKey, + }); + + final ExplorePageData data; + + final ScrollController controller; + + final String comicSourceKey; + + @override + State<_MultiPartExplorePage> createState() => _MultiPartExplorePageState(); +} + +class _MultiPartExplorePageState extends State<_MultiPartExplorePage> { + late final ExplorePageData data; + + List? parts; + + bool loading = true; + + String? message; + + Map get state => { + "loading": loading, + "message": message, + "parts": parts, + }; + + void restoreState(dynamic state) { + if (state == null) return; + loading = state["loading"]; + message = state["message"]; + parts = state["parts"]; + } + + void storeState() { + PageStorage.of(context).writeState(context, state); + } + + @override + void initState() { + super.initState(); + data = widget.data; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + restoreState(PageStorage.of(context).readState(context)); + } + + void load() async { + var res = await data.loadMultiPart!(); + loading = false; + if (mounted) { + setState(() { + if (res.error) { + message = res.errorMessage; + } else { + parts = res.data; + } + }); + storeState(); + } + } + + @override + Widget build(BuildContext context) { + if (loading) { + load(); + return const Center( + child: CircularProgressIndicator(), + ); + } else if (message != null) { + return NetworkError( + message: message!, + retry: () { + setState(() { + loading = true; + message = null; + }); + }, + withAppbar: false, + ); + } else { + return buildPage(); + } + } + + Widget buildPage() { + return SmoothCustomScrollView( + key: const PageStorageKey('scroll'), + controller: widget.controller, + slivers: _buildPage().toList(), + ); + } + + Iterable _buildPage() sync* { + for (var part in parts!) { + yield* _buildExplorePagePart(part, widget.comicSourceKey); + } + } +} diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart index 3cba828..2801e55 100644 --- a/lib/pages/main_page.dart +++ b/lib/pages/main_page.dart @@ -62,10 +62,18 @@ class _MainPageState extends State { } final _pages = [ - const HomePage(), - const FavoritesPage(), - const ExplorePage(), - const CategoriesPage(), + const HomePage( + key: PageStorageKey('home'), + ), + const FavoritesPage( + key: PageStorageKey('favorites'), + ), + const ExplorePage( + key: PageStorageKey('explore'), + ), + const CategoriesPage( + key: PageStorageKey('categories'), + ), ]; var index = 0;