From f2d808f5d3a366b632b6033fb7f10053f16eb33b Mon Sep 17 00:00:00 2001 From: "Florian RICHER (MrDev023)" Date: Thu, 14 Apr 2022 21:57:32 +0200 Subject: [PATCH] Add steam message --- lib/classes/stream_element.dart | 13 ++ lib/utils/mailer.dart | 61 ++++++--- lib/widgets/components/row_input.dart | 8 +- .../components/stream_element_card.dart | 44 ++++++ lib/widgets/forms/email_form.dart | 33 +++++ lib/widgets/pages/home.dart | 125 +++++++++++------- pubspec.lock | 7 + pubspec.yaml | 3 +- 8 files changed, 226 insertions(+), 68 deletions(-) create mode 100644 lib/classes/stream_element.dart create mode 100644 lib/widgets/components/stream_element_card.dart create mode 100644 lib/widgets/forms/email_form.dart diff --git a/lib/classes/stream_element.dart b/lib/classes/stream_element.dart new file mode 100644 index 0000000..1e0c9f0 --- /dev/null +++ b/lib/classes/stream_element.dart @@ -0,0 +1,13 @@ +import 'package:uuid/uuid.dart'; + +enum StreamElementType { log, alert } + +class StreamElement { + StreamElement(this.type, this.message) { + uuid = const Uuid().v4(); + } + + late final String uuid; + final String message; + final StreamElementType type; +} diff --git a/lib/utils/mailer.dart b/lib/utils/mailer.dart index 4422e3d..e8d5fcb 100644 --- a/lib/utils/mailer.dart +++ b/lib/utils/mailer.dart @@ -1,28 +1,38 @@ +import 'dart:async'; + +import 'package:desktopapp/classes/stream_element.dart'; import 'package:desktopapp/utils/logger.dart'; -import 'package:desktopapp/utils/requests.dart'; import 'package:enough_mail/enough_mail.dart'; class Mailer { + static MailClient? _mailClient; + /// High level mail API example - static Future mailExample() async { - const email = 'trexricher1997@gmail.com'; - const password = 'rugnxbafhcwsykzt'; + static Future connect({ + required String email, + required String password, + required StreamController streamController, + }) async { Logger.log(LoggerType.info, Mailer, 'Discovering settings for $email'); final config = await Discover.discover(email); if (config == null) { Logger.log(LoggerType.info, Mailer, 'Unable to autodiscover settings for $email'); - return; + return false; } Logger.log(LoggerType.info, Mailer, 'Settings for $email : $config'); final account = MailAccount.fromDiscoveredSettings( 'my account', email, password, config); - final mailClient = MailClient(account, isLogEnabled: true); + _mailClient = MailClient(account, isLogEnabled: true); try { - await mailClient.connect(); + await _mailClient!.connect(); Logger.log(LoggerType.info, Mailer, 'Connected to $email'); - await mailClient.selectInbox(); - mailClient.eventBus.on().listen((ImapEvent event) async { + streamController + .add(StreamElement(StreamElementType.log, 'Connected to $email')); + await _mailClient!.selectInbox(); + streamController.add( + StreamElement(StreamElementType.log, 'INBOX selected for $email')); + _mailClient!.eventBus.on().listen((ImapEvent event) async { if (event.eventType == ImapEventType.exists) { final evt = event as ImapMessagesExistEvent; if (evt.newMessagesExists <= evt.oldMessagesExists) return; @@ -32,22 +42,35 @@ class Mailer { } else { sequence.add(evt.newMessagesExists); } - final messages = await mailClient.fetchMessageSequence(sequence, - fetchPreference: FetchPreference.full); + final messages = await _mailClient!.fetchMessageSequence(sequence, + fetchPreference: FetchPreference.envelope); for (final message in messages) { - var mailEvt = MailLoadEvent(message, mailClient); - var content = mailEvt.message.decodeSubject(); - var mess = mailEvt.message.decodeTextPlainPart(); - Logger.log(LoggerType.info, Mailer, 'New message $content : $mess'); - Requests.sendAlert('*$content* $mess'); + var mailEvt = MailLoadEvent(message, _mailClient!); + streamController.add(StreamElement(StreamElementType.alert, + mailEvt.message.decodeSubject() ?? '')); + // var content = mailEvt.message.decodeSubject(); + // var mess = mailEvt.message.decodeTextPlainPart(); + + // Logger.log(LoggerType.info, Mailer, 'New message $content : $mess'); + // Requests.sendAlert('*$content* $mess'); } } }); - Logger.log(LoggerType.info, Mailer, 'Start polling mail for $email'); - await mailClient.startPolling(const Duration(seconds: 30)); - Logger.log(LoggerType.info, Mailer, 'Finish polling mail for $email'); + await _mailClient!.startPolling(); + return true; } on MailException catch (e) { + streamController.add(StreamElement(StreamElementType.log, e.toString())); Logger.log(LoggerType.error, Mailer, 'High level API failed with $e'); + return false; } } + + static Future disconnect() async { + if (_mailClient == null) return; + Logger.log(LoggerType.info, Mailer, 'Disconnecting from mail'); + await _mailClient!.stopPolling(); + await _mailClient!.disconnect(); + Logger.log(LoggerType.info, Mailer, 'Disconnected from mail'); + _mailClient = null; + } } diff --git a/lib/widgets/components/row_input.dart b/lib/widgets/components/row_input.dart index 4176e0e..8d40815 100644 --- a/lib/widgets/components/row_input.dart +++ b/lib/widgets/components/row_input.dart @@ -1,11 +1,16 @@ import 'package:flutter/material.dart'; class RowInput extends StatelessWidget { - const RowInput({Key? key, required this.label, required this.controller}) + const RowInput( + {Key? key, + required this.label, + required this.controller, + this.obscureText = false}) : super(key: key); final String label; final TextEditingController controller; + final bool obscureText; @override Widget build(BuildContext context) { @@ -15,6 +20,7 @@ class RowInput extends StatelessWidget { child: Expanded( child: TextField( controller: controller, + obscureText: obscureText, decoration: InputDecoration( hintText: label, ), diff --git a/lib/widgets/components/stream_element_card.dart b/lib/widgets/components/stream_element_card.dart new file mode 100644 index 0000000..de4055b --- /dev/null +++ b/lib/widgets/components/stream_element_card.dart @@ -0,0 +1,44 @@ +import 'package:desktopapp/classes/stream_element.dart'; +import 'package:flutter/material.dart'; + +class StreamElementCard extends StatelessWidget { + StreamElementCard({Key? key, required this.streamElement}) : super(key: key); + + final StreamElement streamElement; + + @override + Widget build(BuildContext context) { + IconData icon; + switch (streamElement.type) { + case StreamElementType.log: + icon = Icons.info; + break; + case StreamElementType.alert: + icon = Icons.notifications_active; + break; + } + return Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + icon, + size: 30, + ), + SizedBox(width: 8), + Text( + streamElement.message, + style: TextStyle(fontSize: 16), + ), + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/widgets/forms/email_form.dart b/lib/widgets/forms/email_form.dart new file mode 100644 index 0000000..bc6f571 --- /dev/null +++ b/lib/widgets/forms/email_form.dart @@ -0,0 +1,33 @@ +import 'package:desktopapp/widgets/components/row_input.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class EmailForm extends StatelessWidget { + EmailForm({Key? key, required this.onValid, required this.prefs}) + : super(key: key); + + final void Function(String, String) onValid; + final SharedPreferences prefs; + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + + @override + Widget build(BuildContext context) { + _emailController.text = prefs.getString('mail_email') ?? ''; + _passwordController.text = prefs.getString('mail_password') ?? ''; + return Column(children: [ + RowInput(label: 'Email', controller: _emailController), + RowInput( + label: 'Password', + controller: _passwordController, + obscureText: true), + TextButton( + onPressed: () async { + prefs.setString('mail_email', _emailController.text.trim()); + prefs.setString('mail_password', _passwordController.text.trim()); + onValid(_emailController.text, _passwordController.text); + }, + child: const Text('Se connecter')), + ]); + } +} diff --git a/lib/widgets/pages/home.dart b/lib/widgets/pages/home.dart index 4b4ee8f..c233463 100644 --- a/lib/widgets/pages/home.dart +++ b/lib/widgets/pages/home.dart @@ -1,53 +1,91 @@ +import 'dart:async'; +import 'dart:math'; + import 'package:desktopapp/classes/routes.dart'; +import 'package:desktopapp/classes/stream_element.dart'; import 'package:desktopapp/utils/mailer.dart'; -import 'package:desktopapp/utils/requests.dart'; +import 'package:desktopapp/widgets/components/stream_element_card.dart'; +import 'package:desktopapp/widgets/forms/email_form.dart'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; -class HomePage extends StatelessWidget { +class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + bool connected = false; + List streamElements = []; + final StreamController controller = + StreamController(); + Widget buildContent(BuildContext context, SharedPreferences prefs) { return Column(children: [ - const Padding( - padding: EdgeInsets.symmetric(vertical: 20), - child: Text('Appuyez sur le bouton pour envoyer une alerte de test', - style: TextStyle(fontSize: 20)), - ), - TextButton( - onPressed: () async { - if (await Requests.sendAlert('*MrDev023* *Break* Test !')) { - showDialog( - context: context, - builder: (_) => AlertDialog( - title: const Text('Info'), - content: const Text('Envoie réussie'), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('OK')) - ], - )); - } else { - showDialog( - context: context, - builder: (_) => AlertDialog( - title: const Text('Erreur'), - content: const Text('Impossible de se connecter.'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK')) - ], - )); + if (!connected) + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + EmailForm( + prefs: prefs, + onValid: (String email, String password) async { + var value = await Mailer.connect( + email: email, + password: password, + streamController: controller); + setState(() { + connected = value; + }); + }) + ]), + if (connected) + Row( + children: [ + TextButton( + onPressed: () async { + await Mailer.disconnect(); + setState(() { + connected = false; + }); + }, + child: const Text('Se déconnecter')), + ], + ), + Expanded( + child: StreamBuilder( + stream: controller.stream, + builder: + (BuildContext context, AsyncSnapshot snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.none: + return const Text('Nothing'); + case ConnectionState.waiting: + return const Center(child: CircularProgressIndicator()); + case ConnectionState.active: + if (snapshot.hasData) { + int skipNumber = max(0, streamElements.length - 10); + bool alreadyAdded = streamElements + .skip(skipNumber) + .cast() + .firstWhere( + (element) => element!.uuid == snapshot.data!.uuid, + orElse: () => null) != + null; + if (!alreadyAdded) streamElements.add(snapshot.data!); + } + return ListView.builder( + itemCount: streamElements.length, + itemBuilder: (BuildContext context, int index) { + return StreamElementCard( + streamElement: streamElements[index]); + }, + ); + case ConnectionState.done: + return const Text('Terminate'); } }, - child: const Text('Envoyer une alerte de test')), - TextButton( - onPressed: () => Mailer.mailExample(), - child: const Text('Test email')), + ), + ) ]); } @@ -68,14 +106,7 @@ class HomePage extends StatelessWidget { } }); - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [buildContent(context, prefs)], - ) - ]); + return buildContent(context, prefs); } else { return const Center(child: CircularProgressIndicator()); } diff --git a/pubspec.lock b/pubspec.lock index 4441071..c6b8b1f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -511,6 +511,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.0" + uuid: + dependency: "direct main" + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.6" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d8a4b82..f6b501c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,8 @@ dependencies: shared_preferences: ^2.0.13 flash: ^2.0.3+2 dio: ^4.0.6 - enough_mail: ^1.3.4 + enough_mail: ^1.3.6 + uuid: ^3.0.6 dev_dependencies: flutter_test: