import '../../database/database.dart'; import '../../l10n/strings.dart'; import '../../mews/document.dart'; import 'controllers/reader_settings_controller.dart' show LibraryController; /// Builds the internal History, Bookmarks and Subscriptions pages as real /// [MewsPage]s: they render like any other page and take part in the walk /// history, so swiping and back/forward treat them like visited pages. class InternalPages { InternalPages( {required MewsDatabase db, required LibraryController library, required Strings strings}) : _db = db, _library = library, _strings = strings; static const historyUrl = 'mews:history'; static const bookmarksUrl = 'mews:bookmarks'; static const subscriptionsUrl = 'mews:subscriptions'; final MewsDatabase _db; final LibraryController _library; final Strings _strings; /// Whether [url] is one of the internal pages. bool handles(String url) => isInternal(url); /// Whether [url] belongs to the internal mews: scheme. static bool isInternal(String url) => Uri.tryParse(url)?.scheme == 'mews'; /// The internal page for [url], or null when the name is unknown. Future pageFor(String url) async { // mews:history is an opaque URI: the page name is the path, not a host. switch (Uri.tryParse(url)?.path) { case 'history': return _history(url); case 'bookmarks': return _bookmarks(url); case 'subscriptions': return _subscriptions(url); default: return null; } } Future _history(String url) async { final visits = await _db.recentVisits(); return _page( url: url, title: _strings.t('history-title'), empty: _strings.t('history-empty'), entries: [ for (final visit in visits) _Entry( url: visit.url, // Dated links are the Mews posts pattern, so history reads like // any other entry list. label: '${_day(visit.visitedAt)} ${_clock(visit.visitedAt)}', title: visit.title ?? visit.url, ), ], ); } Future _bookmarks(String url) async { final bookmarks = await _db.bookmarksByDate(); return _page( url: url, title: _strings.t('bookmarks-title'), empty: _strings.t('bookmarks-empty'), entries: [ for (final bookmark in bookmarks) _Entry( url: bookmark.url, label: _day(bookmark.createdAt), title: bookmark.title ?? bookmark.url, ), ], ); } Future _subscriptions(String url) async { final subscriptions = await _db.allSubscriptions(); return _page( url: url, title: _strings.t('subscriptions-title'), empty: _strings.t('subscriptions-empty'), entries: [ for (final subscription in subscriptions) _Entry( url: subscription.url, label: _day(subscription.createdAt), title: (subscription.title ?? subscription.url) + _newPostsNote(subscription.url), ), ], ); } String _newPostsNote(String siteUrl) { final count = _library.newEntries.value[siteUrl]?.length ?? 0; if (count == 0) return ''; final note = count == 1 ? _strings.t('subscriptions-new-one') : _strings.t('subscriptions-new-many', {'count': count}); return ' \u2014 $note'; } MewsPage _page({ required String url, required String title, required String empty, required List<_Entry> entries, }) { final blocks = [ HeadingBlock(1, [MewsText(title)]), if (entries.isEmpty) ParagraphBlock([MewsText(empty)]) else ListBlock( ordered: false, start: 1, items: [ for (final entry in entries) MewsListItem([ ParagraphBlock([ MewsLink(entry.url, [MewsText('${entry.label} ${entry.title}')]), ]), ]), ], ), ]; return MewsPage( url: url, // Internal pages carry a marker so they render as Mews pages, not as // plainly-shown external ones. specVersion: 'internal', title: title, body: blocks, lang: 'en', direction: TextDirection.ltr, variant: MewsVariant.plain, entries: const [], ); } String _day(DateTime date) => '${date.year.toString().padLeft(4, '0')}-${_two(date.month)}-${_two(date.day)}'; String _clock(DateTime date) => '${_two(date.hour)}:${_two(date.minute)}'; String _two(int value) => value.toString().padLeft(2, '0'); } class _Entry { const _Entry({required this.url, required this.label, required this.title}); final String url; final String label; final String title; }