diff --git a/lib/modules/manga/reader/reader_view.dart b/lib/modules/manga/reader/reader_view.dart index e097951a..e8c1e421 100644 --- a/lib/modules/manga/reader/reader_view.dart +++ b/lib/modules/manga/reader/reader_view.dart @@ -40,10 +40,9 @@ import 'package:mangayomi/modules/manga/reader/widgets/circular_progress_indicat import 'package:mangayomi/modules/manga/reader/widgets/transition_view_paged.dart'; import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart'; import 'package:mangayomi/modules/manga/reader/providers/manga_reader_provider.dart'; -import 'package:mangayomi/modules/manga/reader/virtual_scrolling/virtual_reader_view.dart'; +import 'package:mangayomi/modules/manga/reader/webtoon_view.dart'; import 'package:mangayomi/modules/widgets/progress_center.dart'; import 'package:photo_view/photo_view.dart'; -import 'package:photo_view/photo_view_gallery.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:share_plus/share_plus.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; @@ -243,7 +242,7 @@ class _MangaChapterPageGalleryState WidgetsBinding.instance.addObserver(this); } - final double _horizontalScaleValue = 1.0; + // final double _horizontalScaleValue = 1.0; bool _isNextChapterPreloading = false; late int pagePreloadAmount = ref.read(pagePreloadAmountStateProvider); @@ -565,63 +564,45 @@ class _MangaChapterPageGalleryState return Stack( children: [ _isVerticalOrHorizontalContinous() - ? PhotoViewGallery.builder( - itemCount: 1, - builder: (_, _) => - PhotoViewGalleryPageOptions.customChild( - controller: _photoViewController, - scaleStateController: - _photoViewScaleStateController, - basePosition: _scalePosition, - onScaleEnd: _onScaleEnd, - child: VirtualReaderView( - pages: _uChapDataPreload, - itemScrollController: _itemScrollController, - scrollOffsetController: - _pageOffsetController, - itemPositionsListener: - _itemPositionsListener, - scrollDirection: isHorizontalContinuaous - ? Axis.horizontal - : Axis.vertical, - minCacheExtent: - pagePreloadAmount * context.height(1), - initialScrollIndex: _readerController - .getPageIndex(), - physics: const ClampingScrollPhysics(), - onLongPressData: (data) => - _onLongPressImageDialog(data, context), - onFailedToLoadImage: (value) { - // Handle failed image loading - if (_failedToLoadImage.value != value && - context.mounted) { - _failedToLoadImage.value = value; - } - }, - backgroundColor: backgroundColor, - isDoublePageMode: - _pageMode == PageMode.doublePage && - !isHorizontalContinuaous, - isHorizontalContinuous: - isHorizontalContinuaous, - readerMode: ref.watch(_currentReaderMode)!, - photoViewController: _photoViewController, - photoViewScaleStateController: - _photoViewScaleStateController, - scalePosition: _scalePosition, - onScaleEnd: (details) => _onScaleEnd( - context, - details, - _photoViewController.value, - ), - onDoubleTapDown: (offset) => - _toggleScale(offset), - onDoubleTap: () {}, - // Chapter transition callbacks - onChapterChanged: (newChapter) {}, - onReachedLastPage: (lastPageIndex) {}, - ), - ), + ? WebtoonView( + pages: _uChapDataPreload, + itemScrollController: _itemScrollController, + scrollOffsetController: _pageOffsetController, + itemPositionsListener: _itemPositionsListener, + scrollDirection: isHorizontalContinuaous + ? Axis.horizontal + : Axis.vertical, + minCacheExtent: + pagePreloadAmount * context.height(1), + initialScrollIndex: _readerController + .getPageIndex(), + physics: const ClampingScrollPhysics(), + onLongPressData: (data) => + _onLongPressImageDialog(data, context), + onFailedToLoadImage: (value) { + // Handle failed image loading + if (_failedToLoadImage.value != value && + context.mounted) { + _failedToLoadImage.value = value; + } + }, + backgroundColor: backgroundColor, + isDoublePageMode: + _pageMode == PageMode.doublePage && + !isHorizontalContinuaous, + isHorizontalContinuous: isHorizontalContinuaous, + readerMode: ref.watch(_currentReaderMode)!, + photoViewController: _photoViewController, + photoViewScaleStateController: + _photoViewScaleStateController, + scalePosition: _scalePosition, + onScaleEnd: (details) => _onScaleEnd( + context, + details, + _photoViewController.value, + ), + onDoubleTapDown: (offset) => _toggleScale(offset), + onDoubleTap: () {}, ) : Material( color: getBackgroundColor(backgroundColor), diff --git a/lib/modules/manga/reader/virtual_scrolling/virtual_manga_list.dart b/lib/modules/manga/reader/virtual_scrolling/virtual_manga_list.dart deleted file mode 100644 index 43bfd4dd..00000000 --- a/lib/modules/manga/reader/virtual_scrolling/virtual_manga_list.dart +++ /dev/null @@ -1,343 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; -import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; -import 'package:mangayomi/models/settings.dart'; -import 'package:mangayomi/models/chapter.dart'; -import 'package:mangayomi/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart'; -import 'package:mangayomi/modules/manga/reader/image_view_vertical.dart'; -import 'package:mangayomi/modules/manga/reader/double_columm_view_vertical.dart'; -import 'package:mangayomi/modules/manga/reader/widgets/transition_view_vertical.dart'; -import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart'; -import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; - -/// Widget for displaying manga pages in a virtual scrolling list -class VirtualMangaList extends ConsumerStatefulWidget { - final VirtualPageManager pageManager; - final ItemScrollController itemScrollController; - final ScrollOffsetController scrollOffsetController; - final ItemPositionsListener itemPositionsListener; - final Axis scrollDirection; - final double minCacheExtent; - final int initialScrollIndex; - final ScrollPhysics physics; - final Function(UChapDataPreload data) onLongPressData; - final Function(bool) onFailedToLoadImage; - final BackgroundColor backgroundColor; - final bool isDoublePageMode; - final bool isHorizontalContinuous; - final ReaderMode readerMode; - final Function(Offset) onDoubleTapDown; - final VoidCallback onDoubleTap; - final Function(Chapter chapter)? onChapterChanged; - final Function(int lastPageIndex)? onReachedLastPage; - final Function(int index)? onPageChanged; - - const VirtualMangaList({ - super.key, - required this.pageManager, - required this.itemScrollController, - required this.scrollOffsetController, - required this.itemPositionsListener, - required this.scrollDirection, - required this.minCacheExtent, - required this.initialScrollIndex, - required this.physics, - required this.onLongPressData, - required this.onFailedToLoadImage, - required this.backgroundColor, - required this.isDoublePageMode, - required this.isHorizontalContinuous, - required this.readerMode, - required this.onDoubleTapDown, - required this.onDoubleTap, - this.onChapterChanged, - this.onReachedLastPage, - this.onPageChanged, - }); - - @override - ConsumerState createState() => _VirtualMangaListState(); -} - -class _VirtualMangaListState extends ConsumerState { - Chapter? _currentChapter; - int? _currentIndex; - - @override - void initState() { - super.initState(); - - // Listen to item positions to update virtual page manager - widget.itemPositionsListener.itemPositions.addListener(_onPositionChanged); - - // Initialize current chapter - if (widget.pageManager.pageCount > 0) { - final firstPage = widget.pageManager.getOriginalPage( - widget.initialScrollIndex, - ); - _currentChapter = firstPage?.chapter; - } - } - - @override - void dispose() { - widget.itemPositionsListener.itemPositions.removeListener( - _onPositionChanged, - ); - super.dispose(); - } - - void _onPositionChanged() { - final positions = widget.itemPositionsListener.itemPositions.value; - if (positions.isNotEmpty) { - // Get the first visible item - final firstVisibleIndex = positions.first.index; - final lastVisibleIndex = positions.last.index; - - // Update virtual page manager - widget.pageManager.updateVisibleIndex(firstVisibleIndex); - - // Calculate actual page lengths considering page mode - int pagesLength = - widget.isDoublePageMode && !widget.isHorizontalContinuous - ? (widget.pageManager.pageCount / 2).ceil() + 1 - : widget.pageManager.pageCount; - - // Check if index is valid - if (firstVisibleIndex >= 0 && firstVisibleIndex < pagesLength) { - final currentPage = widget.pageManager.getOriginalPage( - firstVisibleIndex, - ); - - if (currentPage != null) { - // Check for chapter change - if (_currentChapter?.id != currentPage.chapter?.id && - currentPage.chapter != null) { - _currentChapter = currentPage.chapter; - widget.onChapterChanged?.call(currentPage.chapter!); - } - - // Update current index - if (_currentIndex != firstVisibleIndex) { - _currentIndex = firstVisibleIndex; - widget.onPageChanged?.call(firstVisibleIndex); - } - } - - // Check if reached last page to trigger next chapter preload - if (lastVisibleIndex >= pagesLength - 1) { - widget.onReachedLastPage?.call(lastVisibleIndex); - } - } - } - } - - @override - Widget build(BuildContext context) { - return ListenableBuilder( - listenable: widget.pageManager, - builder: (context, child) { - final itemCount = - widget.isDoublePageMode && !widget.isHorizontalContinuous - ? (widget.pageManager.pageCount / 2).ceil() + 1 - : widget.pageManager.pageCount; - - return ScrollablePositionedList.separated( - scrollDirection: widget.scrollDirection, - minCacheExtent: widget.minCacheExtent, - initialScrollIndex: widget.initialScrollIndex, - itemCount: itemCount, - physics: widget.physics, - itemScrollController: widget.itemScrollController, - scrollOffsetController: widget.scrollOffsetController, - itemPositionsListener: widget.itemPositionsListener, - itemBuilder: (context, index) => _buildItem(context, index), - separatorBuilder: _buildSeparator, - ); - }, - ); - } - - Widget _buildItem(BuildContext context, int index) { - if (widget.isDoublePageMode && !widget.isHorizontalContinuous) { - return _buildDoublePageItem(context, index); - } else { - return _buildSinglePageItem(context, index); - } - } - - Widget _buildSinglePageItem(BuildContext context, int index) { - final originalPage = widget.pageManager.getOriginalPage(index); - if (originalPage == null) { - return const SizedBox.shrink(); - } - - // Check if page should be loaded - final pageInfo = widget.pageManager.getPageInfo(index); - final shouldLoad = widget.pageManager.shouldPageBeLoaded(index); - - if (!shouldLoad && - (pageInfo?.loadState == PageLoadState.notLoaded || pageInfo == null)) { - // Return placeholder for unloaded pages - return _buildPlaceholder(context); - } - - if (originalPage.isTransitionPage) { - return GestureDetector( - behavior: HitTestBehavior.translucent, - onDoubleTapDown: (details) => - widget.onDoubleTapDown(details.globalPosition), - onDoubleTap: widget.onDoubleTap, - child: TransitionViewVertical(data: originalPage), - ); - } - - return GestureDetector( - behavior: HitTestBehavior.translucent, - onDoubleTapDown: (details) => - widget.onDoubleTapDown(details.globalPosition), - onDoubleTap: widget.onDoubleTap, - child: ImageViewVertical( - data: originalPage, - failedToLoadImage: widget.onFailedToLoadImage, - onLongPressData: widget.onLongPressData, - isHorizontal: widget.isHorizontalContinuous, - ), - ); - } - - Widget _buildDoublePageItem(BuildContext context, int index) { - if (index >= widget.pageManager.pageCount) { - return const SizedBox.shrink(); - } - - final int index1 = index * 2 - 1; - final int index2 = index1 + 1; - - final List datas = index == 0 - ? [widget.pageManager.getOriginalPage(0), null] - : [ - index1 < widget.pageManager.pageCount - ? widget.pageManager.getOriginalPage(index1) - : null, - index2 < widget.pageManager.pageCount - ? widget.pageManager.getOriginalPage(index2) - : null, - ]; - - // Check if pages should be loaded - final shouldLoad1 = index1 >= 0 - ? widget.pageManager.shouldPageBeLoaded(index1) - : false; - final shouldLoad2 = index2 < widget.pageManager.pageCount - ? widget.pageManager.shouldPageBeLoaded(index2) - : false; - - if (!shouldLoad1 && !shouldLoad2) { - return _buildPlaceholder(context); - } - - return GestureDetector( - behavior: HitTestBehavior.translucent, - onDoubleTapDown: (details) => - widget.onDoubleTapDown(details.globalPosition), - onDoubleTap: widget.onDoubleTap, - child: DoubleColummVerticalView( - datas: datas, - backgroundColor: widget.backgroundColor, - isFailedToLoadImage: widget.onFailedToLoadImage, - onLongPressData: widget.onLongPressData, - ), - ); - } - - Widget _buildPlaceholder(BuildContext context) { - return Container( - height: context.height(0.8), - color: getBackgroundColor(widget.backgroundColor), - child: const Center(child: CircularProgressIndicator()), - ); - } - - Widget _buildSeparator(BuildContext context, int index) { - if (widget.readerMode == ReaderMode.webtoon) { - return const SizedBox.shrink(); - } - - if (widget.isHorizontalContinuous) { - return VerticalDivider( - color: getBackgroundColor(widget.backgroundColor), - width: 6, - ); - } else { - return Divider( - color: getBackgroundColor(widget.backgroundColor), - height: 6, - ); - } - } -} - -/// Debug widget to show virtual page manager statistics -class VirtualPageManagerDebugInfo extends ConsumerWidget { - final VirtualPageManager pageManager; - - const VirtualPageManagerDebugInfo({super.key, required this.pageManager}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return ListenableBuilder( - listenable: pageManager, - builder: (context, child) { - final stats = pageManager.getMemoryStats(); - - return Positioned( - top: 100, - right: 10, - child: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(8), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Virtual Page Manager', - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 4), - Text( - 'Current: ${stats['currentIndex']}/${stats['totalPages']}', - style: const TextStyle(color: Colors.white, fontSize: 12), - ), - Text( - 'Loaded: ${stats['loadedPages']}', - style: const TextStyle(color: Colors.white, fontSize: 12), - ), - Text( - 'Cached: ${stats['cachedPages']}', - style: const TextStyle(color: Colors.white, fontSize: 12), - ), - Text( - 'Errors: ${stats['errorPages']}', - style: const TextStyle(color: Colors.white, fontSize: 12), - ), - Text( - 'Queue: ${stats['preloadQueueSize']}', - style: const TextStyle(color: Colors.white, fontSize: 12), - ), - ], - ), - ), - ); - }, - ); - } -} diff --git a/lib/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart b/lib/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart deleted file mode 100644 index 38a92f85..00000000 --- a/lib/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart +++ /dev/null @@ -1,211 +0,0 @@ -import 'dart:async'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; - -/// Page loading states for virtual scrolling -enum PageLoadState { notLoaded, loaded, error, cached } - -/// Virtual page information for tracking state -class VirtualPageInfo { - final int index; - final UChapDataPreload originalData; - PageLoadState loadState; - DateTime? lastAccessTime; - Object? error; - - VirtualPageInfo({ - required this.index, - required this.originalData, - this.loadState = PageLoadState.notLoaded, - this.lastAccessTime, - this.error, - }); - - bool get isVisible => - loadState == PageLoadState.loaded || loadState == PageLoadState.cached; - bool get needsLoading => loadState == PageLoadState.notLoaded; - bool get hasError => loadState == PageLoadState.error; - - void markAccessed() { - lastAccessTime = DateTime.now(); - } - - Duration get timeSinceAccess { - if (lastAccessTime == null) return Duration.zero; - return DateTime.now().difference(lastAccessTime!); - } -} - -/// Configuration for virtual page manager -class VirtualPageConfig { - final int preloadDistance; - final int maxCachedPages; - final Duration cacheTimeout; - final bool enableMemoryOptimization; - - const VirtualPageConfig({ - this.preloadDistance = 3, - this.maxCachedPages = 10, - this.cacheTimeout = const Duration(minutes: 5), - this.enableMemoryOptimization = true, - }); -} - -/// Manages virtual page loading and memory optimization -class VirtualPageManager extends ChangeNotifier { - final List _originalPages; - final VirtualPageConfig config; - final Map _pageInfoMap = {}; - final Set _preloadQueue = {}; - - int _currentVisibleIndex = 0; - Timer? _cleanupTimer; - - VirtualPageManager({ - required List pages, - this.config = const VirtualPageConfig(), - }) : _originalPages = List.from(pages) { - _initializePages(); - _startCleanupTimer(); - } - - void _initializePages() { - for (int i = 0; i < _originalPages.length; i++) { - _pageInfoMap[i] = VirtualPageInfo( - index: i, - originalData: _originalPages[i], - ); - } - } - - void _startCleanupTimer() { - _cleanupTimer?.cancel(); - _cleanupTimer = Timer.periodic( - const Duration(seconds: 30), - (_) => _performMemoryCleanup(), - ); - } - - @override - void dispose() { - _cleanupTimer?.cancel(); - super.dispose(); - } - - /// Get page count - int get pageCount => _originalPages.length; - - /// Get page info for a specific index - VirtualPageInfo? getPageInfo(int index) { - if (index < 0 || index >= _originalPages.length) return null; - return _pageInfoMap[index]; - } - - /// Get original page data - UChapDataPreload? getOriginalPage(int index) { - if (index < 0 || index >= _originalPages.length) return null; - return _originalPages[index]; - } - - /// Update visible page index and trigger preloading - void updateVisibleIndex(int index) { - if (index == _currentVisibleIndex) return; - - _currentVisibleIndex = index.clamp(0, _originalPages.length - 1); - _pageInfoMap[_currentVisibleIndex]?.markAccessed(); - - _schedulePreloading(); - notifyListeners(); - } - - /// Check if a page should be visible/loaded - bool shouldPageBeLoaded(int index) { - final distance = (index - _currentVisibleIndex).abs(); - return distance <= config.preloadDistance; - } - - /// Schedule preloading for nearby pages - void _schedulePreloading() { - _preloadQueue.clear(); - - // Add pages within preload distance - for (int i = 0; i < _originalPages.length; i++) { - if (shouldPageBeLoaded(i)) { - final pageInfo = _pageInfoMap[i]!; - if (pageInfo.needsLoading) { - _preloadQueue.add(i); - } - } - } - } - - /// Perform memory cleanup - void _performMemoryCleanup() { - if (!config.enableMemoryOptimization) return; - - final pageEntries = _pageInfoMap.entries.toList(); - - // Sort by last access time and distance from current page - pageEntries.sort((a, b) { - final aDistance = (a.key - _currentVisibleIndex).abs(); - final bDistance = (b.key - _currentVisibleIndex).abs(); - final aTime = - a.value.lastAccessTime ?? DateTime.fromMillisecondsSinceEpoch(0); - final bTime = - b.value.lastAccessTime ?? DateTime.fromMillisecondsSinceEpoch(0); - - // First sort by distance, then by access time - final distanceComparison = aDistance.compareTo(bDistance); - return distanceComparison != 0 - ? distanceComparison - : aTime.compareTo(bTime); - }); - - int cachedCount = pageEntries.where((e) => e.value.isVisible).length; - - // Remove old cached pages if we exceed the limit - for (final entry in pageEntries) { - if (cachedCount <= config.maxCachedPages) break; - - final pageInfo = entry.value; - final distance = (entry.key - _currentVisibleIndex).abs(); - - // Don't unload pages within preload distance - if (distance <= config.preloadDistance) continue; - - // Don't unload recently accessed pages - if (pageInfo.timeSinceAccess < config.cacheTimeout) continue; - - if (pageInfo.isVisible) { - pageInfo.loadState = PageLoadState.notLoaded; - pageInfo.error = null; - cachedCount--; - } - } - - if (cachedCount != pageEntries.where((e) => e.value.isVisible).length) { - notifyListeners(); - } - } - - /// Get memory usage statistics - Map getMemoryStats() { - final loadedCount = _pageInfoMap.values - .where((p) => p.loadState == PageLoadState.loaded) - .length; - final cachedCount = _pageInfoMap.values - .where((p) => p.loadState == PageLoadState.cached) - .length; - final errorCount = _pageInfoMap.values.where((p) => p.hasError).length; - - return { - 'totalPages': _originalPages.length, - 'loadedPages': loadedCount, - 'cachedPages': cachedCount, - 'errorPages': errorCount, - 'currentIndex': _currentVisibleIndex, - 'preloadQueueSize': _preloadQueue.length, - }; - } -} diff --git a/lib/modules/manga/reader/virtual_scrolling/virtual_reader_view.dart b/lib/modules/manga/reader/virtual_scrolling/virtual_reader_view.dart deleted file mode 100644 index ae47cd6e..00000000 --- a/lib/modules/manga/reader/virtual_scrolling/virtual_reader_view.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; -import 'package:photo_view/photo_view.dart'; -import 'package:photo_view/photo_view_gallery.dart'; -import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; -import 'package:mangayomi/models/settings.dart'; -import 'package:mangayomi/models/chapter.dart'; -import 'package:mangayomi/modules/manga/reader/virtual_scrolling/virtual_page_manager.dart'; -import 'package:mangayomi/modules/manga/reader/virtual_scrolling/virtual_manga_list.dart'; - -/// Main widget for virtual reading that replaces ScrollablePositionedList -class VirtualReaderView extends ConsumerStatefulWidget { - final List pages; - final ItemScrollController itemScrollController; - final ScrollOffsetController scrollOffsetController; - final ItemPositionsListener itemPositionsListener; - final Axis scrollDirection; - final double minCacheExtent; - final int initialScrollIndex; - final ScrollPhysics physics; - final Function(UChapDataPreload data) onLongPressData; - final Function(bool) onFailedToLoadImage; - final BackgroundColor backgroundColor; - final bool isDoublePageMode; - final bool isHorizontalContinuous; - final ReaderMode readerMode; - final PhotoViewController photoViewController; - final PhotoViewScaleStateController photoViewScaleStateController; - final Alignment scalePosition; - final Function(ScaleEndDetails) onScaleEnd; - final Function(Offset) onDoubleTapDown; - final VoidCallback onDoubleTap; - final bool showDebugInfo; - // Callbacks pour gérer les transitions entre chapitres - final Function(Chapter chapter)? onChapterChanged; - final Function(int lastPageIndex)? onReachedLastPage; - - const VirtualReaderView({ - super.key, - required this.pages, - required this.itemScrollController, - required this.scrollOffsetController, - required this.itemPositionsListener, - required this.scrollDirection, - required this.minCacheExtent, - required this.initialScrollIndex, - required this.physics, - required this.onLongPressData, - required this.onFailedToLoadImage, - required this.backgroundColor, - required this.isDoublePageMode, - required this.isHorizontalContinuous, - required this.readerMode, - required this.photoViewController, - required this.photoViewScaleStateController, - required this.scalePosition, - required this.onScaleEnd, - required this.onDoubleTapDown, - required this.onDoubleTap, - this.showDebugInfo = false, - this.onChapterChanged, - this.onReachedLastPage, - }); - - @override - ConsumerState createState() => _VirtualReaderViewState(); -} - -class _VirtualReaderViewState extends ConsumerState { - late VirtualPageManager _pageManager; - - @override - void initState() { - super.initState(); - _pageManager = VirtualPageManager(pages: widget.pages); - - // Set initial visible index - _pageManager.updateVisibleIndex(widget.initialScrollIndex); - } - - @override - void didUpdateWidget(VirtualReaderView oldWidget) { - super.didUpdateWidget(oldWidget); - - // Update page manager if pages changed - if (widget.pages != oldWidget.pages) { - _pageManager.dispose(); - _pageManager = VirtualPageManager(pages: widget.pages); - _pageManager.updateVisibleIndex(widget.initialScrollIndex); - } - } - - @override - void dispose() { - _pageManager.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - if (_pageManager.pageCount < widget.pages.length) { - _pageManager = VirtualPageManager(pages: widget.pages); - } - return Stack( - children: [ - PhotoViewGallery.builder( - itemCount: 1, - builder: (_, _) => PhotoViewGalleryPageOptions.customChild( - controller: widget.photoViewController, - scaleStateController: widget.photoViewScaleStateController, - basePosition: widget.scalePosition, - onScaleEnd: (context, details, controllerValue) => - widget.onScaleEnd(details), - child: VirtualMangaList( - pageManager: _pageManager, - itemScrollController: widget.itemScrollController, - scrollOffsetController: widget.scrollOffsetController, - itemPositionsListener: widget.itemPositionsListener, - scrollDirection: widget.scrollDirection, - minCacheExtent: widget.minCacheExtent, - initialScrollIndex: widget.initialScrollIndex, - physics: widget.physics, - onLongPressData: widget.onLongPressData, - onFailedToLoadImage: widget.onFailedToLoadImage, - backgroundColor: widget.backgroundColor, - isDoublePageMode: widget.isDoublePageMode, - isHorizontalContinuous: widget.isHorizontalContinuous, - readerMode: widget.readerMode, - onDoubleTapDown: widget.onDoubleTapDown, - onDoubleTap: widget.onDoubleTap, - // Passer les callbacks pour les transitions entre chapitres - onChapterChanged: widget.onChapterChanged, - onReachedLastPage: widget.onReachedLastPage, - onPageChanged: (index) { - // Ici on peut ajouter une logique supplémentaire si nécessaire - // Par exemple, précaching d'images - _pageManager.updateVisibleIndex(index); - }, - ), - ), - ), - - // Debug info overlay - if (widget.showDebugInfo) - VirtualPageManagerDebugInfo(pageManager: _pageManager), - ], - ); - } -} diff --git a/lib/modules/manga/reader/webtoon_view.dart b/lib/modules/manga/reader/webtoon_view.dart new file mode 100644 index 00000000..2d562f15 --- /dev/null +++ b/lib/modules/manga/reader/webtoon_view.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'package:mangayomi/modules/manga/reader/double_columm_view_vertical.dart'; +import 'package:mangayomi/modules/manga/reader/image_view_vertical.dart'; +import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart'; +import 'package:mangayomi/modules/manga/reader/widgets/transition_view_vertical.dart'; +import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:photo_view/photo_view_gallery.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import 'package:mangayomi/models/settings.dart'; + +/// Main widget for virtual reading that replaces ScrollablePositionedList +class WebtoonView extends StatelessWidget { + final List pages; + final ItemScrollController itemScrollController; + final ScrollOffsetController scrollOffsetController; + final ItemPositionsListener itemPositionsListener; + final Axis scrollDirection; + final double minCacheExtent; + final int initialScrollIndex; + final ScrollPhysics physics; + final Function(UChapDataPreload data) onLongPressData; + final Function(bool) onFailedToLoadImage; + final BackgroundColor backgroundColor; + final bool isDoublePageMode; + final bool isHorizontalContinuous; + final ReaderMode readerMode; + final PhotoViewController photoViewController; + final PhotoViewScaleStateController photoViewScaleStateController; + final Alignment scalePosition; + final Function(ScaleEndDetails) onScaleEnd; + final Function(Offset) onDoubleTapDown; + final VoidCallback onDoubleTap; + + const WebtoonView({ + super.key, + required this.pages, + required this.itemScrollController, + required this.scrollOffsetController, + required this.itemPositionsListener, + required this.scrollDirection, + required this.minCacheExtent, + required this.initialScrollIndex, + required this.physics, + required this.onLongPressData, + required this.onFailedToLoadImage, + required this.backgroundColor, + required this.isDoublePageMode, + required this.isHorizontalContinuous, + required this.readerMode, + required this.photoViewController, + required this.photoViewScaleStateController, + required this.scalePosition, + required this.onScaleEnd, + required this.onDoubleTapDown, + required this.onDoubleTap, + }); + + @override + Widget build(BuildContext context) { + return PhotoViewGallery.builder( + itemCount: 1, + builder: (_, _) => PhotoViewGalleryPageOptions.customChild( + controller: photoViewController, + scaleStateController: photoViewScaleStateController, + basePosition: scalePosition, + onScaleEnd: (context, details, controllerValue) => onScaleEnd(details), + child: ScrollablePositionedList.separated( + scrollDirection: scrollDirection, + minCacheExtent: minCacheExtent, + initialScrollIndex: initialScrollIndex, + itemCount: pages.length, + physics: physics, + itemScrollController: itemScrollController, + scrollOffsetController: scrollOffsetController, + itemPositionsListener: itemPositionsListener, + itemBuilder: (context, index) => _buildItem(context, index), + separatorBuilder: _buildSeparator, + ), + ), + ); + } + + Widget _buildItem(BuildContext context, int index) { + if (isDoublePageMode && !isHorizontalContinuous) { + return _buildDoublePageItem(context, index); + } else { + return _buildSinglePageItem(context, index); + } + } + + Widget _buildSinglePageItem(BuildContext context, int index) { + final currentPage = pages[index]; + + if (currentPage.isTransitionPage) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onDoubleTapDown: (details) => onDoubleTapDown(details.globalPosition), + onDoubleTap: onDoubleTap, + child: TransitionViewVertical(data: currentPage), + ); + } + + return GestureDetector( + behavior: HitTestBehavior.translucent, + onDoubleTapDown: (details) => onDoubleTapDown(details.globalPosition), + onDoubleTap: onDoubleTap, + child: ImageViewVertical( + data: currentPage, + failedToLoadImage: onFailedToLoadImage, + onLongPressData: onLongPressData, + isHorizontal: isHorizontalContinuous, + ), + ); + } + + Widget _buildDoublePageItem(BuildContext context, int index) { + final pageLength = pages.length; + if (index >= pageLength) { + return const SizedBox.shrink(); + } + + final int index1 = index * 2 - 1; + final int index2 = index1 + 1; + + final List datas = index == 0 + ? [pages[0], null] + : [ + index1 < pageLength ? pages[index1] : null, + index2 < pageLength ? pages[index2] : null, + ]; + + return GestureDetector( + behavior: HitTestBehavior.translucent, + onDoubleTapDown: (details) => onDoubleTapDown(details.globalPosition), + onDoubleTap: onDoubleTap, + child: DoubleColummVerticalView( + datas: datas, + backgroundColor: backgroundColor, + isFailedToLoadImage: onFailedToLoadImage, + onLongPressData: onLongPressData, + ), + ); + } + + Widget _buildSeparator(BuildContext context, int index) { + if (readerMode == ReaderMode.webtoon) { + return const SizedBox.shrink(); + } + + if (isHorizontalContinuous) { + return VerticalDivider( + color: getBackgroundColor(backgroundColor), + width: 6, + ); + } else { + return Divider(color: getBackgroundColor(backgroundColor), height: 6); + } + } +}