From 189a24bc8bbbffe56c4227f46c7562d7db9bfb4c Mon Sep 17 00:00:00 2001 From: M66B Date: Sat, 18 May 2019 19:44:21 +0200 Subject: [PATCH] Added calendar replies --- README.md | 1 + .../eu/faircode/email/AdapterMessage.java | 181 +++++++++++++++--- .../eu/faircode/email/FragmentCompose.java | 22 ++- .../java/eu/faircode/email/MessageHelper.java | 23 ++- .../res/layout/include_message_calendar.xml | 47 ++++- 5 files changed, 243 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index d691e01e61..8db2e2cccc 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ This app starts a foreground service with a low priority status bar notification * Send messages after selected time * Synchronization scheduling ([instructions](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq78)) * Reply templates +* Accept/decline calendar invitations * Filter rules ([instructions](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq71)) * Search on device or server ([instructions](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq13)) * Keyword management diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index bd9187c63a..8ee7570b97 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -124,7 +124,10 @@ import javax.mail.internet.InternetAddress; import biweekly.Biweekly; import biweekly.ICalendar; import biweekly.component.VEvent; +import biweekly.parameter.ParticipationStatus; import biweekly.property.Attendee; +import biweekly.property.Method; +import biweekly.property.Organizer; import biweekly.property.Summary; import biweekly.util.ICalDate; @@ -264,6 +267,9 @@ public class AdapterMessage extends RecyclerView.Adapter() { @@ -1069,11 +1093,17 @@ public class AdapterMessage extends RecyclerView.Adapter 0); + grpCalendarResponse.setVisibility(canRespond ? View.VISIBLE : View.GONE); } @Override @@ -1125,6 +1163,7 @@ public class AdapterMessage extends RecyclerView.Adapter() { + @Override + protected File onExecute(Context context, Bundle args) throws Throwable { + long id = args.getLong("id"); + int action = args.getInt("action"); + + DB db = DB.getInstance(context); + + EntityMessage message = db.message().getMessage(id); + if (message == null) + return null; + + List attachments = db.attachment().getAttachments(id); + for (EntityAttachment attachment : attachments) + if (attachment.available && "text/calendar".equals(attachment.type)) { + File file = attachment.getFile(context); + ICalendar icalendar = Biweekly.parse(file).first(); + VEvent event = icalendar.getEvents().get(0); + + // https://tools.ietf.org/html/rfc5546#section-4.2.2 + VEvent ev = new VEvent(); + ev.setOrganizer(event.getOrganizer()); + ev.setUid(event.getUid()); + if (event.getSequence() != null) + ev.setSequence(event.getSequence()); + + InternetAddress to = (InternetAddress) message.to[0]; + Attendee attendee = new Attendee(to.getPersonal(), to.getAddress()); + + switch (action) { + case R.id.btnCalendarAccept: + attendee.setParticipationStatus(ParticipationStatus.ACCEPTED); + break; + case R.id.btnCalendarDecline: + attendee.setParticipationStatus(ParticipationStatus.DECLINED); + break; + case R.id.btnCalendarMaybe: + attendee.setParticipationStatus(ParticipationStatus.TENTATIVE); + break; + } + + ev.addAttendee(attendee); + + ICalendar response = new ICalendar(); + response.setMethod(Method.REPLY); + response.addEvent(ev); + + File dir = new File(context.getFilesDir(), "temporary"); + if (!dir.exists()) + dir.mkdir(); + File ics = new File(dir, "meeting.ics"); + response.write(ics); + + return ics; + } + + return null; + } + + @Override + protected void onExecuted(Bundle args, File ics) { + Intent reply = new Intent(context, ActivityCompose.class) + .putExtra("action", "participation") + .putExtra("reference", args.getLong("id")) + .putExtra("ics", ics); + context.startActivity(reply); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(context, owner, ex); + } + }.execute(context, owner, args, "message:participation"); + } + private TupleMessageEx getMessage() { int pos = getAdapterPosition(); if (pos == RecyclerView.NO_POSITION) @@ -1187,18 +1306,30 @@ public class AdapterMessage extends RecyclerView.Adapter 0) { String to = ((InternetAddress) ref.to[0]).getAddress(); int at = to.indexOf('@'); @@ -2072,14 +2075,14 @@ public class FragmentCompose extends FragmentBase { } else if ("receipt".equals(action)) { draft.receipt_request = true; } - } else if ("forward".equals(action)) { draft.thread = draft.msgid; // new thread draft.from = ref.to; } String subject = (ref.subject == null ? "" : ref.subject); - if ("reply".equals(action) || "reply_all".equals(action)) { + if ("reply".equals(action) || "reply_all".equals(action) || + "participation".equals(action)) { String re = context.getString(R.string.title_subject_reply, ""); if (!prefix_once || !subject.startsWith(re)) draft.subject = context.getString(R.string.title_subject_reply, subject); @@ -2172,6 +2175,19 @@ public class FragmentCompose extends FragmentBase { HtmlHelper.getPreview(body), null); + if ("participation".equals(action)) { + EntityAttachment attachment = new EntityAttachment(); + attachment.message = draft.id; + attachment.sequence = 1; + attachment.name = ics.getName(); + attachment.type = "text/calendar"; + attachment.size = ics.length(); + attachment.progress = null; + attachment.available = true; + attachment.id = db.attachment().insertAttachment(attachment); + ics.renameTo(attachment.getFile(context)); + } + Core.updateMessageSize(context, draft.id); // Write reference text diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index 27523877f6..57ddc0c390 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -68,6 +68,9 @@ import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeUtility; import javax.mail.internet.ParseException; +import biweekly.Biweekly; +import biweekly.ICalendar; + public class MessageHelper { private MimeMessage imessage; @@ -373,22 +376,38 @@ public class MessageHelper { for (final EntityAttachment attachment : attachments) if (attachment.available) { BodyPart bpAttachment = new MimeBodyPart(); - bpAttachment.setFileName(attachment.name); File file = attachment.getFile(context); + FileDataSource dataSource = new FileDataSource(file); dataSource.setFileTypeMap(new FileTypeMap() { @Override public String getContentType(File file) { + // https://tools.ietf.org/html/rfc6047 + if ("text/calendar".equals(attachment.type)) + try { + ICalendar icalendar = Biweekly.parse(file).first(); + if (icalendar != null && + icalendar.getMethod() != null && + icalendar.getMethod().isReply()) + return "text/calendar" + + "; method=REPLY" + + "; charset=" + Charset.defaultCharset().name(); + } catch (IOException ex) { + Log.e(ex); + } + return attachment.type; } @Override public String getContentType(String filename) { - return attachment.type; + return getContentType(new File(filename)); } }); bpAttachment.setDataHandler(new DataHandler(dataSource)); + + bpAttachment.setFileName(attachment.name); if (attachment.disposition != null) bpAttachment.setDisposition(attachment.disposition); if (attachment.cid != null) diff --git a/app/src/main/res/layout/include_message_calendar.xml b/app/src/main/res/layout/include_message_calendar.xml index 4b0221efc8..a3f9f632a3 100644 --- a/app/src/main/res/layout/include_message_calendar.xml +++ b/app/src/main/res/layout/include_message_calendar.xml @@ -71,12 +71,51 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tvCalendarEnd" /> +