Added colored stars

This commit is contained in:
M66B 2019-05-15 11:10:47 +02:00
parent 0b12570133
commit 5529099ade
12 changed files with 1926 additions and 21 deletions

2
FAQ.md
View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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();
}

View File

@ -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);

View File

@ -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) &&

View File

@ -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));

View File

@ -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());

View File

@ -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" />

View File

@ -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 &#8230;</string>
<string name="title_unflag">Remove star</string>
<string name="title_forward">Forward</string>
<string name="title_create_rule">Create rule &#8230;</string>