iCalendar support

This commit is contained in:
M66B 2019-05-18 11:19:39 +02:00
parent 336dd61470
commit 1c494057e3
7 changed files with 241 additions and 3 deletions

View File

@ -166,8 +166,9 @@ FairEmail uses:
* [ShortcutBadger](https://github.com/leolin310148/ShortcutBadger). Copyright 2014 Leo Lin. [Apache license](https://github.com/leolin310148/ShortcutBadger/blob/master/LICENSE).
* [PhotoView](https://github.com/chrisbanes/PhotoView). Copyright 2018 Chris Banes. [Apache License](https://github.com/chrisbanes/PhotoView/blob/master/LICENSE).
* [Bugsnag exception reporter for Android](https://github.com/bugsnag/bugsnag-android). Copyright (c) 2012 Bugsnag. [MIT License](https://github.com/bugsnag/bugsnag-android/blob/master/LICENSE.txt).
* [biweekly](https://github.com/mangstadt/biweekly). Copyright (c) 2013-2018, Michael Angstadt. [BSD 2-Clause](https://github.com/mangstadt/biweekly/blob/master/LICENSE).
FairEmail is sponsored by:
Error reporting is sponsored by:
![Bugsnag Logo](/images/bugsnag_logo_navy.png)

View File

@ -133,6 +133,7 @@ dependencies {
def badge_version = "1.1.22"
def photoview_version = "2.3.0"
def bugsnag_version = "4.14.0"
def biweekly_version = "0.6.3"
// https://developer.android.com/jetpack/androidx/releases/
@ -217,6 +218,11 @@ dependencies {
// https://github.com/bugsnag/bugsnag-android
implementation "com.bugsnag:bugsnag-android:$bugsnag_version"
// https://github.com/mangstadt/biweekly
implementation("net.sf.biweekly:biweekly:$biweekly_version") {
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-core'
}
// git clone https://android.googlesource.com/platform/frameworks/opt/colorpicker
implementation project(path: ':colorpicker')
}

View File

@ -121,6 +121,13 @@ import java.util.List;
import javax.mail.Address;
import javax.mail.internet.InternetAddress;
import biweekly.Biweekly;
import biweekly.ICalendar;
import biweekly.component.VEvent;
import biweekly.property.Attendee;
import biweekly.property.Summary;
import biweekly.util.ICalDate;
public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHolder> {
private Context context;
private LayoutInflater inflater;
@ -253,10 +260,17 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private ContentLoadingProgressBar pbBody;
private TextView tvNoInternetBody;
private TextView tvCalendarSummary;
private TextView tvCalendarStart;
private TextView tvCalendarEnd;
private TextView tvAttendees;
private ContentLoadingProgressBar pbCalendarWait;
private RecyclerView rvImage;
private Group grpAddresses;
private Group grpHeaders;
private Group grpCalendar;
private Group grpAttachments;
private Group grpImages;
@ -333,6 +347,13 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
pbHeaders = itemView.findViewById(R.id.pbHeaders);
tvNoInternetHeaders = itemView.findViewById(R.id.tvNoInternetHeaders);
tvCalendarSummary = view.findViewById(R.id.tvCalendarSummary);
tvCalendarStart = view.findViewById(R.id.tvCalendarStart);
tvCalendarEnd = view.findViewById(R.id.tvCalendarEnd);
tvAttendees = view.findViewById(R.id.tvAttendees);
pbCalendarWait = view.findViewById(R.id.pbCalendarWait);
rvAttachment = attachments.findViewById(R.id.rvAttachment);
rvAttachment.setHasFixedSize(false);
LinearLayoutManager llm = new LinearLayoutManager(context);
@ -367,6 +388,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
grpAddresses = itemView.findViewById(R.id.grpAddresses);
grpHeaders = itemView.findViewById(R.id.grpHeaders);
grpCalendar = itemView.findViewById(R.id.grpCalendar);
grpAttachments = attachments.findViewById(R.id.grpAttachments);
grpImages = itemView.findViewById(R.id.grpImages);
}
@ -693,6 +715,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
grpAddresses.setVisibility(View.GONE);
grpHeaders.setVisibility(View.GONE);
grpCalendar.setVisibility(View.GONE);
grpAttachments.setVisibility(View.GONE);
grpImages.setVisibility(View.GONE);
@ -724,6 +747,12 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
pbHeaders.setVisibility(View.GONE);
tvNoInternetHeaders.setVisibility(View.GONE);
tvCalendarSummary.setVisibility(View.GONE);
tvCalendarStart.setVisibility(View.GONE);
tvCalendarEnd.setVisibility(View.GONE);
tvAttendees.setVisibility(View.GONE);
pbCalendarWait.setVisibility(View.GONE);
cbInline.setVisibility(View.GONE);
btnDownloadAttachments.setVisibility(View.GONE);
btnSaveAttachments.setVisibility(View.GONE);
@ -997,6 +1026,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
boolean download = false;
boolean save = (attachments.size() > 1);
boolean downloading = false;
boolean calendar = false;
List<EntityAttachment> a = new ArrayList<>();
for (EntityAttachment attachment : attachments) {
boolean inline = (TextUtils.isEmpty(attachment.name) ||
@ -1011,9 +1041,92 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
downloading = true;
if (show_inline || !inline)
a.add(attachment);
if (attachment.available && "text/calendar".endsWith(attachment.type)) {
// https://tools.ietf.org/html/rfc5546
calendar = true;
Bundle args = new Bundle();
args.putSerializable("file", attachment.getFile(context));
new SimpleTask<ICalendar>() {
@Override
protected void onPreExecute(Bundle args) {
grpCalendar.setVisibility(View.VISIBLE);
pbCalendarWait.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(Bundle args) {
pbCalendarWait.setVisibility(View.GONE);
}
@Override
protected ICalendar onExecute(Context context, Bundle args) throws IOException {
File file = (File) args.getSerializable("file");
return Biweekly.parse(file).first();
}
@Override
protected void onExecuted(Bundle args, ICalendar icalendar) {
if (icalendar == null || icalendar.getEvents().size() == 0)
return;
DateFormat df = SimpleDateFormat.getDateTimeInstance();
VEvent event = icalendar.getEvents().get(0);
Summary summary = event.getSummary();
ICalDate start = event.getDateStart() == null ? null : event.getDateStart().getValue();
ICalDate end = event.getDateEnd() == null ? null : event.getDateEnd().getValue();
List<String> attendee = new ArrayList<>();
for (Attendee a : event.getAttendees()) {
String email = a.getEmail();
String name = a.getCommonName();
if (TextUtils.isEmpty(name)) {
if (!TextUtils.isEmpty(email))
attendee.add(email);
} else {
if (TextUtils.isEmpty(email) || name.equals(email))
attendee.add(name);
else
attendee.add(name + " (" + email + ")");
}
}
tvCalendarSummary.setText(summary == null ? null : summary.getValue());
tvCalendarSummary.setVisibility(summary == null ? View.GONE : View.VISIBLE);
tvCalendarStart.setText(start == null ? null : df.format(start.getTime()));
tvCalendarStart.setVisibility(start == null ? View.GONE : View.VISIBLE);
tvCalendarEnd.setText(end == null ? null : df.format(end.getTime()));
tvCalendarEnd.setVisibility(end == null ? View.GONE : View.VISIBLE);
tvAttendees.setText(TextUtils.join(", ", attendee));
tvAttendees.setVisibility(attendee.size() == 0 ? View.GONE : View.VISIBLE);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:calendar");
}
}
adapterAttachment.set(a);
if (!calendar) {
tvCalendarSummary.setVisibility(View.GONE);
tvCalendarStart.setVisibility(View.GONE);
tvCalendarEnd.setVisibility(View.GONE);
tvAttendees.setVisibility(View.GONE);
pbCalendarWait.setVisibility(View.GONE);
grpCalendar.setVisibility(View.GONE);
}
cbInline.setOnCheckedChangeListener(null);
cbInline.setChecked(show_inline);
cbInline.setVisibility(has_inline ? View.VISIBLE : View.GONE);

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".ActivityView">
<View
android:id="@+id/vSeparatorCalendar"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="6dp"
android:layout_marginTop="3dp"
android:layout_marginEnd="6dp"
android:background="?attr/colorSeparator"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvCalendarSummary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="3dp"
android:layout_marginEnd="6dp"
android:text="Summary"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="?android:attr/textColorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vSeparatorCalendar" />
<TextView
android:id="@+id/tvCalendarStart"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="6dp"
android:text="12:00:00"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvCalendarSummary" />
<TextView
android:id="@+id/tvCalendarEnd"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="3dp"
android:layout_marginEnd="6dp"
android:text="12:00:00"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvCalendarStart" />
<TextView
android:id="@+id/tvAttendees"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="6dp"
android:text="You, me"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvCalendarEnd" />
<View
android:id="@+id/paddingBottom"
android:layout_width="wrap_content"
android:layout_height="6dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvAttendees" />
<eu.faircode.email.ContentLoadingProgressBar
android:id="@+id/pbCalendarWait"
style="@style/Base.Widget.AppCompat.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpCalendar"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="vSeparatorCalendar,paddingBottom" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -350,13 +350,21 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/inMessage" />
<include
android:id="@+id/InCalendar"
layout="@layout/include_message_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vwBody" />
<include
android:id="@+id/inAttachmentsAlt"
layout="@layout/include_message_attachments"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vwBody" />
app:layout_constraintTop_toBottomOf="@id/InCalendar" />
<include
android:id="@+id/inImages"

View File

@ -343,13 +343,21 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/inMessage" />
<include
android:id="@+id/InCalendar"
layout="@layout/include_message_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vwBody" />
<include
android:id="@+id/inAttachmentsAlt"
layout="@layout/include_message_attachments"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vwBody" />
app:layout_constraintTop_toBottomOf="@id/InCalendar" />
<include
android:id="@+id/inImages"

View File

@ -682,6 +682,10 @@
<string name="title_no_charset">Unsupported encoding: %1$s</string>
<string name="title_via">Via: %1$s</string>
<string name="title_icalendar_accept">Accept</string>
<string name="title_icalendar_decline">Decline</string>
<string name="title_icalendar_maybe">Maybe</string>
<string name="title_try">Try FairEmail, an open source, privacy friendly email app for Android</string>
<string name="title_pro_feature">This is a pro feature</string>