part of 'comic_page.dart'; class _ComicChapters extends StatelessWidget { const _ComicChapters({this.history, required this.groupedMode}); final History? history; final bool groupedMode; @override Widget build(BuildContext context) { return groupedMode ? _GroupedComicChapters(history) : _NormalComicChapters(history); } } class _NormalComicChapters extends StatefulWidget { const _NormalComicChapters(this.history); final History? history; @override State<_NormalComicChapters> createState() => _NormalComicChaptersState(); } class _NormalComicChaptersState extends State<_NormalComicChapters> { late _ComicPageState state; bool reverse = false; bool showAll = false; late History? history; late ComicChapters chapters; @override void initState() { super.initState(); history = widget.history; } @override void didChangeDependencies() { state = context.findAncestorStateOfType<_ComicPageState>()!; chapters = state.comic.chapters!; super.didChangeDependencies(); } @override void didUpdateWidget(covariant _NormalComicChapters oldWidget) { super.didUpdateWidget(oldWidget); setState(() { history = widget.history; }); } @override Widget build(BuildContext context) { return SliverLayoutBuilder( builder: (context, constrains) { int length = chapters.length; bool canShowAll = showAll; if (!showAll) { var width = constrains.crossAxisExtent - 16; var crossItems = width ~/ 200; if (width % 200 != 0) { crossItems += 1; } length = math.min(length, crossItems * 8); if (length == chapters.length) { canShowAll = true; } } return SliverMainAxisGroup( slivers: [ SliverToBoxAdapter( child: ListTile( title: Text("Chapters".tl), trailing: Tooltip( message: "Order".tl, child: IconButton( icon: Icon(reverse ? Icons.vertical_align_top : Icons.vertical_align_bottom_outlined), onPressed: () { setState(() { reverse = !reverse; }); }, ), ), ), ), SliverGrid( delegate: SliverChildBuilderDelegate( childCount: length, (context, i) { if (reverse) { i = chapters.length - i - 1; } var key = chapters.ids.elementAt(i); var value = chapters[key]!; bool visited = (history?.readEpisode ?? {}).contains(i + 1); return Padding( padding: const EdgeInsets.fromLTRB(6, 4, 6, 4), child: Material( color: context.colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(16), child: InkWell( onTap: () => state.read(i + 1), borderRadius: BorderRadius.circular(16), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Center( child: Text( value, maxLines: 1, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, style: TextStyle( color: visited ? context.colorScheme.outline : null, ), ), ), ), ), ), ); }, ), gridDelegate: const SliverGridDelegateWithFixedHeight( maxCrossAxisExtent: 200, itemHeight: 48, ), ).sliverPadding(const EdgeInsets.symmetric(horizontal: 8)), if (!canShowAll) SliverToBoxAdapter( child: Align( alignment: Alignment.center, child: TextButton.icon( icon: const Icon(Icons.arrow_drop_down), onPressed: () { setState(() { showAll = true; }); }, label: Text("${"Show all".tl} (${chapters.length})"), ).paddingTop(12), ), ), const SliverToBoxAdapter( child: Divider(), ), ], ); }, ); } } class _GroupedComicChapters extends StatefulWidget { const _GroupedComicChapters(this.history); final History? history; @override State<_GroupedComicChapters> createState() => _GroupedComicChaptersState(); } class _GroupedComicChaptersState extends State<_GroupedComicChapters> with SingleTickerProviderStateMixin { late _ComicPageState state; bool reverse = false; bool showAll = false; late History? history; late ComicChapters chapters; late TabController tabController; int index = 0; @override void initState() { super.initState(); history = widget.history; } @override void didChangeDependencies() { state = context.findAncestorStateOfType<_ComicPageState>()!; chapters = state.comic.chapters!; tabController = TabController( length: chapters.ids.length, vsync: this, ); tabController.addListener(onTabChange); super.didChangeDependencies(); } void onTabChange() { if (index != tabController.index) { setState(() { index = tabController.index; }); } } @override void didUpdateWidget(covariant _GroupedComicChapters oldWidget) { super.didUpdateWidget(oldWidget); setState(() { history = widget.history; }); } @override Widget build(BuildContext context) { return SliverLayoutBuilder( builder: (context, constrains) { var group = chapters.getGroupByIndex(index); int length = group.length; bool canShowAll = showAll; if (!showAll) { var width = constrains.crossAxisExtent - 16; var crossItems = width ~/ 200; if (width % 200 != 0) { crossItems += 1; } length = math.min(length, crossItems * 8); if (length == group.length) { canShowAll = true; } } return SliverMainAxisGroup( slivers: [ SliverToBoxAdapter( child: ListTile( title: Text("Chapters".tl), trailing: Tooltip( message: "Order".tl, child: IconButton( icon: Icon(reverse ? Icons.vertical_align_top : Icons.vertical_align_bottom_outlined), onPressed: () { setState(() { reverse = !reverse; }); }, ), ), ), ), SliverToBoxAdapter( child: AppTabBar( withUnderLine: false, controller: tabController, tabs: chapters.groups.map((e) => Tab(text: e)).toList(), ), ), SliverPadding(padding: const EdgeInsets.only(top: 8)), SliverGrid( delegate: SliverChildBuilderDelegate( childCount: length, (context, i) { if (reverse) { i = group.length - i - 1; } var key = group.keys.elementAt(i); var value = group[key]!; var chapterIndex = 0; for (var j = 0; j < chapters.groupCount; j++) { if (j == index) { chapterIndex += i; break; } chapterIndex += chapters.getGroupByIndex(j).length; } bool visited = (history?.readEpisode ?? {}).contains(chapterIndex + 1); return Padding( padding: const EdgeInsets.fromLTRB(6, 4, 6, 4), child: Material( color: context.colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(16), child: InkWell( onTap: () => state.read(chapterIndex + 1), borderRadius: BorderRadius.circular(16), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Center( child: Text( value, maxLines: 1, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, style: TextStyle( color: visited ? context.colorScheme.outline : null, ), ), ), ), ), ), ); }, ), gridDelegate: const SliverGridDelegateWithFixedHeight( maxCrossAxisExtent: 200, itemHeight: 48, ), ).sliverPadding(const EdgeInsets.symmetric(horizontal: 8)), if (!canShowAll) SliverToBoxAdapter( child: Align( alignment: Alignment.center, child: TextButton.icon( icon: const Icon(Icons.arrow_drop_down), onPressed: () { setState(() { showAll = true; }); }, label: Text("${"Show all".tl} (${group.length})"), ).paddingTop(12), ), ), const SliverToBoxAdapter( child: Divider(), ), ], ); }, ); } }