feat: add Gemini and Spartan protocol support

This commit is contained in:
randogoth 2026-10-11 22:07:25 +03:00
parent a4a49f41d7
commit fa7aa5557c
48 changed files with 4992 additions and 161 deletions

View file

@ -1,4 +1,6 @@
import 'dart:convert' show base64;
import 'dart:io' show File;
import 'dart:typed_data';
import 'package:drift/native.dart';
import 'package:flutter/material.dart';
@ -24,7 +26,7 @@ void main() {
await tester.pumpAndSettle();
expect(find.byType(TextField), findsOneWidget);
expect(find.text('Enter a web address'), findsOneWidget);
expect(find.text('Enter an address'), findsOneWidget);
// The hub opens from the left icon; no floating button remains.
expect(find.byIcon(Icons.menu), findsOneWidget);
expect(find.byType(FloatingActionButton), findsNothing);
@ -52,7 +54,7 @@ void main() {
await tester.pumpWidget(
MaterialApp(
home: StringsScope(
strings: (await _strings()),
strings: await _strings(),
child: MewsPageView(
page: page,
palette: paletteFor(
@ -77,6 +79,84 @@ void main() {
await tester.pump();
expect(opened, 'https://example.com/next.html');
});
testWidgets('an image on a capsule arrives over the protocol', (tester) async {
// A 1x1 PNG, small enough to spell out.
final png = Uint8List.fromList(base64.decode(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=='));
final dot = MewsPage(
url: 'gemini://capsule.example/dot.png',
lang: 'en',
direction: TextDirection.ltr,
variant: MewsVariant.plain,
specVersion: 'gemtext',
entries: const [],
body: const [
ImageBlock(src: 'gemini://capsule.example/dot.png', alt: 'A dot', allowed: true),
],
);
Future<Uint8List?> load(String url) async =>
url == 'gemini://capsule.example/dot.png' ? png : null;
await tester.pumpWidget(
MaterialApp(
home: StringsScope(
strings: await _strings(),
child: MewsPageView(
page: dot,
palette: paletteFor(
pageVariant: MewsVariant.plain, readerVariant: 'site', dark: false),
fontFamily: 'Atkinson Hyperlegible',
fontScale: 1.0,
visitedUrls: const {},
onLinkTap: (_) {},
onFormSubmit: (_, _) {},
fetchImageBytes: load,
),
),
),
);
await tester.pumpAndSettle();
// The image itself, decoded from the protocol's bytes.
expect(find.byType(Image), findsOneWidget);
// An image that never arrives falls back to its alt text, the way a
// missing image on the web does.
final missing = MewsPage(
url: 'gemini://capsule.example/none.png',
lang: 'en',
direction: TextDirection.ltr,
variant: MewsVariant.plain,
specVersion: 'gemtext',
entries: const [],
body: const [
ImageBlock(src: 'gemini://capsule.example/none.png', alt: 'A dot', allowed: true),
],
);
await tester.pumpWidget(
MaterialApp(
home: StringsScope(
strings: await _strings(),
child: MewsPageView(
page: missing,
palette: paletteFor(
pageVariant: MewsVariant.plain, readerVariant: 'site', dark: false),
fontFamily: 'Atkinson Hyperlegible',
fontScale: 1.0,
visitedUrls: const {},
onLinkTap: (_) {},
onFormSubmit: (_, _) {},
fetchImageBytes: load,
),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(Image), findsNothing);
expect(find.textContaining('could not be loaded'), findsOneWidget);
});
}
Future<Strings> _strings() async {