Browse Source

feat: improve new mail notifications per account

- add notification groups per account
- add notification color account
main
Distopico Vegan 6 years ago
parent
commit
0149a10fd8
8 changed files with 233 additions and 135 deletions
  1. +0
    -4
      app/src/main/java/org/dystopia/email/ActivityMain.java
  2. +34
    -43
      app/src/main/java/org/dystopia/email/DaoMessage.java
  3. +0
    -1
      app/src/main/java/org/dystopia/email/FragmentAccount.java
  4. +1
    -1
      app/src/main/java/org/dystopia/email/FragmentOptions.java
  5. +12
    -0
      app/src/main/java/org/dystopia/email/Helper.java
  6. +144
    -86
      app/src/main/java/org/dystopia/email/ServiceSynchronize.java
  7. +1
    -0
      app/src/main/java/org/dystopia/email/TupleMessageEx.java
  8. +41
    -0
      app/src/main/java/org/dystopia/email/TupleNotification.java

+ 0
- 4
app/src/main/java/org/dystopia/email/ActivityMain.java View File

@ -20,17 +20,13 @@ package org.dystopia.email;
*/ */
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import java.util.List; import java.util.List;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
public class ActivityMain extends AppCompatActivity implements FragmentManager.OnBackStackChangedListener { public class ActivityMain extends AppCompatActivity implements FragmentManager.OnBackStackChangedListener {


+ 34
- 43
app/src/main/java/org/dystopia/email/DaoMessage.java View File

@ -17,6 +17,7 @@ package org.dystopia.email;
along with FairEmail. If not, see <http://www.gnu.org/licenses/>. along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018, Marcel Bokhorst (M66B) Copyright 2018, Marcel Bokhorst (M66B)
Copyright 2018, Distopico (dystopia project) <distopico@riseup.net> and contributors
*/ */
import java.util.List; import java.util.List;
@ -69,34 +70,34 @@ public interface DaoMessage {
", folder.name AS folderName, folder.display AS folderDisplay, folder.type AS folderType" + ", folder.name AS folderName, folder.display AS folderDisplay, folder.type AS folderType" +
", COUNT(message.id) AS count" + ", COUNT(message.id) AS count" +
", SUM(CASE WHEN message.ui_seen" + ", SUM(CASE WHEN message.ui_seen" +
" OR (folder.id <> :folder AND folder.type = '" + EntityFolder.ARCHIVE + "')" +
" OR (folder.id <> :folder AND folder.type = '" + EntityFolder.OUTBOX + "')" +
" OR (folder.id <> :folder AND folder.type = '" + EntityFolder.DRAFTS + "') THEN 0 ELSE 1 END) AS unseen" +
" OR (folder.id <> :folderId AND folder.type = '" + EntityFolder.ARCHIVE + "')" +
" OR (folder.id <> :folderId AND folder.type = '" + EntityFolder.OUTBOX + "')" +
" OR (folder.id <> :folderId AND folder.type = '" + EntityFolder.DRAFTS + "') THEN 0 ELSE 1 END) AS unseen" +
", SUM(CASE WHEN message.ui_flagged" + ", SUM(CASE WHEN message.ui_flagged" +
" AND NOT (folder.id <> :folder AND folder.type = '" + EntityFolder.ARCHIVE + "')" +
" AND NOT (folder.id <> :folder AND folder.type = '" + EntityFolder.OUTBOX + "')" +
" AND NOT (folder.id <> :folder AND folder.type = '" + EntityFolder.DRAFTS + "') THEN 0 ELSE 1 END) AS unflagged" +
" AND NOT (folder.id <> :folderId AND folder.type = '" + EntityFolder.ARCHIVE + "')" +
" AND NOT (folder.id <> :folderId AND folder.type = '" + EntityFolder.OUTBOX + "')" +
" AND NOT (folder.id <> :folderId AND folder.type = '" + EntityFolder.DRAFTS + "') THEN 0 ELSE 1 END) AS unflagged" +
", (SELECT COUNT(a.id) FROM attachment a WHERE a.message = message.id) AS attachments" + ", (SELECT COUNT(a.id) FROM attachment a WHERE a.message = message.id) AS attachments" +
" FROM message" + " FROM message" +
" JOIN account ON account.id = message.account" + " JOIN account ON account.id = message.account" +
" JOIN folder ON folder.id = message.folder" + " JOIN folder ON folder.id = message.folder" +
" JOIN folder f ON f.id = :folder" +
" JOIN folder f ON f.id = :folderId" +
" WHERE (message.account = f.account OR folder.type = '" + EntityFolder.OUTBOX + "')" + " WHERE (message.account = f.account OR folder.type = '" + EntityFolder.OUTBOX + "')" +
" AND CASE WHEN (:folderType = '" + EntityFolder.TRASH + "' " + " AND CASE WHEN (:folderType = '" + EntityFolder.TRASH + "' " +
" OR :folderType = '" + EntityFolder.OUTBOX + "') " + " OR :folderType = '" + EntityFolder.OUTBOX + "') " +
" THEN 1" + " THEN 1" +
" ELSE folder.id = :folder" +
" ELSE folder.id = :folderId" +
" END" + " END" +
" AND (NOT message.ui_hide OR :debug)" + " AND (NOT message.ui_hide OR :debug)" +
" AND ui_found = :found" + " AND ui_found = :found" +
" GROUP BY CASE WHEN message.thread IS NULL THEN message.id ELSE message.thread END" + " GROUP BY CASE WHEN message.thread IS NULL THEN message.id ELSE message.thread END" +
" HAVING SUM(CASE WHEN folder.id = :folder THEN 1 ELSE 0 END) > 0" +
" HAVING SUM(CASE WHEN folder.id = :folderId THEN 1 ELSE 0 END) > 0" +
" ORDER BY CASE" + " ORDER BY CASE" +
" WHEN 'unread' = :sort THEN NOT message.ui_seen" + " WHEN 'unread' = :sort THEN NOT message.ui_seen" +
" WHEN 'starred' = :sort THEN message.ui_flagged" + " WHEN 'starred' = :sort THEN message.ui_flagged" +
" ELSE 0" + " ELSE 0" +
" END DESC, message.received DESC, message.sent DESC") " END DESC, message.received DESC, message.sent DESC")
DataSource.Factory<Integer, TupleMessageEx> pagedFolder(long folder, String folderType, String sort, boolean found, boolean debug);
DataSource.Factory<Integer, TupleMessageEx> pagedFolder(long folderId, String folderType, String sort, boolean found, boolean debug);
@Query("SELECT message.*" + @Query("SELECT message.*" +
", account.name AS accountName, account.color AS accountColor" + ", account.name AS accountName, account.color AS accountColor" +
@ -111,13 +112,13 @@ public interface DaoMessage {
" WHERE message.account = :account" + " WHERE message.account = :account" +
" AND message.thread = :thread" + " AND message.thread = :thread" +
" AND (NOT message.ui_hide OR :debug)" + " AND (NOT message.ui_hide OR :debug)" +
" AND NOT (folder.type = '" + EntityFolder.TRASH + "' AND folder.id <> :folder)" +
" AND NOT (folder.type = '" + EntityFolder.TRASH + "' AND folder.id <> :folderId)" +
" ORDER BY CASE" + " ORDER BY CASE" +
" WHEN 'unread' = :sort THEN NOT message.ui_seen" + " WHEN 'unread' = :sort THEN NOT message.ui_seen" +
" WHEN 'starred' = :sort THEN message.ui_flagged" + " WHEN 'starred' = :sort THEN message.ui_flagged" +
" ELSE 0" + " ELSE 0" +
" END DESC, message.received DESC, message.sent DESC") " END DESC, message.received DESC, message.sent DESC")
DataSource.Factory<Integer, TupleMessageEx> pagedThread(long account, long folder, String thread, String sort, boolean debug);
DataSource.Factory<Integer, TupleMessageEx> pagedThread(long account, long folderId, String thread, String sort, boolean debug);
@Query("SELECT COUNT(id)" + @Query("SELECT COUNT(id)" +
" FROM message" + " FROM message" +
@ -131,16 +132,16 @@ public interface DaoMessage {
@Query("SELECT *" + @Query("SELECT *" +
" FROM message" + " FROM message" +
" WHERE folder = :folder" +
" WHERE folder = :folderId" +
" AND uid = :uid" + " AND uid = :uid" +
" AND ui_found = :found") " AND ui_found = :found")
EntityMessage getMessageByUid(long folder, long uid, boolean found);
EntityMessage getMessageByUid(long folderId, long uid, boolean found);
@Query("SELECT *" + @Query("SELECT *" +
" FROM message" + " FROM message" +
" WHERE folder = :folder" +
" WHERE folder = :folderId" +
" AND NOT ui_found") " AND NOT ui_found")
List<EntityMessage> getMessageByFolder(long folder);
List<EntityMessage> getMessageByFolder(long folderId);
@Query("SELECT *" + @Query("SELECT *" +
" FROM message" + " FROM message" +
@ -149,19 +150,6 @@ public interface DaoMessage {
" AND NOT ui_found") " AND NOT ui_found")
List<EntityMessage> getMessageByThread(long account, String thread); List<EntityMessage> getMessageByThread(long account, String thread);
@Query("SELECT message.*" +
", account.name AS accountName, account.color AS accountColor" +
", folder.name AS folderName, folder.display AS folderDisplay, folder.type AS folderType" +
", (SELECT COUNT(m1.id) FROM message m1 WHERE m1.account = message.account AND m1.thread = message.thread AND NOT m1.ui_hide) AS count" +
", CASE WHEN message.ui_seen THEN 0 ELSE 1 END AS unseen" +
", CASE WHEN message.ui_flagged THEN 0 ELSE 1 END AS unflagged" +
", (SELECT COUNT(a.id) FROM attachment a WHERE a.message = message.id) AS attachments" +
" FROM message" +
" JOIN account ON account.id = message.account" +
" JOIN folder ON folder.id = message.folder" +
" WHERE message.id = :id")
TupleMessageEx getAccountByMessage(long id);
@Query("SELECT message.* FROM message" + @Query("SELECT message.* FROM message" +
" JOIN folder ON folder.id = message.folder" + " JOIN folder ON folder.id = message.folder" +
" WHERE message.account = :account" + " WHERE message.account = :account" +
@ -171,9 +159,9 @@ public interface DaoMessage {
List<EntityMessage> getMessageByMsgId(long account, String msgid, String reference, boolean found); List<EntityMessage> getMessageByMsgId(long account, String msgid, String reference, boolean found);
@Query("SELECT * FROM message" + @Query("SELECT * FROM message" +
" WHERE folder = :folder" +
" WHERE folder = :folderId" +
" AND ui_seen") " AND ui_seen")
List<EntityMessage> getMessageSeen(long folder);
List<EntityMessage> getMessageSeen(long folderId);
@Query("SELECT message.*" + @Query("SELECT message.*" +
", account.name AS accountName, account.color AS accountColor" + ", account.name AS accountName, account.color AS accountColor" +
@ -188,7 +176,10 @@ public interface DaoMessage {
" WHERE message.id = :id") " WHERE message.id = :id")
LiveData<TupleMessageEx> liveMessage(long id); LiveData<TupleMessageEx> liveMessage(long id);
@Query("SELECT message.* FROM message" +
@Query("SELECT message.*" +
", account.name AS accountName, account.color AS accountColor" +
", folder.name AS folderName, folder.display AS folderDisplay, folder.type AS folderType" +
" FROM message" +
" JOIN account ON account.id = message.account" + " JOIN account ON account.id = message.account" +
" JOIN folder ON folder.id = message.folder" + " JOIN folder ON folder.id = message.folder" +
" WHERE account.`synchronize`" + " WHERE account.`synchronize`" +
@ -198,14 +189,14 @@ public interface DaoMessage {
" AND NOT message.ui_found" + " AND NOT message.ui_found" +
" AND NOT message.ui_ignored" + " AND NOT message.ui_ignored" +
" ORDER BY message.received") " ORDER BY message.received")
LiveData<List<EntityMessage>> liveUnseenUnified();
LiveData<List<TupleNotification>> liveUnseenUnified();
@Query("SELECT uid FROM message" + @Query("SELECT uid FROM message" +
" WHERE folder = :folder" +
" WHERE folder = :folderId" +
" AND received >= :received" + " AND received >= :received" +
" AND NOT uid IS NULL" + " AND NOT uid IS NULL" +
" AND NOT ui_found" /* keep found messages */) " AND NOT ui_found" /* keep found messages */)
List<Long> getUids(long folder, long received);
List<Long> getUids(long folderId, long received);
@Insert @Insert
long insertMessage(EntityMessage message); long insertMessage(EntityMessage message);
@ -254,17 +245,17 @@ public interface DaoMessage {
@Query("DELETE FROM message WHERE id = :id") @Query("DELETE FROM message WHERE id = :id")
int deleteMessage(long id); int deleteMessage(long id);
@Query("DELETE FROM message WHERE folder = :folder AND uid = :uid")
int deleteMessage(long folder, long uid);
@Query("DELETE FROM message WHERE folder = :folderId AND uid = :uid")
int deleteMessage(long folderId, long uid);
@Query("DELETE FROM message WHERE folder = :folder AND NOT uid IS NULL")
int deleteLocalMessages(long folder);
@Query("DELETE FROM message WHERE folder = :folderId AND NOT uid IS NULL")
int deleteLocalMessages(long folderId);
@Query("DELETE FROM message WHERE folder = :folder AND seen")
int deleteSeenMessages(long folder);
@Query("DELETE FROM message WHERE folder = :folderId AND seen")
int deleteSeenMessages(long folderId);
@Query("DELETE FROM message WHERE folder = :folder AND received < :received AND NOT uid IS NULL")
int deleteMessagesBefore(long folder, long received);
@Query("DELETE FROM message WHERE folder = :folderId AND received < :received AND NOT uid IS NULL")
int deleteMessagesBefore(long folderId, long received);
@Query("DELETE FROM message WHERE ui_found") @Query("DELETE FROM message WHERE ui_found")
int deleteFoundMessages(); int deleteFoundMessages();


+ 0
- 1
app/src/main/java/org/dystopia/email/FragmentAccount.java View File

@ -87,7 +87,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.Group; import androidx.constraintlayout.widget.Group;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import static android.accounts.AccountManager.newChooseAccountIntent; import static android.accounts.AccountManager.newChooseAccountIntent;


+ 1
- 1
app/src/main/java/org/dystopia/email/FragmentOptions.java View File

@ -26,8 +26,8 @@ import android.preference.PreferenceManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Switch;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.Switch;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;


+ 12
- 0
app/src/main/java/org/dystopia/email/Helper.java View File

@ -17,16 +17,19 @@ package org.dystopia.email;
along with FairEmail. If not, see <http://www.gnu.org/licenses/>. along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018, Marcel Bokhorst (M66B) Copyright 2018, Marcel Bokhorst (M66B)
Copyright 2018, Distopico (dystopia project) <distopico@riseup.net> and contributors
*/ */
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager; import android.accounts.AccountManager;
import android.app.Notification;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
@ -204,6 +207,15 @@ public class Helper {
} }
} }
static Notification.Builder getNotificationBuilder(Context context, String channelId) {
Notification.Builder pbuilder;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return new Notification.Builder(context);
} else {
return new Notification.Builder(context, channelId);
}
}
static String getExtension(String filename) { static String getExtension(String filename) {
if (filename == null) if (filename == null)
return null; return null;


+ 144
- 86
app/src/main/java/org/dystopia/email/ServiceSynchronize.java View File

@ -34,7 +34,6 @@ import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Color;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
@ -52,6 +51,7 @@ import android.provider.ContactsContract;
import android.text.Html; import android.text.Html;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import com.sun.mail.iap.ConnectionException; import com.sun.mail.iap.ConnectionException;
import com.sun.mail.imap.AppendUID; import com.sun.mail.imap.AppendUID;
@ -170,41 +170,81 @@ public class ServiceSynchronize extends LifecycleService {
} }
}); });
db.message().liveUnseenUnified().observe(this, new Observer<List<EntityMessage>>() {
private List<Integer> notifying = new ArrayList<>();
db.message().liveUnseenUnified().observe(this, new Observer<List<TupleNotification>>() {
private Map<Long, List<Integer>> notifyingAll = new HashMap<>();
private Map<Long, Pair> accounts = new HashMap<>();
@Override @Override
public void onChanged(List<EntityMessage> messages) {
public void onChanged(List<TupleNotification> messages) {
NotificationManager nm = getSystemService(NotificationManager.class); NotificationManager nm = getSystemService(NotificationManager.class);
List<Notification> notifications = getNotificationUnseen(messages);
List<Integer> all = new ArrayList<>();
List<Integer> added = new ArrayList<>();
List<Integer> removed = new ArrayList<>(notifying);
for (Notification notification : notifications) {
Integer id = (int) notification.extras.getLong("id", 0);
if (id > 0) {
all.add(id);
if (removed.contains(id))
removed.remove(id);
else
added.add(id);
Map<Long, ArrayList<TupleNotification>> messagesByAccount = new HashMap<>();
// Update unseen for all account
setWidgetUnseen(messages);
// Organize messages per account
for (TupleNotification message : messages) {
Long accountKey = message.account;
ArrayList<TupleNotification> newList = new ArrayList<>();
newList.add(message);
if (messagesByAccount.containsKey(accountKey)) {
ArrayList<TupleNotification> msgList = messagesByAccount.get(accountKey);
newList.addAll(msgList);
} }
if (!accounts.containsKey(accountKey)) {
String accountName = message.accountName;
Integer accountColor = message.accountColor;
accounts.put(accountKey, new Pair<String, Integer>(accountName, accountColor));
}
messagesByAccount.put(accountKey, newList);
} }
if (notifications.size() == 0)
nm.cancel("unseen", 0);
// Set and group notification per account
for (Map.Entry<Long, ArrayList<TupleNotification>> messagesAccount : messagesByAccount.entrySet()) {
Long accountId = messagesAccount.getKey();
List<Notification> notifications = getNotificationUnseen(messagesAccount.getValue(), accounts.get(accountId));
List<Integer> all = new ArrayList<>();
List<Integer> added = new ArrayList<>();
List<Integer> removed = new ArrayList<>();
String tag = "unseen-" + accountId;
if (notifyingAll.containsKey(accountId)) {
removed = notifyingAll.get(accountId);
}
for (Notification notification : notifications) {
Integer id = (int) notification.extras.getLong("id", 0);
if (id > 0) {
all.add(id);
if (removed.contains(id)) {
removed.remove(id);
} else {
added.add(id);
}
}
}
for (Integer id : removed)
nm.cancel("unseen", id);
if (notifications.size() == 0) {
nm.cancel(tag, 0);
}
for (Notification notification : notifications) {
Integer id = (int) notification.extras.getLong("id", 0);
if ((id == 0 && added.size() + removed.size() > 0) || added.contains(id))
nm.notify("unseen", id, notification);
}
for (Integer id : removed) {
nm.cancel(tag, id);
}
notifying = all;
for (Notification notification : notifications) {
Integer id = (int) notification.extras.getLong("id", 0);
if ((id == 0 && added.size() + removed.size() > 0) || added.contains(id)) {
nm.notify(tag, id, notification);
}
}
notifyingAll.put(accountId, all);
}
} }
}); });
} }
@ -309,11 +349,7 @@ public class ServiceSynchronize extends LifecycleService {
this, ActivityView.REQUEST_UNIFIED, intent, PendingIntent.FLAG_UPDATE_CURRENT); this, ActivityView.REQUEST_UNIFIED, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// Build notification // Build notification
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
builder = new Notification.Builder(this, "service");
else
builder = new Notification.Builder(this);
Notification.Builder builder = Helper.getNotificationBuilder(this, "service");
builder builder
.setSmallIcon(R.drawable.baseline_compare_arrows_white_24) .setSmallIcon(R.drawable.baseline_compare_arrows_white_24)
@ -340,17 +376,36 @@ public class ServiceSynchronize extends LifecycleService {
return builder; return builder;
} }
private List<Notification> getNotificationUnseen(List<EntityMessage> messages) {
/**
* Update widget unseen message for all accounts
* @param messages - list of unseen messages
*/
private void setWidgetUnseen(List<TupleNotification> messages) {
Widget.update(this, messages.size());
}
/**
* Get public/summary and individual notifications per account
* @param messages - list of unseen notifications
* @param account - account information (name, color)
* @return List<Notification>
*/
private List<Notification> getNotificationUnseen(List<TupleNotification> messages, Pair account) {
// https://developer.android.com/training/notify-user/group // https://developer.android.com/training/notify-user/group
List<Notification> notifications = new ArrayList<>(); List<Notification> notifications = new ArrayList<>();
Widget.update(this, messages.size());
if (messages.size() == 0)
if (messages.size() == 0) {
return notifications; return notifications;
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String accountName = (String) account.first;
Integer accountColor = (int) account.second;
Integer groupColor = accountColor != null ? accountColor : ContextCompat.getColor(getBaseContext(), R.color.colorPrimary);
String groupKey = BuildConfig.APPLICATION_ID + accountName;
String channelId = "notification";
// Build pending intent // Build pending intent
Intent view = new Intent(this, ActivityView.class); Intent view = new Intent(this, ActivityView.class);
view.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); view.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@ -361,60 +416,74 @@ public class ServiceSynchronize extends LifecycleService {
clear.setAction("clear"); clear.setAction("clear");
PendingIntent piClear = PendingIntent.getService(this, PI_CLEAR, clear, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent piClear = PendingIntent.getService(this, PI_CLEAR, clear, PendingIntent.FLAG_UPDATE_CURRENT);
// Build notification
Notification.Builder builder;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
builder = new Notification.Builder(this);
else
builder = new Notification.Builder(this, "notification");
builder
// Public notification
Notification.Builder pbuilder = Helper.getNotificationBuilder(this, channelId);
pbuilder
.setSmallIcon(R.drawable.ic_mail_icon) .setSmallIcon(R.drawable.ic_mail_icon)
.setContentTitle(getResources().getQuantityString(R.plurals.title_notification_unseen, messages.size(), messages.size())) .setContentTitle(getResources().getQuantityString(R.plurals.title_notification_unseen, messages.size(), messages.size()))
.setContentText("")
.setContentText(accountName)
.setContentIntent(piView) .setContentIntent(piView)
.setNumber(messages.size()) .setNumber(messages.size())
.setShowWhen(false)
.setColor(ContextCompat.getColor(getBaseContext(), R.color.colorPrimary))
.setShowWhen(true)
.setColor(groupColor)
.setDeleteIntent(piClear)
.setPriority(Notification.PRIORITY_DEFAULT)
.setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_PUBLIC);
// Summary notification
Notification.Builder gbuilder = Helper.getNotificationBuilder(this, channelId);
String summaryText = getResources().getQuantityString(R.plurals.title_notification_unseen, messages.size(), messages.size());
gbuilder
.setSmallIcon(R.drawable.ic_mail_icon)
.setContentTitle(summaryText)
.setContentIntent(piView)
.setNumber(messages.size())
.setShowWhen(true)
.setColor(groupColor)
.setDeleteIntent(piClear) .setDeleteIntent(piClear)
.setPriority(Notification.PRIORITY_DEFAULT) .setPriority(Notification.PRIORITY_DEFAULT)
.setCategory(Notification.CATEGORY_STATUS) .setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_PRIVATE) .setVisibility(Notification.VISIBILITY_PRIVATE)
.setGroup(BuildConfig.APPLICATION_ID)
.setGroupSummary(true);
.setGroup(groupKey)
.setGroupSummary(true)
.setPublicVersion(pbuilder.build());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
builder.setSound(null);
else
builder.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
gbuilder.setSound(null);
} else {
gbuilder.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN);
}
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O && if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O &&
prefs.getBoolean("light", false)) { prefs.getBoolean("light", false)) {
builder.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_LIGHTS);
builder.setLights(0xff00ff00, 1000, 1000);
gbuilder.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_LIGHTS);
gbuilder.setLights(0xff00ff00, 1000, 1000);
} }
DateFormat df = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.SHORT, SimpleDateFormat.SHORT); DateFormat df = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.SHORT, SimpleDateFormat.SHORT);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (EntityMessage message : messages) { for (EntityMessage message : messages) {
sb.append("<strong>").append(MessageHelper.getFormattedAddresses(message.from, false)).append("</strong>"); sb.append("<strong>").append(MessageHelper.getFormattedAddresses(message.from, false)).append("</strong>");
if (!TextUtils.isEmpty(message.subject))
if (!TextUtils.isEmpty(message.subject)) {
sb.append(": ").append(message.subject); sb.append(": ").append(message.subject);
}
sb.append(" ").append(df.format(new Date(message.sent == null ? message.received : message.sent))); sb.append(" ").append(df.format(new Date(message.sent == null ? message.received : message.sent)));
sb.append("<br>"); sb.append("<br>");
} }
builder.setStyle(new Notification.BigTextStyle().bigText(Html.fromHtml(sb.toString())));
Notification.BigTextStyle gstyle = new Notification.BigTextStyle()
.bigText(Html.fromHtml(sb.toString())).setSummaryText(accountName);
gbuilder.setStyle(gstyle);
notifications.add(builder.build());
notifications.add(gbuilder.build());
Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
for (EntityMessage message : messages) {
Bundle args = new Bundle();
args.putLong("id", message.id);
//final DB db = DB.getInstance(getBaseContext());
//TupleMessageEx messageEx = db.message().getAccountByMessage(message.id);
for (TupleNotification message : messages) {
Bundle mArgs = new Bundle();
mArgs.putLong("id", message.id);
Intent thread = new Intent(this, ActivityView.class); Intent thread = new Intent(this, ActivityView.class);
thread.setAction("thread:" + message.thread); thread.setAction("thread:" + message.thread);
@ -445,47 +514,40 @@ public class ServiceSynchronize extends LifecycleService {
getString(R.string.title_trash), getString(R.string.title_trash),
piTrash); piTrash);
Notification.Builder mbuilder;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
mbuilder = new Notification.Builder(this);
else
mbuilder = new Notification.Builder(this, "notification");
Notification.Builder mbuilder = Helper.getNotificationBuilder(this, channelId);
Notification.InboxStyle mstyle = new Notification.InboxStyle();
mbuilder mbuilder
.addExtras(args)
.addExtras(mArgs)
.setSmallIcon(R.drawable.ic_stat_name) .setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle(MessageHelper.getFormattedAddresses(message.from, true)) .setContentTitle(MessageHelper.getFormattedAddresses(message.from, true))
.setContentIntent(piContent) .setContentIntent(piContent)
.setDeleteIntent(piDelete)
.setSound(uri) .setSound(uri)
.setColor(groupColor)
.setWhen(message.sent == null ? message.received : message.sent) .setWhen(message.sent == null ? message.received : message.sent)
.setDeleteIntent(piDelete)
.setPriority(Notification.PRIORITY_DEFAULT) .setPriority(Notification.PRIORITY_DEFAULT)
.setCategory(Notification.CATEGORY_STATUS) .setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setGroup(BuildConfig.APPLICATION_ID)
.setVisibility(Notification.VISIBILITY_SECRET)
.setGroup(groupKey)
.setGroupSummary(false) .setGroupSummary(false)
.setSortKey(message.account_name)
.addAction(actionSeen.build()) .addAction(actionSeen.build())
.addAction(actionTrash.build()); .addAction(actionTrash.build());
//if (messageEx != null && messageEx.accountColor != null) {
// mbuilder.setColor(ContextCompat.getColor(getBaseContext(), messageEx.accountColor));
//}
if (messages.size() == 1) {
mbuilder.setColor(ContextCompat.getColor(getBaseContext(), R.color.colorPrimary));
}
if (!TextUtils.isEmpty(message.subject)) { if (!TextUtils.isEmpty(message.subject)) {
mbuilder.setContentText(message.subject); mbuilder.setContentText(message.subject);
mstyle.addLine(message.subject);
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mbuilder.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN); mbuilder.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN);
} }
mstyle.setBigContentTitle(MessageHelper.getFormattedAddresses(message.from, false));
mbuilder.setStyle(mstyle);
notifications.add(mbuilder.build()); notifications.add(mbuilder.build());
} }
return notifications; return notifications;
} }
@ -497,11 +559,7 @@ public class ServiceSynchronize extends LifecycleService {
this, ActivityView.REQUEST_ERROR, intent, PendingIntent.FLAG_UPDATE_CURRENT); this, ActivityView.REQUEST_ERROR, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// Build notification // Build notification
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
builder = new Notification.Builder(this, "error");
else
builder = new Notification.Builder(this);
Notification.Builder builder = Helper.getNotificationBuilder(this, "error");
builder builder
.setSmallIcon(android.R.drawable.stat_notify_error) .setSmallIcon(android.R.drawable.stat_notify_error)


+ 1
- 0
app/src/main/java/org/dystopia/email/TupleMessageEx.java View File

@ -17,6 +17,7 @@ package org.dystopia.email;
along with FairEmail. If not, see <http://www.gnu.org/licenses/>. along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018, Marcel Bokhorst (M66B) Copyright 2018, Marcel Bokhorst (M66B)
Copyright 2018, Distopico (dystopia project) <distopico@riseup.net> and contributors
*/ */
public class TupleMessageEx extends EntityMessage { public class TupleMessageEx extends EntityMessage {


+ 41
- 0
app/src/main/java/org/dystopia/email/TupleNotification.java View File

@ -0,0 +1,41 @@
package org.dystopia.email;
/*
This file is part of SimpleEmail.
Copyright 2018, Distopico (dystopia project) <distopico@riseup.net> and contributors
This program 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.
This program 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 <http://www.gnu.org/licenses/>.
*/
public class TupleNotification extends EntityMessage {
public String accountName;
public Integer accountColor;
public String folderName;
public String folderDisplay;
public String folderType;
@Override
public boolean equals(Object obj) {
if (obj instanceof TupleMessageEx) {
TupleMessageEx other = (TupleMessageEx) obj;
return (super.equals(obj) &&
(this.accountName == null ? other.accountName == null : this.accountName.equals(other.accountName)) &&
(this.accountColor == null ? other.accountColor == null : this.accountColor.equals(other.accountColor)) &&
this.folderName.equals(other.folderName) &&
(this.folderDisplay == null ? other.folderDisplay == null : this.folderDisplay.equals(other.folderDisplay)) &&
this.folderType.equals(other.folderType));
}
return super.equals(obj);
}
}

Loading…
Cancel
Save