mirror of
https://github.com/M66B/FairEmail.git
synced 2025-02-28 08:46:01 +00:00
Added account option to individually notify new messages
This commit is contained in:
parent
21f148955e
commit
7e9ab05aff
10 changed files with 1204 additions and 43 deletions
FAQ.md
app
schemas/eu.faircode.email.DB
src/main
1
FAQ.md
1
FAQ.md
|
@ -48,7 +48,6 @@ Anything on this list is in random order and *might* be added in the near future
|
|||
* Shortcut frequently contacted: Android [doesn't support this anymore](https://developer.android.com/guide/topics/providers/contacts-provider#ObsoleteData).
|
||||
* Pull down to refresh: new messages are received in real-time, so manual refreshing is not needed, see also [this FAQ](#user-content-faq2).
|
||||
* Switch language: although it is possible to change the language of an app, Android is not designed for this. Better fix the translation in your language if needed, see [this FAQ](#user-content-faq26) about how to.
|
||||
* Notification per account: this would result in multiple icons in the status bar and most people don't like this. Note that the account colors are shown in the notifications too.
|
||||
* Select identities to show in unified inbox: this would add complexity for something which would hardly be used.
|
||||
* Better design: please let me know what you have in mind [in this forum](https://forum.xda-developers.com/android/apps-games/source-email-t3824168).
|
||||
* Hide archived messages: hiding archived messages which exists in other folders too would have a performance impact.
|
||||
|
|
1110
app/schemas/eu.faircode.email.DB/6.json
Normal file
1110
app/schemas/eu.faircode.email.DB/6.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -46,7 +46,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
|
|||
// https://developer.android.com/topic/libraries/architecture/room.html
|
||||
|
||||
@Database(
|
||||
version = 5,
|
||||
version = 6,
|
||||
entities = {
|
||||
EntityIdentity.class,
|
||||
EntityAccount.class,
|
||||
|
@ -155,6 +155,13 @@ public abstract class DB extends RoomDatabase {
|
|||
db.execSQL("ALTER TABLE `message` ADD COLUMN `last_attempt` INTEGER");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(5, 6) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `account` ADD COLUMN `notify` INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ public interface DaoMessage {
|
|||
// https://www.sqlite.org/lang_select.html
|
||||
|
||||
@Query("SELECT message.*" +
|
||||
", account.name AS accountName, IFNULL(identity.color, account.color) AS accountColor" +
|
||||
", account.name AS accountName, IFNULL(identity.color, account.color) AS accountColor, account.notify AS accountNotify" +
|
||||
", folder.name AS folderName, folder.display AS folderDisplay, folder.type AS folderType" +
|
||||
", COUNT(message.id) AS count" +
|
||||
", SUM(CASE WHEN message.ui_seen" +
|
||||
|
@ -69,7 +69,7 @@ public interface DaoMessage {
|
|||
DataSource.Factory<Integer, TupleMessageEx> pagedUnifiedInbox(String sort, boolean debug);
|
||||
|
||||
@Query("SELECT message.*" +
|
||||
", account.name AS accountName, identity.color AS accountColor" +
|
||||
", account.name AS accountName, identity.color AS accountColor, account.notify AS accountNotify" +
|
||||
", folder.name AS folderName, folder.display AS folderDisplay, folder.type AS folderType" +
|
||||
", COUNT(message.id) AS count" +
|
||||
", SUM(CASE WHEN message.ui_seen" +
|
||||
|
@ -102,7 +102,7 @@ public interface DaoMessage {
|
|||
DataSource.Factory<Integer, TupleMessageEx> pagedFolder(long folder, String sort, boolean found, boolean debug);
|
||||
|
||||
@Query("SELECT message.*" +
|
||||
", account.name AS accountName, identity.color AS accountColor" +
|
||||
", account.name AS accountName, identity.color AS accountColor, account.notify AS accountNotify" +
|
||||
", 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" +
|
||||
|
@ -185,7 +185,7 @@ public interface DaoMessage {
|
|||
List<Long> getMessageWithoutPreview();
|
||||
|
||||
@Query("SELECT message.*" +
|
||||
", account.name AS accountName, identity.color AS accountColor" +
|
||||
", account.name AS accountName, identity.color AS accountColor, account.notify AS accountNotify" +
|
||||
", 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" +
|
||||
|
@ -200,7 +200,7 @@ public interface DaoMessage {
|
|||
LiveData<TupleMessageEx> liveMessage(long id);
|
||||
|
||||
@Query("SELECT message.*" +
|
||||
", account.name AS accountName, IFNULL(identity.color, account.color) AS accountColor" +
|
||||
", account.name AS accountName, IFNULL(identity.color, account.color) AS accountColor, account.notify AS accountNotify" +
|
||||
", folder.name AS folderName, folder.display AS folderDisplay, folder.type AS folderType" +
|
||||
", 1 AS count" +
|
||||
", 1 AS unseen" +
|
||||
|
|
|
@ -58,6 +58,8 @@ public class EntityAccount {
|
|||
public Boolean primary;
|
||||
public Integer color;
|
||||
@NonNull
|
||||
public Boolean notify;
|
||||
@NonNull
|
||||
public Integer poll_interval; // keep-alive interval
|
||||
public Long created;
|
||||
public String state;
|
||||
|
@ -78,6 +80,7 @@ public class EntityAccount {
|
|||
json.put("primary", primary);
|
||||
if (color != null)
|
||||
json.put("color", color);
|
||||
json.put("notify", notify);
|
||||
json.put("poll_interval", poll_interval);
|
||||
// not created
|
||||
// not state
|
||||
|
@ -101,6 +104,8 @@ public class EntityAccount {
|
|||
account.primary = json.getBoolean("primary");
|
||||
if (json.has("color"))
|
||||
account.color = json.getInt("color");
|
||||
if (json.has("notify"))
|
||||
account.notify = json.getBoolean("notify");
|
||||
account.poll_interval = json.getInt("poll_interval");
|
||||
return account;
|
||||
}
|
||||
|
@ -120,6 +125,7 @@ public class EntityAccount {
|
|||
this.synchronize.equals(other.synchronize) &&
|
||||
this.primary.equals(other.primary) &&
|
||||
(this.color == null ? other.color == null : this.color.equals(other.color)) &&
|
||||
this.notify.equals(other.notify) &&
|
||||
this.poll_interval.equals(other.poll_interval) &&
|
||||
(this.created == null ? other.created == null : this.created.equals(other.created)) &&
|
||||
(this.state == null ? other.state == null : this.state.equals(other.state)) &&
|
||||
|
|
|
@ -111,6 +111,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
private Button btnColor;
|
||||
private View vwColor;
|
||||
private ImageView ibColorDefault;
|
||||
private CheckBox cbNotify;
|
||||
|
||||
private CheckBox cbSynchronize;
|
||||
private CheckBox cbPrimary;
|
||||
|
@ -183,6 +184,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
btnColor = view.findViewById(R.id.btnColor);
|
||||
vwColor = view.findViewById(R.id.vwColor);
|
||||
ibColorDefault = view.findViewById(R.id.ibColorDefault);
|
||||
cbNotify = view.findViewById(R.id.cbNotify);
|
||||
|
||||
cbSynchronize = view.findViewById(R.id.cbSynchronize);
|
||||
cbPrimary = view.findViewById(R.id.cbPrimary);
|
||||
|
@ -599,6 +601,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
|
||||
args.putString("name", etName.getText().toString());
|
||||
args.putInt("color", color);
|
||||
args.putBoolean("notify", cbNotify.isChecked());
|
||||
|
||||
args.putBoolean("synchronize", cbSynchronize.isChecked());
|
||||
args.putBoolean("primary", cbPrimary.isChecked());
|
||||
|
@ -624,6 +627,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
|
||||
String name = args.getString("name");
|
||||
Integer color = args.getInt("color");
|
||||
boolean notify = args.getBoolean("notify");
|
||||
|
||||
boolean synchronize = args.getBoolean("synchronize");
|
||||
boolean primary = args.getBoolean("primary");
|
||||
|
@ -706,6 +710,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
|
||||
account.name = name;
|
||||
account.color = color;
|
||||
account.notify = notify;
|
||||
|
||||
account.synchronize = synchronize;
|
||||
account.primary = (account.synchronize && primary);
|
||||
|
@ -954,6 +959,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
tilPassword.getEditText().setText(account == null ? null : account.password);
|
||||
|
||||
etName.setText(account == null ? null : account.name);
|
||||
cbNotify.setChecked(account == null ? false : account.notify);
|
||||
|
||||
cbSynchronize.setChecked(account == null ? true : account.synchronize);
|
||||
cbPrimary.setChecked(account == null ? true : account.primary);
|
||||
|
|
|
@ -48,6 +48,7 @@ import android.provider.ContactsContract;
|
|||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.LongSparseArray;
|
||||
|
||||
import com.sun.mail.iap.ConnectionException;
|
||||
import com.sun.mail.imap.AppendUID;
|
||||
|
@ -173,40 +174,60 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
});
|
||||
|
||||
db.message().liveUnseenUnified().observe(this, new Observer<List<TupleMessageEx>>() {
|
||||
private List<Integer> notifying = new ArrayList<>();
|
||||
private LongSparseArray<List<Integer>> notifying = new LongSparseArray<>();
|
||||
|
||||
@Override
|
||||
public void onChanged(List<TupleMessageEx> messages) {
|
||||
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);
|
||||
Widget.update(ServiceSynchronize.this, messages.size());
|
||||
|
||||
LongSparseArray<List<TupleMessageEx>> accountMessages = new LongSparseArray<>();
|
||||
|
||||
for (int i = 0; i < notifying.size(); i++)
|
||||
accountMessages.put(notifying.keyAt(i), new ArrayList<TupleMessageEx>());
|
||||
|
||||
for (TupleMessageEx message : messages) {
|
||||
long account = (message.accountNotify ? message.account : 0);
|
||||
if (accountMessages.indexOfKey(account) < 0)
|
||||
accountMessages.put(account, new ArrayList<TupleMessageEx>());
|
||||
accountMessages.get(account).add(message);
|
||||
if (notifying.indexOfKey(account) < 0)
|
||||
notifying.put(account, new ArrayList<Integer>());
|
||||
}
|
||||
|
||||
for (int i = 0; i < accountMessages.size(); i++) {
|
||||
long account = accountMessages.keyAt(i);
|
||||
List<Notification> notifications = getNotificationUnseen(account, accountMessages.get(account));
|
||||
|
||||
List<Integer> all = new ArrayList<>();
|
||||
List<Integer> added = new ArrayList<>();
|
||||
List<Integer> removed = notifying.get(account);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (notifications.size() == 0)
|
||||
nm.cancel("unseen:" + account, 0);
|
||||
|
||||
for (Integer id : removed)
|
||||
nm.cancel("unseen:" + account, id);
|
||||
|
||||
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:" + account, id, notification);
|
||||
}
|
||||
|
||||
notifying.put(account, all);
|
||||
}
|
||||
|
||||
if (notifications.size() == 0)
|
||||
nm.cancel("unseen", 0);
|
||||
|
||||
for (Integer id : removed)
|
||||
nm.cancel("unseen", id);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
notifying = all;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -264,7 +285,7 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
else if ("clear".equals(action)) {
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onLoad(Context context, Bundle args) throws Throwable {
|
||||
protected Void onLoad(Context context, Bundle args) {
|
||||
DB.getInstance(context).message().ignoreAll();
|
||||
return null;
|
||||
}
|
||||
|
@ -370,18 +391,18 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
return builder;
|
||||
}
|
||||
|
||||
private List<Notification> getNotificationUnseen(List<TupleMessageEx> messages) {
|
||||
// https://developer.android.com/training/notify-user/group
|
||||
private List<Notification> getNotificationUnseen(long account, List<TupleMessageEx> messages) {
|
||||
List<Notification> notifications = new ArrayList<>();
|
||||
|
||||
Widget.update(this, messages.size());
|
||||
|
||||
if (messages.size() == 0)
|
||||
return notifications;
|
||||
|
||||
boolean pro = Helper.isPro(this);
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
// https://developer.android.com/training/notify-user/group
|
||||
String group = Long.toString(account);
|
||||
|
||||
// Build pending intent
|
||||
Intent view = new Intent(this, ActivityView.class);
|
||||
view.setAction("unified");
|
||||
|
@ -429,7 +450,7 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
.setCategory(Notification.CATEGORY_STATUS)
|
||||
.setVisibility(Notification.VISIBILITY_PRIVATE)
|
||||
.setPublicVersion(pbuilder.build())
|
||||
.setGroup(BuildConfig.APPLICATION_ID)
|
||||
.setGroup(group)
|
||||
.setGroupSummary(true);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||
|
@ -520,7 +541,7 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
.setPriority(Notification.PRIORITY_DEFAULT)
|
||||
.setCategory(Notification.CATEGORY_MESSAGE)
|
||||
.setVisibility(Notification.VISIBILITY_PRIVATE)
|
||||
.setGroup(BuildConfig.APPLICATION_ID)
|
||||
.setGroup(group)
|
||||
.setGroupSummary(false)
|
||||
.addAction(actionSeen.build())
|
||||
.addAction(actionArchive.build())
|
||||
|
|
|
@ -22,6 +22,7 @@ package eu.faircode.email;
|
|||
public class TupleMessageEx extends EntityMessage {
|
||||
public String accountName;
|
||||
public Integer accountColor;
|
||||
public boolean accountNotify;
|
||||
public String folderName;
|
||||
public String folderDisplay;
|
||||
public String folderType;
|
||||
|
@ -38,6 +39,7 @@ public class TupleMessageEx extends EntityMessage {
|
|||
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.accountNotify == other.accountNotify &&
|
||||
this.folderName.equals(other.folderName) &&
|
||||
(this.folderDisplay == null ? other.folderDisplay == null : this.folderDisplay.equals(other.folderDisplay)) &&
|
||||
this.folderType.equals(other.folderType) &&
|
||||
|
|
|
@ -289,6 +289,15 @@
|
|||
app:layout_constraintStart_toEndOf="@id/vwColor"
|
||||
app:layout_constraintTop_toBottomOf="@id/etName" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbNotify"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_account_notify"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnColor" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbSynchronize"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -296,7 +305,7 @@
|
|||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_synchronize_account"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnColor" />
|
||||
app:layout_constraintTop_toBottomOf="@id/cbNotify" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbPrimary"
|
||||
|
@ -529,7 +538,7 @@
|
|||
android:id="@+id/grpAdvanced"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="tvName,etName,btnColor,vwColor,ibColorDefault,cbSynchronize,cbPrimary,tvInterval,etInterval" />
|
||||
app:constraint_referenced_ids="tvName,etName,btnColor,vwColor,ibColorDefault,cbNotify,cbSynchronize,cbPrimary,tvInterval,etInterval" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpFolders"
|
||||
|
|
|
@ -123,6 +123,7 @@
|
|||
<string name="title_account_name_hint">Used to differentiate folders</string>
|
||||
<string name="title_account_signature">Signature text</string>
|
||||
<string name="title_account_color">Color</string>
|
||||
<string name="title_account_notify">Separate notifications</string>
|
||||
<string name="title_domain">Domain name</string>
|
||||
<string name="title_autoconfig">Get settings</string>
|
||||
<string name="title_no_settings">Settings not found</string>
|
||||
|
|
Loading…
Reference in a new issue