feat: show real neighboring pages during back and forward swipes

This commit is contained in:
randogoth 2026-10-11 11:05:03 +03:00
parent 4e07c9a305
commit c104c2026b
7 changed files with 283 additions and 71 deletions

View file

@ -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 {