feat: show real neighboring pages during back and forward swipes
This commit is contained in:
parent
4e07c9a305
commit
c104c2026b
7 changed files with 283 additions and 71 deletions
|
|
@ -31,6 +31,15 @@ class SubmitForm extends FetchCommand {
|
|||
final Map<String, String> fields;
|
||||
}
|
||||
|
||||
/// Parses a page's source without touching the network; used to prepare the
|
||||
/// neighboring pages that swipes reveal.
|
||||
class ParseSource extends FetchCommand {
|
||||
const ParseSource({required this.url, required this.source});
|
||||
|
||||
final String url;
|
||||
final String source;
|
||||
}
|
||||
|
||||
sealed class FetchResult {
|
||||
const FetchResult();
|
||||
}
|
||||
|
|
@ -94,6 +103,7 @@ class FetchHandler with Handler<FetchCommand, FetchResult> {
|
|||
await _fetchPage(url, etag, lastModified, cachedSource),
|
||||
SubmitForm(:final url, :final method, :final fields) =>
|
||||
await _submitForm(url, method, fields),
|
||||
ParseSource(:final url, :final source) => _parse(source, url),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:actors/actors.dart';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:signals/signals.dart';
|
||||
|
|
@ -47,9 +45,10 @@ class BrowserController {
|
|||
/// Pages opened this session, so links can show the visited color.
|
||||
final visitedUrls = signal(<String>{});
|
||||
|
||||
/// Titles of the stack's neighboring pages, shown while swiping.
|
||||
final backTitle = signal<String?>(null);
|
||||
final forwardTitle = signal<String?>(null);
|
||||
/// The parsed pages next to the current one in the stack, ready to show
|
||||
/// while swiping between them.
|
||||
final backPage = signal<MewsPage?>(null);
|
||||
final forwardPage = signal<MewsPage?>(null);
|
||||
|
||||
bool get canGoBack => _stackIndex > 0;
|
||||
bool get canGoForward => _stackIndex >= 0 && _stack.length - 1 > _stackIndex;
|
||||
|
|
@ -69,24 +68,36 @@ class BrowserController {
|
|||
_stack.removeRange(_stackIndex + 1, _stack.length);
|
||||
_stack.add(url);
|
||||
_stackIndex++;
|
||||
_updatePeeks();
|
||||
_updateNeighbors();
|
||||
await _loadCurrent();
|
||||
}
|
||||
|
||||
Future<void> back() async {
|
||||
if (!canGoBack) return;
|
||||
_stackIndex--;
|
||||
_updatePeeks();
|
||||
// The neighbor was parsed for the swipe already; show it right away so
|
||||
// the transition lands on content instead of a blank page while
|
||||
// _loadCurrent revalidates it against the server.
|
||||
_showParsedNeighbor(backPage.value);
|
||||
_updateNeighbors();
|
||||
await _loadCurrent();
|
||||
}
|
||||
|
||||
Future<void> forward() async {
|
||||
if (!canGoForward) return;
|
||||
_stackIndex++;
|
||||
_updatePeeks();
|
||||
_showParsedNeighbor(forwardPage.value);
|
||||
_updateNeighbors();
|
||||
await _loadCurrent();
|
||||
}
|
||||
|
||||
void _showParsedNeighbor(MewsPage? page) {
|
||||
if (page == null) return;
|
||||
currentPage.value = page;
|
||||
error.value = null;
|
||||
visitedUrls.value = {...visitedUrls.value, page.url};
|
||||
}
|
||||
|
||||
Future<void> reload() => _loadCurrent();
|
||||
|
||||
Future<void> submitForm(String action, String method, Map<String, String> fields) async {
|
||||
|
|
@ -99,7 +110,7 @@ class BrowserController {
|
|||
_stack.removeRange(_stackIndex + 1, _stack.length);
|
||||
_stack.add(page.url);
|
||||
_stackIndex++;
|
||||
_updatePeeks();
|
||||
_updateNeighbors();
|
||||
_show(page);
|
||||
case _:
|
||||
error.value = const LoadFailed();
|
||||
|
|
@ -112,21 +123,34 @@ class BrowserController {
|
|||
if (url == null) return;
|
||||
_stack.add(url);
|
||||
_stackIndex = 0;
|
||||
_updatePeeks();
|
||||
_updateNeighbors();
|
||||
await _loadCurrent();
|
||||
}
|
||||
|
||||
/// Refreshes the neighboring-page titles from the cache; a page without a
|
||||
/// cached title falls back to its URL.
|
||||
void _updatePeeks() {
|
||||
Future<String?> peek(int index) async {
|
||||
if (index < 0 || index >= _stack.length) return null;
|
||||
final url = _stack[index];
|
||||
return (await _db.cachedPage(url))?.title ?? url;
|
||||
}
|
||||
/// Parses the stack's neighboring pages in the fetch actor so swipes can
|
||||
/// show the real content. Results arriving after another navigation are
|
||||
/// dropped, so rapid navigation never shows a stale neighbor.
|
||||
void _updateNeighbors() {
|
||||
_loadNeighbor(_stackIndex - 1, backPage);
|
||||
_loadNeighbor(_stackIndex + 1, forwardPage);
|
||||
}
|
||||
|
||||
unawaited(peek(_stackIndex - 1).then((t) => backTitle.value = t));
|
||||
unawaited(peek(_stackIndex + 1).then((t) => forwardTitle.value = t));
|
||||
Future<void> _loadNeighbor(int index, Signal<MewsPage?> target) async {
|
||||
final expectedIndex = _stackIndex;
|
||||
if (index < 0 || index >= _stack.length) {
|
||||
target.value = null;
|
||||
return;
|
||||
}
|
||||
final url = _stack[index];
|
||||
final cached = await _db.cachedPage(url);
|
||||
if (_stackIndex != expectedIndex) return;
|
||||
if (cached == null) {
|
||||
target.value = null;
|
||||
return;
|
||||
}
|
||||
final result = await _fetch.send(ParseSource(url: url, source: cached.source));
|
||||
if (_stackIndex != expectedIndex) return;
|
||||
target.value = result is FetchPageOk ? result.page : null;
|
||||
}
|
||||
|
||||
Future<void> _loadCurrent() async {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue