mirror of https://github.com/M66B/FairEmail.git
iCalendar support
This commit is contained in:
parent
336dd61470
commit
1c494057e3
|
@ -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)
|
||||
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue