diff --git a/FAQ.md b/FAQ.md
index fd64dec087..4301e97ace 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -54,7 +54,6 @@ Anything on this list is in random order and *might* be added in the near future
## Frequently requested features
* *Design*: the design is based on many discussions and if you like you can discuss about it [in this forum](https://forum.xda-developers.com/android/apps-games/source-email-t3824168) too. See below for the design goals.
-* *Widget to read messages*: widgets can have limited user interaction only, so a widget to read conversations would not be very convenient. Moreover, it would be not very useful to duplicate functions which are already available in the app.
* *ActiveSync*: using the Exchange ActiveSync protocol requires [a license](https://en.wikipedia.org/wiki/Exchange_ActiveSync#Licensing), so this cannot be added.
The goal of the design is to be minimalistic (no unnecessary menus, buttons, etc) and non distracting (no fancy colors, animations, etc).
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ba4be76b01..ca45260399 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -255,6 +255,23 @@
android:resource="@xml/widget" />
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/eu/faircode/email/ActivityView.java b/app/src/main/java/eu/faircode/email/ActivityView.java
index f0f443a683..c46f18bed0 100644
--- a/app/src/main/java/eu/faircode/email/ActivityView.java
+++ b/app/src/main/java/eu/faircode/email/ActivityView.java
@@ -102,6 +102,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
static final int REQUEST_OUTBOX = 4;
static final int REQUEST_ERROR = 5;
static final int REQUEST_UPDATE = 6;
+ static final int REQUEST_WIDGET = 7;
static final String ACTION_VIEW_FOLDERS = BuildConfig.APPLICATION_ID + ".VIEW_FOLDERS";
static final String ACTION_VIEW_MESSAGES = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGES";
@@ -506,7 +507,8 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
else if (action.startsWith("thread")) {
intent.putExtra("thread", action.split(":", 2)[1]);
onViewThread(intent);
- }
+ } else if (action.equals("widget"))
+ onViewThread(intent);
}
if (intent.hasExtra(Intent.EXTRA_PROCESS_TEXT)) {
diff --git a/app/src/main/java/eu/faircode/email/DaoMessage.java b/app/src/main/java/eu/faircode/email/DaoMessage.java
index 86dd6207fe..5e783b28b7 100644
--- a/app/src/main/java/eu/faircode/email/DaoMessage.java
+++ b/app/src/main/java/eu/faircode/email/DaoMessage.java
@@ -273,6 +273,17 @@ public interface DaoMessage {
" ORDER BY message.received")
LiveData> liveUnseenNotify();
+ @Query("SELECT message.*" +
+ " FROM message" +
+ " JOIN account ON account.id = message.account" +
+ " JOIN folder ON folder.id = message.folder" +
+ " WHERE account.`synchronize`" +
+ " AND folder.unified" +
+ " AND message.ui_hide = 0" +
+ " AND message.ui_snoozed IS NULL" +
+ " ORDER BY message.received DESC")
+ LiveData> liveWidgetUnified();
+
@Query("SELECT COUNT(message.id) FROM message" +
" JOIN account ON account.id = message.account" +
" JOIN folder ON folder.id = message.folder" +
diff --git a/app/src/main/java/eu/faircode/email/WidgetUnified.java b/app/src/main/java/eu/faircode/email/WidgetUnified.java
new file mode 100644
index 0000000000..d79996ec67
--- /dev/null
+++ b/app/src/main/java/eu/faircode/email/WidgetUnified.java
@@ -0,0 +1,67 @@
+package eu.faircode.email;
+
+/*
+ This file is part of FairEmail.
+
+ FairEmail is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ FairEmail is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with FairEmail. If not, see .
+
+ Copyright 2018-2019 by Marcel Bokhorst (M66B)
+*/
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.widget.RemoteViews;
+
+public class WidgetUnified extends AppWidgetProvider {
+ @Override
+ public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) {
+ Intent view = new Intent(context, ActivityView.class);
+ view.setAction("unified");
+ view.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PendingIntent pi = PendingIntent.getActivity(context, ActivityView.REQUEST_UNIFIED, view, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ for (int id : appWidgetIds) {
+ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_unified);
+
+ views.setOnClickPendingIntent(R.id.title, pi);
+
+ Intent service = new Intent(context, WidgetUnifiedService.class);
+ service.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id);
+ service.setData(Uri.parse(service.toUri(Intent.URI_INTENT_SCHEME)));
+
+ views.setRemoteAdapter(R.id.lv, service);
+
+ Intent thread = new Intent(context, ActivityView.class);
+ thread.setAction("widget");
+ thread.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PendingIntent piItem = PendingIntent.getActivity(
+ context, ActivityView.REQUEST_WIDGET, thread, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ views.setPendingIntentTemplate(R.id.lv, piItem);
+
+ appWidgetManager.updateAppWidget(id, views);
+ }
+ }
+
+ static void update(Context context) {
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, WidgetUnified.class));
+ appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.lv);
+ }
+}
diff --git a/app/src/main/java/eu/faircode/email/WidgetUnifiedRemoteViewsFactory.java b/app/src/main/java/eu/faircode/email/WidgetUnifiedRemoteViewsFactory.java
new file mode 100644
index 0000000000..542634a907
--- /dev/null
+++ b/app/src/main/java/eu/faircode/email/WidgetUnifiedRemoteViewsFactory.java
@@ -0,0 +1,173 @@
+package eu.faircode.email;
+
+/*
+ This file is part of FairEmail.
+
+ FairEmail is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ FairEmail is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with FairEmail. If not, see .
+
+ Copyright 2018-2019 by Marcel Bokhorst (M66B)
+*/
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Typeface;
+import android.os.Handler;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import androidx.lifecycle.Observer;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static android.os.Looper.getMainLooper;
+
+public class WidgetUnifiedRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
+ private Context context;
+ private DateFormat DTF;
+
+ private Handler handler;
+ private TwoStateOwner owner;
+ private List messages = new ArrayList<>();
+
+ WidgetUnifiedRemoteViewsFactory(final Context context) {
+ this.context = context;
+ this.DTF = Helper.getDateTimeInstance(context, SimpleDateFormat.SHORT, SimpleDateFormat.SHORT);
+
+ this.handler = new Handler(getMainLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ DB db = DB.getInstance(context);
+ owner = new TwoStateOwner("WidgetUnified");
+ db.message().liveWidgetUnified().observe(owner, new Observer>() {
+ @Override
+ public void onChanged(List messages) {
+ if (messages == null)
+ messages = new ArrayList<>();
+
+ boolean changed = false;
+ if (WidgetUnifiedRemoteViewsFactory.this.messages.size() == messages.size()) {
+ for (int i = 0; i < messages.size(); i++) {
+ EntityMessage m1 = messages.get(i);
+ EntityMessage m2 = WidgetUnifiedRemoteViewsFactory.this.messages.get(i);
+ if (!m1.id.equals(m2.id) ||
+ !MessageHelper.equal(m1.from, m2.from) ||
+ !m1.received.equals(m2.received) ||
+ !Objects.equals(m1.subject, m2.subject) ||
+ m1.ui_seen != m2.ui_seen) {
+ changed = true;
+ break;
+ }
+ }
+ } else
+ changed = true;
+
+ WidgetUnifiedRemoteViewsFactory.this.messages = messages;
+
+ if (changed)
+ WidgetUnified.update(context);
+ }
+ });
+ }
+ });
+ }
+
+ @Override
+ public void onCreate() {
+ Log.i("Widget factory create");
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ owner.start();
+ }
+ });
+ }
+
+ @Override
+ public void onDataSetChanged() {
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i("Widget factory destroy");
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ owner.destroy();
+ }
+ });
+ }
+
+ @Override
+ public int getCount() {
+ return messages.size();
+ }
+
+ @Override
+ public RemoteViews getViewAt(int position) {
+ EntityMessage message = messages.get(position);
+
+ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.item_widget_unified);
+
+ Intent thread = new Intent(context, ActivityView.class);
+ thread.putExtra("account", message.account);
+ thread.putExtra("thread", message.thread);
+ thread.putExtra("id", message.id);
+ views.setOnClickFillInIntent(R.id.llMessage, thread);
+
+ SpannableString from = new SpannableString(MessageHelper.formatAddressesShort(message.from));
+ SpannableString time = new SpannableString(DTF.format(message.received));
+ SpannableString subject = new SpannableString(TextUtils.isEmpty(message.subject) ? "" : message.subject);
+
+ if (!message.ui_seen) {
+ from.setSpan(new StyleSpan(Typeface.BOLD), 0, from.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ time.setSpan(new StyleSpan(Typeface.BOLD), 0, time.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ subject.setSpan(new StyleSpan(Typeface.BOLD), 0, subject.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+
+ views.setTextViewText(R.id.tvFrom, from);
+ views.setTextViewText(R.id.tvTime, time);
+ views.setTextViewText(R.id.tvSubject, subject);
+
+ return views;
+ }
+
+ @Override
+ public RemoteViews getLoadingView() {
+ return null;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return messages.get(position).id;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+}
diff --git a/app/src/main/java/eu/faircode/email/WidgetUnifiedService.java b/app/src/main/java/eu/faircode/email/WidgetUnifiedService.java
new file mode 100644
index 0000000000..6e3ed7fcce
--- /dev/null
+++ b/app/src/main/java/eu/faircode/email/WidgetUnifiedService.java
@@ -0,0 +1,30 @@
+package eu.faircode.email;
+
+/*
+ This file is part of FairEmail.
+
+ FairEmail is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ FairEmail is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with FairEmail. If not, see .
+
+ Copyright 2018-2019 by Marcel Bokhorst (M66B)
+*/
+
+import android.content.Intent;
+import android.widget.RemoteViewsService;
+
+public class WidgetUnifiedService extends RemoteViewsService {
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ return new WidgetUnifiedRemoteViewsFactory(this.getApplicationContext());
+ }
+}
diff --git a/app/src/main/res/layout/item_widget_unified.xml b/app/src/main/res/layout/item_widget_unified.xml
new file mode 100644
index 0000000000..f627701bcd
--- /dev/null
+++ b/app/src/main/res/layout/item_widget_unified.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/widget_unified.xml b/app/src/main/res/layout/widget_unified.xml
new file mode 100644
index 0000000000..70feb2af92
--- /dev/null
+++ b/app/src/main/res/layout/widget_unified.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/widget_unified.xml b/app/src/main/res/xml/widget_unified.xml
new file mode 100644
index 0000000000..f53a376d9a
--- /dev/null
+++ b/app/src/main/res/xml/widget_unified.xml
@@ -0,0 +1,8 @@
+
+