feat: add quotations from marked text, with citations and sharing
This commit is contained in:
parent
0e2027c9e2
commit
e6e84fdc01
25 changed files with 1558 additions and 4 deletions
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:share_plus/share_plus.dart' show ShareParams, SharePlus;
|
||||
|
||||
import '../database/database.dart';
|
||||
import '../l10n/strings.dart';
|
||||
|
|
@ -11,6 +12,7 @@ import 'controllers/reader_settings_controller.dart'
|
|||
import 'controllers/subscriptions_controller.dart';
|
||||
import 'key_store.dart';
|
||||
import 'pages/mews_urls.dart';
|
||||
import 'pages/quotations_page.dart' show citationFor;
|
||||
import 'pem.dart';
|
||||
|
||||
/// Applies the change an action address encodes, e.g. mews:do/set-theme.
|
||||
|
|
@ -34,6 +36,7 @@ class InternalActions {
|
|||
required KeyStore keys,
|
||||
required Strings strings,
|
||||
Future<PickedPem?> Function(String title)? pickPemFile,
|
||||
Future<void> Function(String text)? share,
|
||||
}) : _db = db,
|
||||
_applyLanguage = applyLanguage,
|
||||
_settings = settings,
|
||||
|
|
@ -41,7 +44,8 @@ class InternalActions {
|
|||
_subscriptions = subscriptions,
|
||||
_keys = keys,
|
||||
_strings = strings,
|
||||
_pickPemFile = pickPemFile ?? _pickPemFromFileSystem;
|
||||
_pickPemFile = pickPemFile ?? _pickPemFromFileSystem,
|
||||
_share = share ?? _shareThroughSystem;
|
||||
|
||||
final MewsDatabase _db;
|
||||
final Future<void> Function() _applyLanguage;
|
||||
|
|
@ -51,6 +55,11 @@ class InternalActions {
|
|||
final KeyStore _keys;
|
||||
final Strings _strings;
|
||||
final Future<PickedPem?> Function(String title) _pickPemFile;
|
||||
final Future<void> Function(String text) _share;
|
||||
|
||||
/// Hands [text] to the system share sheet.
|
||||
static Future<void> _shareThroughSystem(String text) =>
|
||||
SharePlus.instance.share(ShareParams(text: text));
|
||||
|
||||
/// Opens the platform file picker for one PEM file, titled with what the
|
||||
/// step asks for. A single file may hold both the certificate and its key,
|
||||
|
|
@ -92,6 +101,8 @@ class InternalActions {
|
|||
'import-certificate' => _importCertificate(),
|
||||
'rename-certificate' => _renameCertificate(args),
|
||||
'delete-certificate' => _deleteCertificate(args),
|
||||
'share-quotation' => _shareQuotation(args['id']),
|
||||
'remove-quotation' => _removeQuotation(args['id']),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
|
@ -216,4 +227,27 @@ class InternalActions {
|
|||
await _subscriptions.refreshOne(siteUrl);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Shares the quotation with its citation. Nothing on the page changes, so
|
||||
/// the reader stays exactly where they were.
|
||||
Future<bool> _shareQuotation(String? id) async {
|
||||
final quotation = await _quotation(id);
|
||||
if (quotation == null) return false;
|
||||
await _share('${quotation.quote}\n\n'
|
||||
'${citationFor(quotation, _strings)}');
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> _removeQuotation(String? id) async {
|
||||
final quotation = await _quotation(id);
|
||||
if (quotation == null) return false;
|
||||
await _db.removeQuotation(quotation.id);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<Quotation?> _quotation(String? id) async {
|
||||
final parsed = int.tryParse(id ?? '');
|
||||
if (parsed == null) return null;
|
||||
return _db.quotationById(parsed);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import 'pages/history_page.dart';
|
|||
import 'pages/hub_page.dart';
|
||||
import 'pages/mews_urls.dart';
|
||||
import 'pages/posts_page.dart';
|
||||
import 'pages/quotations_page.dart';
|
||||
import 'pages/settings_page.dart';
|
||||
import 'pages/tags_page.dart';
|
||||
import 'pages/subscriptions_page.dart';
|
||||
|
|
@ -47,6 +48,8 @@ class InternalPages {
|
|||
db: _db,
|
||||
strings: _strings,
|
||||
refreshing: _subscriptions.refreshing.value),
|
||||
'quotations' =>
|
||||
buildQuotationsPage(uri, db: _db, strings: _strings),
|
||||
'bookmarks' => buildBookmarksPage(uri, db: _db, strings: _strings),
|
||||
'subscriptions' => buildSubscriptionsPage(uri, db: _db, strings: _strings),
|
||||
'history' => buildHistoryPage(uri, db: _db, strings: _strings),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ Future<MewsPage> buildHubPage(
|
|||
final sites = await db.countSubscriptions();
|
||||
final visits = await db.countHistory();
|
||||
final unread = await db.unreadCount();
|
||||
final quotations = await db.countQuotations();
|
||||
final tags = await db.tagCounts();
|
||||
|
||||
return internalPage(
|
||||
|
|
@ -35,6 +36,14 @@ Future<MewsPage> buildHubPage(
|
|||
one: 'count-unread-one',
|
||||
many: 'count-unread-many'),
|
||||
),
|
||||
itemRow(
|
||||
href: MewsUrls.quotations,
|
||||
label: strings.t('nav-quotations'),
|
||||
note: countText(strings, quotations,
|
||||
none: 'count-quotations-none',
|
||||
one: 'count-quotations-one',
|
||||
many: 'count-quotations-many'),
|
||||
),
|
||||
itemRow(
|
||||
href: MewsUrls.bookmarks,
|
||||
label: strings.t('nav-bookmarks'),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ abstract final class MewsUrls {
|
|||
|
||||
static const hub = 'mews:hub';
|
||||
static const posts = 'mews:posts';
|
||||
static const quotations = 'mews:quotations';
|
||||
static const bookmarks = 'mews:bookmarks';
|
||||
static const subscriptions = 'mews:subscriptions';
|
||||
static const history = 'mews:history';
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ typedef NavItem = ({String url, String labelId});
|
|||
const navItems = <NavItem>[
|
||||
(url: MewsUrls.hub, labelId: 'nav-hub'),
|
||||
(url: MewsUrls.posts, labelId: 'nav-posts'),
|
||||
(url: MewsUrls.quotations, labelId: 'nav-quotations'),
|
||||
(url: MewsUrls.bookmarks, labelId: 'nav-bookmarks'),
|
||||
(url: MewsUrls.subscriptions, labelId: 'nav-subscriptions'),
|
||||
(url: MewsUrls.history, labelId: 'nav-history'),
|
||||
|
|
|
|||
139
app/lib/application/pages/quotations_page.dart
Normal file
139
app/lib/application/pages/quotations_page.dart
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import '../../database/database.dart';
|
||||
import '../../l10n/strings.dart';
|
||||
import '../../mews/document.dart';
|
||||
import 'mews_urls.dart';
|
||||
import 'page_blocks.dart';
|
||||
|
||||
/// Every text the reader kept from a page, with the page it came from. The
|
||||
/// page mirrors the posts page: a searchable list ordered by the day the
|
||||
/// text was kept or by the page it came from, one row per quotation, each
|
||||
/// row carrying the citation's author when the page named one.
|
||||
Future<MewsPage> buildQuotationsPage(
|
||||
Uri url, {
|
||||
required MewsDatabase db,
|
||||
required Strings strings,
|
||||
}) async {
|
||||
final query = url.queryParameters['q'] ?? '';
|
||||
final sort = QuotationSort.parse(url.queryParameters['sort']);
|
||||
final quotations = await db.quotationsList(sort: sort, query: query);
|
||||
final total = await db.countQuotations();
|
||||
final title = strings.t('quotations-title');
|
||||
|
||||
return internalPage(
|
||||
url: url,
|
||||
title: title,
|
||||
strings: strings,
|
||||
body: [
|
||||
...chrome(title: title, current: url, strings: strings),
|
||||
textForm(
|
||||
action: MewsUrls.quotations,
|
||||
label: strings.t('quotations-search-label'),
|
||||
submitLabel: strings.t('action-search'),
|
||||
field: 'q',
|
||||
value: query,
|
||||
),
|
||||
...linkLine([
|
||||
if (query.isNotEmpty)
|
||||
action(MewsUrls.page(url, {'q': null}),
|
||||
strings.t('action-clear-search')),
|
||||
]),
|
||||
...choiceLine(
|
||||
label: strings.t('posts-sort-label'),
|
||||
options: [
|
||||
for (final option in const [
|
||||
(QuotationSort.date, 'posts-sort-date'),
|
||||
(QuotationSort.page, 'quotations-sort-page'),
|
||||
])
|
||||
(
|
||||
href: MewsUrls.page(url, {'sort': option.$1.name}),
|
||||
label: strings.t(option.$2),
|
||||
current: option.$1 == sort,
|
||||
),
|
||||
],
|
||||
),
|
||||
const RuleBlock(),
|
||||
ParagraphBlock([
|
||||
MewsEmphasis(EmphasisStyle.emphasis, [
|
||||
MewsText(countText(strings, total,
|
||||
none: 'count-quotations-none',
|
||||
one: 'count-quotations-one',
|
||||
many: 'count-quotations-many')),
|
||||
]),
|
||||
]),
|
||||
if (quotations.isEmpty)
|
||||
ParagraphBlock([
|
||||
MewsText(strings.t(
|
||||
query.isEmpty ? 'quotations-empty' : 'quotations-no-matches')),
|
||||
])
|
||||
else
|
||||
itemList([
|
||||
for (final quotation in quotations)
|
||||
itemRow(
|
||||
href: quotation.pageUrl,
|
||||
label: quotation.quote,
|
||||
date: dayText(quotation.createdAt),
|
||||
// The row names the page the text came from, not just the
|
||||
// text: the author and the article's title when they are
|
||||
// known, and the site by its own title when the reader's
|
||||
// subscriptions know it, otherwise by its host.
|
||||
note: _rowNote(quotation),
|
||||
actions: [
|
||||
action(
|
||||
MewsUrls.action('share-quotation', {'id': '${quotation.id}'}),
|
||||
strings.t('quotations-share')),
|
||||
action(
|
||||
MewsUrls.action(
|
||||
'remove-quotation', {'id': '${quotation.id}'}),
|
||||
strings.t('quotations-remove')),
|
||||
],
|
||||
),
|
||||
]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Who and what the row names behind a quotation: the author and the
|
||||
/// article's title when they are known, and the site the page came from by
|
||||
/// its own title, or by its host when the subscriptions do not know it.
|
||||
/// Nothing is named twice.
|
||||
String? _rowNote(Quotation quotation) {
|
||||
final host = Uri.tryParse(quotation.pageUrl)?.host;
|
||||
final source = (quotation.siteTitle?.trim().isNotEmpty ?? false)
|
||||
? quotation.siteTitle!.trim()
|
||||
: (host != null && host.isNotEmpty ? host : quotation.pageUrl);
|
||||
final named = <String>{};
|
||||
final parts = [
|
||||
for (final part in [
|
||||
quotation.author?.trim(),
|
||||
quotation.pageTitle?.trim(),
|
||||
source,
|
||||
])
|
||||
if (part != null && part.isNotEmpty && named.add(part)) part,
|
||||
];
|
||||
return parts.isEmpty ? null : parts.join(actionSeparator);
|
||||
}
|
||||
|
||||
/// The citation a quotation travels with, shaped as APA names a web page:
|
||||
/// the author first when the page's metadata carried one, otherwise the
|
||||
/// article's title; the year as a no-date marker, since a page declares no
|
||||
/// publication date; then the article's title, the site's own title and the
|
||||
/// address. The site is named by its title when the reader's subscriptions
|
||||
/// know it, and by its host when they do not; nothing is named twice.
|
||||
String citationFor(Quotation quotation, Strings strings) {
|
||||
final noDate = strings.t('quotations-no-date');
|
||||
final uri = Uri.tryParse(quotation.pageUrl);
|
||||
final host =
|
||||
uri != null && uri.host.isNotEmpty ? uri.host : quotation.pageUrl;
|
||||
final author = quotation.author?.trim();
|
||||
final title = quotation.pageTitle?.trim();
|
||||
final site = (quotation.siteTitle?.trim().isNotEmpty ?? false)
|
||||
? quotation.siteTitle!.trim()
|
||||
: host;
|
||||
final lead = (author != null && author.isNotEmpty) ? author : title ?? site;
|
||||
final named = [
|
||||
if (lead != title && title != null && title.isNotEmpty) title,
|
||||
if (site != lead && site != title) site,
|
||||
];
|
||||
if (named.isEmpty) return '$lead ($noDate). ${quotation.pageUrl}.';
|
||||
return '$lead ($noDate). ${named.join('. ')}. ${quotation.pageUrl}.';
|
||||
}
|
||||
|
|
@ -148,6 +148,21 @@ class SiteCertificates extends Table {
|
|||
Set<Column> get primaryKey => {host, port};
|
||||
}
|
||||
|
||||
/// A stretch of text the reader marked on a page and kept, together with the
|
||||
/// page it came from. The author is carried only when the page's metadata
|
||||
/// said one, and the site's title when the reader's subscriptions know the
|
||||
/// site the page belongs to. The column is named [Quotations.quote] because
|
||||
/// `text` is the drift builder the declaration would otherwise call.
|
||||
class Quotations extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get pageUrl => text()();
|
||||
TextColumn get pageTitle => text().nullable()();
|
||||
TextColumn get author => text().nullable()();
|
||||
TextColumn get siteTitle => text().nullable()();
|
||||
TextColumn get quote => text()();
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
}
|
||||
|
||||
@DriftDatabase(tables: [
|
||||
HistoryItems,
|
||||
Bookmarks,
|
||||
|
|
@ -160,6 +175,7 @@ class SiteCertificates extends Table {
|
|||
ClientCertificates,
|
||||
SiteCertificates,
|
||||
FirstStart,
|
||||
Quotations,
|
||||
])
|
||||
class MewsDatabase extends _$MewsDatabase {
|
||||
MewsDatabase() : super(_open());
|
||||
|
|
@ -168,7 +184,7 @@ class MewsDatabase extends _$MewsDatabase {
|
|||
MewsDatabase.test(super.executor);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 6;
|
||||
int get schemaVersion => 8;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
|
|
@ -197,6 +213,12 @@ class MewsDatabase extends _$MewsDatabase {
|
|||
if (from < 6) {
|
||||
await m.createTable(firstStart);
|
||||
}
|
||||
if (from < 7) {
|
||||
await m.createTable(quotations);
|
||||
}
|
||||
if (from < 8) {
|
||||
await m.addColumn(quotations, quotations.siteTitle);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -717,6 +739,87 @@ class MewsDatabase extends _$MewsDatabase {
|
|||
return [for (final row in rows) '${row.host}:${row.port}'];
|
||||
}
|
||||
|
||||
// Quotations --------------------------------------------------------------
|
||||
|
||||
/// Keeps a marked stretch of text together with the page it came from,
|
||||
/// and with the site's title when the reader's subscriptions know the site
|
||||
/// the page belongs to: the site whose posts listed the page, or the page
|
||||
/// itself when it is the subscribed one.
|
||||
Future<void> addQuotation({
|
||||
required String quote,
|
||||
required String pageUrl,
|
||||
String? pageTitle,
|
||||
String? author,
|
||||
}) async {
|
||||
into(quotations).insert(QuotationsCompanion.insert(
|
||||
quote: quote,
|
||||
pageUrl: pageUrl,
|
||||
pageTitle: Value(pageTitle),
|
||||
author: Value(author),
|
||||
siteTitle: Value(await siteTitleOf(pageUrl)),
|
||||
));
|
||||
}
|
||||
|
||||
/// The title of the site [pageUrl] belongs to, or null when the reader's
|
||||
/// subscriptions do not know it. A page the site lists as a post takes the
|
||||
/// subscribing site's title; a subscribed page is its own site.
|
||||
Future<String?> siteTitleOf(String pageUrl) async {
|
||||
final site = alias(subscriptions, 'site');
|
||||
final throughPosts = await ((select(feedEntries).join([
|
||||
leftOuterJoin(site, site.url.equalsExp(feedEntries.siteUrl)),
|
||||
]))
|
||||
..where(feedEntries.url.equals(pageUrl)))
|
||||
.get();
|
||||
if (throughPosts.isNotEmpty) {
|
||||
return throughPosts.first.readTableOrNull(site)?.title;
|
||||
}
|
||||
final own =
|
||||
await (select(subscriptions)..where((r) => r.url.equals(pageUrl)))
|
||||
.getSingleOrNull();
|
||||
return own?.title;
|
||||
}
|
||||
|
||||
Future<void> removeQuotation(int id) =>
|
||||
(delete(quotations)..where((q) => q.id.equals(id))).go();
|
||||
|
||||
/// One quotation, so an action address can act on it.
|
||||
Future<Quotation?> quotationById(int id) =>
|
||||
(select(quotations)..where((q) => q.id.equals(id))).getSingleOrNull();
|
||||
|
||||
/// Every kept quotation, for the quotations page. [query] narrows by the
|
||||
/// quoted text, the page's title, its author or its address.
|
||||
Future<List<Quotation>> quotationsList({
|
||||
QuotationSort sort = QuotationSort.date,
|
||||
String? query,
|
||||
}) async {
|
||||
final statement = select(quotations)
|
||||
..orderBy(switch (sort) {
|
||||
QuotationSort.date => [
|
||||
// Tiebreak quotations saved within the same clock tick.
|
||||
(r) => OrderingTerm.desc(r.createdAt),
|
||||
(r) => OrderingTerm.asc(r.id),
|
||||
],
|
||||
QuotationSort.page => [
|
||||
// A page without a title sorts under its address, so the order
|
||||
// never depends on a null.
|
||||
(r) => OrderingTerm.asc(coalesce([r.pageTitle, r.pageUrl])),
|
||||
(r) => OrderingTerm.desc(r.createdAt),
|
||||
],
|
||||
});
|
||||
final filter = _search(
|
||||
[quotations.quote, quotations.pageTitle, quotations.author, quotations.pageUrl],
|
||||
query);
|
||||
if (filter != null) statement.where((_) => filter);
|
||||
return statement.get();
|
||||
}
|
||||
|
||||
/// How many quotations are held, for the hub.
|
||||
Future<int> countQuotations() async {
|
||||
final total = quotations.id.count();
|
||||
final query = selectOnly(quotations)..addColumns([total]);
|
||||
return (await query.getSingle()).read(total) ?? 0;
|
||||
}
|
||||
|
||||
/// Binds a certificate to a host, replacing whatever was bound before.
|
||||
Future<void> bindCertificate(String host, int port, int certificateId) =>
|
||||
into(siteCertificates).insert(
|
||||
|
|
@ -743,6 +846,17 @@ enum EntrySort {
|
|||
};
|
||||
}
|
||||
|
||||
/// How the quotations page is ordered.
|
||||
enum QuotationSort {
|
||||
date,
|
||||
page;
|
||||
|
||||
/// Reads the order from an address, falling back to the default rather than
|
||||
/// refusing a value the reader may have typed.
|
||||
static QuotationSort parse(String? name) =>
|
||||
name == 'page' ? QuotationSort.page : QuotationSort.date;
|
||||
}
|
||||
|
||||
/// How widely one tag is used.
|
||||
class TagUse {
|
||||
const TagUse({required this.bookmarks, required this.sites});
|
||||
|
|
|
|||
|
|
@ -3579,6 +3579,450 @@ class FirstStartCompanion extends UpdateCompanion<FirstStartData> {
|
|||
}
|
||||
}
|
||||
|
||||
class $QuotationsTable extends Quotations
|
||||
with TableInfo<$QuotationsTable, Quotation> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$QuotationsTable(this.attachedDatabase, [this._alias]);
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id',
|
||||
aliasedName,
|
||||
false,
|
||||
hasAutoIncrement: true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'PRIMARY KEY AUTOINCREMENT',
|
||||
),
|
||||
);
|
||||
static const VerificationMeta _pageUrlMeta = const VerificationMeta(
|
||||
'pageUrl',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<String> pageUrl = GeneratedColumn<String>(
|
||||
'page_url',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
static const VerificationMeta _pageTitleMeta = const VerificationMeta(
|
||||
'pageTitle',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<String> pageTitle = GeneratedColumn<String>(
|
||||
'page_title',
|
||||
aliasedName,
|
||||
true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
static const VerificationMeta _authorMeta = const VerificationMeta('author');
|
||||
@override
|
||||
late final GeneratedColumn<String> author = GeneratedColumn<String>(
|
||||
'author',
|
||||
aliasedName,
|
||||
true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
static const VerificationMeta _siteTitleMeta = const VerificationMeta(
|
||||
'siteTitle',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<String> siteTitle = GeneratedColumn<String>(
|
||||
'site_title',
|
||||
aliasedName,
|
||||
true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
static const VerificationMeta _quoteMeta = const VerificationMeta('quote');
|
||||
@override
|
||||
late final GeneratedColumn<String> quote = GeneratedColumn<String>(
|
||||
'quote',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
static const VerificationMeta _createdAtMeta = const VerificationMeta(
|
||||
'createdAt',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
|
||||
'created_at',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.dateTime,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: currentDateAndTime,
|
||||
);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [
|
||||
id,
|
||||
pageUrl,
|
||||
pageTitle,
|
||||
author,
|
||||
siteTitle,
|
||||
quote,
|
||||
createdAt,
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'quotations';
|
||||
@override
|
||||
VerificationContext validateIntegrity(
|
||||
Insertable<Quotation> instance, {
|
||||
bool isInserting = false,
|
||||
}) {
|
||||
final context = VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('id')) {
|
||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||
}
|
||||
if (data.containsKey('page_url')) {
|
||||
context.handle(
|
||||
_pageUrlMeta,
|
||||
pageUrl.isAcceptableOrUnknown(data['page_url']!, _pageUrlMeta),
|
||||
);
|
||||
} else if (isInserting) {
|
||||
context.missing(_pageUrlMeta);
|
||||
}
|
||||
if (data.containsKey('page_title')) {
|
||||
context.handle(
|
||||
_pageTitleMeta,
|
||||
pageTitle.isAcceptableOrUnknown(data['page_title']!, _pageTitleMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('author')) {
|
||||
context.handle(
|
||||
_authorMeta,
|
||||
author.isAcceptableOrUnknown(data['author']!, _authorMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('site_title')) {
|
||||
context.handle(
|
||||
_siteTitleMeta,
|
||||
siteTitle.isAcceptableOrUnknown(data['site_title']!, _siteTitleMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('quote')) {
|
||||
context.handle(
|
||||
_quoteMeta,
|
||||
quote.isAcceptableOrUnknown(data['quote']!, _quoteMeta),
|
||||
);
|
||||
} else if (isInserting) {
|
||||
context.missing(_quoteMeta);
|
||||
}
|
||||
if (data.containsKey('created_at')) {
|
||||
context.handle(
|
||||
_createdAtMeta,
|
||||
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta),
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
Quotation map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return Quotation(
|
||||
id: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}id'],
|
||||
)!,
|
||||
pageUrl: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}page_url'],
|
||||
)!,
|
||||
pageTitle: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}page_title'],
|
||||
),
|
||||
author: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}author'],
|
||||
),
|
||||
siteTitle: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}site_title'],
|
||||
),
|
||||
quote: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}quote'],
|
||||
)!,
|
||||
createdAt: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}created_at'],
|
||||
)!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
$QuotationsTable createAlias(String alias) {
|
||||
return $QuotationsTable(attachedDatabase, alias);
|
||||
}
|
||||
}
|
||||
|
||||
class Quotation extends DataClass implements Insertable<Quotation> {
|
||||
final int id;
|
||||
final String pageUrl;
|
||||
final String? pageTitle;
|
||||
final String? author;
|
||||
final String? siteTitle;
|
||||
final String quote;
|
||||
final DateTime createdAt;
|
||||
const Quotation({
|
||||
required this.id,
|
||||
required this.pageUrl,
|
||||
this.pageTitle,
|
||||
this.author,
|
||||
this.siteTitle,
|
||||
required this.quote,
|
||||
required this.createdAt,
|
||||
});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<int>(id);
|
||||
map['page_url'] = Variable<String>(pageUrl);
|
||||
if (!nullToAbsent || pageTitle != null) {
|
||||
map['page_title'] = Variable<String>(pageTitle);
|
||||
}
|
||||
if (!nullToAbsent || author != null) {
|
||||
map['author'] = Variable<String>(author);
|
||||
}
|
||||
if (!nullToAbsent || siteTitle != null) {
|
||||
map['site_title'] = Variable<String>(siteTitle);
|
||||
}
|
||||
map['quote'] = Variable<String>(quote);
|
||||
map['created_at'] = Variable<DateTime>(createdAt);
|
||||
return map;
|
||||
}
|
||||
|
||||
QuotationsCompanion toCompanion(bool nullToAbsent) {
|
||||
return QuotationsCompanion(
|
||||
id: Value(id),
|
||||
pageUrl: Value(pageUrl),
|
||||
pageTitle: pageTitle == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(pageTitle),
|
||||
author: author == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(author),
|
||||
siteTitle: siteTitle == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(siteTitle),
|
||||
quote: Value(quote),
|
||||
createdAt: Value(createdAt),
|
||||
);
|
||||
}
|
||||
|
||||
factory Quotation.fromJson(
|
||||
Map<String, dynamic> json, {
|
||||
ValueSerializer? serializer,
|
||||
}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return Quotation(
|
||||
id: serializer.fromJson<int>(json['id']),
|
||||
pageUrl: serializer.fromJson<String>(json['pageUrl']),
|
||||
pageTitle: serializer.fromJson<String?>(json['pageTitle']),
|
||||
author: serializer.fromJson<String?>(json['author']),
|
||||
siteTitle: serializer.fromJson<String?>(json['siteTitle']),
|
||||
quote: serializer.fromJson<String>(json['quote']),
|
||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<int>(id),
|
||||
'pageUrl': serializer.toJson<String>(pageUrl),
|
||||
'pageTitle': serializer.toJson<String?>(pageTitle),
|
||||
'author': serializer.toJson<String?>(author),
|
||||
'siteTitle': serializer.toJson<String?>(siteTitle),
|
||||
'quote': serializer.toJson<String>(quote),
|
||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||
};
|
||||
}
|
||||
|
||||
Quotation copyWith({
|
||||
int? id,
|
||||
String? pageUrl,
|
||||
Value<String?> pageTitle = const Value.absent(),
|
||||
Value<String?> author = const Value.absent(),
|
||||
Value<String?> siteTitle = const Value.absent(),
|
||||
String? quote,
|
||||
DateTime? createdAt,
|
||||
}) => Quotation(
|
||||
id: id ?? this.id,
|
||||
pageUrl: pageUrl ?? this.pageUrl,
|
||||
pageTitle: pageTitle.present ? pageTitle.value : this.pageTitle,
|
||||
author: author.present ? author.value : this.author,
|
||||
siteTitle: siteTitle.present ? siteTitle.value : this.siteTitle,
|
||||
quote: quote ?? this.quote,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
Quotation copyWithCompanion(QuotationsCompanion data) {
|
||||
return Quotation(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
pageUrl: data.pageUrl.present ? data.pageUrl.value : this.pageUrl,
|
||||
pageTitle: data.pageTitle.present ? data.pageTitle.value : this.pageTitle,
|
||||
author: data.author.present ? data.author.value : this.author,
|
||||
siteTitle: data.siteTitle.present ? data.siteTitle.value : this.siteTitle,
|
||||
quote: data.quote.present ? data.quote.value : this.quote,
|
||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('Quotation(')
|
||||
..write('id: $id, ')
|
||||
..write('pageUrl: $pageUrl, ')
|
||||
..write('pageTitle: $pageTitle, ')
|
||||
..write('author: $author, ')
|
||||
..write('siteTitle: $siteTitle, ')
|
||||
..write('quote: $quote, ')
|
||||
..write('createdAt: $createdAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(id, pageUrl, pageTitle, author, siteTitle, quote, createdAt);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is Quotation &&
|
||||
other.id == this.id &&
|
||||
other.pageUrl == this.pageUrl &&
|
||||
other.pageTitle == this.pageTitle &&
|
||||
other.author == this.author &&
|
||||
other.siteTitle == this.siteTitle &&
|
||||
other.quote == this.quote &&
|
||||
other.createdAt == this.createdAt);
|
||||
}
|
||||
|
||||
class QuotationsCompanion extends UpdateCompanion<Quotation> {
|
||||
final Value<int> id;
|
||||
final Value<String> pageUrl;
|
||||
final Value<String?> pageTitle;
|
||||
final Value<String?> author;
|
||||
final Value<String?> siteTitle;
|
||||
final Value<String> quote;
|
||||
final Value<DateTime> createdAt;
|
||||
const QuotationsCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.pageUrl = const Value.absent(),
|
||||
this.pageTitle = const Value.absent(),
|
||||
this.author = const Value.absent(),
|
||||
this.siteTitle = const Value.absent(),
|
||||
this.quote = const Value.absent(),
|
||||
this.createdAt = const Value.absent(),
|
||||
});
|
||||
QuotationsCompanion.insert({
|
||||
this.id = const Value.absent(),
|
||||
required String pageUrl,
|
||||
this.pageTitle = const Value.absent(),
|
||||
this.author = const Value.absent(),
|
||||
this.siteTitle = const Value.absent(),
|
||||
required String quote,
|
||||
this.createdAt = const Value.absent(),
|
||||
}) : pageUrl = Value(pageUrl),
|
||||
quote = Value(quote);
|
||||
static Insertable<Quotation> custom({
|
||||
Expression<int>? id,
|
||||
Expression<String>? pageUrl,
|
||||
Expression<String>? pageTitle,
|
||||
Expression<String>? author,
|
||||
Expression<String>? siteTitle,
|
||||
Expression<String>? quote,
|
||||
Expression<DateTime>? createdAt,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (pageUrl != null) 'page_url': pageUrl,
|
||||
if (pageTitle != null) 'page_title': pageTitle,
|
||||
if (author != null) 'author': author,
|
||||
if (siteTitle != null) 'site_title': siteTitle,
|
||||
if (quote != null) 'quote': quote,
|
||||
if (createdAt != null) 'created_at': createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
QuotationsCompanion copyWith({
|
||||
Value<int>? id,
|
||||
Value<String>? pageUrl,
|
||||
Value<String?>? pageTitle,
|
||||
Value<String?>? author,
|
||||
Value<String?>? siteTitle,
|
||||
Value<String>? quote,
|
||||
Value<DateTime>? createdAt,
|
||||
}) {
|
||||
return QuotationsCompanion(
|
||||
id: id ?? this.id,
|
||||
pageUrl: pageUrl ?? this.pageUrl,
|
||||
pageTitle: pageTitle ?? this.pageTitle,
|
||||
author: author ?? this.author,
|
||||
siteTitle: siteTitle ?? this.siteTitle,
|
||||
quote: quote ?? this.quote,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<int>(id.value);
|
||||
}
|
||||
if (pageUrl.present) {
|
||||
map['page_url'] = Variable<String>(pageUrl.value);
|
||||
}
|
||||
if (pageTitle.present) {
|
||||
map['page_title'] = Variable<String>(pageTitle.value);
|
||||
}
|
||||
if (author.present) {
|
||||
map['author'] = Variable<String>(author.value);
|
||||
}
|
||||
if (siteTitle.present) {
|
||||
map['site_title'] = Variable<String>(siteTitle.value);
|
||||
}
|
||||
if (quote.present) {
|
||||
map['quote'] = Variable<String>(quote.value);
|
||||
}
|
||||
if (createdAt.present) {
|
||||
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('QuotationsCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('pageUrl: $pageUrl, ')
|
||||
..write('pageTitle: $pageTitle, ')
|
||||
..write('author: $author, ')
|
||||
..write('siteTitle: $siteTitle, ')
|
||||
..write('quote: $quote, ')
|
||||
..write('createdAt: $createdAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$MewsDatabase extends GeneratedDatabase {
|
||||
_$MewsDatabase(QueryExecutor e) : super(e);
|
||||
$MewsDatabaseManager get managers => $MewsDatabaseManager(this);
|
||||
|
|
@ -3596,6 +4040,7 @@ abstract class _$MewsDatabase extends GeneratedDatabase {
|
|||
this,
|
||||
);
|
||||
late final $FirstStartTable firstStart = $FirstStartTable(this);
|
||||
late final $QuotationsTable quotations = $QuotationsTable(this);
|
||||
late final Index historyItemsUrl = Index(
|
||||
'history_items_url',
|
||||
'CREATE INDEX history_items_url ON history_items (url)',
|
||||
|
|
@ -3616,6 +4061,7 @@ abstract class _$MewsDatabase extends GeneratedDatabase {
|
|||
clientCertificates,
|
||||
siteCertificates,
|
||||
firstStart,
|
||||
quotations,
|
||||
historyItemsUrl,
|
||||
];
|
||||
}
|
||||
|
|
@ -5669,6 +6115,235 @@ typedef $$FirstStartTableProcessedTableManager =
|
|||
FirstStartData,
|
||||
PrefetchHooks Function()
|
||||
>;
|
||||
typedef $$QuotationsTableCreateCompanionBuilder =
|
||||
QuotationsCompanion Function({
|
||||
Value<int> id,
|
||||
required String pageUrl,
|
||||
Value<String?> pageTitle,
|
||||
Value<String?> author,
|
||||
Value<String?> siteTitle,
|
||||
required String quote,
|
||||
Value<DateTime> createdAt,
|
||||
});
|
||||
typedef $$QuotationsTableUpdateCompanionBuilder =
|
||||
QuotationsCompanion Function({
|
||||
Value<int> id,
|
||||
Value<String> pageUrl,
|
||||
Value<String?> pageTitle,
|
||||
Value<String?> author,
|
||||
Value<String?> siteTitle,
|
||||
Value<String> quote,
|
||||
Value<DateTime> createdAt,
|
||||
});
|
||||
|
||||
class $$QuotationsTableFilterComposer
|
||||
extends Composer<_$MewsDatabase, $QuotationsTable> {
|
||||
$$QuotationsTableFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<int> get id => $composableBuilder(
|
||||
column: $table.id,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get pageUrl => $composableBuilder(
|
||||
column: $table.pageUrl,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get pageTitle => $composableBuilder(
|
||||
column: $table.pageTitle,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get author => $composableBuilder(
|
||||
column: $table.author,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get siteTitle => $composableBuilder(
|
||||
column: $table.siteTitle,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get quote => $composableBuilder(
|
||||
column: $table.quote,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$QuotationsTableOrderingComposer
|
||||
extends Composer<_$MewsDatabase, $QuotationsTable> {
|
||||
$$QuotationsTableOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<int> get id => $composableBuilder(
|
||||
column: $table.id,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get pageUrl => $composableBuilder(
|
||||
column: $table.pageUrl,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get pageTitle => $composableBuilder(
|
||||
column: $table.pageTitle,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get author => $composableBuilder(
|
||||
column: $table.author,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get siteTitle => $composableBuilder(
|
||||
column: $table.siteTitle,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get quote => $composableBuilder(
|
||||
column: $table.quote,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$QuotationsTableAnnotationComposer
|
||||
extends Composer<_$MewsDatabase, $QuotationsTable> {
|
||||
$$QuotationsTableAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<int> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get pageUrl =>
|
||||
$composableBuilder(column: $table.pageUrl, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get pageTitle =>
|
||||
$composableBuilder(column: $table.pageTitle, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get author =>
|
||||
$composableBuilder(column: $table.author, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get siteTitle =>
|
||||
$composableBuilder(column: $table.siteTitle, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get quote =>
|
||||
$composableBuilder(column: $table.quote, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<DateTime> get createdAt =>
|
||||
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||
}
|
||||
|
||||
class $$QuotationsTableTableManager
|
||||
extends
|
||||
RootTableManager<
|
||||
_$MewsDatabase,
|
||||
$QuotationsTable,
|
||||
Quotation,
|
||||
$$QuotationsTableFilterComposer,
|
||||
$$QuotationsTableOrderingComposer,
|
||||
$$QuotationsTableAnnotationComposer,
|
||||
$$QuotationsTableCreateCompanionBuilder,
|
||||
$$QuotationsTableUpdateCompanionBuilder,
|
||||
(
|
||||
Quotation,
|
||||
BaseReferences<_$MewsDatabase, $QuotationsTable, Quotation>,
|
||||
),
|
||||
Quotation,
|
||||
PrefetchHooks Function()
|
||||
> {
|
||||
$$QuotationsTableTableManager(_$MewsDatabase db, $QuotationsTable table)
|
||||
: super(
|
||||
TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$$QuotationsTableFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$$QuotationsTableOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$$QuotationsTableAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback:
|
||||
({
|
||||
Value<int> id = const Value.absent(),
|
||||
Value<String> pageUrl = const Value.absent(),
|
||||
Value<String?> pageTitle = const Value.absent(),
|
||||
Value<String?> author = const Value.absent(),
|
||||
Value<String?> siteTitle = const Value.absent(),
|
||||
Value<String> quote = const Value.absent(),
|
||||
Value<DateTime> createdAt = const Value.absent(),
|
||||
}) => QuotationsCompanion(
|
||||
id: id,
|
||||
pageUrl: pageUrl,
|
||||
pageTitle: pageTitle,
|
||||
author: author,
|
||||
siteTitle: siteTitle,
|
||||
quote: quote,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
createCompanionCallback:
|
||||
({
|
||||
Value<int> id = const Value.absent(),
|
||||
required String pageUrl,
|
||||
Value<String?> pageTitle = const Value.absent(),
|
||||
Value<String?> author = const Value.absent(),
|
||||
Value<String?> siteTitle = const Value.absent(),
|
||||
required String quote,
|
||||
Value<DateTime> createdAt = const Value.absent(),
|
||||
}) => QuotationsCompanion.insert(
|
||||
id: id,
|
||||
pageUrl: pageUrl,
|
||||
pageTitle: pageTitle,
|
||||
author: author,
|
||||
siteTitle: siteTitle,
|
||||
quote: quote,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
typedef $$QuotationsTableProcessedTableManager =
|
||||
ProcessedTableManager<
|
||||
_$MewsDatabase,
|
||||
$QuotationsTable,
|
||||
Quotation,
|
||||
$$QuotationsTableFilterComposer,
|
||||
$$QuotationsTableOrderingComposer,
|
||||
$$QuotationsTableAnnotationComposer,
|
||||
$$QuotationsTableCreateCompanionBuilder,
|
||||
$$QuotationsTableUpdateCompanionBuilder,
|
||||
(Quotation, BaseReferences<_$MewsDatabase, $QuotationsTable, Quotation>),
|
||||
Quotation,
|
||||
PrefetchHooks Function()
|
||||
>;
|
||||
|
||||
class $MewsDatabaseManager {
|
||||
final _$MewsDatabase _db;
|
||||
|
|
@ -5695,4 +6370,6 @@ class $MewsDatabaseManager {
|
|||
$$SiteCertificatesTableTableManager(_db, _db.siteCertificates);
|
||||
$$FirstStartTableTableManager get firstStart =>
|
||||
$$FirstStartTableTableManager(_db, _db.firstStart);
|
||||
$$QuotationsTableTableManager get quotations =>
|
||||
$$QuotationsTableTableManager(_db, _db.quotations);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue