This repository has been archived on 2020-05-24. You can view files and clone it, but cannot push or open issues or pull requests.
gopher/lib/page/gopher.dart

236 lines
8.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:share/share.dart';
import 'dart:io';
import 'dart:convert';
import '../constants.dart';
import '../gopher.dart';
import './text.dart';
import './image.dart';
class GopherPage extends StatefulWidget {
final Uri uri;
const GopherPage({Key key, this.uri}) : super(key: key);
createState() => new GopherPageState();
static void openURI(BuildContext context, Uri uri) {
if (!uri.hasScheme || uri.scheme == "gopher") {
Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) {
return GopherPage(uri: uri);
},
));
} else {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("URL does not have the gopher scheme")));
}
}
}
class GopherPageState extends State<GopherPage> {
var content = <Widget>[];
var done = false;
TextStyle tstyle = const TextStyle(fontFamily: 'monospace', fontSize: DEFAULT_FONT_SIZE);
GopherPageState() {
SharedPreferences.getInstance().then((prefs) {
setState(() { this.tstyle = TextStyle(fontFamily: 'monospace', fontSize: prefs.getDouble('font_size')); });
});
}
void initState() {
super.initState();
_fetchContent(widget.uri);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(generateGopherTitle(widget.uri)),
actions: <Widget>[
IconButton(
icon: Icon(Icons.home),
tooltip: "go back",
onPressed: () => Navigator.popUntil(context, ModalRoute.withName('/'))
),
IconButton(
icon: Icon(Icons.share),
tooltip: "Share URL",
onPressed: () => Share.share(widget.uri.toString()),
),
],
),
body: ListView.builder(
padding: EdgeInsets.all(4.0),
itemBuilder: (BuildContext context, int index) {
if (index < content.length) {
return content[index];
} else if (!done && content.length == index) {
return Divider(color: Colors.red);
} else {
return null;
}
}),
);
}
void _launchURL(BuildContext context, String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
Scaffold.of(context)
.showSnackBar(const SnackBar(content: Text("Unable to open URL")));
}
}
void _fetchContent(Uri uri) {
var path = GopherPath(uri.path);
if (path.type != GopherType.Directory && path.type != GopherType.Search)
throw "Invalid gopher type, expected 1 or 7";
Socket.connect(uri.host, uri.port != 0 ? uri.port : 70).then((socket) {
socket.write(Uri.decodeComponent(path.selector) + "\r\n");
socket
.cast<List<int>>()
.transform(utf8.decoder)
.transform(const LineSplitter())
.forEach((line) {
if (mounted) {
setState(() {
if (line.isNotEmpty) {
var type = line.substring(0, 1);
var fields = line.substring(1).split("\t");
Uri buildUri(String type) {
return Uri(
scheme: "gopher",
host: fields[2],
path: "/" + type + fields[1],
port: int.parse(fields[3]));
}
switch (type) {
case "0":
content.add(ListTile(
title: Text(fields[0], style: tstyle),
trailing: Icon(Icons.short_text),
onTap: () {
TextPage.openURI(context, buildUri("0"));
}));
break;
case "1":
content.add(ListTile(
title: Text(fields[0], style: tstyle),
trailing: Icon(Icons.arrow_forward),
onTap: () {
GopherPage.openURI(context, buildUri("1"));
}));
break;
case "3":
content.add(Padding(
child: ListTile(
title: Text(fields[0], style: tstyle),
trailing: Icon(Icons.error, color: Colors.red)),
padding: EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 2.0),
));
break;
case "7":
content.add(ListTile(
title: Text(fields[0], style: tstyle),
trailing: Icon(Icons.search),
onTap: () {
showDialog<void>(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: const Text("Search"),
contentPadding: const EdgeInsets.fromLTRB(
12.0, 12.0, 12.0, 16.0),
children: <Widget>[
TextField(
autofocus: true,
enabled: true,
enableInteractiveSelection: true,
onSubmitted: (s) {
Navigator.of(context).pop(true);
GopherPage.openURI(
context,
Uri(
host: fields[2],
path: "/7" + fields[1] + "\t" + s,
port: int.parse(fields[3]),
));
},
),
],
);
},
);
}));
break;
case "i":
content.add(Padding(
child: Text(
fields[0],
style: tstyle,
softWrap: true,
),
padding: EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 2.0),
));
break;
case "h":
const PREFIX = "URL:";
content.add(ListTile(
title: Text(fields[0], style: tstyle),
trailing: Icon(fields[1].startsWith(PREFIX)
? Icons.launch
: Icons.block),
onTap: () {
if (fields[1].startsWith(PREFIX)) {
_launchURL(context, fields[1].substring(4));
} else {
Scaffold.of(context).showSnackBar(const SnackBar(
content: Text("Malformed gopher entry")));
}
}));
break;
case "g":
case "G":
case "i":
case "I":
case "p":
case "P":
content.add(ListTile(
title: Text(fields[0], style: tstyle),
trailing: Icon(Icons.arrow_forward),
onTap: () {
ImagePage.openURI(context, buildUri(type));
}));
break;
case ".":
content.add(Divider(color: Colors.green));
done = true;
break;
default:
content.add(ListTile(
title: Text(fields[0], style: tstyle),
trailing: Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
Text(type),
Icon(Icons.block, color: Colors.pink.withAlpha(150)),
],
),
));
break;
}
}
});
}
});
}).catchError((e) { Navigator.pop(context); });
}
}