import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:async'; import 'package:shuqi/public.dart'; import 'article_provider.dart'; import 'reader_utils.dart'; import 'reader_config.dart'; import 'reader_page_agent.dart'; import 'reader_menu.dart'; import 'reader_view.dart'; enum PageJumpType { stay, firstPage, lastPage } class ReaderScene extends StatefulWidget { final int articleId; ReaderScene({required this.articleId}); @override ReaderSceneState createState() => ReaderSceneState(); } class ReaderSceneState extends State with RouteAware { int pageIndex = 0; bool isMenuVisiable = false; PageController pageController = PageController(keepPage: false); bool isLoading = false; double topSafeHeight = 0; Article? preArticle; Article? currentArticle; Article? nextArticle; List chapters = []; @override void initState() { super.initState(); pageController.addListener(onScroll); setup(); } @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute); } @override void didPop() { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); } @override void dispose() { pageController.dispose(); routeObserver.unsubscribe(this); super.dispose(); } void setup() async { await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); // 不延迟的话,安卓获取到的topSafeHeight是错的。 await Future.delayed(const Duration(milliseconds: 100), () {}); SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark); topSafeHeight = Screen.topSafeHeight; List chaptersResponse = await Request.get(action: 'catalog'); chaptersResponse.forEach((data) { chapters.add(Chapter.fromJson(data)); }); await resetContent(this.widget.articleId, PageJumpType.stay); } resetContent(int articleId, PageJumpType jumpType) async { currentArticle = await fetchArticle(articleId); if (currentArticle!.preArticleId > 0) { preArticle = await fetchArticle(currentArticle!.preArticleId); } else { preArticle = null; } if (currentArticle!.nextArticleId > 0) { nextArticle = await fetchArticle(currentArticle!.nextArticleId); } else { nextArticle = null; } if (jumpType == PageJumpType.firstPage) { pageIndex = 0; } else if (jumpType == PageJumpType.lastPage) { pageIndex = currentArticle!.pageCount - 1; } if (jumpType != PageJumpType.stay) { pageController.jumpToPage((preArticle != null ? preArticle!.pageCount : 0) + pageIndex); } setState(() {}); } onScroll() { var page = pageController.offset / Screen.width; var nextArtilePage = currentArticle!.pageCount + (preArticle != null ? preArticle!.pageCount : 0); if (page >= nextArtilePage) { print('到达下个章节了'); preArticle = currentArticle; currentArticle = nextArticle; nextArticle = null; pageIndex = 0; pageController.jumpToPage(preArticle!.pageCount); fetchNextArticle(currentArticle!.nextArticleId); setState(() {}); } if (preArticle != null && page <= preArticle!.pageCount - 1) { print('到达上个章节了'); nextArticle = currentArticle; currentArticle = preArticle; preArticle = null; pageIndex = currentArticle!.pageCount - 1; pageController.jumpToPage(currentArticle!.pageCount - 1); fetchPreviousArticle(currentArticle!.preArticleId); setState(() {}); } } fetchPreviousArticle(int articleId) async { if (preArticle != null || isLoading || articleId == 0) { return; } isLoading = true; preArticle = await fetchArticle(articleId); pageController.jumpToPage(preArticle!.pageCount + pageIndex); isLoading = false; setState(() {}); } fetchNextArticle(int articleId) async { if (nextArticle != null || isLoading || articleId == 0) { return; } isLoading = true; nextArticle = await fetchArticle(articleId); isLoading = false; setState(() {}); } Future
fetchArticle(int articleId) async { var article = await ArticleProvider.fetchArticle(articleId); var contentHeight = Screen.height - topSafeHeight - ReaderUtils.topOffset - Screen.bottomSafeHeight - ReaderUtils.bottomOffset - 20; var contentWidth = Screen.width - 15 - 10; article.pageOffsets = ReaderPageAgent.getPageOffsets(article.content, contentHeight, contentWidth, ReaderConfig.instance.fontSize); return article; } onTap(Offset position) async { double xRate = position.dx / Screen.width; if (xRate > 0.33 && xRate < 0.66) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); setState(() { isMenuVisiable = true; }); } else if (xRate >= 0.66) { nextPage(); } else { previousPage(); } } onPageChanged(int index) { var page = index - (preArticle != null ? preArticle!.pageCount : 0); if (page < currentArticle!.pageCount && page >= 0) { setState(() { pageIndex = page; }); } } previousPage() { if (pageIndex == 0 && currentArticle!.preArticleId == 0) { Toast.show('已经是第一页了'); return; } pageController.previousPage(duration: Duration(milliseconds: 250), curve: Curves.easeOut); } nextPage() { if (pageIndex >= currentArticle!.pageCount - 1 && currentArticle!.nextArticleId == 0) { Toast.show('已经是最后一页了'); return; } pageController.nextPage(duration: Duration(milliseconds: 250), curve: Curves.easeOut); } Widget buildPage(BuildContext context, int index) { var page = index - (preArticle != null ? preArticle!.pageCount : 0); var article; if (page >= this.currentArticle!.pageCount) { // 到达下一章了 article = nextArticle; page = 0; } else if (page < 0) { // 到达上一章了 article = preArticle; page = preArticle!.pageCount - 1; } else { article = this.currentArticle; } return GestureDetector( onTapUp: (TapUpDetails details) { onTap(details.globalPosition); }, child: ReaderView(article: article, page: page, topSafeHeight: topSafeHeight), ); } buildPageView() { if (currentArticle == null) { return Container(); } int itemCount = (preArticle != null ? preArticle!.pageCount : 0) + currentArticle!.pageCount + (nextArticle != null ? nextArticle!.pageCount : 0); return PageView.builder( physics: BouncingScrollPhysics(), controller: pageController, itemCount: itemCount, itemBuilder: buildPage, onPageChanged: onPageChanged, ); } buildMenu() { if (!isMenuVisiable) { return Container(); } return ReaderMenu( chapters: chapters, articleIndex: currentArticle!.index, onTap: hideMenu, onPreviousArticle: () { resetContent(currentArticle!.preArticleId, PageJumpType.firstPage); }, onNextArticle: () { resetContent(currentArticle!.nextArticleId, PageJumpType.firstPage); }, onToggleChapter: (Chapter chapter) { resetContent(chapter.id, PageJumpType.firstPage); }, ); } hideMenu() { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); setState(() { this.isMenuVisiable = false; }); } @override Widget build(BuildContext context) { if (currentArticle == null) { return Scaffold(); } return Scaffold( body: AnnotatedRegion( value: SystemUiOverlayStyle.dark, child: Stack( children: [ Positioned( left: 0, top: 0, right: 0, bottom: 0, child: Image.asset('assets/img/read_bg.png', fit: BoxFit.cover)), buildPageView(), buildMenu(), ], ), ), ); } }