Repository navigation
feat: expose reader activity and stable content coverage #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import 'reader_locator.dart'; | ||
|
|
||
| /// Navigation is descriptive; hosts must not count skipped locator distances. | ||
| enum ReaderNavigationCause { restore, turn, jump, reflow, viewport } | ||
|
|
||
| /// Stable visible content. EPUB extents use normalized chapter UTF-16 offsets. | ||
| final class ReaderContentCoverage { | ||
| const ReaderContentCoverage({ | ||
| required this.key, | ||
| required this.start, | ||
| required this.end, | ||
| this.chapterCount, | ||
| this.pdfPageIndex, | ||
| }); | ||
| final String key; | ||
| final double start; | ||
| final double end; | ||
| final int? chapterCount; | ||
| final int? pdfPageIndex; | ||
| } | ||
|
|
||
| /// Generic observation only. The host owns timing, persistence, and goal rules. | ||
| final class ReaderActivityEvent { | ||
| const ReaderActivityEvent({ | ||
| required this.ready, | ||
| required this.visible, | ||
| required this.cause, | ||
| this.locator, | ||
| this.coverage = const [], | ||
| this.atEnd = false, | ||
| }); | ||
| final bool ready; | ||
| final bool visible; | ||
| final ReaderNavigationCause cause; | ||
| final ReaderLocator? locator; | ||
| final List<ReaderContentCoverage> coverage; | ||
| final bool atEnd; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import 'reader_capabilities.dart'; | ||
| import 'reader_activity.dart'; | ||
| import 'reader_document.dart'; | ||
| import 'reader_exception.dart'; | ||
| import 'reader_locator.dart'; | ||
|
|
@@ -24,6 +25,9 @@ sealed class ReaderSnapshot { | |
| ReaderException? get error; | ||
|
|
||
| String? get locationLabel => null; | ||
| List<ReaderContentCoverage> get coverage => const []; | ||
| bool get contentReady => status == ReaderStatus.ready; | ||
| bool get atEnd => false; | ||
| } | ||
|
|
||
| final class ReaderIdleSnapshot extends ReaderSnapshot { | ||
|
|
@@ -88,11 +92,20 @@ final class ReaderReadySnapshot extends ReaderSnapshot { | |
| this.locator, | ||
| List<ReaderTocEntry> toc = const [], | ||
| this.locationLabel, | ||
| this.coverage = const [], | ||
| this.contentReady = true, | ||
| this.atEnd = false, | ||
| }) : toc = List.unmodifiable(toc); | ||
|
Comment on lines
93
to
98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a host modifies Useful? React with 👍 / 👎. |
||
|
|
||
| @override | ||
| ReaderStatus get status => ReaderStatus.ready; | ||
|
|
||
| @override | ||
| final List<ReaderContentCoverage> coverage; | ||
| @override | ||
| final bool contentReady; | ||
| @override | ||
| final bool atEnd; | ||
| @override | ||
| final String? locationLabel; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,7 @@ final class PdfViewportConfiguration { | |
| required this.layoutMode, | ||
| required this.onPageChanged, | ||
| this.onPositionChanged, | ||
| this.onVisiblePagesChanged, | ||
| }); | ||
|
|
||
| final int pageIndex; | ||
|
|
@@ -47,6 +48,7 @@ final class PdfViewportConfiguration { | |
| final ReaderLayoutMode layoutMode; | ||
| final ValueChanged<int> onPageChanged; | ||
| final void Function(int pageIndex, double pageOffset)? onPositionChanged; | ||
| final ValueChanged<List<int>>? onVisiblePagesChanged; | ||
| } | ||
|
|
||
| abstract interface class PdfFacade { | ||
|
|
@@ -373,6 +375,18 @@ final class _PdfrxFacade implements PdfFacade, DisposablePdfFacade { | |
| 0.0, | ||
| 1.0, | ||
| ); | ||
| final visible = _controller.visibleRect; | ||
| final exposed = <int>[]; | ||
| for (var i = 0; i < _controller.layout.pageLayouts.length; i++) { | ||
| final rect = _controller.layout.pageLayouts[i]; | ||
| if (!rect.overlaps(visible)) continue; | ||
| final intersection = rect.intersect(visible); | ||
| if (intersection.width * intersection.height >= | ||
| rect.width * rect.height * .25) { | ||
| exposed.add(i); | ||
|
Comment on lines
+383
to
+386
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a PDF page is zoomed enough that the viewport covers less than 25% of the page, this filter returns no exposed pages even though the user is actively reading a visible portion. Useful? React with 👍 / 👎. |
||
| } | ||
| } | ||
| config?.onVisiblePagesChanged?.call(exposed); | ||
| _pageIndex = index; | ||
| _pageOffset = offset; | ||
| if (config?.onPositionChanged != null) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The newly exported
ReaderActivityEventand its nestedReaderContentCoverageprovide notoJson/fromJsonrepresentation, so hosts cannot serialize the new host-facing observations through the package's domain API and must invent incompatible enum and field encodings. Add a stable versioned serialization format for both types.AGENTS.md reference: AGENTS.md:L7-L7
Useful? React with 👍 / 👎.