Cleanup to get back on track

This commit is contained in:
Felisp 2024-11-05 01:30:46 +01:00
parent 5c285fc082
commit 2221bd1d04
10 changed files with 658 additions and 140 deletions

View file

@ -7,6 +7,9 @@
# The following line activates a set of recommended lints for Flutter apps, # The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices. # packages, and plugins designed to encourage good coding practices.
analyzer:
errors:
avoid_print: ignore
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
linter: linter:

184
lib/gopher_browser.dart Normal file
View 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
View 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
View 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});
}

View file

@ -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 'package:flutter/material.dart';
import 'dart:io';
import 'package:gophershy/gopher_browser.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const GopherShy());
} }
class MyApp extends StatelessWidget { class GopherShy extends StatelessWidget {
const MyApp({super.key}); const GopherShy({super.key});
// This widget is the root of your application. // This widget is the root of your application.
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'Flutter Demo', title: 'GopherShy',
theme: ThemeData( 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), colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true, useMaterial3: true,
), ),
home: const MyHomePage(title: 'Flutter Demo Home Page'), home: SafeArea(
); child: Scaffold(
} appBar: PreferredSize(
} preferredSize: Size.fromHeight(50),
child: Text(
class MyHomePage extends StatefulWidget { "Whoaaaaa",
const MyHomePage({super.key, required this.title}); style: TextStyle(fontSize: 50),
),
// 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 // In future replace with bookmark view
// how it looks. body: GopherBrowser(initialUrl: "gopher://treebrary.org"),
)),
// 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,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
); );
} }
} }

View file

@ -49,6 +49,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" 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: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -67,39 +83,35 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
gopherlib: flutter_web_plugins:
dependency: "direct main" dependency: transitive
description: description: flutter
path: "." source: sdk
ref: stable version: "0.0.0"
resolved-ref: "2e6bf1902e9f21fdd59f2033196f984460814559"
url: "https://git.treebrary.org/Felisp/gopherlib.git"
source: git
version: "0.0.1"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.0" version: "10.0.5"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.5"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_testing name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -120,18 +132,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.0" version: "0.11.1"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.15.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -140,6 +152,102 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -189,10 +297,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" version: "0.7.2"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -205,9 +313,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted 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: sdks:
dart: ">=3.3.4 <=3.4.0" dart: ">=3.4.0 <4.0.0"
flutter: ">=3.19.0"

View file

@ -27,10 +27,7 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
gopherlib: shared_preferences: ^2.2.3
git:
url: https://git.treebrary.org/Felisp/gopherlib.git
ref: stable # Development will follow alongside
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -70,10 +67,10 @@ flutter:
# "family" key with the font family name, and a "fonts" key with a # "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For # list giving the asset and other descriptors for the font. For
# example: # example:
# fonts: fonts:
# - family: Schyler - family: SourceCodePro
# fonts: fonts:
# - asset: fonts/Schyler-Regular.ttf - asset: res/fonts/SourceCodePro-VariableFont_wght.ttf
# - asset: fonts/Schyler-Italic.ttf # - asset: fonts/Schyler-Italic.ttf
# style: italic # style: italic
# - family: Trajan Pro # - family: Trajan Pro

Binary file not shown.

Binary file not shown.

Binary file not shown.