feat: retire FOB and sheet; settings as a Mews page and actions on the address bar
This commit is contained in:
parent
6f9c0e587a
commit
28ff7ca327
10 changed files with 324 additions and 361 deletions
|
|
@ -84,6 +84,15 @@ class BrowserController {
|
|||
Future<void> open(String input) async {
|
||||
final url = normalizeAddress(input);
|
||||
if (url.isEmpty) return;
|
||||
|
||||
// Setting links change a value and stay on the current page instead of
|
||||
// joining the walk history.
|
||||
if (InternalPages.isSettingUrl(url)) {
|
||||
await _internalPages?.applySetting(url);
|
||||
await _loadCurrent();
|
||||
return;
|
||||
}
|
||||
|
||||
_stack.removeRange(_stackIndex + 1, _stack.length);
|
||||
_stack.add(url);
|
||||
_stackIndex++;
|
||||
|
|
|
|||
|
|
@ -2,26 +2,30 @@ import '../../database/database.dart';
|
|||
import '../../l10n/strings.dart';
|
||||
import '../../mews/document.dart';
|
||||
import 'controllers/reader_settings_controller.dart'
|
||||
show LibraryController;
|
||||
show LibraryController, ReaderSettingsController;
|
||||
|
||||
/// 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,
|
||||
InternalPages({
|
||||
required MewsDatabase db,
|
||||
required LibraryController library,
|
||||
required ReaderSettingsController settings,
|
||||
required Strings strings,
|
||||
}) : _db = db,
|
||||
_library = library,
|
||||
_settings = settings,
|
||||
_strings = strings;
|
||||
|
||||
static const historyUrl = 'mews:history';
|
||||
static const bookmarksUrl = 'mews:bookmarks';
|
||||
static const subscriptionsUrl = 'mews:subscriptions';
|
||||
static const settingsUrl = 'mews:settings';
|
||||
|
||||
final MewsDatabase _db;
|
||||
final LibraryController _library;
|
||||
final ReaderSettingsController _settings;
|
||||
final Strings _strings;
|
||||
|
||||
/// Whether [url] is one of the internal pages.
|
||||
|
|
@ -41,11 +45,145 @@ class InternalPages {
|
|||
return _bookmarks(url);
|
||||
case 'subscriptions':
|
||||
return _subscriptions(url);
|
||||
case 'settings':
|
||||
return _settingsPage(url);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether [url] changes a setting instead of visiting a page, e.g.
|
||||
/// mews:setting/theme/light.
|
||||
static bool isSettingUrl(String url) =>
|
||||
Uri.tryParse(url)?.path.startsWith('setting/') ?? false;
|
||||
|
||||
/// Applies the setting encoded in [url]; the page stays where it is and
|
||||
/// rerenders with the new value.
|
||||
Future<void> applySetting(String url) async {
|
||||
final segments =
|
||||
Uri.tryParse(url)?.path.split('/') ?? const <String>[];
|
||||
if (segments.length < 3) return;
|
||||
final value = Uri.decodeComponent(segments.last);
|
||||
switch (segments[1]) {
|
||||
case 'font':
|
||||
await _settings.setFont(value);
|
||||
case 'size':
|
||||
if (value == 'decrease') {
|
||||
await _settings.decreaseSize();
|
||||
} else if (value == 'increase') {
|
||||
await _settings.increaseSize();
|
||||
}
|
||||
case 'theme':
|
||||
await _settings.setThemeMode(value);
|
||||
case 'colors':
|
||||
await _settings.setVariant(value);
|
||||
}
|
||||
}
|
||||
|
||||
MewsPage _settingsPage(String url) {
|
||||
return _page(
|
||||
url: url,
|
||||
title: _strings.t('settings-page-title'),
|
||||
empty: '',
|
||||
entries: const [],
|
||||
extraBlocks: [
|
||||
// Typeface: the current choice is plain text; alternatives are links.
|
||||
HeadingBlock(2, [MewsText(_strings.t('settings-font-heading'))]),
|
||||
..._options('font', [
|
||||
_Option(_strings.t('settings-font-atkinson'), 'Atkinson Hyperlegible'),
|
||||
], current: _settings.font.value),
|
||||
// Text size steps between the configured bounds; the current value
|
||||
// stays plain text between the two actions.
|
||||
HeadingBlock(2, [MewsText(_strings.t('settings-size-heading'))]),
|
||||
ListBlock(
|
||||
ordered: false,
|
||||
start: 1,
|
||||
items: [
|
||||
if (_settings.fontScale.value > ReaderSettingsController.minScale)
|
||||
MewsListItem([
|
||||
ParagraphBlock([
|
||||
_link('mews:setting/size/decrease',
|
||||
_strings.t('settings-size-decrease')),
|
||||
]),
|
||||
]),
|
||||
MewsListItem([
|
||||
ParagraphBlock([
|
||||
MewsEmphasis(EmphasisStyle.strong, [
|
||||
MewsText('${(_settings.fontScale.value * 100).round()}%'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
if (_settings.fontScale.value < ReaderSettingsController.maxScale)
|
||||
MewsListItem([
|
||||
ParagraphBlock([
|
||||
_link('mews:setting/size/increase',
|
||||
_strings.t('settings-size-increase')),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
),
|
||||
HeadingBlock(2, [MewsText(_strings.t('settings-theme-heading'))]),
|
||||
..._options('theme', [
|
||||
_Option(_strings.t('settings-theme-light'), 'light'),
|
||||
_Option(_strings.t('settings-theme-dark'), 'dark'),
|
||||
_Option(_strings.t('settings-theme-system'), 'system'),
|
||||
], current: _settings.themeMode.value),
|
||||
HeadingBlock(2, [MewsText(_strings.t('settings-variant-heading'))]),
|
||||
..._options('colors', [
|
||||
_Option(_strings.t('settings-variant-site'), 'site'),
|
||||
_Option(_strings.t('settings-variant-warm'), 'warm'),
|
||||
_Option(_strings.t('settings-variant-cool'), 'cool'),
|
||||
_Option(_strings.t('settings-variant-green'), 'green'),
|
||||
_Option(_strings.t('settings-variant-mono'), 'mono'),
|
||||
], current: _settings.variant.value),
|
||||
// The library pages stay reachable now the sheet is gone.
|
||||
HeadingBlock(2, [MewsText(_strings.t('settings-library-heading'))]),
|
||||
ListBlock(
|
||||
ordered: false,
|
||||
start: 1,
|
||||
items: [
|
||||
for (final entry in [
|
||||
(historyUrl, _strings.t('settings-history')),
|
||||
(bookmarksUrl, _strings.t('settings-bookmarks')),
|
||||
(subscriptionsUrl, _strings.t('settings-subscriptions')),
|
||||
])
|
||||
MewsListItem([
|
||||
ParagraphBlock([_link(entry.$1, entry.$2)]),
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// One item per option: the current value in bold, the others as links.
|
||||
List<MewsBlock> _options(String group, List<_Option> options,
|
||||
{required String current}) {
|
||||
return [
|
||||
ListBlock(
|
||||
ordered: false,
|
||||
start: 1,
|
||||
items: [
|
||||
for (final option in options)
|
||||
MewsListItem([
|
||||
if (option.value == current)
|
||||
ParagraphBlock([
|
||||
MewsEmphasis(
|
||||
EmphasisStyle.strong, [MewsText(option.label)]),
|
||||
])
|
||||
else
|
||||
ParagraphBlock([
|
||||
_link('mews:setting/$group/${option.value}', option.label),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
MewsLink _link(String href, String label) =>
|
||||
MewsLink(href, [MewsText(label)]);
|
||||
|
||||
Future<MewsPage> _history(String url) async {
|
||||
final visits = await _db.recentVisits();
|
||||
return _page(
|
||||
|
|
@ -114,12 +252,13 @@ class InternalPages {
|
|||
required String title,
|
||||
required String empty,
|
||||
required List<_Entry> entries,
|
||||
List<MewsBlock> extraBlocks = const [],
|
||||
}) {
|
||||
final blocks = <MewsBlock>[
|
||||
HeadingBlock(1, [MewsText(title)]),
|
||||
if (entries.isEmpty)
|
||||
if (entries.isEmpty && extraBlocks.isEmpty)
|
||||
ParagraphBlock([MewsText(empty)])
|
||||
else
|
||||
else if (entries.isNotEmpty)
|
||||
ListBlock(
|
||||
ordered: false,
|
||||
start: 1,
|
||||
|
|
@ -134,6 +273,8 @@ class InternalPages {
|
|||
),
|
||||
];
|
||||
|
||||
blocks.addAll(extraBlocks);
|
||||
|
||||
return MewsPage(
|
||||
url: url,
|
||||
// Internal pages carry a marker so they render as Mews pages, not as
|
||||
|
|
@ -163,3 +304,11 @@ class _Entry {
|
|||
final String label;
|
||||
final String title;
|
||||
}
|
||||
|
||||
/// One selectable value in a settings option group.
|
||||
class _Option {
|
||||
const _Option(this.label, this.value);
|
||||
|
||||
final String label;
|
||||
final String value;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue