mirror of https://github.com/M66B/FairEmail.git
Added colored stars
This commit is contained in:
parent
0b12570133
commit
5529099ade
2
FAQ.md
2
FAQ.md
|
@ -36,9 +36,9 @@ For authorizing:
|
|||
* ~~Synchronize on demand (manual)~~
|
||||
* ~~Semi-automatic encryption~~
|
||||
* ~~Copy message~~
|
||||
* ~~Colored stars~~
|
||||
* Notification settings per folder
|
||||
* Select local images for signatures
|
||||
* Color label messages
|
||||
|
||||
Anything on this list is in random order and *might* be added in the near future.
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ This app starts a foreground service with a low priority status bar notification
|
|||
## Pro features
|
||||
|
||||
* Account/identity colors
|
||||
* Colored stars
|
||||
* Notifications per account (requires Android 8 Oreo or later)
|
||||
* Notification sound per sender (requires Android 8 Oreo or later)
|
||||
* Configurable notification actions
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -67,6 +67,8 @@ import androidx.recyclerview.widget.DividerItemDecoration;
|
|||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.colorpicker.ColorPickerDialog;
|
||||
import com.android.colorpicker.ColorPickerSwatch;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
@ -143,6 +145,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
|
|||
static final String ACTION_EDIT_RULE = BuildConfig.APPLICATION_ID + ".EDIT_RULE";
|
||||
static final String ACTION_STORE_ATTACHMENT = BuildConfig.APPLICATION_ID + ".STORE_ATTACHMENT";
|
||||
static final String ACTION_STORE_ATTACHMENTS = BuildConfig.APPLICATION_ID + ".STORE_ATTACHMENTS";
|
||||
static final String ACTION_COLOR = BuildConfig.APPLICATION_ID + ".COLOR";
|
||||
static final String ACTION_PRINT = BuildConfig.APPLICATION_ID + ".PRINT";
|
||||
static final String ACTION_DECRYPT = BuildConfig.APPLICATION_ID + ".DECRYPT";
|
||||
static final String ACTION_SHOW_PRO = BuildConfig.APPLICATION_ID + ".SHOW_PRO";
|
||||
|
@ -545,6 +548,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
|
|||
iff.addAction(ACTION_EDIT_RULE);
|
||||
iff.addAction(ACTION_STORE_ATTACHMENT);
|
||||
iff.addAction(ACTION_STORE_ATTACHMENTS);
|
||||
iff.addAction(ACTION_COLOR);
|
||||
iff.addAction(ACTION_PRINT);
|
||||
iff.addAction(ACTION_DECRYPT);
|
||||
iff.addAction(ACTION_SHOW_PRO);
|
||||
|
@ -1034,6 +1038,8 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
|
|||
onStoreAttachment(intent);
|
||||
else if (ACTION_STORE_ATTACHMENTS.equals(action))
|
||||
onStoreAttachments(intent);
|
||||
else if (ACTION_COLOR.equals(action))
|
||||
onColor(intent);
|
||||
else if (ACTION_PRINT.equals(action))
|
||||
onPrint(intent);
|
||||
else if (ACTION_DECRYPT.equals(action))
|
||||
|
@ -1174,6 +1180,55 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
|
|||
startActivityForResult(Helper.getChooser(this, tree), REQUEST_ATTACHMENTS);
|
||||
}
|
||||
|
||||
private void onColor(final Intent intent) {
|
||||
if (!Helper.isPro(this) && !BuildConfig.BETA_RELEASE) {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentPro()).addToBackStack("pro");
|
||||
fragmentTransaction.commit();
|
||||
return;
|
||||
}
|
||||
|
||||
int color = intent.getIntExtra("color", -1);
|
||||
int[] colors = getResources().getIntArray(R.array.colorPicker);
|
||||
ColorPickerDialog colorPickerDialog = new ColorPickerDialog();
|
||||
colorPickerDialog.initialize(R.string.title_account_color, colors, color, 4, colors.length);
|
||||
colorPickerDialog.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener() {
|
||||
@Override
|
||||
public void onColorSelected(int color) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", intent.getLongExtra("id", -1));
|
||||
args.putInt("color", color);
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onExecute(final Context context, Bundle args) {
|
||||
final long id = args.getLong("id");
|
||||
final int color = args.getInt("color");
|
||||
|
||||
final DB db = DB.getInstance(context);
|
||||
db.runInTransaction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
if (message == null)
|
||||
return;
|
||||
|
||||
EntityOperation.queue(context, db, message, EntityOperation.FLAG, true, color);
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
Helper.unexpectedError(ActivityView.this, ActivityView.this, ex);
|
||||
}
|
||||
}.execute(ActivityView.this, ActivityView.this, args, "message:color");
|
||||
}
|
||||
});
|
||||
colorPickerDialog.show(getSupportFragmentManager(), "colorpicker");
|
||||
}
|
||||
|
||||
private void onPrint(Intent intent) {
|
||||
long id = intent.getLongExtra("id", -1);
|
||||
|
||||
|
|
|
@ -735,7 +735,9 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
private void bindFlagged(TupleMessageEx message) {
|
||||
int flagged = (message.count - message.unflagged);
|
||||
ivFlagged.setImageResource(flagged > 0 ? R.drawable.baseline_star_24 : R.drawable.baseline_star_border_24);
|
||||
ivFlagged.setImageTintList(ColorStateList.valueOf(flagged > 0 ? colorAccent : textColorSecondary));
|
||||
ivFlagged.setImageTintList(ColorStateList.valueOf(flagged > 0
|
||||
? message.color == null ? colorAccent : message.color
|
||||
: textColorSecondary));
|
||||
ivFlagged.setVisibility(flags ? (message.uid == null ? View.INVISIBLE : View.VISIBLE) : View.GONE);
|
||||
}
|
||||
|
||||
|
@ -2115,6 +2117,15 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
}.execute(context, owner, args, "message:unseen");
|
||||
}
|
||||
|
||||
private void onMenuColoredStar(final ActionData data) {
|
||||
Intent color = new Intent(ActivityView.ACTION_COLOR);
|
||||
color.putExtra("id", data.message.id);
|
||||
color.putExtra("color", data.message.color == null ? Color.TRANSPARENT : data.message.color);
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(color);
|
||||
}
|
||||
|
||||
private void onMenuCopy(final ActionData data) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", data.message.id);
|
||||
|
@ -2637,6 +2648,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
popupMenu.getMenu().findItem(R.id.menu_forward).setEnabled(data.message.content);
|
||||
|
||||
popupMenu.getMenu().findItem(R.id.menu_unseen).setEnabled(data.message.uid != null);
|
||||
popupMenu.getMenu().findItem(R.id.menu_flag_color).setEnabled(data.message.uid != null);
|
||||
|
||||
popupMenu.getMenu().findItem(R.id.menu_copy).setEnabled(data.message.uid != null);
|
||||
popupMenu.getMenu().findItem(R.id.menu_delete).setVisible(debug);
|
||||
|
@ -2674,6 +2686,9 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
case R.id.menu_unseen:
|
||||
onMenuUnseen(data);
|
||||
return true;
|
||||
case R.id.menu_flag_color:
|
||||
onMenuColoredStar(data);
|
||||
return true;
|
||||
case R.id.menu_copy:
|
||||
onMenuCopy(data);
|
||||
return true;
|
||||
|
|
|
@ -51,7 +51,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
|
|||
// https://developer.android.com/topic/libraries/architecture/room.html
|
||||
|
||||
@Database(
|
||||
version = 82,
|
||||
version = 83,
|
||||
entities = {
|
||||
EntityIdentity.class,
|
||||
EntityAccount.class,
|
||||
|
@ -811,6 +811,13 @@ public abstract class DB extends RoomDatabase {
|
|||
db.execSQL("CREATE INDEX `index_operation_state` ON `operation` (`state`)");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(82, 83) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i("DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `message` ADD COLUMN `color` INTEGER");
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -354,6 +354,9 @@ public interface DaoMessage {
|
|||
@Query("UPDATE message SET ui_ignored = :ui_ignored WHERE id = :id")
|
||||
int setMessageUiIgnored(long id, boolean ui_ignored);
|
||||
|
||||
@Query("UPDATE message SET color = :color WHERE id = :id")
|
||||
int setMessageColor(long id, Integer color);
|
||||
|
||||
@Query("UPDATE message SET received = :sent, sent = :sent WHERE id = :id")
|
||||
int setMessageSent(long id, Long sent);
|
||||
|
||||
|
|
|
@ -139,6 +139,7 @@ public class EntityMessage implements Serializable {
|
|||
@NonNull
|
||||
public Boolean ui_browsed = false;
|
||||
public Long ui_snoozed;
|
||||
public Integer color;
|
||||
public Integer revision; // compose
|
||||
public Integer revisions; // compose
|
||||
public String warning; // persistent
|
||||
|
@ -241,6 +242,7 @@ public class EntityMessage implements Serializable {
|
|||
this.ui_ignored.equals(other.ui_ignored) &&
|
||||
this.ui_browsed.equals(other.ui_browsed) &&
|
||||
Objects.equals(this.ui_snoozed, other.ui_snoozed) &&
|
||||
Objects.equals(this.color, other.color) &&
|
||||
Objects.equals(this.warning, other.warning) &&
|
||||
Objects.equals(this.error, other.error)
|
||||
// last_attempt
|
||||
|
@ -296,6 +298,7 @@ public class EntityMessage implements Serializable {
|
|||
this.ui_ignored.equals(other.ui_ignored) &&
|
||||
this.ui_browsed.equals(other.ui_browsed) &&
|
||||
Objects.equals(this.ui_snoozed, other.ui_snoozed) &&
|
||||
Objects.equals(this.color, other.color) &&
|
||||
Objects.equals(this.revision, other.revision) &&
|
||||
Objects.equals(this.revisions, other.revisions) &&
|
||||
Objects.equals(this.warning, other.warning) &&
|
||||
|
|
|
@ -101,11 +101,15 @@ public class EntityOperation {
|
|||
db.message().setMessageUiIgnored(similar.id, true);
|
||||
}
|
||||
|
||||
} else if (FLAG.equals(name))
|
||||
for (EntityMessage similar : db.message().getMessageByMsgId(message.account, message.msgid))
|
||||
db.message().setMessageUiFlagged(similar.id, jargs.getBoolean(0));
|
||||
} else if (FLAG.equals(name)) {
|
||||
boolean flagged = jargs.getBoolean(0);
|
||||
Integer color = (jargs.length() > 1 && !jargs.isNull(1) ? jargs.getInt(1) : null);
|
||||
for (EntityMessage similar : db.message().getMessageByMsgId(message.account, message.msgid)) {
|
||||
db.message().setMessageUiFlagged(similar.id, flagged);
|
||||
db.message().setMessageColor(similar.id, flagged ? color : null);
|
||||
}
|
||||
|
||||
else if (ANSWERED.equals(name))
|
||||
} else if (ANSWERED.equals(name))
|
||||
for (EntityMessage similar : db.message().getMessageByMsgId(message.account, message.msgid))
|
||||
db.message().setMessageUiAnswered(similar.id, jargs.getBoolean(0));
|
||||
|
||||
|
|
|
@ -86,6 +86,8 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.android.colorpicker.ColorPickerDialog;
|
||||
import com.android.colorpicker.ColorPickerSwatch;
|
||||
import com.bugsnag.android.Bugsnag;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
@ -185,11 +187,12 @@ public class FragmentMessages extends FragmentBase {
|
|||
private final int action_snooze = 3;
|
||||
private final int action_flag = 4;
|
||||
private final int action_unflag = 5;
|
||||
private final int action_archive = 6;
|
||||
private final int action_trash = 7;
|
||||
private final int action_delete = 8;
|
||||
private final int action_junk = 9;
|
||||
private final int action_move = 10;
|
||||
private final int action_flag_color = 6;
|
||||
private final int action_archive = 7;
|
||||
private final int action_trash = 8;
|
||||
private final int action_delete = 9;
|
||||
private final int action_junk = 10;
|
||||
private final int action_move = 11;
|
||||
|
||||
private NumberFormat nf = NumberFormat.getNumberInstance();
|
||||
|
||||
|
@ -1399,18 +1402,20 @@ public class FragmentMessages extends FragmentBase {
|
|||
popupMenu.getMenu().add(Menu.NONE, action_flag, 4, R.string.title_flag);
|
||||
if (result.flagged)
|
||||
popupMenu.getMenu().add(Menu.NONE, action_unflag, 5, R.string.title_unflag);
|
||||
if (result.unflagged || result.flagged)
|
||||
popupMenu.getMenu().add(Menu.NONE, action_flag_color, 6, R.string.title_flag_color);
|
||||
|
||||
if (result.hasArchive && !result.isArchive) // has archive and not is archive/drafts
|
||||
popupMenu.getMenu().add(Menu.NONE, action_archive, 6, R.string.title_archive);
|
||||
popupMenu.getMenu().add(Menu.NONE, action_archive, 7, R.string.title_archive);
|
||||
|
||||
if (result.isTrash) // is trash
|
||||
popupMenu.getMenu().add(Menu.NONE, action_delete, 7, R.string.title_delete);
|
||||
popupMenu.getMenu().add(Menu.NONE, action_delete, 8, R.string.title_delete);
|
||||
|
||||
if (!result.isTrash && result.hasTrash) // not trash and has trash
|
||||
popupMenu.getMenu().add(Menu.NONE, action_trash, 8, R.string.title_trash);
|
||||
popupMenu.getMenu().add(Menu.NONE, action_trash, 9, R.string.title_trash);
|
||||
|
||||
if (result.hasJunk && !result.isJunk && !result.isDrafts) // has junk and not junk/drafts
|
||||
popupMenu.getMenu().add(Menu.NONE, action_junk, 9, R.string.title_spam);
|
||||
popupMenu.getMenu().add(Menu.NONE, action_junk, 10, R.string.title_spam);
|
||||
|
||||
int order = 11;
|
||||
for (EntityAccount account : result.accounts) {
|
||||
|
@ -1437,10 +1442,13 @@ public class FragmentMessages extends FragmentBase {
|
|||
onActionSnoozeSelection();
|
||||
return true;
|
||||
case action_flag:
|
||||
onActionFlagSelection(true);
|
||||
onActionFlagSelection(true, null);
|
||||
return true;
|
||||
case action_unflag:
|
||||
onActionFlagSelection(false);
|
||||
onActionFlagSelection(false, null);
|
||||
return true;
|
||||
case action_flag_color:
|
||||
onActionFlagColorSelection();
|
||||
return true;
|
||||
case action_archive:
|
||||
onActionMoveSelection(EntityFolder.ARCHIVE);
|
||||
|
@ -1589,10 +1597,12 @@ public class FragmentMessages extends FragmentBase {
|
|||
});
|
||||
}
|
||||
|
||||
private void onActionFlagSelection(boolean flagged) {
|
||||
private void onActionFlagSelection(boolean flagged, Integer color) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLongArray("ids", getSelection());
|
||||
args.putBoolean("flagged", flagged);
|
||||
if (color != null)
|
||||
args.putInt("color", color);
|
||||
|
||||
selectionTracker.clearSelection();
|
||||
|
||||
|
@ -1601,6 +1611,7 @@ public class FragmentMessages extends FragmentBase {
|
|||
protected Void onExecute(Context context, Bundle args) {
|
||||
long[] ids = args.getLongArray("ids");
|
||||
boolean flagged = args.getBoolean("flagged");
|
||||
Integer color = (args.containsKey("color") ? args.getInt("color") : null);
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
try {
|
||||
|
@ -1608,11 +1619,11 @@ public class FragmentMessages extends FragmentBase {
|
|||
|
||||
for (long id : ids) {
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
if (message != null && message.ui_flagged != flagged) {
|
||||
if (message != null) {
|
||||
List<EntityMessage> messages = db.message().getMessageByThread(
|
||||
message.account, message.thread, threading ? null : id, message.folder);
|
||||
for (EntityMessage threaded : messages)
|
||||
EntityOperation.queue(context, db, threaded, EntityOperation.FLAG, flagged);
|
||||
EntityOperation.queue(context, db, threaded, EntityOperation.FLAG, flagged, color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1631,6 +1642,26 @@ public class FragmentMessages extends FragmentBase {
|
|||
}.execute(FragmentMessages.this, args, "messages:flag");
|
||||
}
|
||||
|
||||
private void onActionFlagColorSelection() {
|
||||
if (!Helper.isPro(getContext()) && !BuildConfig.BETA_RELEASE) {
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentPro()).addToBackStack("pro");
|
||||
fragmentTransaction.commit();
|
||||
return;
|
||||
}
|
||||
|
||||
int[] colors = getResources().getIntArray(R.array.colorPicker);
|
||||
ColorPickerDialog colorPickerDialog = new ColorPickerDialog();
|
||||
colorPickerDialog.initialize(R.string.title_account_color, colors, Color.TRANSPARENT, 4, colors.length);
|
||||
colorPickerDialog.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener() {
|
||||
@Override
|
||||
public void onColorSelected(int color) {
|
||||
onActionFlagSelection(true, color);
|
||||
}
|
||||
});
|
||||
colorPickerDialog.show(getFragmentManager(), "colorpicker");
|
||||
}
|
||||
|
||||
private void onActionDeleteSelection() {
|
||||
Bundle args = new Bundle();
|
||||
args.putLongArray("selected", getSelection());
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
android:id="@+id/menu_unseen"
|
||||
android:title="@string/title_unseen" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_flag_color"
|
||||
android:title="@string/title_flag_color" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_copy"
|
||||
android:title="@string/title_copy" />
|
||||
|
|
|
@ -377,6 +377,7 @@
|
|||
<string name="title_seen">Mark read</string>
|
||||
<string name="title_unseen">Mark unread</string>
|
||||
<string name="title_flag">Add star</string>
|
||||
<string name="title_flag_color">Colored star …</string>
|
||||
<string name="title_unflag">Remove star</string>
|
||||
<string name="title_forward">Forward</string>
|
||||
<string name="title_create_rule">Create rule …</string>
|
||||
|
|
Loading…
Reference in New Issue