Use PageStorage to store state

This commit is contained in:
2024-12-09 19:56:43 +08:00
parent 1104d28f14
commit 72b146a9bf
5 changed files with 188 additions and 79 deletions

View File

@@ -281,7 +281,9 @@ class _FilledTabBarState extends State<FilledTabBar> {
_IndicatorPainter? painter; _IndicatorPainter? painter;
var scrollController = ScrollController(); var scrollController = ScrollController(
keepScrollOffset: false
);
var tabBarKey = GlobalKey(); var tabBarKey = GlobalKey();
@@ -298,12 +300,20 @@ class _FilledTabBarState extends State<FilledTabBar> {
super.dispose(); super.dispose();
} }
PageStorageBucket get bucket => PageStorage.of(context);
@override @override
void didChangeDependencies() { void didChangeDependencies() {
_controller = widget.controller ?? DefaultTabController.of(context); _controller = widget.controller ?? DefaultTabController.of(context);
_controller.animation!.addListener(onTabChanged); _controller.animation!.addListener(onTabChanged);
initPainter(); initPainter();
super.didChangeDependencies(); super.didChangeDependencies();
var prevIndex = bucket.readState(context) as int?;
if (prevIndex != null && prevIndex != _controller.index) {
Future.microtask(() {
_controller.index = prevIndex;
});
}
} }
@override @override
@@ -387,6 +397,7 @@ class _FilledTabBarState extends State<FilledTabBar> {
} }
updateScrollOffset(i); updateScrollOffset(i);
previousIndex = i; previousIndex = i;
bucket.writeState(context, i);
} }
void updateScrollOffset(int i) { void updateScrollOffset(int i) {

View File

@@ -865,6 +865,39 @@ class ComicListState extends State<ComicList> {
String? _nextUrl; String? _nextUrl;
Map<String, dynamic> get state => {
'maxPage': _maxPage,
'data': _data,
'page': _page,
'error': _error,
'loading': _loading,
'nextUrl': _nextUrl,
};
void restoreState(Map<String, dynamic>? 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) { void remove(Comic c) {
if (_data[_page] == null || !_data[_page]!.remove(c)) { if (_data[_page] == null || !_data[_page]!.remove(c)) {
for (var page in _data.values) { for (var page in _data.values) {
@@ -1025,6 +1058,7 @@ class ComicListState extends State<ComicList> {
} }
} finally { } finally {
_loading[page] = false; _loading[page] = false;
storeState();
} }
} }
@@ -1073,6 +1107,7 @@ class ComicListState extends State<ComicList> {
); );
} }
return SmoothCustomScrollView( return SmoothCustomScrollView(
key: const PageStorageKey('scroll'),
controller: widget.controller, controller: widget.controller,
slivers: [ slivers: [
if (widget.leadingSliver != null) widget.leadingSliver!, if (widget.leadingSliver != null) widget.leadingSliver!,

View File

@@ -53,6 +53,7 @@ class CategoriesPage extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
FilledTabBar( FilledTabBar(
key: PageStorageKey(categories.toString()),
tabs: categories.map((e) { tabs: categories.map((e) {
String title = e; String title = e;
try { try {

View File

@@ -110,7 +110,7 @@ class _ExplorePageState extends State<ExplorePage>
return Tab(text: i.ts(comicSource.key), key: Key(i)); 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() { Widget buildEmpty() {
var msg = "No Explore Pages".tl; var msg = "No Explore Pages".tl;
@@ -147,7 +147,7 @@ class _ExplorePageState extends State<ExplorePage>
Widget tabBar = Material( Widget tabBar = Material(
child: FilledTabBar( child: FilledTabBar(
key: Key(pages.toString()), key: PageStorageKey(pages.toString()),
tabs: pages.map((e) => buildTab(e)).toList(), tabs: pages.map((e) => buildTab(e)).toList(),
controller: controller, controller: controller,
), ),
@@ -240,12 +240,6 @@ class _SingleExplorePageState extends StateWithController<_SingleExplorePage>
with AutomaticKeepAliveClientMixin<_SingleExplorePage> { with AutomaticKeepAliveClientMixin<_SingleExplorePage> {
late final ExplorePageData data; late final ExplorePageData data;
bool loading = true;
String? message;
List<ExplorePagePart>? parts;
late final String comicSourceKey; late final String comicSourceKey;
int key = 0; int key = 0;
@@ -288,9 +282,19 @@ class _SingleExplorePageState extends StateWithController<_SingleExplorePage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
if (data.loadMultiPart != null) { 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) { } 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) { } else if (data.loadMixed != null) {
return _MixedExplorePage( return _MixedExplorePage(
data, 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<Widget> _buildPage() sync* {
for (var part in parts!) {
yield* _buildExplorePagePart(part, comicSourceKey);
}
}
@override @override
Object? get tag => widget.title; Object? get tag => widget.title;
@override @override
void refresh() { void refresh() {
message = null; setState(() {
if (data.loadMultiPart != null) { key++;
setState(() { });
loading = true;
});
} else {
setState(() {
key++;
});
}
} }
@override @override
@@ -393,7 +337,8 @@ class _SingleExplorePageState extends StateWithController<_SingleExplorePage>
} }
class _MixedExplorePage extends StatefulWidget { 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; final ExplorePageData data;
@@ -518,3 +463,112 @@ Iterable<Widget> _buildExplorePagePart(
yield buildTitle(part); yield buildTitle(part);
yield buildComics(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<ExplorePagePart>? parts;
bool loading = true;
String? message;
Map<String, dynamic> 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<Widget> _buildPage() sync* {
for (var part in parts!) {
yield* _buildExplorePagePart(part, widget.comicSourceKey);
}
}
}

View File

@@ -62,10 +62,18 @@ class _MainPageState extends State<MainPage> {
} }
final _pages = [ final _pages = [
const HomePage(), const HomePage(
const FavoritesPage(), key: PageStorageKey('home'),
const ExplorePage(), ),
const CategoriesPage(), const FavoritesPage(
key: PageStorageKey('favorites'),
),
const ExplorePage(
key: PageStorageKey('explore'),
),
const CategoriesPage(
key: PageStorageKey('categories'),
),
]; ];
var index = 0; var index = 0;