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:provider/provider.dart';
import 'package:wind/create_thread_screen.dart';
import 'package:wind/newsgroup_list_view.dart';
import 'package:wind/nntp_client.dart';
import 'package:wind/thread_list_view.dart';
@ -47,6 +48,9 @@ class MyApp extends StatelessWidget {
threadNumber:
int.parse(uriData.queryParametersAll['num']!.first));
break;
case '/thread/create':
pageView = CreateThreadScreen();
break;
default:
pageView = MyHomePage(title: 'Wind');
break;
@ -82,64 +86,69 @@ class _MyHomePageState extends State<MyHomePage> {
@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(
// 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),
actions: [
TextButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Обновление списка тредов...')),
);
Provider.of<ThreadListModel>(context, listen: false).update();
},
label: const Text("Обновить"),
style: TextButton.styleFrom(
primary: Theme.of(context).colorScheme.onPrimary),
icon: Icon(Icons.sync))
],
),
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())),
))
],
),
return Consumer<ThreadListModel>(
builder: (context, value, child) => Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: value.currentGroup != ""
? [
TextButton.icon(
onPressed: () =>
Navigator.pushNamed(context, "/thread/create"),
icon: Icon(Icons.add),
label: Text("Создать тред"),
style: TextButton.styleFrom(
primary: Theme.of(context).colorScheme.onPrimary),
),
TextButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Обновление списка тредов...')),
);
Provider.of<ThreadListModel>(context, listen: false)
.update();
},
label: const Text("Обновить"),
style: TextButton.styleFrom(
primary: Theme.of(context).colorScheme.onPrimary),
icon: Icon(Icons.sync))
]
: [],
),
));
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
Widget build(BuildContext context) {
return Container(
margin: this.isOpPost
padding: this.isOpPost
? EdgeInsets.only(bottom: 10, left: 10, right: 10, top: 16)
: isLast
? EdgeInsets.only(left: 10, right: 10, bottom: 16)

View File

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

View File

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