Added colored stars

pull/156/head
M66B 6 years ago
parent 0b12570133
commit 5529099ade

@ -36,9 +36,9 @@ For authorizing:
* ~~Synchronize on demand (manual)~~ * ~~Synchronize on demand (manual)~~
* ~~Semi-automatic encryption~~ * ~~Semi-automatic encryption~~
* ~~Copy message~~ * ~~Copy message~~
* ~~Colored stars~~
* Notification settings per folder * Notification settings per folder
* Select local images for signatures * Select local images for signatures
* Color label messages
Anything on this list is in random order and *might* be added in the near future. 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 ## Pro features
* Account/identity colors * Account/identity colors
* Colored stars
* Notifications per account (requires Android 8 Oreo or later) * Notifications per account (requires Android 8 Oreo or later)
* Notification sound per sender (requires Android 8 Oreo or later) * Notification sound per sender (requires Android 8 Oreo or later)
* Configurable notification actions * 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.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.android.colorpicker.ColorPickerDialog;
import com.android.colorpicker.ColorPickerSwatch;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import org.json.JSONArray; 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_EDIT_RULE = BuildConfig.APPLICATION_ID + ".EDIT_RULE";
static final String ACTION_STORE_ATTACHMENT = BuildConfig.APPLICATION_ID + ".STORE_ATTACHMENT"; 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_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_PRINT = BuildConfig.APPLICATION_ID + ".PRINT";
static final String ACTION_DECRYPT = BuildConfig.APPLICATION_ID + ".DECRYPT"; static final String ACTION_DECRYPT = BuildConfig.APPLICATION_ID + ".DECRYPT";
static final String ACTION_SHOW_PRO = BuildConfig.APPLICATION_ID + ".SHOW_PRO"; 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_EDIT_RULE);
iff.addAction(ACTION_STORE_ATTACHMENT); iff.addAction(ACTION_STORE_ATTACHMENT);
iff.addAction(ACTION_STORE_ATTACHMENTS); iff.addAction(ACTION_STORE_ATTACHMENTS);
iff.addAction(ACTION_COLOR);
iff.addAction(ACTION_PRINT); iff.addAction(ACTION_PRINT);
iff.addAction(ACTION_DECRYPT); iff.addAction(ACTION_DECRYPT);
iff.addAction(ACTION_SHOW_PRO); iff.addAction(ACTION_SHOW_PRO);
@ -1034,6 +1038,8 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
onStoreAttachment(intent); onStoreAttachment(intent);
else if (ACTION_STORE_ATTACHMENTS.equals(action)) else if (ACTION_STORE_ATTACHMENTS.equals(action))
onStoreAttachments(intent); onStoreAttachments(intent);
else if (ACTION_COLOR.equals(action))
onColor(intent);
else if (ACTION_PRINT.equals(action)) else if (ACTION_PRINT.equals(action))
onPrint(intent); onPrint(intent);
else if (ACTION_DECRYPT.equals(action)) 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); 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) { private void onPrint(Intent intent) {
long id = intent.getLongExtra("id", -1); long id = intent.getLongExtra("id", -1);

@ -735,7 +735,9 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private void bindFlagged(TupleMessageEx message) { private void bindFlagged(TupleMessageEx message) {
int flagged = (message.count - message.unflagged); int flagged = (message.count - message.unflagged);
ivFlagged.setImageResource(flagged > 0 ? R.drawable.baseline_star_24 : R.drawable.baseline_star_border_24); 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); 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"); }.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) { private void onMenuCopy(final ActionData data) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("id", data.message.id); 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_forward).setEnabled(data.message.content);
popupMenu.getMenu().findItem(R.id.menu_unseen).setEnabled(data.message.uid != null); 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_copy).setEnabled(data.message.uid != null);
popupMenu.getMenu().findItem(R.id.menu_delete).setVisible(debug); 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: case R.id.menu_unseen:
onMenuUnseen(data); onMenuUnseen(data);
return true; return true;
case R.id.menu_flag_color:
onMenuColoredStar(data);
return true;
case R.id.menu_copy: case R.id.menu_copy:
onMenuCopy(data); onMenuCopy(data);
return true; return true;

@ -51,7 +51,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html // https://developer.android.com/topic/libraries/architecture/room.html
@Database( @Database(
version = 82, version = 83,
entities = { entities = {
EntityIdentity.class, EntityIdentity.class,
EntityAccount.class, EntityAccount.class,
@ -811,6 +811,13 @@ public abstract class DB extends RoomDatabase {
db.execSQL("CREATE INDEX `index_operation_state` ON `operation` (`state`)"); 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(); .build();
} }

@ -354,6 +354,9 @@ public interface DaoMessage {
@Query("UPDATE message SET ui_ignored = :ui_ignored WHERE id = :id") @Query("UPDATE message SET ui_ignored = :ui_ignored WHERE id = :id")
int setMessageUiIgnored(long id, boolean ui_ignored); 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") @Query("UPDATE message SET received = :sent, sent = :sent WHERE id = :id")
int setMessageSent(long id, Long sent); int setMessageSent(long id, Long sent);

@ -139,6 +139,7 @@ public class EntityMessage implements Serializable {
@NonNull @NonNull
public Boolean ui_browsed = false; public Boolean ui_browsed = false;
public Long ui_snoozed; public Long ui_snoozed;
public Integer color;
public Integer revision; // compose public Integer revision; // compose
public Integer revisions; // compose public Integer revisions; // compose
public String warning; // persistent public String warning; // persistent
@ -241,6 +242,7 @@ public class EntityMessage implements Serializable {
this.ui_ignored.equals(other.ui_ignored) && this.ui_ignored.equals(other.ui_ignored) &&
this.ui_browsed.equals(other.ui_browsed) && this.ui_browsed.equals(other.ui_browsed) &&
Objects.equals(this.ui_snoozed, other.ui_snoozed) && Objects.equals(this.ui_snoozed, other.ui_snoozed) &&
Objects.equals(this.color, other.color) &&
Objects.equals(this.warning, other.warning) && Objects.equals(this.warning, other.warning) &&
Objects.equals(this.error, other.error) Objects.equals(this.error, other.error)
// last_attempt // last_attempt
@ -296,6 +298,7 @@ public class EntityMessage implements Serializable {
this.ui_ignored.equals(other.ui_ignored) && this.ui_ignored.equals(other.ui_ignored) &&
this.ui_browsed.equals(other.ui_browsed) && this.ui_browsed.equals(other.ui_browsed) &&
Objects.equals(this.ui_snoozed, other.ui_snoozed) && Objects.equals(this.ui_snoozed, other.ui_snoozed) &&
Objects.equals(this.color, other.color) &&
Objects.equals(this.revision, other.revision) && Objects.equals(this.revision, other.revision) &&
Objects.equals(this.revisions, other.revisions) && Objects.equals(this.revisions, other.revisions) &&
Objects.equals(this.warning, other.warning) && Objects.equals(this.warning, other.warning) &&

@ -101,11 +101,15 @@ public class EntityOperation {
db.message().setMessageUiIgnored(similar.id, true); db.message().setMessageUiIgnored(similar.id, true);
} }
} else if (FLAG.equals(name)) } else if (FLAG.equals(name)) {
for (EntityMessage similar : db.message().getMessageByMsgId(message.account, message.msgid)) boolean flagged = jargs.getBoolean(0);
db.message().setMessageUiFlagged(similar.id, 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)) for (EntityMessage similar : db.message().getMessageByMsgId(message.account, message.msgid))
db.message().setMessageUiAnswered(similar.id, jargs.getBoolean(0)); db.message().setMessageUiAnswered(similar.id, jargs.getBoolean(0));

@ -86,6 +86,8 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.android.colorpicker.ColorPickerDialog;
import com.android.colorpicker.ColorPickerSwatch;
import com.bugsnag.android.Bugsnag; import com.bugsnag.android.Bugsnag;
import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.floatingactionbutton.FloatingActionButton; 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_snooze = 3;
private final int action_flag = 4; private final int action_flag = 4;
private final int action_unflag = 5; private final int action_unflag = 5;
private final int action_archive = 6; private final int action_flag_color = 6;
private final int action_trash = 7; private final int action_archive = 7;
private final int action_delete = 8; private final int action_trash = 8;
private final int action_junk = 9; private final int action_delete = 9;
private final int action_move = 10; private final int action_junk = 10;
private final int action_move = 11;
private NumberFormat nf = NumberFormat.getNumberInstance(); 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); popupMenu.getMenu().add(Menu.NONE, action_flag, 4, R.string.title_flag);
if (result.flagged) if (result.flagged)
popupMenu.getMenu().add(Menu.NONE, action_unflag, 5, R.string.title_unflag); 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 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 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 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 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; int order = 11;
for (EntityAccount account : result.accounts) { for (EntityAccount account : result.accounts) {
@ -1437,10 +1442,13 @@ public class FragmentMessages extends FragmentBase {
onActionSnoozeSelection(); onActionSnoozeSelection();
return true; return true;
case action_flag: case action_flag:
onActionFlagSelection(true); onActionFlagSelection(true, null);
return true; return true;
case action_unflag: case action_unflag:
onActionFlagSelection(false); onActionFlagSelection(false, null);
return true;
case action_flag_color:
onActionFlagColorSelection();
return true; return true;
case action_archive: case action_archive:
onActionMoveSelection(EntityFolder.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(); Bundle args = new Bundle();
args.putLongArray("ids", getSelection()); args.putLongArray("ids", getSelection());
args.putBoolean("flagged", flagged); args.putBoolean("flagged", flagged);
if (color != null)
args.putInt("color", color);
selectionTracker.clearSelection(); selectionTracker.clearSelection();
@ -1601,6 +1611,7 @@ public class FragmentMessages extends FragmentBase {
protected Void onExecute(Context context, Bundle args) { protected Void onExecute(Context context, Bundle args) {
long[] ids = args.getLongArray("ids"); long[] ids = args.getLongArray("ids");
boolean flagged = args.getBoolean("flagged"); boolean flagged = args.getBoolean("flagged");
Integer color = (args.containsKey("color") ? args.getInt("color") : null);
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
try { try {
@ -1608,11 +1619,11 @@ public class FragmentMessages extends FragmentBase {
for (long id : ids) { for (long id : ids) {
EntityMessage message = db.message().getMessage(id); EntityMessage message = db.message().getMessage(id);
if (message != null && message.ui_flagged != flagged) { if (message != null) {
List<EntityMessage> messages = db.message().getMessageByThread( List<EntityMessage> messages = db.message().getMessageByThread(
message.account, message.thread, threading ? null : id, message.folder); message.account, message.thread, threading ? null : id, message.folder);
for (EntityMessage threaded : messages) 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"); }.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() { private void onActionDeleteSelection() {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLongArray("selected", getSelection()); args.putLongArray("selected", getSelection());

@ -8,6 +8,10 @@
android:id="@+id/menu_unseen" android:id="@+id/menu_unseen"
android:title="@string/title_unseen" /> android:title="@string/title_unseen" />
<item
android:id="@+id/menu_flag_color"
android:title="@string/title_flag_color" />
<item <item
android:id="@+id/menu_copy" android:id="@+id/menu_copy"
android:title="@string/title_copy" /> android:title="@string/title_copy" />

@ -377,6 +377,7 @@
<string name="title_seen">Mark read</string> <string name="title_seen">Mark read</string>
<string name="title_unseen">Mark unread</string> <string name="title_unseen">Mark unread</string>
<string name="title_flag">Add star</string> <string name="title_flag">Add star</string>
<string name="title_flag_color">Colored star &#8230;</string>
<string name="title_unflag">Remove star</string> <string name="title_unflag">Remove star</string>
<string name="title_forward">Forward</string> <string name="title_forward">Forward</string>
<string name="title_create_rule">Create rule &#8230;</string> <string name="title_create_rule">Create rule &#8230;</string>

Loading…
Cancel
Save