Implement new thread creation

This commit is contained in:
ChronosX88 2022-05-02 00:49:03 +03:00
parent cf15aae5ac
commit 1bcc2613b4
Signed by: ChronosXYZ
GPG Key ID: 085A69A82C8C511A
5 changed files with 193 additions and 63 deletions

View File

@ -0,0 +1,112 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wind/thread_list_model.dart';
import 'package:wind/thread_model.dart';
class CreateThreadScreen extends StatefulWidget {
@override
State<StatefulWidget> createState() => _CreateThreadScreenState();
}
class _CreateThreadScreenState extends State<CreateThreadScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Создать новый тред")),
body: Center(
child: Container(
width: 640,
child: Consumer<ThreadModel>(
builder: (context, value, child) => CreateThreadForm(model: value),
),
)));
}
}
class CreateThreadForm extends StatefulWidget {
CreateThreadForm({Key? key, required this.model}) : super(key: key);
ThreadModel model;
@override
CreateThreadFormState createState() {
return CreateThreadFormState(model);
}
}
// Define a corresponding State class.
// This class holds data related to the form.
class CreateThreadFormState extends State<CreateThreadForm> {
CreateThreadFormState(this.model);
ThreadModel model;
final _formKey = GlobalKey<FormState>();
String _subject = "";
String _text = "";
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: TextFormField(
onSaved: ((newValue) {
_subject = newValue!;
}),
decoration: InputDecoration(
border: OutlineInputBorder(), labelText: "Название поста"),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Пожалуйста, введите название';
}
return null;
}),
),
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: TextFormField(
onSaved: (newValue) {
_text = newValue!;
},
minLines: 5,
keyboardType: TextInputType.multiline,
maxLines: 35,
decoration: InputDecoration(
border: OutlineInputBorder(), labelText: "Текст поста"),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Пожалуйста, введите текст';
}
return null;
},
)),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
model.createThread(_subject, _text).then((value) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Тред создан!')),
);
Provider.of<ThreadListModel>(context, listen: false)
.update();
Navigator.pop(context);
}, onError: (error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'При создании треда произошла ошибка: ${error.toString()}')),
);
});
}
},
child: Text("Создать"))
],
),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:wind/create_thread_screen.dart';
import 'package:wind/newsgroup_list_view.dart'; import 'package:wind/newsgroup_list_view.dart';
import 'package:wind/nntp_client.dart'; import 'package:wind/nntp_client.dart';
import 'package:wind/thread_list_view.dart'; import 'package:wind/thread_list_view.dart';
@ -47,6 +48,9 @@ class MyApp extends StatelessWidget {
threadNumber: threadNumber:
int.parse(uriData.queryParametersAll['num']!.first)); int.parse(uriData.queryParametersAll['num']!.first));
break; break;
case '/thread/create':
pageView = CreateThreadScreen();
break;
default: default:
pageView = MyHomePage(title: 'Wind'); pageView = MyHomePage(title: 'Wind');
break; break;
@ -82,64 +86,69 @@ class _MyHomePageState extends State<MyHomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done return Consumer<ThreadListModel>(
// by the _incrementCounter method above. builder: (context, value, child) => Scaffold(
// appBar: AppBar(
// The Flutter framework has been optimized to make rerunning build methods title: Text(widget.title),
// fast, so that you can just rebuild anything that needs updating rather actions: value.currentGroup != ""
// than having to individually change instances of widgets. ? [
return Scaffold( TextButton.icon(
appBar: AppBar( onPressed: () =>
// Here we take the value from the MyHomePage object that was created by Navigator.pushNamed(context, "/thread/create"),
// the App.build method, and use it to set our appbar title. icon: Icon(Icons.add),
title: Text(widget.title), label: Text("Создать тред"),
actions: [ style: TextButton.styleFrom(
TextButton.icon( primary: Theme.of(context).colorScheme.onPrimary),
onPressed: () { ),
ScaffoldMessenger.of(context).showSnackBar( TextButton.icon(
const SnackBar( onPressed: () {
content: Text('Обновление списка тредов...')), ScaffoldMessenger.of(context).showSnackBar(
); const SnackBar(
Provider.of<ThreadListModel>(context, listen: false).update(); content: Text('Обновление списка тредов...')),
}, );
label: const Text("Обновить"), Provider.of<ThreadListModel>(context, listen: false)
style: TextButton.styleFrom( .update();
primary: Theme.of(context).colorScheme.onPrimary), },
icon: Icon(Icons.sync)) label: const Text("Обновить"),
], style: TextButton.styleFrom(
), primary: Theme.of(context).colorScheme.onPrimary),
body: Center( icon: Icon(Icons.sync))
child: Container( ]
child: Row( : [],
children: [
SizedBox(
width: 300,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.all(16),
child: Text(
"Новостные группы",
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 20),
)),
Expanded(
child: Builder(
builder: (context) =>
NewsgroupListView(client: nntpClient))),
],
),
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(
child: Center(
child: Consumer<ThreadListModel>(
builder: ((context, value, child) => ThreadListView())),
))
],
),
), ),
)); body: Center(
child: Container(
child: Row(
children: [
SizedBox(
width: 300,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.all(16),
child: Text(
"Новостные группы",
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 20),
)),
Expanded(
child: Builder(
builder: (context) =>
NewsgroupListView(client: nntpClient))),
],
),
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(
child: Center(
child: Consumer<ThreadListModel>(
builder: ((context, value, child) => ThreadListView())),
))
],
),
),
)),
);
} }
} }

View File

@ -18,7 +18,7 @@ class MessageItemView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
margin: this.isOpPost padding: this.isOpPost
? EdgeInsets.only(bottom: 10, left: 10, right: 10, top: 16) ? EdgeInsets.only(bottom: 10, left: 10, right: 10, top: 16)
: isLast : isLast
? EdgeInsets.only(left: 10, right: 10, bottom: 16) ? EdgeInsets.only(left: 10, right: 10, bottom: 16)

View File

@ -40,7 +40,7 @@ class ThreadListModel extends ChangeNotifier {
_curItems.add(ThreadItem( _curItems.add(ThreadItem(
msg.getHeaderValue("Message-Id")!, msg.getHeaderValue("Message-Id")!,
number, number,
msg.getHeaderValue("Subject")!, msg.decodeSubject()!,
msg.getHeaderValue("From")!, msg.getHeaderValue("From")!,
msg.getHeaderValue("Date")!, msg.getHeaderValue("Date")!,
msg.decodeTextPlainPart()!)); msg.decodeTextPlainPart()!));

View File

@ -13,7 +13,7 @@ class ThreadModel extends ChangeNotifier {
var mi = MessageItem( var mi = MessageItem(
msg.getHeaderValue("Message-Id")!, msg.getHeaderValue("Message-Id")!,
number, number,
msg.getHeaderValue("Subject")!, msg.decodeSubject()!,
msg.getHeaderValue("From")!, msg.getHeaderValue("From")!,
msg.getHeaderValue("Date")!, msg.getHeaderValue("Date")!,
msg.decodeTextPlainPart()!); msg.decodeTextPlainPart()!);
@ -37,7 +37,7 @@ class ThreadModel extends ChangeNotifier {
null, null,
message.getHeaderValue("From")!, message.getHeaderValue("From")!,
message.getHeaderValue("Date")!, message.getHeaderValue("Date")!,
message.decodeTextPlainPart()!)); message.decodeTextPlainPart()!.trim()));
}); });
return items; return items;
@ -45,7 +45,7 @@ class ThreadModel extends ChangeNotifier {
Future<int> postMessage(MimeMessage opPost, String text) async { Future<int> postMessage(MimeMessage opPost, String text) async {
var msg = MessageBuilder.buildSimpleTextMessage( var msg = MessageBuilder.buildSimpleTextMessage(
MailAddress.empty(), [], text, MailAddress.empty(), [], text.trim(),
subject: "Re: " + opPost.decodeSubject()!); subject: "Re: " + opPost.decodeSubject()!);
msg.setHeader("From", "anonymous"); msg.setHeader("From", "anonymous");
msg.addHeader("In-Reply-To", opPost.getHeaderValue("Message-Id")); msg.addHeader("In-Reply-To", opPost.getHeaderValue("Message-Id"));
@ -54,6 +54,15 @@ class ThreadModel extends ChangeNotifier {
return await client!.postArticle(msg); return await client!.postArticle(msg);
} }
Future<int> createThread(String subject, String text) async {
var msg = MessageBuilder.buildSimpleTextMessage(
MailAddress.empty(), [], text.trim(),
subject: subject);
msg.setHeader("From", "anonymous");
msg.addHeader("Newsgroups", client!.currentGroup!);
return await client!.postArticle(msg);
}
void update() { void update() {
notifyListeners(); notifyListeners();
} }