mirror of
https://github.com/ChronosX88/wind.git
synced 2024-12-04 18:12:18 +00:00
Implement thread list loading
This commit is contained in:
parent
b6a8a9fa96
commit
fabb0767b5
@ -1,9 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wind/newsgroup_list_view.dart';
|
||||
import 'package:wind/nntp_client.dart';
|
||||
import 'package:wind/thread_list_view.dart';
|
||||
import 'package:wind/thread_screen.dart';
|
||||
|
||||
import 'thread_list_model.dart';
|
||||
|
||||
void main() {
|
||||
runApp(MyApp());
|
||||
runApp(MultiProvider(providers: [
|
||||
Provider<NNTPClient>(create: ((context) => NNTPClient("localhost:1120"))),
|
||||
ChangeNotifierProxyProvider<NNTPClient, ThreadListModel>(
|
||||
create: (context) => ThreadListModel(),
|
||||
update: (context, client, model) {
|
||||
model!.client = client;
|
||||
return model;
|
||||
}),
|
||||
], child: MyApp()));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@ -16,6 +29,7 @@ class MyApp extends StatelessWidget {
|
||||
primarySwatch: Colors.indigo,
|
||||
),
|
||||
home: MyHomePage(title: 'Wind'),
|
||||
routes: {'/thread': (context) => ThreadScreen()},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -72,14 +86,15 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (context) =>
|
||||
NewsgroupListView(nntpClient))),
|
||||
NewsgroupListView(client: nntpClient))),
|
||||
],
|
||||
),
|
||||
),
|
||||
const VerticalDivider(thickness: 1, width: 1),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: ThreadsListView(),
|
||||
child: Consumer<ThreadListModel>(
|
||||
builder: ((context, value, child) => ThreadListView())),
|
||||
))
|
||||
],
|
||||
),
|
||||
@ -87,67 +102,3 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadsListView extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() => ThreadsListViewState();
|
||||
}
|
||||
|
||||
class ThreadsListViewState extends State<ThreadsListView> {
|
||||
String currentGroup = "no group selected";
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(width: 640, child: Center(child: Text(currentGroup)));
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadListItemView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 5,
|
||||
child: InkWell(
|
||||
splashColor: Colors.indigo.withAlpha(30),
|
||||
onTap: () => {},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
child: Text(
|
||||
"A question for those who watched The Matrix in 1999.",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 21),
|
||||
),
|
||||
margin: EdgeInsets.only(top: 16, left: 16, right: 16),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"@sample_user",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.blue,
|
||||
fontSize: 15),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
"14/04/22 Чтв 00:57:52",
|
||||
style: TextStyle(fontSize: 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
margin: EdgeInsets.only(top: 5, bottom: 2, left: 16, right: 16),
|
||||
),
|
||||
Container(
|
||||
child: Text(
|
||||
"So I'm 16 years old, and I finally watched the first Matrix movie yesterday, and I found it amazing. Everything about it was wonderful. Anyway, I watched it with my mom and she was gushing over it again, telling me about how it blew her away when she watched it in the theaters when it came out.\nAnd that inspired me to ask this question. To any of you in this subreddit who watched the movie when it was released, do you have any fond memories or stories about your experience in the theater that you can tell me about?\nI'd really like to hear them. 👍",
|
||||
style: TextStyle(fontSize: 17)),
|
||||
margin: EdgeInsets.all(16),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wind/nntp_client.dart';
|
||||
import 'package:wind/thread_list_model.dart';
|
||||
|
||||
class NewsgroupListView extends StatefulWidget {
|
||||
NewsgroupListView({Key? key, required this.client}) : super(key: key);
|
||||
|
||||
class NewsgroupListView extends StatelessWidget {
|
||||
final NNTPClient client;
|
||||
|
||||
NewsgroupListView(this.client);
|
||||
@override
|
||||
State<StatefulWidget> createState() => new NewsgroupListViewState(client);
|
||||
}
|
||||
|
||||
class NewsgroupListViewState extends State<NewsgroupListView> {
|
||||
late NNTPClient client;
|
||||
int _selectedIndex = -1;
|
||||
|
||||
NewsgroupListViewState(this.client);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -13,7 +25,7 @@ class NewsgroupListView extends StatelessWidget {
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
List<GroupInfo> data = snapshot.data!;
|
||||
return _newgroupListView(data);
|
||||
return _newsgroupListView(data);
|
||||
} else if (snapshot.hasError) {
|
||||
return Text("${snapshot.error}");
|
||||
}
|
||||
@ -26,7 +38,7 @@ class NewsgroupListView extends StatelessWidget {
|
||||
return await client.getNewsGroupList();
|
||||
}
|
||||
|
||||
ListView _newgroupListView(List<GroupInfo> data) {
|
||||
Widget _newsgroupListView(List<GroupInfo> data) {
|
||||
return ListView.builder(
|
||||
itemCount: data.length,
|
||||
itemBuilder: (context, index) {
|
||||
@ -34,8 +46,14 @@ class NewsgroupListView extends StatelessWidget {
|
||||
style: ListTileStyle.drawer,
|
||||
title: Text(data[index].name),
|
||||
subtitle: Text(data[index].description),
|
||||
onTap: () => {});
|
||||
},
|
||||
);
|
||||
selected: index == _selectedIndex,
|
||||
onTap: () {
|
||||
setState(() => _selectedIndex = index);
|
||||
var model = context.read<ThreadListModel>();
|
||||
model
|
||||
.selectNewsgroup(data[index].name)
|
||||
.whenComplete(() => null);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'package:enough_mail/mime_message.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class NNTPClient {
|
||||
late WebSocketChannel _channel;
|
||||
final Queue<_NNTPCommand> commandQueue = new Queue();
|
||||
final List<String> tempBuffer = [];
|
||||
|
||||
String? currentGroup;
|
||||
|
||||
NNTPClient(String addr) {
|
||||
_channel = WebSocketChannel.connect(
|
||||
@ -12,14 +17,30 @@ class NNTPClient {
|
||||
);
|
||||
|
||||
_channel.stream.listen((data) {
|
||||
if ((data as String).contains("201")) {
|
||||
if ((data as String).startsWith("201")) {
|
||||
// skip welcome message
|
||||
return;
|
||||
}
|
||||
var command = commandQueue.removeFirst();
|
||||
var resp = data.toString();
|
||||
var respLines = resp.split("\r\n");
|
||||
respLines.removeWhere((element) => element == "");
|
||||
if (respLines.last == "") respLines.removeLast(); // trailing empty line
|
||||
|
||||
if ((respLines.length > 1 || tempBuffer.isNotEmpty) &&
|
||||
respLines.last.codeUnits.last != ".".codeUnits.first) {
|
||||
// if it's multiline response and it doesn't contain dot in the end
|
||||
// then looks like we need to wait for next message to concatenate with current msg
|
||||
tempBuffer.add(resp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tempBuffer.isNotEmpty) {
|
||||
tempBuffer.add(resp);
|
||||
resp = tempBuffer.join();
|
||||
respLines = resp.split("\r\n");
|
||||
respLines.removeLast(); // trailing empty line
|
||||
tempBuffer.clear();
|
||||
}
|
||||
var command = commandQueue.removeFirst();
|
||||
var respCode = int.parse(respLines[0].split(" ")[0]);
|
||||
command.responseCompleter.complete(_CommandResponse(respCode, respLines));
|
||||
});
|
||||
@ -76,6 +97,35 @@ class NNTPClient {
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
Future<void> selectGroup(String name) async {
|
||||
await _sendCommand("GROUP", [name]).then((value) => {currentGroup = name});
|
||||
}
|
||||
|
||||
Future<List<Tuple2<int, MimeMessage>>> getNewThreads(
|
||||
int perPage, int pageNum) async {
|
||||
if (currentGroup == null) throw new ArgumentError("current group is null");
|
||||
|
||||
List<Tuple2<int, MimeMessage>> threads = [];
|
||||
|
||||
var newThreadList = await _sendCommand(
|
||||
"NEWTHREADS", [perPage.toString(), pageNum.toString()]);
|
||||
|
||||
newThreadList.lines.removeAt(0);
|
||||
newThreadList.lines.removeLast(); // remove dot
|
||||
|
||||
await Future.forEach<String>(newThreadList.lines, (element) async {
|
||||
await _sendCommand("ARTICLE", [element]).then((response) {
|
||||
response.lines.removeAt(0);
|
||||
response.lines.removeLast();
|
||||
var rawMsg = response.lines.join("\r\n");
|
||||
threads
|
||||
.add(Tuple2(int.parse(element), MimeMessage.parseFromText(rawMsg)));
|
||||
});
|
||||
});
|
||||
|
||||
return threads;
|
||||
}
|
||||
}
|
||||
|
||||
class _NNTPCommand {
|
||||
|
50
lib/thread_list_model.dart
Normal file
50
lib/thread_list_model.dart
Normal file
@ -0,0 +1,50 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:wind/nntp_client.dart';
|
||||
import 'package:wind/thread_list_view.dart';
|
||||
|
||||
class ThreadListModel extends ChangeNotifier {
|
||||
String currentGroup = "";
|
||||
NNTPClient? client;
|
||||
Map<String, Map<int, List<ThreadItem>>> threads = {};
|
||||
|
||||
Future<void> selectNewsgroup(String name) async {
|
||||
if (currentGroup == name) return;
|
||||
|
||||
currentGroup = name;
|
||||
await client!.selectGroup(name);
|
||||
threads.putIfAbsent(name, () => {});
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<List<ThreadItem>> getNewThreads(
|
||||
int perPage, int pageNum, bool clearCache) async {
|
||||
if (currentGroup == "") return [];
|
||||
List<ThreadItem> items = [];
|
||||
|
||||
if (clearCache) {
|
||||
threads[currentGroup]?.clear();
|
||||
}
|
||||
|
||||
if (threads[currentGroup]!.containsKey(pageNum)) {
|
||||
items.addAll(threads[currentGroup]![pageNum]!);
|
||||
} else {
|
||||
var resp = await client!.getNewThreads(perPage, pageNum);
|
||||
resp.forEach((pair) {
|
||||
var number = pair.item1;
|
||||
var msg = pair.item2;
|
||||
items.add(ThreadItem(
|
||||
msg.getHeaderValue("Message-Id")!,
|
||||
number,
|
||||
msg.getHeaderValue("Subject")!,
|
||||
msg.getHeaderValue("From")!,
|
||||
msg.getHeaderValue("Date")!,
|
||||
msg.decodeTextPlainPart()!));
|
||||
});
|
||||
|
||||
threads[currentGroup]![pageNum] = items;
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
150
lib/thread_list_view.dart
Normal file
150
lib/thread_list_view.dart
Normal file
@ -0,0 +1,150 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wind/thread_list_model.dart';
|
||||
|
||||
class ThreadListView extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() => ThreadListViewState();
|
||||
}
|
||||
|
||||
class ThreadListViewState extends State<ThreadListView> {
|
||||
List<ThreadItem> _items = [];
|
||||
int _pageNum = 0;
|
||||
String _curGroup = "";
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 640,
|
||||
padding: EdgeInsets.only(top: 16),
|
||||
child: FutureBuilder<List<ThreadItem>>(
|
||||
future: _fetchThreadList(context),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState != ConnectionState.waiting) {
|
||||
List<ThreadItem> data = List.from(snapshot.data!);
|
||||
_items.addAll(data);
|
||||
if (_items.isNotEmpty &&
|
||||
_items.last.number != -100500 &&
|
||||
data.isNotEmpty)
|
||||
_items.add(ThreadItem("", -100500, "", "", "",
|
||||
"")); // magic item (for button "load more")
|
||||
return _curGroup != ""
|
||||
? _threadView()
|
||||
: Center(
|
||||
child: Text("Newsgroup is not selected",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 16)));
|
||||
} else if (snapshot.hasError) {
|
||||
return Text("${snapshot.error}");
|
||||
}
|
||||
return Center(child: CircularProgressIndicator());
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
Future<List<ThreadItem>> _fetchThreadList(BuildContext context) async {
|
||||
var model = context.read<ThreadListModel>();
|
||||
if (model.currentGroup != _curGroup) {
|
||||
_items.clear();
|
||||
_curGroup = model.currentGroup;
|
||||
_pageNum = 0;
|
||||
}
|
||||
return await model.getNewThreads(10, _pageNum, false);
|
||||
}
|
||||
|
||||
Widget _threadView() {
|
||||
return _items.isNotEmpty
|
||||
? ListView.builder(
|
||||
itemCount: _items.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (_items[index].number == -100500) {
|
||||
return Container(
|
||||
height: 100,
|
||||
padding: EdgeInsets.all(20),
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
foregroundColor:
|
||||
MaterialStateProperty.all<Color>(Colors.blue),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_pageNum += 1;
|
||||
_items.removeLast();
|
||||
});
|
||||
},
|
||||
child: Text('Load more'),
|
||||
),
|
||||
);
|
||||
} else
|
||||
return ThreadListItemView(item: _items[index]);
|
||||
})
|
||||
: Center(
|
||||
child: Text("This newsgroup is empty",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)));
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadListItemView extends StatelessWidget {
|
||||
const ThreadListItemView({Key? key, required this.item}) : super(key: key);
|
||||
|
||||
final ThreadItem item;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 5,
|
||||
child: InkWell(
|
||||
splashColor: Colors.indigo.withAlpha(30),
|
||||
onTap: () => Navigator.pushNamed(context, "/thread"),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
child: Text(
|
||||
item.subject,
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 21),
|
||||
),
|
||||
margin: EdgeInsets.only(top: 16, left: 16, right: 16),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
item.author,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.blue,
|
||||
fontSize: 15),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
item.date,
|
||||
style: TextStyle(fontSize: 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
margin: EdgeInsets.only(top: 5, bottom: 2, left: 16, right: 16),
|
||||
),
|
||||
Container(
|
||||
child: Text(item.body, style: TextStyle(fontSize: 17)),
|
||||
margin: EdgeInsets.all(16),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadItem {
|
||||
final String id;
|
||||
final int number;
|
||||
final String subject;
|
||||
final String author;
|
||||
final String date;
|
||||
final String body;
|
||||
|
||||
ThreadItem(
|
||||
this.id, this.number, this.subject, this.author, this.date, this.body);
|
||||
}
|
17
lib/thread_screen.dart
Normal file
17
lib/thread_screen.dart
Normal file
@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ThreadScreen extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() => ThreadScreenState();
|
||||
}
|
||||
|
||||
class ThreadScreenState extends State<ThreadScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Thread"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
162
pubspec.lock
162
pubspec.lock
@ -1,6 +1,20 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
asn1lib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -8,6 +22,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
basic_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: basic_utils
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.9.4"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -43,6 +64,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -57,6 +85,41 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
encrypt:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: encrypt
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.1"
|
||||
enough_convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: enough_convert
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
enough_mail:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: enough_mail
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.6"
|
||||
enough_serialization:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: enough_serialization
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
event_bus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: event_bus
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -74,6 +137,48 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.13.4"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.17.0"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.4"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.4.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -95,6 +200,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -102,6 +214,41 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
pedantic:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pedantic
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.5.2"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1+1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -149,6 +296,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.9"
|
||||
tuple:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: tuple
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -170,5 +324,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.4.1"
|
||||
sdks:
|
||||
dart: ">=2.17.0-0 <3.0.0"
|
||||
flutter: ">=1.16.0"
|
||||
|
@ -29,6 +29,9 @@ dependencies:
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
web_socket_channel: ^2.1.0
|
||||
provider: ^6.0.2
|
||||
enough_mail: ^1.3.6
|
||||
tuple: ^2.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
Reference in New Issue
Block a user