diff --git a/lib/pages/reader/chapters.dart b/lib/pages/reader/chapters.dart new file mode 100644 index 0000000..2b9a629 --- /dev/null +++ b/lib/pages/reader/chapters.dart @@ -0,0 +1,242 @@ +part of 'reader.dart'; + +class _ChaptersView extends StatefulWidget { + const _ChaptersView(this.reader); + + final _ReaderState reader; + + @override + State<_ChaptersView> createState() => _ChaptersViewState(); +} + +class _ChaptersViewState extends State<_ChaptersView> { + bool desc = false; + + late final ScrollController _scrollController; + + var downloaded = []; + + @override + void initState() { + super.initState(); + int epIndex = widget.reader.chapter - 2; + _scrollController = ScrollController( + initialScrollOffset: (epIndex * 48.0 + 52).clamp(0, double.infinity), + ); + var local = LocalManager().find(widget.reader.cid, widget.reader.type); + if (local != null) { + downloaded = local.downloadedChapters; + } + } + + @override + Widget build(BuildContext context) { + var chapters = widget.reader.widget.chapters!; + var current = widget.reader.chapter - 1; + return Scaffold( + body: SmoothCustomScrollView( + controller: _scrollController, + slivers: [ + SliverAppbar( + style: AppbarStyle.shadow, + title: Text("Chapters".tl), + actions: [ + Tooltip( + message: "Click to change the order".tl, + child: TextButton.icon( + icon: Icon( + !desc ? Icons.arrow_upward : Icons.arrow_downward, + size: 18, + ), + label: Text(!desc ? "Ascending".tl : "Descending".tl), + onPressed: () { + setState(() { + desc = !desc; + }); + }, + ), + ), + ], + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if (desc) { + index = chapters.length - 1 - index; + } + var chapter = chapters.titles.elementAt(index); + return _ChapterListTile( + onTap: () { + widget.reader.toChapter(index + 1); + Navigator.of(context).pop(); + }, + title: chapter, + isActive: current == index, + isDownloaded: + downloaded.contains(chapters.ids.elementAt(index)), + ); + }, + childCount: chapters.length, + ), + ), + ], + ), + ); + } +} + +class _GroupedChaptersView extends StatefulWidget { + const _GroupedChaptersView(this.reader); + + final _ReaderState reader; + + @override + State<_GroupedChaptersView> createState() => _GroupedChaptersViewState(); +} + +class _GroupedChaptersViewState extends State<_GroupedChaptersView> + with SingleTickerProviderStateMixin { + ComicChapters get chapters => widget.reader.widget.chapters!; + + late final TabController tabController; + + late final ScrollController _scrollController; + + late final String initialGroupName; + + var downloaded = []; + + @override + void initState() { + super.initState(); + int index = 0; + int epIndex = widget.reader.chapter - 1; + while (epIndex >= 0) { + epIndex -= chapters.getGroupByIndex(index).length; + index++; + } + tabController = TabController( + length: chapters.groups.length, + vsync: this, + initialIndex: index - 1, + ); + initialGroupName = chapters.groups.elementAt(index - 1); + var epIndexAtGroup = widget.reader.chapter - 1; + for (var i = 0; i < index - 1; i++) { + epIndexAtGroup -= chapters.getGroupByIndex(i).length; + } + _scrollController = ScrollController( + initialScrollOffset: (epIndexAtGroup * 48.0).clamp(0, double.infinity), + ); + var local = LocalManager().find(widget.reader.cid, widget.reader.type); + if (local != null) { + downloaded = local.downloadedChapters; + } + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Appbar(title: Text("Chapters".tl)), + AppTabBar( + controller: tabController, + tabs: chapters.groups.map((e) => Tab(text: e)).toList(), + ), + Expanded( + child: TabViewBody( + controller: tabController, + children: chapters.groups.map(buildGroup).toList(), + ), + ), + ], + ); + } + + Widget buildGroup(String groupName) { + var group = chapters.getGroup(groupName); + return SmoothCustomScrollView( + controller: initialGroupName == groupName ? _scrollController : null, + slivers: [ + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + var name = group.values.elementAt(index); + var i = 0; + for (var g in chapters.groups) { + if (g == groupName) { + break; + } + i += chapters.getGroup(g).length; + } + i += index + 1; + return _ChapterListTile( + onTap: () { + widget.reader.toChapter(i); + context.pop(); + }, + title: name, + isActive: widget.reader.chapter == i, + isDownloaded: downloaded.contains(group.keys.elementAt(index)), + ); + }, + childCount: group.length, + ), + ), + ], + ); + } +} + +class _ChapterListTile extends StatelessWidget { + const _ChapterListTile({ + required this.title, + required this.isActive, + required this.isDownloaded, + required this.onTap, + }); + + final String title; + + final bool isActive; + + final bool isDownloaded; + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Container( + height: 48, + padding: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + border: Border( + left: BorderSide( + color: + isActive ? context.colorScheme.primary : Colors.transparent, + width: 4, + ), + ), + ), + child: Row( + children: [ + Text( + title, + style: isActive + ? ts.withColor(context.colorScheme.primary).bold.s16 + : ts.s16, + ), + const Spacer(), + if (isDownloaded) + Icon( + Icons.download_done_rounded, + color: context.colorScheme.secondary, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/reader/reader.dart b/lib/pages/reader/reader.dart index ef0cbbe..d1d0cea 100644 --- a/lib/pages/reader/reader.dart +++ b/lib/pages/reader/reader.dart @@ -49,6 +49,8 @@ part 'comic_image.dart'; part 'loading.dart'; +part 'chapters.dart'; + extension _ReaderContext on BuildContext { _ReaderState get reader => findAncestorStateOfType<_ReaderState>()!; diff --git a/lib/pages/reader/scaffold.dart b/lib/pages/reader/scaffold.dart index a468bff..41ef5e6 100644 --- a/lib/pages/reader/scaffold.dart +++ b/lib/pages/reader/scaffold.dart @@ -940,218 +940,3 @@ class _ClockWidgetState extends State<_ClockWidget> { ); } } - -class _ChaptersView extends StatefulWidget { - const _ChaptersView(this.reader); - - final _ReaderState reader; - - @override - State<_ChaptersView> createState() => _ChaptersViewState(); -} - -class _ChaptersViewState extends State<_ChaptersView> { - bool desc = false; - - late final ScrollController _scrollController; - - @override - void initState() { - super.initState(); - int epIndex = widget.reader.chapter - 2; - _scrollController = ScrollController( - initialScrollOffset: (epIndex * 48.0 + 52).clamp(0, double.infinity), - ); - } - - @override - Widget build(BuildContext context) { - var chapters = widget.reader.widget.chapters!; - var current = widget.reader.chapter - 1; - return Scaffold( - body: SmoothCustomScrollView( - controller: _scrollController, - slivers: [ - SliverAppbar( - style: AppbarStyle.shadow, - title: Text("Chapters".tl), - actions: [ - Tooltip( - message: "Click to change the order".tl, - child: TextButton.icon( - icon: Icon( - !desc ? Icons.arrow_upward : Icons.arrow_downward, - size: 18, - ), - label: Text(!desc ? "Ascending".tl : "Descending".tl), - onPressed: () { - setState(() { - desc = !desc; - }); - }, - ), - ), - ], - ), - SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - if (desc) { - index = chapters.length - 1 - index; - } - var chapter = chapters.titles.elementAt(index); - return InkWell( - onTap: () { - widget.reader.toChapter(index + 1); - Navigator.of(context).pop(); - }, - child: Container( - height: 48, - padding: const EdgeInsets.only(left: 16), - decoration: BoxDecoration( - border: Border( - left: BorderSide( - color: current == index - ? context.colorScheme.primary - : Colors.transparent, - width: 2, - ), - ), - ), - child: Align( - alignment: Alignment.centerLeft, - child: Text( - chapter, - style: current == index - ? ts.withColor(context.colorScheme.primary).bold.s16 - : ts.s16, - ), - ), - ), - ); - }, - childCount: chapters.length, - ), - ), - ], - ), - ); - } -} - -class _GroupedChaptersView extends StatefulWidget { - const _GroupedChaptersView(this.reader); - - final _ReaderState reader; - - @override - State<_GroupedChaptersView> createState() => _GroupedChaptersViewState(); -} - -class _GroupedChaptersViewState extends State<_GroupedChaptersView> - with SingleTickerProviderStateMixin { - ComicChapters get chapters => widget.reader.widget.chapters!; - - late final TabController tabController; - - late final ScrollController _scrollController; - - late final String initialGroupName; - - @override - void initState() { - super.initState(); - int index = 0; - int epIndex = widget.reader.chapter - 1; - while (epIndex >= 0) { - epIndex -= chapters.getGroupByIndex(index).length; - index++; - } - tabController = TabController( - length: chapters.groups.length, - vsync: this, - initialIndex: index - 1, - ); - initialGroupName = chapters.groups.elementAt(index - 1); - var epIndexAtGroup = widget.reader.chapter - 1; - for (var i = 0; i < index-1; i++) { - epIndexAtGroup -= chapters.getGroupByIndex(i).length; - } - _scrollController = ScrollController( - initialScrollOffset: (epIndexAtGroup * 48.0).clamp(0, double.infinity), - ); - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Appbar(title: Text("Chapters".tl)), - AppTabBar( - controller: tabController, - tabs: chapters.groups.map((e) => Tab(text: e)).toList(), - ), - Expanded( - child: TabViewBody( - controller: tabController, - children: chapters.groups.map(buildGroup).toList(), - ), - ), - ], - ); - } - - Widget buildGroup(String groupName) { - var group = chapters.getGroup(groupName); - return SmoothCustomScrollView( - controller: initialGroupName == groupName ? _scrollController : null, - slivers: [ - SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - var name = group.values.elementAt(index); - var i = 0; - for (var g in chapters.groups) { - if (g == groupName) { - break; - } - i += chapters.getGroup(g).length; - } - i += index + 1; - return InkWell( - onTap: () { - widget.reader.toChapter(i); - context.pop(); - }, - child: Container( - height: 48, - padding: const EdgeInsets.only(left: 16), - decoration: BoxDecoration( - border: Border( - left: BorderSide( - color: widget.reader.chapter == i - ? context.colorScheme.primary - : Colors.transparent, - width: 2, - ), - ), - ), - child: Align( - alignment: Alignment.centerLeft, - child: Text( - name, - style: widget.reader.chapter == i - ? ts.withColor(context.colorScheme.primary).bold.s16 - : ts.s16, - ), - ), - ), - ); - }, - childCount: group.length, - ), - ), - ], - ); - } -}