Compare commits
4 Commits
b79d68c7a2
...
9d6ec2b6bc
Author | SHA1 | Date | |
---|---|---|---|
9d6ec2b6bc | |||
9d05e612cc | |||
5dc749eaec | |||
ab0adf62e4 |
185
lib/Compose.dart
185
lib/Compose.dart
@ -1,3 +1,5 @@
|
|||||||
|
import 'package:crab_ui/api_service.dart';
|
||||||
|
import 'package:crab_ui/structs.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:super_editor/super_editor.dart';
|
import 'package:super_editor/super_editor.dart';
|
||||||
import 'package:super_editor_markdown/super_editor_markdown.dart';
|
import 'package:super_editor_markdown/super_editor_markdown.dart';
|
||||||
@ -6,12 +8,14 @@ class ComposeEmail extends StatefulWidget {
|
|||||||
final VoidCallback onClose;
|
final VoidCallback onClose;
|
||||||
final Function(String) onMinimize;
|
final Function(String) onMinimize;
|
||||||
final Function(String) onSendMessage;
|
final Function(String) onSendMessage;
|
||||||
const ComposeEmail({
|
GetThreadResponse? emailDraftID;
|
||||||
Key? key,
|
ComposeEmail(
|
||||||
required this.onMinimize,
|
{Key? key,
|
||||||
required this.onClose,
|
required this.onMinimize,
|
||||||
required this.onSendMessage,
|
required this.onClose,
|
||||||
}) : super(key: key);
|
required this.onSendMessage,
|
||||||
|
this.emailDraftID})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ComposeEmailState createState() => _ComposeEmailState();
|
_ComposeEmailState createState() => _ComposeEmailState();
|
||||||
@ -23,20 +27,38 @@ class _ComposeEmailState extends State<ComposeEmail> {
|
|||||||
late final MutableDocument _document;
|
late final MutableDocument _document;
|
||||||
late final MutableDocumentComposer _composer;
|
late final MutableDocumentComposer _composer;
|
||||||
TextEditingController _emailRecipientController = TextEditingController();
|
TextEditingController _emailRecipientController = TextEditingController();
|
||||||
|
TextEditingController _emailSubjectController = TextEditingController();
|
||||||
|
List<String>? contentOfDraft;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_loadDraftContent();
|
||||||
|
}
|
||||||
|
|
||||||
_document = MutableDocument(nodes: [
|
void _loadDraftContent() async {
|
||||||
ParagraphNode(
|
if (widget.emailDraftID != null) {
|
||||||
id: Editor.createNodeId(),
|
String? drafted = widget.emailDraftID?.messages.last;
|
||||||
text: AttributedText("hello world!"),
|
if (drafted != null) {
|
||||||
)
|
contentOfDraft =
|
||||||
]);
|
await ApiService().fetchMarkdownContent([drafted!], "Drafts");
|
||||||
_composer = MutableDocumentComposer();
|
setState(() {
|
||||||
_editor =
|
_document = MutableDocument(nodes: [
|
||||||
createDefaultDocumentEditor(document: _document!, composer: _composer!);
|
ParagraphNode(
|
||||||
|
id: Editor.createNodeId(),
|
||||||
|
text: AttributedText(contentOfDraft?[0] ??
|
||||||
|
""), // NOW THIS SHOULD BE WTV ITS IN DRAFTS
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
_composer = MutableDocumentComposer();
|
||||||
|
_editor = createDefaultDocumentEditor(
|
||||||
|
document: _document, composer: _composer);
|
||||||
|
_emailRecipientController.text =
|
||||||
|
widget.emailDraftID!.to[0].address;
|
||||||
|
_emailSubjectController.text = widget.emailDraftID!.subject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -70,7 +92,7 @@ class _ComposeEmailState extends State<ComposeEmail> {
|
|||||||
icon: Icon(Icons.minimize, color: Colors.grey[600])),
|
icon: Icon(Icons.minimize, color: Colors.grey[600])),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
//TODO: implement that maximizing
|
//TODO: implement maximizing the window or widget
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.maximize, color: Colors.grey[600])),
|
icon: Icon(Icons.maximize, color: Colors.grey[600])),
|
||||||
IconButton(
|
IconButton(
|
||||||
@ -81,9 +103,9 @@ class _ComposeEmailState extends State<ComposeEmail> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
// TODO: WHEN NOT CLICKED ITS ONLY A TEXTFIELD WITH A HINT, AND THEN WHEN CLICKED THIS
|
// TODO: WHEN NOT CLICKED ITS ONLY A TEXTFIELD WITH A HINT, AND THEN WHEN CLICKED THIS
|
||||||
width: 500.0,
|
// width: 500.0,
|
||||||
height: 40.0,
|
// height: 40.0,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
TextButton(onPressed: () {}, child: Text("To:")),
|
TextButton(onPressed: () {}, child: Text("To:")),
|
||||||
@ -100,25 +122,95 @@ class _ComposeEmailState extends State<ComposeEmail> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
// TODO: WHEN NOT CLICKED ITS ONLY A TEXTFIELD WITH A HINT, AND THEN WHEN CLICKED THIS
|
||||||
|
width: 500.0,
|
||||||
|
// height: 40.0,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _emailSubjectController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Subject",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
//here the widget goes
|
//here the widget goes
|
||||||
child: SuperEditor(
|
child: SuperEditor(
|
||||||
editor: _editor!,
|
//make this its own
|
||||||
plugins: {MarkdownInlineUpstreamSyntaxPlugin()},
|
editor: _editor!,
|
||||||
stylesheet: Stylesheet(
|
plugins: {MarkdownInlineUpstreamSyntaxPlugin()},
|
||||||
rules: [StyleRule(BlockSelector.all, (doc, docNode) {
|
|
||||||
return {
|
// stylesheet: Stylesheet(
|
||||||
Styles.maxWidth: 640.0,
|
// rules: [StyleRule(BlockSelector.all, (doc, docNode) {
|
||||||
Styles.padding: const CascadingPadding.symmetric(horizontal: 24),
|
// return {
|
||||||
Styles.textStyle: const TextStyle(
|
// Styles.maxWidth: 640.0,
|
||||||
color: Colors.black,
|
// Styles.padding: const CascadingPadding.symmetric(horizontal: 24),
|
||||||
fontSize: 15,
|
// Styles.textStyle: const TextStyle(
|
||||||
height: 1.4,
|
// color: Colors.black,
|
||||||
|
// // fontSize: 15,
|
||||||
|
// height: 1.4,
|
||||||
|
// ),
|
||||||
|
// };
|
||||||
|
// }),],
|
||||||
|
// inlineTextStyler: defaultInlineTextStyler)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 5),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
print('sent');
|
||||||
|
String markdown =
|
||||||
|
serializeDocumentToMarkdown(_editor.document);
|
||||||
|
|
||||||
|
print(_emailRecipientController.text);
|
||||||
|
print(_emailSubjectController.text);
|
||||||
|
print(markdown);
|
||||||
|
ApiService().sendEmail(_emailRecipientController.text,
|
||||||
|
_emailSubjectController.text, markdown);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(30)),
|
||||||
|
elevation: 4,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
backgroundColor: Colors.blueAccent),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Send',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
};
|
// const SizedBox(
|
||||||
}),],
|
// width: 8,
|
||||||
inlineTextStyler: defaultInlineTextStyler)
|
// ),
|
||||||
),
|
// Container(
|
||||||
|
// height: 30, width: 1.0, color: Colors.white),
|
||||||
|
// const SizedBox(
|
||||||
|
// width: 8,
|
||||||
|
// ),
|
||||||
|
// const Icon(
|
||||||
|
// Icons.arrow_drop_down,
|
||||||
|
// size: 24,
|
||||||
|
// )
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
])),
|
])),
|
||||||
),
|
),
|
||||||
@ -131,6 +223,7 @@ class OverlayService {
|
|||||||
factory OverlayService() => _instance;
|
factory OverlayService() => _instance;
|
||||||
OverlayService._internal();
|
OverlayService._internal();
|
||||||
OverlayEntry? _overlayEntry;
|
OverlayEntry? _overlayEntry;
|
||||||
|
GetThreadResponse? draftID;
|
||||||
|
|
||||||
void showPersistentWidget(BuildContext context) {
|
void showPersistentWidget(BuildContext context) {
|
||||||
if (_overlayEntry != null) {
|
if (_overlayEntry != null) {
|
||||||
@ -139,16 +232,16 @@ class OverlayService {
|
|||||||
}
|
}
|
||||||
_overlayEntry = OverlayEntry(
|
_overlayEntry = OverlayEntry(
|
||||||
builder: (context) => ComposeEmail(
|
builder: (context) => ComposeEmail(
|
||||||
onClose: () {
|
onClose: () {
|
||||||
removeComposeWidget();
|
removeComposeWidget();
|
||||||
},
|
},
|
||||||
onMinimize: (String content) {
|
onMinimize: (String content) {
|
||||||
minimizeComposeWidget(content);
|
minimizeComposeWidget(content);
|
||||||
},
|
},
|
||||||
onSendMessage: (message) {
|
onSendMessage: (message) {
|
||||||
print('msg senf form overlay $message');
|
print('msg senf form overlay $message');
|
||||||
},
|
},
|
||||||
));
|
emailDraftID: draftID));
|
||||||
Navigator.of(context).overlay?.insert(_overlayEntry!);
|
Navigator.of(context).overlay?.insert(_overlayEntry!);
|
||||||
print("inserted into tree");
|
print("inserted into tree");
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ import 'package:http/http.dart' as http;
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
static String ip = "";
|
static String ip = '127.0.0.1';
|
||||||
static String port = "";
|
static String port = "3001";
|
||||||
static List<AttachmentResponse> threadAttachments =
|
static List<AttachmentResponse> threadAttachments =
|
||||||
[]; //holds attachments of the thread
|
[]; //holds attachments of the thread
|
||||||
static String currFolder = "";
|
static String currFolder = "";
|
||||||
@ -169,7 +169,7 @@ class ApiService {
|
|||||||
Future<bool> moveEmail(
|
Future<bool> moveEmail(
|
||||||
//only moves the first email of the thread //or perhaps should do the last
|
//only moves the first email of the thread //or perhaps should do the last
|
||||||
String fromFolder,
|
String fromFolder,
|
||||||
String thread_id,
|
String thread_id, //uid
|
||||||
String toFolder) async {
|
String toFolder) async {
|
||||||
var url = Uri.http('$ip:$port', 'move_email');
|
var url = Uri.http('$ip:$port', 'move_email');
|
||||||
|
|
||||||
@ -185,7 +185,7 @@ class ApiService {
|
|||||||
Map<String, String> requestBody = {
|
Map<String, String> requestBody = {
|
||||||
'from': fromFolder,
|
'from': fromFolder,
|
||||||
'uid': firstMail.uid.toString(),
|
'uid': firstMail.uid.toString(),
|
||||||
'to': toFolder,
|
'to': "Deleted Crabmail",
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -356,7 +356,7 @@ class ApiService {
|
|||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
counter += 1;
|
counter += 1;
|
||||||
Map<String, dynamic> json = jsonDecode(response.body);
|
Map<String, dynamic> json = jsonDecode(response.body);
|
||||||
|
|
||||||
MDofThread.add(json['md'] ?? '');
|
MDofThread.add(json['md'] ?? '');
|
||||||
try {
|
try {
|
||||||
List<AttachmentInfo> attachments =
|
List<AttachmentInfo> attachments =
|
||||||
@ -374,7 +374,110 @@ class ApiService {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('_getMDContent caught error: $e');
|
print('_getMDContent caught error: $e');
|
||||||
}
|
}
|
||||||
|
print("IDS inside fetch md content $IDsString");
|
||||||
|
|
||||||
return MDofThread;
|
return MDofThread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> markAsSeen(int thread_id) async {
|
||||||
|
try {
|
||||||
|
var url = Uri.http(
|
||||||
|
'$ip:$port', 'post_seen_thread', {'id': thread_id.toString()});
|
||||||
|
var response = await http.get(url);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
var result = response.body;
|
||||||
|
print("data $result");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("markAsSeen failed $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> markAsUnseen(int thread_id) async {
|
||||||
|
try {
|
||||||
|
var url = Uri.http(
|
||||||
|
'$ip:$port', 'post_unseen_thread', {'id': thread_id.toString()});
|
||||||
|
var response = await http.get(url);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
var result = response.body;
|
||||||
|
print("data $result");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("markAsUnseen failed $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> deleteEmail(String from_folder, int thread_id) async {
|
||||||
|
// post
|
||||||
|
try {
|
||||||
|
List<SerializableMessage> mailsInSerializable =
|
||||||
|
await this.threadsInSerializable(thread_id.toString());
|
||||||
|
|
||||||
|
if (mailsInSerializable.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, String> requestBody = {
|
||||||
|
"from": from_folder,
|
||||||
|
"uid": mailsInSerializable.first.uid.toString(),
|
||||||
|
"to": "not used"
|
||||||
|
};
|
||||||
|
|
||||||
|
//delete the email that is given to the
|
||||||
|
var url = Uri.http("$ip:$port", 'delete_email');
|
||||||
|
var response = await http.post(url,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: jsonEncode(requestBody));
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
print("response body: ${response.body}");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
print("not 200: ${response.body}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("error in deleteEmail $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> sendEmail(
|
||||||
|
String? to, String? subject, String? emailContent) async {
|
||||||
|
try {
|
||||||
|
var url = Uri.http('$ip:$port', 'send_email');
|
||||||
|
|
||||||
|
Map<String, dynamic> requestBody = {
|
||||||
|
"to": [to ?? ""],
|
||||||
|
"cc": [],
|
||||||
|
"bcc": [],
|
||||||
|
"subject": subject ?? "Untitled",
|
||||||
|
"in_reply_to": "",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"message": emailContent ?? "",
|
||||||
|
"is_html": false}],
|
||||||
|
"attachments": [],
|
||||||
|
"inline_images": [],
|
||||||
|
};
|
||||||
|
var response = await http.post(
|
||||||
|
url,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: jsonEncode(requestBody),
|
||||||
|
);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
print("response body: ${response.body}");
|
||||||
|
} else {
|
||||||
|
print('error: ${response.statusCode}, response body: ${response.body}');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
print("error in post send email $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,20 @@ class _EmailListScreenState extends State<EmailListScreen>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool moveOfSelected(String destinyFolder) {
|
||||||
|
//this should be called from a widget
|
||||||
|
print("move of folder");
|
||||||
|
setState(() {
|
||||||
|
for (int email = 0; email < selectedEmails.length; email++) {
|
||||||
|
ApiService().moveEmail(
|
||||||
|
widget.folder, selectedEmails[email].id.toString(), destinyFolder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget moveOfFolderWidget()
|
||||||
|
|
||||||
List<GetThreadResponse> listOfSelectedThreads() {
|
List<GetThreadResponse> listOfSelectedThreads() {
|
||||||
return selectedEmails;
|
return selectedEmails;
|
||||||
}
|
}
|
||||||
@ -153,7 +167,7 @@ class _EmailListScreenState extends State<EmailListScreen>
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.mark_email_read_outlined),
|
icon: Icon(Icons.mark_email_read_outlined),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
//mark email as read
|
// mark email as read
|
||||||
setState(() {
|
setState(() {
|
||||||
widget.emails[index].seen = true;
|
widget.emails[index].seen = true;
|
||||||
ApiService().markAsSeen(email.id);
|
ApiService().markAsSeen(email.id);
|
||||||
@ -163,7 +177,8 @@ class _EmailListScreenState extends State<EmailListScreen>
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.delete_outline),
|
icon: Icon(Icons.delete_outline),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
//delete email
|
// delete email
|
||||||
|
ApiService().deleteEmail(widget.folder, email.id);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -297,8 +312,13 @@ class EmailPageState extends State<EmailPage> {
|
|||||||
return selectionType;
|
return selectionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool moveSelectedOfFolder(String folder) {
|
||||||
|
emailListKey.currentState?.moveOfSelected(folder);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
List<GetThreadResponse> getListOfSelected() {
|
List<GetThreadResponse> getListOfSelected() {
|
||||||
return emailListKey.currentState!.listOfSelectedThreads() ?? [];
|
return emailListKey.currentState!.listOfSelectedThreads();
|
||||||
}
|
}
|
||||||
// return [GetThreadResponse(id: 1, messages: [], subject: "subject", date: DateTime(2025), from_name: "from_name", from_address: "from_address", to: [], seen: false)];
|
// return [GetThreadResponse(id: 1, messages: [], subject: "subject", date: DateTime(2025), from_name: "from_name", from_address: "from_address", to: [], seen: false)];
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user