From 755679c90f5929bf5f46b574e55be19ce050c32d Mon Sep 17 00:00:00 2001 From: ChronosX88 Date: Mon, 18 Apr 2022 02:26:57 +0300 Subject: [PATCH] Prototype send message form, fix bug with route --- lib/main.dart | 22 +++++++- lib/message_item_view.dart | 89 ++++++++++++++++---------------- lib/nntp_client.dart | 12 +++-- lib/thread_list_view.dart | 4 +- lib/thread_model.dart | 18 +++++++ lib/thread_screen.dart | 101 +++++++++++++++++++++++++++++++++---- 6 files changed, 184 insertions(+), 62 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 721c290..7c5e281 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -36,7 +36,27 @@ class MyApp extends StatelessWidget { primarySwatch: Colors.indigo, ), home: MyHomePage(title: 'Wind'), - routes: {'/thread': (context) => ThreadScreen()}, + onGenerateRoute: (settings) { + Widget? pageView; + if (settings.name != null) { + var uriData = Uri.parse(settings.name!); + + switch (uriData.path) { + case '/thread': + pageView = ThreadScreen( + threadNumber: + int.parse(uriData.queryParametersAll['num']!.first)); + break; + default: + pageView = MyHomePage(title: 'Wind'); + break; + } + } + if (pageView != null) { + return MaterialPageRoute( + settings: settings, builder: (BuildContext context) => pageView!); + } + }, ); } } diff --git a/lib/message_item_view.dart b/lib/message_item_view.dart index fab63d0..8cc3f18 100644 --- a/lib/message_item_view.dart +++ b/lib/message_item_view.dart @@ -13,56 +13,55 @@ class MessageItemView extends StatelessWidget { margin: this.isOpPost ? EdgeInsets.only(bottom: 10) : EdgeInsets.all(0), child: Card( elevation: 5, - child: InkWell( - splashColor: Colors.indigo.withAlpha(30), - onTap: () => {}, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - isOpPost - ? Container( - child: Text( - item.subject!, - style: TextStyle( - fontWeight: FontWeight.bold, fontSize: 21), - ), - margin: EdgeInsets.only(top: 16, left: 16, right: 16), - ) - : Container(), - Container( - child: Row( - children: [ - Text( - item.author, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + isOpPost + ? Container( + child: Text( + item.subject!, style: TextStyle( - fontWeight: FontWeight.normal, - color: Colors.blue, - fontSize: 15), + fontWeight: FontWeight.bold, fontSize: 21), ), - SizedBox(width: 5), - Text( - item.date, - style: TextStyle(fontSize: 15), - ), - SizedBox(width: 5), - Text( + margin: EdgeInsets.only(top: 16, left: 16, right: 16), + ) + : Container(), + 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), + ), + SizedBox(width: 5), + InkWell( + child: Text( "#${item.number}", style: TextStyle(fontSize: 15, color: Colors.grey), - ) - ], - ), - margin: isOpPost - ? EdgeInsets.only( - top: 5, bottom: 2, left: 16, right: 16) - : EdgeInsets.only(top: 16, left: 16), + ), + onTap: () => {}, + ) + ], ), - Container( - child: Text(item.body, style: TextStyle(fontSize: 17)), - margin: EdgeInsets.all(16), - ) - ], - ), + margin: isOpPost + ? EdgeInsets.only(top: 5, bottom: 2, left: 16, right: 16) + : EdgeInsets.only(top: 16, left: 16), + ), + Container( + child: + SelectableText(item.body, style: TextStyle(fontSize: 17)), + margin: EdgeInsets.all(16), + ) + ], ))); } } diff --git a/lib/nntp_client.dart b/lib/nntp_client.dart index 999b6d0..1482436 100644 --- a/lib/nntp_client.dart +++ b/lib/nntp_client.dart @@ -127,14 +127,18 @@ class NNTPClient { return threads; } - Future>> getThread( - int threadNumber) async { + Future getPost(int postNumber) async { + var resp = await _sendCommand("ARTICLE", [postNumber.toString()]); + resp.lines.removeLast(); + return MimeMessage.parseFromText(resp.lines.join("\r\n")); + } + + Future>> getThread(int threadNumber) async { if (currentGroup == null) throw new ArgumentError("current group is null"); List> threads = []; - var newThreadList = await _sendCommand( - "THREAD", [threadNumber.toString()]); + var newThreadList = await _sendCommand("THREAD", [threadNumber.toString()]); newThreadList.lines.removeAt(0); newThreadList.lines.removeLast(); // remove dot diff --git a/lib/thread_list_view.dart b/lib/thread_list_view.dart index 901d449..5c45ff7 100644 --- a/lib/thread_list_view.dart +++ b/lib/thread_list_view.dart @@ -100,8 +100,8 @@ class ThreadListItemView extends StatelessWidget { elevation: 5, child: InkWell( splashColor: Colors.indigo.withAlpha(30), - onTap: () => Navigator.pushNamed(context, "/thread", - arguments: ThreadScreenArguments(item)), + onTap: () => + Navigator.pushNamed(context, "/thread?num=${item.number}"), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, diff --git a/lib/thread_model.dart b/lib/thread_model.dart index 297bdfa..bade876 100644 --- a/lib/thread_model.dart +++ b/lib/thread_model.dart @@ -5,6 +5,24 @@ import 'package:wind/nntp_client.dart'; class ThreadModel extends ChangeNotifier { NNTPClient? client; + String _commentText = ""; + String get commentText => _commentText; + set commentText(String text) { + _commentText = text; + notifyListeners(); + } + + Future getPost(int number) async { + var msg = await client!.getPost(number); + return MessageItem( + msg.getHeaderValue("Message-Id")!, + number, + msg.getHeaderValue("Subject")!, + msg.getHeaderValue("From")!, + msg.getHeaderValue("Date")!, + msg.decodeTextPlainPart()!); + } + Future> getThread(int threadNumber) async { if (client!.currentGroup == "") return []; diff --git a/lib/thread_screen.dart b/lib/thread_screen.dart index eace40a..ca0b7c0 100644 --- a/lib/thread_screen.dart +++ b/lib/thread_screen.dart @@ -12,24 +12,27 @@ class ThreadScreenArguments { } class ThreadScreen extends StatefulWidget { + ThreadScreen({Key? key, required this.threadNumber}) : super(key: key); + + late int threadNumber; + @override - State createState() => ThreadScreenState(); + State createState() => ThreadScreenState(threadNumber); } class ThreadScreenState extends State { + ThreadScreenState(this.threadNumber); + late NNTPClient client; late int threadNumber; @override Widget build(BuildContext context) { - var args = - ModalRoute.of(context)!.settings.arguments as ThreadScreenArguments; client = context.read(); - threadNumber = args.item.number; return Scaffold( appBar: AppBar( - title: Text("Thread #${args.item.number}"), + title: Text("Thread #${this.threadNumber}"), ), body: Center( child: Container( @@ -39,10 +42,7 @@ class ThreadScreenState extends State { builder: (context, snapshot) { if (snapshot.hasData) { List data = List.from(snapshot.data!); - data.insert( - 0, - MessageItem(args.item.id, args.item.number, args.item.subject, - args.item.author, args.item.date, args.item.body)); + data.insert(1, MessageItem("reply", 0, "", "", "", "")); // reply return _listView(data); } else if (snapshot.hasError) { return Text("${snapshot.error}"); @@ -56,14 +56,95 @@ class ThreadScreenState extends State { Future> _fetch(BuildContext context) async { var model = context.read(); - return await model.getThread(threadNumber); + List posts = []; + + var threadPosts = await model.getThread(threadNumber); + posts.addAll(threadPosts); + var opPost = await model.getPost(threadNumber); + posts.insert(0, opPost); + + return posts; } Widget _listView(List data) { return ListView.builder( itemCount: data.length, itemBuilder: (context, index) { + if (index == 1) { + return SendMessageForm(); + } return MessageItemView(item: data[index], isOpPost: index == 0); }); } } + +class SendMessageForm extends StatefulWidget { + const SendMessageForm({Key? key}) : super(key: key); + + @override + SendMessageFormState createState() { + return SendMessageFormState(); + } +} + +// Create a corresponding State class. +// This class holds data related to the form. +class SendMessageFormState extends State { + // Create a global key that uniquely identifies the Form widget + // and allows validation of the form. + // + // Note: This is a GlobalKey, + // not a GlobalKey. + final _formKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + // Build a Form widget using the _formKey created above. + return Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Column(children: [ + TextFormField( + minLines: 5, + keyboardType: TextInputType.multiline, + maxLines: null, + decoration: InputDecoration( + border: OutlineInputBorder(), labelText: "Comment"), + // The validator receives the text that the user has entered. + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter some text'; + } + return null; + }, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + onPressed: () { + // Validate returns true if the form is valid, or false otherwise. + if (_formKey.currentState!.validate()) { + // If the form is valid, display a snackbar. In the real world, + // you'd often call a server or save the information in a database. + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Processing Data')), + ); + } + }, + child: const Text('Send'), + ) + ])) + ]), + ) + ], + ), + ); + } +}