feat: add Gemini and Spartan protocol support
This commit is contained in:
parent
a4a49f41d7
commit
fa7aa5557c
48 changed files with 4992 additions and 161 deletions
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'dart:ui' as ui show TextDirection;
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
|
|
@ -20,6 +22,7 @@ class MewsPageView extends StatefulWidget {
|
|||
required this.visitedUrls,
|
||||
required this.onLinkTap,
|
||||
required this.onFormSubmit,
|
||||
this.fetchImageBytes,
|
||||
this.scrollController,
|
||||
this.ownPage = false,
|
||||
super.key,
|
||||
|
|
@ -32,6 +35,10 @@ class MewsPageView extends StatefulWidget {
|
|||
final Set<String> visitedUrls;
|
||||
final void Function(String url) onLinkTap;
|
||||
final void Function(FormBlock form, Map<String, String> values) onFormSubmit;
|
||||
|
||||
/// The bytes of an image living on a protocol page, where no address exists
|
||||
/// that the platform could load from. Null when the page holds none.
|
||||
final Future<Uint8List?> Function(String url)? fetchImageBytes;
|
||||
final ScrollController? scrollController;
|
||||
|
||||
/// Whether this is one of the client's own pages. Those are built from the
|
||||
|
|
@ -47,6 +54,10 @@ class MewsPageView extends StatefulWidget {
|
|||
class _MewsPageViewState extends State<MewsPageView> {
|
||||
final _recognizers = <MewsLink, TapGestureRecognizer>{};
|
||||
|
||||
/// Images already asked for, so a rebuild shows the same one arriving
|
||||
/// instead of asking again. A failure is forgotten, so trying again works.
|
||||
final _images = <String, Future<Uint8List?>>{};
|
||||
|
||||
@override
|
||||
void didUpdateWidget(MewsPageView oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
|
@ -312,13 +323,7 @@ class _MewsPageViewState extends State<MewsPageView> {
|
|||
),
|
||||
ImageBlock(:final src, :final alt, :final allowed) => Padding(
|
||||
padding: EdgeInsets.only(bottom: base),
|
||||
child: allowed
|
||||
? Image.network(
|
||||
src,
|
||||
fit: BoxFit.scaleDown,
|
||||
errorBuilder: (_, _, _) => _imageFallback(alt, base, unavailable: true),
|
||||
)
|
||||
: _imageFallback(alt, base),
|
||||
child: allowed ? _image(src, alt, base) : _imageFallback(alt, base),
|
||||
),
|
||||
FormBlock() => Padding(
|
||||
padding: EdgeInsets.only(bottom: base),
|
||||
|
|
@ -332,6 +337,40 @@ class _MewsPageViewState extends State<MewsPageView> {
|
|||
};
|
||||
}
|
||||
|
||||
/// An allowed image, from the platform on the web or from the protocol's
|
||||
/// own bytes on a capsule. A blocked or unreachable image falls back to its
|
||||
/// alt text, the way a missing image does.
|
||||
Widget _image(String src, String alt, double base) {
|
||||
final scheme = Uri.tryParse(src)?.scheme;
|
||||
if (scheme != 'gemini' && scheme != 'spartan') {
|
||||
return Image.network(
|
||||
src,
|
||||
fit: BoxFit.scaleDown,
|
||||
errorBuilder: (_, _, _) => _imageFallback(alt, base, unavailable: true),
|
||||
);
|
||||
}
|
||||
final loader = widget.fetchImageBytes;
|
||||
if (loader == null) return _imageFallback(alt, base, unavailable: true);
|
||||
return FutureBuilder<Uint8List?>(
|
||||
future: _images.putIfAbsent(src, () {
|
||||
final arriving = loader(src);
|
||||
arriving.then((bytes) {
|
||||
if (bytes == null) _images.remove(src);
|
||||
});
|
||||
return arriving;
|
||||
}),
|
||||
builder: (context, snapshot) => snapshot.connectionState != ConnectionState.done
|
||||
? const SizedBox.shrink()
|
||||
: snapshot.data == null
|
||||
? _imageFallback(alt, base, unavailable: true)
|
||||
: Image.memory(
|
||||
snapshot.data!,
|
||||
fit: BoxFit.scaleDown,
|
||||
errorBuilder: (_, _, _) => _imageFallback(alt, base, unavailable: true),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _imageFallback(String alt, double base, {bool unavailable = false}) {
|
||||
final palette = widget.palette;
|
||||
final strings = StringsScope.of(context);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue