From 839f91d7358a5ed59089a545407e5c47c323b9b4 Mon Sep 17 00:00:00 2001 From: ChronosX88 Date: Fri, 15 Apr 2022 07:03:42 +0300 Subject: [PATCH] Implement thread loading --- lib/main.dart | 7 ++++ lib/message_item_view.dart | 80 ++++++++++++++++++++++++++++++++++++++ lib/nntp_client.dart | 25 ++++++++++++ lib/thread_list_view.dart | 9 ++++- lib/thread_model.dart | 29 ++++++++++++++ lib/thread_screen.dart | 54 ++++++++++++++++++++++++- 6 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 lib/message_item_view.dart create mode 100644 lib/thread_model.dart diff --git a/lib/main.dart b/lib/main.dart index 53fb761..721c290 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ 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_model.dart'; import 'package:wind/thread_screen.dart'; import 'thread_list_model.dart'; @@ -16,6 +17,12 @@ void main() { model!.client = client; return model; }), + ChangeNotifierProxyProvider( + create: (context) => ThreadModel(), + update: (context, client, model) { + model!.client = client; + return model; + }), ], child: MyApp())); } diff --git a/lib/message_item_view.dart b/lib/message_item_view.dart new file mode 100644 index 0000000..fab63d0 --- /dev/null +++ b/lib/message_item_view.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; + +class MessageItemView extends StatelessWidget { + const MessageItemView({Key? key, required this.item, required this.isOpPost}) + : super(key: key); + + final MessageItem item; + final bool isOpPost; + + @override + Widget build(BuildContext context) { + return Container( + 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, + style: TextStyle( + fontWeight: FontWeight.normal, + color: Colors.blue, + fontSize: 15), + ), + SizedBox(width: 5), + Text( + item.date, + style: TextStyle(fontSize: 15), + ), + SizedBox(width: 5), + 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), + ), + Container( + child: Text(item.body, style: TextStyle(fontSize: 17)), + margin: EdgeInsets.all(16), + ) + ], + ), + ))); + } +} + +class MessageItem { + final String id; + final int number; + final String? subject; + final String author; + final String date; + final String body; + + MessageItem( + this.id, this.number, this.subject, this.author, this.date, this.body); +} diff --git a/lib/nntp_client.dart b/lib/nntp_client.dart index 84d1d73..999b6d0 100644 --- a/lib/nntp_client.dart +++ b/lib/nntp_client.dart @@ -126,6 +126,31 @@ class NNTPClient { return threads; } + + Future>> getThread( + int threadNumber) async { + if (currentGroup == null) throw new ArgumentError("current group is null"); + + List> threads = []; + + var newThreadList = await _sendCommand( + "THREAD", [threadNumber.toString()]); + + newThreadList.lines.removeAt(0); + newThreadList.lines.removeLast(); // remove dot + + await Future.forEach(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 { diff --git a/lib/thread_list_view.dart b/lib/thread_list_view.dart index bf5191b..ea96634 100644 --- a/lib/thread_list_view.dart +++ b/lib/thread_list_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:wind/thread_list_model.dart'; +import 'package:wind/thread_screen.dart'; class ThreadListView extends StatefulWidget { @override @@ -96,7 +97,8 @@ class ThreadListItemView extends StatelessWidget { elevation: 5, child: InkWell( splashColor: Colors.indigo.withAlpha(30), - onTap: () => Navigator.pushNamed(context, "/thread"), + onTap: () => Navigator.pushNamed(context, "/thread", + arguments: ThreadScreenArguments(item)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -123,6 +125,11 @@ class ThreadListItemView extends StatelessWidget { item.date, style: TextStyle(fontSize: 15), ), + SizedBox(width: 5), + Text( + "#${item.number}", + style: TextStyle(fontSize: 15, color: Colors.grey), + ) ], ), margin: EdgeInsets.only(top: 5, bottom: 2, left: 16, right: 16), diff --git a/lib/thread_model.dart b/lib/thread_model.dart new file mode 100644 index 0000000..297bdfa --- /dev/null +++ b/lib/thread_model.dart @@ -0,0 +1,29 @@ +import 'package:flutter/cupertino.dart'; +import 'package:wind/message_item_view.dart'; +import 'package:wind/nntp_client.dart'; + +class ThreadModel extends ChangeNotifier { + NNTPClient? client; + + Future> getThread(int threadNumber) async { + if (client!.currentGroup == "") return []; + + List items = []; + + var thread = await client!.getThread(threadNumber); + + thread.forEach((pair) { + var messageNum = pair.item1; + var message = pair.item2; + items.add(MessageItem( + message.getHeaderValue("Message-Id")!, + messageNum, + null, + message.getHeaderValue("From")!, + message.getHeaderValue("Date")!, + message.decodeTextPlainPart()!)); + }); + + return items; + } +} diff --git a/lib/thread_screen.dart b/lib/thread_screen.dart index 3805a3d..eace40a 100644 --- a/lib/thread_screen.dart +++ b/lib/thread_screen.dart @@ -1,4 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:wind/message_item_view.dart'; +import 'package:wind/nntp_client.dart'; +import 'package:wind/thread_list_view.dart'; +import 'package:wind/thread_model.dart'; + +class ThreadScreenArguments { + final ThreadItem item; + + ThreadScreenArguments(this.item); +} class ThreadScreen extends StatefulWidget { @override @@ -6,12 +17,53 @@ class ThreadScreen extends StatefulWidget { } class ThreadScreenState extends State { + 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"), + title: Text("Thread #${args.item.number}"), ), + body: Center( + child: Container( + width: 640, + child: FutureBuilder>( + future: _fetch(context), + 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)); + return _listView(data); + } else if (snapshot.hasError) { + return Text("${snapshot.error}"); + } + return Center(child: CircularProgressIndicator()); + }, + ), + )), ); } + + Future> _fetch(BuildContext context) async { + var model = context.read(); + return await model.getThread(threadNumber); + } + + Widget _listView(List data) { + return ListView.builder( + itemCount: data.length, + itemBuilder: (context, index) { + return MessageItemView(item: data[index], isOpPost: index == 0); + }); + } }