Cleanup to get back on track
This commit is contained in:
parent
5c285fc082
commit
2221bd1d04
10 changed files with 658 additions and 140 deletions
|
@ -7,6 +7,9 @@
|
|||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
analyzer:
|
||||
errors:
|
||||
avoid_print: ignore
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
|
|
184
lib/gopher_browser.dart
Normal file
184
lib/gopher_browser.dart
Normal file
|
@ -0,0 +1,184 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:gophershy/gopherlib.dart';
|
||||
import 'package:gophershy/gopher_widgets.dart';
|
||||
|
||||
class _GopherBrowserState extends State<GopherBrowser> {
|
||||
// TODO: when is this updated? how is the widget.initialUrl at play?
|
||||
// TODO: We must have button: Menu! to attempt to render any text file as menu
|
||||
// Because when we visit from direct url we don't know what file type we get
|
||||
// Other detection methods could be applied later
|
||||
String currentUrl = "gopher://treebrary.org";
|
||||
// Directory listing doesn't have to end with / so we can never
|
||||
// be sure whether we're visiting menu or not.
|
||||
// When true, we try to parse anything as menu
|
||||
bool forceMenu = false;
|
||||
|
||||
void gotoUrl(String link) {
|
||||
if (link == currentUrl) return;
|
||||
setState(() {
|
||||
print("prev: $currentUrl Submitted: $link");
|
||||
currentUrl = link;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
print("GopherBrowseState: $currentUrl");
|
||||
return Column(
|
||||
children: [
|
||||
TextField(onSubmitted: gotoUrl),
|
||||
Expanded(
|
||||
child: GopherLoader(
|
||||
key: ValueKey(currentUrl),
|
||||
forceMenu: forceMenu,
|
||||
parsedGopherItem: ParsedGopherItem.parseUrl(currentUrl),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GopherBrowser extends StatefulWidget {
|
||||
final String initialUrl;
|
||||
const GopherBrowser({super.key, required this.initialUrl});
|
||||
|
||||
@override
|
||||
State<GopherBrowser> createState() => _GopherBrowserState();
|
||||
}
|
||||
|
||||
class _GopherLoaderState extends State<GopherLoader> {
|
||||
Future<LoadedGopherItem>? _data;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_data = widget.parsedGopherItem.load(forceMenu: widget.forceMenu);
|
||||
print("Reiniterd");
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
print("did:change: _data $_data");
|
||||
setState(() {
|
||||
_data = widget.parsedGopherItem.load(forceMenu: widget.forceMenu);
|
||||
});
|
||||
|
||||
print('didChangeDependencies, mounted: ${1} done? ');
|
||||
print("$_data");
|
||||
}
|
||||
|
||||
void goToNext(ParsedGopherItem item) {
|
||||
setState(() {
|
||||
print("requesting view of: ${item.url} ${item.documentType}");
|
||||
_data = item.load(forceMenu: false);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: FutureBuilder<LoadedGopherItem>(
|
||||
future: _data,
|
||||
builder: (cont, snap) {
|
||||
print("${snap.connectionState}");
|
||||
if (snap.connectionState != ConnectionState.done) {
|
||||
return const CircularProgressIndicator();
|
||||
} else {
|
||||
if (snap.hasData) {
|
||||
print("data & doctype: ${snap.data!}, ${snap.data!.parsed.documentType}, ");
|
||||
return switch (snap.data!.parsed.documentType) {
|
||||
DocumentType.TextFile => GopherText(snap.data!),
|
||||
DocumentType.Directory => GopherDirectory(
|
||||
parsedDirectory: snap.data!,
|
||||
onItemClick: goToNext,
|
||||
),
|
||||
_ => Container(color: Colors.yellow),
|
||||
};
|
||||
} else {
|
||||
return const Text("Errored");
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class GopherLoader extends StatefulWidget {
|
||||
final ParsedGopherItem parsedGopherItem;
|
||||
final bool? forceMenu;
|
||||
const GopherLoader(
|
||||
{super.key, required this.parsedGopherItem, this.forceMenu});
|
||||
|
||||
@override
|
||||
State<GopherLoader> createState() => _GopherLoaderState();
|
||||
}
|
||||
|
||||
class MenuItem extends StatelessWidget {
|
||||
static const Map<DocumentType, IconData> _iconMap = {
|
||||
DocumentType.TextFile: Icons.short_text,
|
||||
DocumentType.Directory: Icons.folder,
|
||||
DocumentType.CSO: Icons.contact_page,
|
||||
DocumentType.ErrorCode: Icons.error,
|
||||
DocumentType.BinHex: Icons.precision_manufacturing_sharp,
|
||||
DocumentType.DosBinary: Icons.precision_manufacturing_rounded,
|
||||
DocumentType.Uuencoded: Icons.precision_manufacturing_outlined,
|
||||
DocumentType.Search: Icons.search_rounded,
|
||||
DocumentType.Telnet: Icons.smart_screen_sharp,
|
||||
DocumentType.Telnet3270: Icons.smart_screen_sharp,
|
||||
DocumentType.Binary: Icons.file_download,
|
||||
DocumentType.MirrorOrAlternate: Icons.content_copy,
|
||||
DocumentType.Gif: Icons.animation,
|
||||
DocumentType.Image: Icons.image,
|
||||
DocumentType.Bitmap: Icons.picture_as_pdf,
|
||||
DocumentType.Movie: Icons.local_movies,
|
||||
DocumentType.Soundfile: Icons.music_note,
|
||||
DocumentType.Doc: Icons.document_scanner,
|
||||
DocumentType.Html: Icons.html,
|
||||
DocumentType.Informational: Icons.info,
|
||||
DocumentType.PngImage: Icons.hide_image_sharp,
|
||||
DocumentType.RichTextDocument: Icons.format_color_text,
|
||||
DocumentType.SoundFile: Icons.library_music,
|
||||
DocumentType.Pdf: Icons.picture_as_pdf,
|
||||
DocumentType.Xml: Icons.html_rounded,
|
||||
DocumentType.Unknown: Icons.question_mark,
|
||||
};
|
||||
|
||||
IconData _getIcon(DocumentType forWhat) {
|
||||
return _iconMap[forWhat] ?? Icons.question_mark_sharp;
|
||||
}
|
||||
|
||||
final ParsedGopherItem item;
|
||||
const MenuItem(this.item, {super.key});
|
||||
|
||||
void _itemTap(BuildContext cont) {
|
||||
Navigator.push(cont, MaterialPageRoute(builder: (cont) {
|
||||
if (item.documentType == DocumentType.Directory) {
|
||||
return const Text("it was a directory");
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO: Something something virtual item here as well
|
||||
if (item.documentType == DocumentType.Unknown) {
|
||||
return Text(item.name ?? "?");
|
||||
}
|
||||
// Put it into GestureDetector and on click open new route loading given item
|
||||
return GestureDetector(
|
||||
onTap: () => _itemTap(context),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(_getIcon(item.documentType)),
|
||||
Column(
|
||||
children: [
|
||||
Text(item.name ?? ""),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
66
lib/gopher_widgets.dart
Normal file
66
lib/gopher_widgets.dart
Normal file
|
@ -0,0 +1,66 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gophershy/gopherlib.dart';
|
||||
|
||||
/// Basic Gopher plaintext widget
|
||||
/// Should be able to display ASCII ART
|
||||
// TODO: separate ASCIIArtTextWidget and PlainTextWidget
|
||||
// First will scroll horizontlay, preserving formating,
|
||||
// Second will wrap lines to make it more readable on phones
|
||||
// TODO: Somehow add option to re-render plaintext as menu view
|
||||
class GopherText extends StatelessWidget {
|
||||
|
||||
final LoadedGopherItem item;
|
||||
|
||||
const GopherText(this.item, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO: Figure out how to stop line wrap
|
||||
// TODO: figure out how to switch between line wrap and no wrap
|
||||
return ListView.builder(
|
||||
itemCount: item.data?.length,
|
||||
itemBuilder: (context, int n) {
|
||||
return Text(
|
||||
utf8.decode(item.data![n], allowMalformed: true),
|
||||
style: const TextStyle(
|
||||
fontFamily: "SourceCodePro",
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.0,
|
||||
// Might need to be tuned for ASCII art
|
||||
letterSpacing: 1,
|
||||
wordSpacing: 1,
|
||||
overflow: TextOverflow.clip
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: BinaryWidget with option of downloading it
|
||||
//TODO: MediaWidget with option for displaying images, maybe playing WAVs?
|
||||
|
||||
/// Shows listing of gopher directory
|
||||
class GopherDirectory extends StatelessWidget {
|
||||
|
||||
final LoadedGopherItem parsedDirectory;
|
||||
final Function(ParsedGopherItem clickedOn) onItemClick;
|
||||
|
||||
const GopherDirectory({super.key, required this.parsedDirectory, required this.onItemClick});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
itemCount: parsedDirectory.asMenu!.length,
|
||||
itemBuilder: (context, int n) {
|
||||
final item = parsedDirectory.asMenu![n];
|
||||
return GestureDetector(
|
||||
onTap: () => item.documentType == DocumentType.Informational ? null : onItemClick(item),
|
||||
child: Text(item.name ?? "err"));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
220
lib/gopherlib.dart
Normal file
220
lib/gopherlib.dart
Normal file
|
@ -0,0 +1,220 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
enum DocumentType {
|
||||
/*Canonical types RFC1436 line 548*/
|
||||
/// Item is a file (0)
|
||||
TextFile("0"),
|
||||
|
||||
/// Item is a directory (1)
|
||||
Directory("1"),
|
||||
|
||||
/// Item is a CSO phone-book server (2)
|
||||
CSO("2"),
|
||||
|
||||
/// Error returned by server (3)
|
||||
ErrorCode("3"),
|
||||
|
||||
/// Item is a BinHexed Macintosh file. (4)
|
||||
BinHex("4"),
|
||||
|
||||
/// Item is DOS binary archive of some sort. (5)
|
||||
DosBinary("5"),
|
||||
|
||||
/// Item is a UNIX uuencoded file. (6)
|
||||
Uuencoded("6"),
|
||||
|
||||
/// Item is an Index-Search server. (7)
|
||||
Search("7"),
|
||||
|
||||
/// Item points to a text-based telnet session. (8)
|
||||
Telnet("8"),
|
||||
|
||||
/// Item is a binary file! (9)
|
||||
Binary("9"),
|
||||
|
||||
/// It em is a redundant server (*)
|
||||
MirrorOrAlternate("*"),
|
||||
|
||||
/// Item points to a text-based tn3270 session. (T)
|
||||
Telnet3270("T"),
|
||||
|
||||
/// Item is a GIF format graphics file. (g)
|
||||
Gif("g"),
|
||||
|
||||
/// Item is some kind of image file. Client decides how to display. (I)
|
||||
Image("I"),
|
||||
/**** Gopher+ ****/
|
||||
/// Bitmap image (:)
|
||||
Bitmap(":"),
|
||||
|
||||
/// Movie file (;)
|
||||
Movie(";"),
|
||||
|
||||
/// Sound file (<)
|
||||
Soundfile("<"),
|
||||
/*non-canonical types*/
|
||||
/// Doc. Used for .doc and .pdf (d)
|
||||
Doc("d"),
|
||||
|
||||
/// HTML file (h)
|
||||
Html("h"),
|
||||
|
||||
/// Informational message, widely used (i)
|
||||
Informational("i"),
|
||||
|
||||
/// Usually png image (p)
|
||||
PngImage("p"),
|
||||
|
||||
/// Rich text document (r)
|
||||
RichTextDocument("r"),
|
||||
|
||||
/// usually WAV (s)
|
||||
SoundFile("s"),
|
||||
|
||||
/// Pdf (P)
|
||||
Pdf("P"),
|
||||
|
||||
/// XML file (X)
|
||||
Xml("X"),
|
||||
|
||||
/// Document type isn't any of the known types
|
||||
Unknown("?");
|
||||
|
||||
const DocumentType(this.code);
|
||||
final String code;
|
||||
|
||||
/// Returns the canonical name of the document type
|
||||
String get typeName => name;
|
||||
|
||||
/// Returns character code used for this document type
|
||||
String get typeCode => code;
|
||||
}
|
||||
|
||||
class ParsedGopherItem {
|
||||
static final docMap = {for (var item in DocumentType.values) item.code: item};
|
||||
|
||||
Uri url;
|
||||
String? name;
|
||||
DocumentType documentType;
|
||||
|
||||
ParsedGopherItem._(this.url, this.name, this.documentType);
|
||||
|
||||
/// Parse gopher item from url, from url file type is not known
|
||||
/// so it is guessed by file extension
|
||||
// TODO: Do actuall filetype guessing
|
||||
static ParsedGopherItem parseUrl(String url) {
|
||||
if (!url.startsWith("gopher://")) url = "gopher://$url";
|
||||
var uri = Uri.parse(url);
|
||||
|
||||
// We expect TextFile by default unless we think it's menu
|
||||
final doctype = (uri.path == "" || uri.path.endsWith("/")) ? DocumentType.Directory : DocumentType.TextFile;
|
||||
|
||||
if (uri.port == 0) uri = uri.replace(port: 70);
|
||||
|
||||
return ParsedGopherItem._(uri, "", doctype);
|
||||
}
|
||||
|
||||
/// Parses single gopher item from single menu line
|
||||
static ParsedGopherItem? parseMenuLine(String menuLine) {
|
||||
// If theres not even the item type and name we won't bother
|
||||
if (menuLine.length < 2) return null;
|
||||
|
||||
var parts = menuLine.split("\t");
|
||||
|
||||
if (parts.length != 4) return null;
|
||||
|
||||
// If string is correct, we should have 4 parts
|
||||
// 0Pony Prices Prices/Ponies pserver.bookstore.umn.edu 70
|
||||
//type;name ; selektor ; host ; port
|
||||
// 0;Pony Prices; Prices/Ponies; pserver.bookstore.umn.edu; 70
|
||||
var uri = Uri(
|
||||
scheme: "gopher",
|
||||
path: parts[1],
|
||||
host: parts[2],
|
||||
port: int.tryParse(parts[3]) ?? 70,
|
||||
);
|
||||
|
||||
String type = menuLine[0];
|
||||
String name = parts[0].substring(1);
|
||||
|
||||
final docType = docMap.containsKey(type) ? docMap[type]! : docMap["?"]!;
|
||||
return ParsedGopherItem._(uri, name, docType);
|
||||
}
|
||||
|
||||
/// Reads gopher directory/menu from host:port and optionally selector
|
||||
static Future<LoadedGopherItem> loadMenu(String host, int port,
|
||||
{String selector = ""}) async {
|
||||
|
||||
final Socket sock = await Socket.connect(host, port);
|
||||
sock.write("$selector\r\n");
|
||||
await sock.flush();
|
||||
print("Entered load menu");
|
||||
final ret = <ParsedGopherItem>[];
|
||||
final List<Uint8List> rawRet = <Uint8List>[];
|
||||
final subscription = sock.listen(
|
||||
(Uint8List data) {
|
||||
rawRet.add(data);
|
||||
String rcvd = utf8.decode(data, allowMalformed: true);
|
||||
|
||||
ParsedGopherItem? apend;
|
||||
// TODO: This is flawed, it usually works but it's just a matter of time before it breaks
|
||||
// If \n is in the next buffer, the part before \n gets cutoff
|
||||
// resulting in unparsable line
|
||||
// the substrining must begin outside the loop listen call
|
||||
// or it must be rewritten using LineSplitter()
|
||||
int start = 0;
|
||||
for (int i = 0; i < rcvd.length; i++) {
|
||||
if (rcvd[i] == "\n") {
|
||||
var menuLine = rcvd.substring(start, i);
|
||||
// TODO: Gophernicus sends .\r for some reason which doesnt seem acording to spec
|
||||
// Maybe ignore when sent from menu=forced?
|
||||
if (menuLine.startsWith(".")) {
|
||||
print("Received starting dot, this should be the end I guess");
|
||||
return;
|
||||
}
|
||||
apend = parseMenuLine(menuLine);
|
||||
if (apend != null) ret.add(apend);
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
await subscription.asFuture<void>();
|
||||
sock.close();
|
||||
return LoadedGopherItem(
|
||||
ParsedGopherItem._(Uri.parse("gopher://$host:$port$selector"), selector,
|
||||
DocumentType.Directory),
|
||||
asMenu: ret,
|
||||
data: rawRet,
|
||||
);
|
||||
}
|
||||
|
||||
Future<LoadedGopherItem> loadItem() async {
|
||||
final Socket sock = await Socket.connect(url.host, url.port);
|
||||
|
||||
print("Sending request to ${url.host}[:${url.port}] => '${url.path}'");
|
||||
sock.write("${url.path}\r\n");
|
||||
var data = await sock.toList();
|
||||
print("Closing: ${sock.close()} as type ${this.documentType}");
|
||||
|
||||
return LoadedGopherItem(this, data: data);
|
||||
}
|
||||
|
||||
Future<LoadedGopherItem> load({bool? forceMenu}) async {
|
||||
if ((documentType == DocumentType.Directory) || (forceMenu ?? false)) {
|
||||
print("Forcing? ($forceMenu) reload as menu for: $url");
|
||||
return loadMenu(url.host, url.port, selector: url.path);
|
||||
}
|
||||
return loadItem();
|
||||
}
|
||||
}
|
||||
|
||||
class LoadedGopherItem {
|
||||
final ParsedGopherItem parsed;
|
||||
final List<Uint8List>? data;
|
||||
final List<ParsedGopherItem>? asMenu;
|
||||
|
||||
LoadedGopherItem(this.parsed, {this.data, this.asMenu});
|
||||
}
|
129
lib/main.dart
129
lib/main.dart
|
@ -1,125 +1,40 @@
|
|||
// Warnings ignored for development, we can spit 'const' everywhere
|
||||
// once it will matter
|
||||
// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:gophershy/gopher_browser.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
runApp(const GopherShy());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
class GopherShy extends StatelessWidget {
|
||||
const GopherShy({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
title: 'GopherShy',
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
// TRY THIS: Try running your application with "flutter run". You'll see
|
||||
// the application has a purple toolbar. Then, without quitting the app,
|
||||
// try changing the seedColor in the colorScheme below to Colors.green
|
||||
// and then invoke "hot reload" (save your changes or press the "hot
|
||||
// reload" button in a Flutter-supported IDE, or press "r" if you used
|
||||
// the command line to start the app).
|
||||
//
|
||||
// Notice that the counter didn't reset back to zero; the application
|
||||
// state is not lost during the reload. To reset the state, use hot
|
||||
// restart instead.
|
||||
//
|
||||
// This works for code too, not just values: Most code changes can be
|
||||
// tested with just a hot reload.
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
// This call to setState tells the Flutter framework that something has
|
||||
// changed in this State, which causes it to rerun the build method below
|
||||
// so that the display can reflect the updated values. If we changed
|
||||
// _counter without calling setState(), then the build method would not be
|
||||
// called again, and so nothing would appear to happen.
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// TRY THIS: Try changing the color here to a specific color (to
|
||||
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
|
||||
// change color while the other colors stay the same.
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
//
|
||||
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
|
||||
// action in the IDE, or press "p" in the console), to see the
|
||||
// wireframe for each widget.
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You have pushed the button this many times:',
|
||||
),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
],
|
||||
home: SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(50),
|
||||
child: Text(
|
||||
"Whoaaaaa",
|
||||
style: TextStyle(fontSize: 50),
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
// In future replace with bookmark view
|
||||
body: GopherBrowser(initialUrl: "gopher://treebrary.org"),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
181
pubspec.lock
181
pubspec.lock
|
@ -49,6 +49,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -67,39 +83,35 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
gopherlib:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: stable
|
||||
resolved-ref: "2e6bf1902e9f21fdd59f2033196f984460814559"
|
||||
url: "https://git.treebrary.org/Felisp/gopherlib.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.0"
|
||||
version: "10.0.5"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "3.0.5"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -120,18 +132,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
version: "1.15.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -140,6 +152,102 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -189,10 +297,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
version: "0.7.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -205,9 +313,34 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.0"
|
||||
version: "14.2.5"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.4"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
sdks:
|
||||
dart: ">=3.3.4 <=3.4.0"
|
||||
dart: ">=3.4.0 <4.0.0"
|
||||
flutter: ">=3.19.0"
|
||||
|
|
15
pubspec.yaml
15
pubspec.yaml
|
@ -27,10 +27,7 @@ environment:
|
|||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
gopherlib:
|
||||
git:
|
||||
url: https://git.treebrary.org/Felisp/gopherlib.git
|
||||
ref: stable # Development will follow alongside
|
||||
shared_preferences: ^2.2.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -70,11 +67,11 @@ flutter:
|
|||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
fonts:
|
||||
- family: SourceCodePro
|
||||
fonts:
|
||||
- asset: res/fonts/SourceCodePro-VariableFont_wght.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
|
|
BIN
res/fonts/Domine-VariableFont_wght.ttf
Normal file
BIN
res/fonts/Domine-VariableFont_wght.ttf
Normal file
Binary file not shown.
BIN
res/fonts/SourceCodePro-Medium.ttf
Normal file
BIN
res/fonts/SourceCodePro-Medium.ttf
Normal file
Binary file not shown.
BIN
res/fonts/SourceCodePro-VariableFont_wght.ttf
Normal file
BIN
res/fonts/SourceCodePro-VariableFont_wght.ttf
Normal file
Binary file not shown.
Loading…
Reference in a new issue