flutter_epub_viewer 2.0.0 copy "flutter_epub_viewer: ^2.0.0" to clipboard
flutter_epub_viewer: ^2.0.0 copied to clipboard

A Flutter package for viewing Epub documents developed by combining the power of Epubjs and flutter_inappwebview

example/lib/main.dart

import 'package:flutter_epub_viewer/flutter_epub_viewer.dart';
import 'search_page.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Epub Viewer Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final epubController = EpubController();

  var textSelectionCfi = '';

  bool isLoading = true;

  double progress = 0.0;

  List<EpubChapter> chapters = [];

  /// Web only: whether the beside-the-viewer chapters panel is visible.
  bool _showChapters = false;

  @override
  Widget build(BuildContext context) {
    // On mobile the epub is a native platform view we can freely overlay, so we
    // use the standard Drawer for chapters and epub.js swipe for page turning.
    //
    // On the web the epub <iframe> composites above the Flutter scene, so
    // overlays (Drawer / routes) placed over it don't receive taps and there is
    // no swipe with a mouse. There we use a side panel beside the viewer plus
    // AppBar page-turn buttons — all outside the iframe's rectangle.
    return kIsWeb ? _buildWebLayout(context) : _buildMobileLayout(context);
  }

  // ---------------------------------------------------------------------------
  // Mobile: Drawer + swipe (original behavior)
  // ---------------------------------------------------------------------------
  Widget _buildMobileLayout(BuildContext context) {
    return Scaffold(
      drawer: Drawer(
        child: SafeArea(
          child: _chapterList(onTap: () => Navigator.pop(context)),
        ),
      ),
      appBar: _appBar(context, leading: null, showPageButtons: false),
      body: SafeArea(
        child: Column(
          children: [
            _progressBar(),
            Expanded(child: _viewerStack()),
          ],
        ),
      ),
    );
  }

  // ---------------------------------------------------------------------------
  // Web: side panel + AppBar page-turn buttons
  // ---------------------------------------------------------------------------
  Widget _buildWebLayout(BuildContext context) {
    return Scaffold(
      appBar: _appBar(
        context,
        leading: IconButton(
          tooltip: 'Chapters',
          icon: const Icon(Icons.menu),
          onPressed: () => setState(() => _showChapters = !_showChapters),
        ),
        showPageButtons: true,
      ),
      body: SafeArea(
        child: Column(
          children: [
            _progressBar(),
            Expanded(
              child: Row(
                children: [
                  if (_showChapters)
                    Container(
                      width: 280,
                      decoration: BoxDecoration(
                        border: Border(
                          right:
                              BorderSide(color: Theme.of(context).dividerColor),
                        ),
                      ),
                      child: _chapterList(),
                    ),
                  Expanded(child: _viewerStack()),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  // ---------------------------------------------------------------------------
  // Shared pieces
  // ---------------------------------------------------------------------------
  AppBar _appBar(
    BuildContext context, {
    required Widget? leading,
    required bool showPageButtons,
  }) {
    return AppBar(
      backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      leading: leading,
      title: Text(widget.title),
      actions: [
        if (showPageButtons) ...[
          IconButton(
            tooltip: 'Previous page',
            icon: const Icon(Icons.chevron_left),
            onPressed: () => epubController.prev(),
          ),
          IconButton(
            tooltip: 'Next page',
            icon: const Icon(Icons.chevron_right),
            onPressed: () => epubController.next(),
          ),
        ],
        IconButton(
          icon: const Icon(Icons.search),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) =>
                    SearchPage(epubController: epubController),
              ),
            );
          },
        ),
      ],
    );
  }

  Widget _progressBar() => LinearProgressIndicator(
        value: progress,
        backgroundColor: Colors.transparent,
      );

  Widget _chapterList({VoidCallback? onTap}) {
    if (chapters.isEmpty) {
      return const Center(child: Text('Chapters not loaded yet'));
    }
    return ListView.builder(
      itemCount: chapters.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(chapters[index].title),
          onTap: () {
            epubController.display(cfi: chapters[index].href);
            onTap?.call();
          },
        );
      },
    );
  }

  Widget _viewerStack() {
    return Stack(
      children: [
        EpubViewer(
          initialCfi: 'epubcfi(/6/20!/4/2[introduction]/2[c1_h]/1:0)',
          // Bundled asset so it works on every platform (including web, where
          // cross-origin URLs are blocked by CORS). On web you can also use
          // EpubSource.fromData(bytes).
          epubSource: EpubSource.fromAsset('assets/sample.epub'),
          epubController: epubController,
          displaySettings: EpubDisplaySettings(
            flow: EpubFlow.paginated,
            useSnapAnimationAndroid: false,
            snap: true,
            theme: EpubTheme.light(),
            // Safe here because the bundled sample is trusted. Keep this `false`
            // for user-provided / untrusted EPUBs (see EpubDisplaySettings docs).
            allowScriptedContent: true,
          ),
          selectionContextMenu: EpubContextMenu(
            hideDefaultSystemItems: true,
            items: [
              EpubContextMenuItem(
                title: "Highlight",
                id: 1,
                action: () {
                  epubController.addHighlight(cfi: textSelectionCfi);
                },
              ),
            ],
          ),
          onChaptersLoaded: (loaded) {
            setState(() {
              chapters = loaded;
              isLoading = false;
            });
          },
          onEpubLoaded: () async {
            debugPrint('Epub loaded');
          },
          onRelocated: (value) {
            debugPrint("Reloacted to $value");
            setState(() {
              progress = value.progress;
            });
          },
          onAnnotationClicked: (cfi, data) {
            debugPrint("Annotation clicked $cfi");
          },
          onTextSelected: (epubTextSelection) {
            textSelectionCfi = epubTextSelection.selectionCfi;
            debugPrint(textSelectionCfi);
          },
          onLocationLoaded: () {
            debugPrint('on location loaded');
          },
          onSelection: (selectedText, cfiRange, selectionRect, viewRect) {
            debugPrint("On selection changes");
          },
          onDeselection: () {
            debugPrint("on delection");
          },
          onSelectionChanging: () {
            debugPrint("on slection chnages");
          },
          onTouchDown: (x, y) {
            debugPrint("Touch down at $x , $y");
          },
          onTouchUp: (x, y) {
            debugPrint("Touch up at $x , $y");
          },
          selectAnnotationRange: true,
        ),
        Visibility(
          visible: isLoading,
          child: const Center(child: CircularProgressIndicator()),
        ),
      ],
    );
  }
}
32
likes
150
points
1.76k
downloads

Documentation

API reference

Publisher

verified publisherfayis.dev

Weekly Downloads

A Flutter package for viewing Epub documents developed by combining the power of Epubjs and flutter_inappwebview

Repository (GitHub)
View/report issues

License

BSD-3-Clause (license)

Dependencies

flutter, flutter_inappwebview, http, json_annotation, web

More

Packages that depend on flutter_epub_viewer