feat: add quotations from marked text, with citations and sharing

This commit is contained in:
randogoth 2026-10-12 10:37:32 +03:00
parent 0e2027c9e2
commit e6e84fdc01
25 changed files with 1558 additions and 4 deletions

View file

@ -36,6 +36,7 @@ class AppDependencies {
Strings? strings,
KeyStore? keys,
Future<PickedPem?> Function(String title)? pickPemFile,
Future<void> Function(String text)? share,
}) : db = database ?? MewsDatabase(),
fetch = fetch ?? Actor.create(FetchHandler.new),
strings = strings ?? Strings() {
@ -61,6 +62,7 @@ class AppDependencies {
keys: this.keys,
strings: this.strings,
pickPemFile: pickPemFile,
share: share,
);
browser = BrowserController(
db: db,

View file

@ -36,6 +36,7 @@ class MewsPageView extends StatefulWidget {
this.onFormChange,
this.fetchImageBytes,
this.scrollController,
this.onSaveQuotation,
this.ownPage = false,
super.key,
});
@ -57,6 +58,10 @@ class MewsPageView extends StatefulWidget {
final Future<Uint8List?> Function(String url)? fetchImageBytes;
final ScrollController? scrollController;
/// Called with text the reader marked and chose to keep as a quotation.
/// Null on a view that cannot keep one, such as a swipe peek.
final void Function(String text)? onSaveQuotation;
/// Whether this is one of the client's own pages. Those are built from the
/// same blocks, but they are interface rather than an article, so they are
/// laid out denser and wider, rows are divided instead of bulleted, and
@ -171,8 +176,14 @@ class _MewsPageViewState extends State<MewsPageView> {
textDirection: widget.page.direction == TextDirection.rtl
? ui.TextDirection.rtl
: ui.TextDirection.ltr,
// Page text is the reader's to copy; forms manage their own selection.
// Page text is the reader's to copy and to keep; forms manage
// their own selection. The toolbar offers to keep the marked
// text as a quotation, so the selection's text is carried here.
child: SelectionArea(
onSelectionChanged: (content) =>
_markedText = content?.plainText,
contextMenuBuilder: (context, selectableRegion) =>
_selectionToolbar(context, selectableRegion),
child: ColoredBox(
color: palette.background,
child: ListView(
@ -201,6 +212,36 @@ class _MewsPageViewState extends State<MewsPageView> {
);
}
/// The text of the selection now on the page, if any, so the toolbar can
/// offer to keep it.
String? _markedText;
/// The selection toolbar: the platform's own buttons, plus the choice to
/// keep the marked text as a quotation when a place to keep it is given.
Widget _selectionToolbar(
BuildContext context, SelectableRegionState selectableRegion) {
final items = selectableRegion.contextMenuButtonItems;
final marked = _markedText?.trim();
if (widget.onSaveQuotation != null && marked != null && marked.isNotEmpty) {
items.insert(
0,
ContextMenuButtonItem(
type: ContextMenuButtonType.custom,
label: StringsScope.of(context).t('quotations-save'),
onPressed: () {
widget.onSaveQuotation!(marked);
selectableRegion.hideToolbar();
selectableRegion.clearSelection();
},
),
);
}
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: selectableRegion.contextMenuAnchors,
buttonItems: items,
);
}
Widget _notice(String text, double base, MewsPalette palette) => Padding(
padding: EdgeInsets.only(bottom: base),
child: Text(

View file

@ -336,6 +336,14 @@ class _ReaderScreenState extends State<ReaderScreen> {
},
onFormChange: (form, values) =>
deps.browser.narrowWhileTyping(form.action, values),
// The reader's marked text is kept with the page it came from, so
// its citation can name the author when the page did.
onSaveQuotation: (text) => deps.db.addQuotation(
quote: text,
pageUrl: page.url,
pageTitle: page.title,
author: page.author,
),
fetchImageBytes: deps.browser.fetchImageBytes,
scrollController: _scroll,
);