diff --git a/app/src/main/java/eu/faircode/email/EntityAnswer.java b/app/src/main/java/eu/faircode/email/EntityAnswer.java
index a3fd91915c..4432285ef9 100644
--- a/app/src/main/java/eu/faircode/email/EntityAnswer.java
+++ b/app/src/main/java/eu/faircode/email/EntityAnswer.java
@@ -24,6 +24,9 @@ import org.json.JSONObject;
import java.io.Serializable;
+import javax.mail.Address;
+import javax.mail.internet.InternetAddress;
+
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@@ -47,6 +50,25 @@ public class EntityAnswer implements Serializable {
@NonNull
public String text;
+ static String getAnswerText(DB db, long id, Address[] from) {
+ EntityAnswer answer = db.answer().getAnswer(id);
+ if (answer == null)
+ return null;
+
+ String name = null;
+ String email = null;
+ if (from != null && from.length > 0) {
+ name = ((InternetAddress) from[0]).getPersonal();
+ email = ((InternetAddress) from[0]).getAddress();
+ }
+
+ String text = answer.text;
+ text = text.replace("$name$", name == null ? "" : name);
+ text = text.replace("$email$", email == null ? "" : email);
+
+ return text;
+ }
+
public JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
json.put("name", name);
@@ -71,4 +93,10 @@ public class EntityAnswer implements Serializable {
}
return false;
}
+
+ @NonNull
+ @Override
+ public String toString() {
+ return name;
+ }
}
diff --git a/app/src/main/java/eu/faircode/email/EntityIdentity.java b/app/src/main/java/eu/faircode/email/EntityIdentity.java
index e1079b5a94..3b906d91c7 100644
--- a/app/src/main/java/eu/faircode/email/EntityIdentity.java
+++ b/app/src/main/java/eu/faircode/email/EntityIdentity.java
@@ -198,4 +198,10 @@ public class EntityIdentity {
String getDisplayName() {
return (display == null ? name : display);
}
+
+ @NonNull
+ @Override
+ public String toString() {
+ return getDisplayName() + (primary ? " ★" : "");
+ }
}
diff --git a/app/src/main/java/eu/faircode/email/EntityRule.java b/app/src/main/java/eu/faircode/email/EntityRule.java
index b8654db2e6..fcc660691e 100644
--- a/app/src/main/java/eu/faircode/email/EntityRule.java
+++ b/app/src/main/java/eu/faircode/email/EntityRule.java
@@ -24,6 +24,8 @@ import android.content.Context;
import org.json.JSONException;
import org.json.JSONObject;
+import java.io.IOException;
+import java.util.Date;
import java.util.Enumeration;
import java.util.regex.Pattern;
@@ -74,6 +76,7 @@ public class EntityRule {
static final int TYPE_SEEN = 1;
static final int TYPE_UNSEEN = 2;
static final int TYPE_MOVE = 3;
+ static final int TYPE_ANSWER = 4;
boolean matches(Context context, EntityMessage message, Message imessage) throws MessagingException {
try {
@@ -152,7 +155,7 @@ public class EntityRule {
return haystack.toLowerCase().contains(needle.toLowerCase());
}
- void execute(Context context, DB db, EntityMessage message) {
+ void execute(Context context, DB db, EntityMessage message) throws IOException {
try {
JSONObject jargs = new JSONObject(action);
int type = jargs.getInt("type");
@@ -168,6 +171,9 @@ public class EntityRule {
case TYPE_MOVE:
onActionMove(context, db, message, jargs);
break;
+ case TYPE_ANSWER:
+ onActionAnswer(context, db, message, jargs);
+ break;
}
} catch (JSONException ex) {
Log.e(ex);
@@ -184,6 +190,38 @@ public class EntityRule {
EntityOperation.queue(context, db, message, EntityOperation.MOVE, target, false);
}
+ private void onActionAnswer(Context context, DB db, EntityMessage message, JSONObject jargs) throws JSONException, IOException {
+ long iid = jargs.getLong("identity");
+ long aid = jargs.getLong("answer");
+
+ EntityIdentity identity = db.identity().getIdentity(iid);
+ if (identity == null)
+ throw new IllegalArgumentException("Rule identity not found");
+
+ String body = EntityAnswer.getAnswerText(db, aid, message.from);
+ if (body == null)
+ throw new IllegalArgumentException("Rule answer not found");
+
+ EntityMessage reply = new EntityMessage();
+ reply.account = message.account;
+ reply.folder = db.folder().getOutbox().id;
+ reply.identity = identity.id;
+ reply.msgid = EntityMessage.generateMessageId();
+ reply.thread = message.thread;
+ reply.replying = message.id;
+ reply.to = (message.reply == null || message.reply.length == 0 ? message.from : message.reply);
+ reply.from = new InternetAddress[]{new InternetAddress(identity.email, identity.name)};
+ reply.subject = context.getString(R.string.title_subject_reply, message.subject == null ? "" : message.subject);
+ reply.sender = MessageHelper.getSortKey(reply.from);
+ reply.received = new Date().getTime();
+ reply.setContactInfo(context);
+ reply.id = db.message().insertMessage(reply);
+ reply.write(context, body);
+ db.message().setMessageContent(reply.id, true, HtmlHelper.getPreview(body));
+
+ EntityOperation.queue(context, db, reply, EntityOperation.SEND);
+ }
+
@Override
public boolean equals(Object obj) {
if (obj instanceof EntityRule) {
diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java
index f177e610a9..084300eff5 100644
--- a/app/src/main/java/eu/faircode/email/FragmentCompose.java
+++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java
@@ -1471,12 +1471,8 @@ public class FragmentCompose extends FragmentBase {
body = args.getString("body", "");
body = body.replaceAll("\\r?\\n", "
");
- if (answer > 0) {
- String text = db.answer().getAnswer(answer).text;
- text = text.replace("$name$", "");
- text = text.replace("$email$", "");
- body = text + body;
- }
+ if (answer > 0)
+ body = EntityAnswer.getAnswerText(db, answer, null) + body;
} else {
result.draft.thread = ref.thread;
@@ -1514,20 +1510,8 @@ public class FragmentCompose extends FragmentBase {
result.draft.subject = context.getString(R.string.title_subject_forward,
ref.subject == null ? "" : ref.subject);
- if (answer > 0 && ("reply".equals(action) || "reply_all".equals(action))) {
- String text = db.answer().getAnswer(answer).text;
-
- String name = null;
- String email = null;
- if (result.draft.to != null && result.draft.to.length > 0) {
- name = ((InternetAddress) result.draft.to[0]).getPersonal();
- email = ((InternetAddress) result.draft.to[0]).getAddress();
- }
- text = text.replace("$name$", name == null ? "" : name);
- text = text.replace("$email$", email == null ? "" : email);
-
- body = text + body;
- }
+ if (answer > 0 && ("reply".equals(action) || "reply_all".equals(action)))
+ body = EntityAnswer.getAnswerText(db, answer, result.draft.to) + body;
}
// Select identity matching from address
diff --git a/app/src/main/java/eu/faircode/email/FragmentRule.java b/app/src/main/java/eu/faircode/email/FragmentRule.java
index d3f20ebaea..bad31d0a87 100644
--- a/app/src/main/java/eu/faircode/email/FragmentRule.java
+++ b/app/src/main/java/eu/faircode/email/FragmentRule.java
@@ -65,13 +65,18 @@ public class FragmentRule extends FragmentBase {
private CheckBox cbHeader;
private Spinner spAction;
private Spinner spTarget;
+ private Spinner spIdent;
+ private Spinner spAnswer;
private BottomNavigationView bottom_navigation;
private ContentLoadingProgressBar pbWait;
private Group grpReady;
private Group grpMove;
+ private Group grpAnswer;
private ArrayAdapter adapterAction;
private ArrayAdapter adapterTarget;
+ private ArrayAdapter adapterIdentity;
+ private ArrayAdapter adapterAnswer;
private long id = -1;
private long account = -1;
@@ -108,10 +113,13 @@ public class FragmentRule extends FragmentBase {
cbHeader = view.findViewById(R.id.cbHeader);
spAction = view.findViewById(R.id.spAction);
spTarget = view.findViewById(R.id.spTarget);
+ spIdent = view.findViewById(R.id.spIdent);
+ spAnswer = view.findViewById(R.id.spAnswer);
bottom_navigation = view.findViewById(R.id.bottom_navigation);
pbWait = view.findViewById(R.id.pbWait);
grpReady = view.findViewById(R.id.grpReady);
grpMove = view.findViewById(R.id.grpMove);
+ grpAnswer = view.findViewById(R.id.grpAnswer);
adapterAction = new ArrayAdapter<>(getContext(), R.layout.spinner_item1, android.R.id.text1, new ArrayList());
adapterAction.setDropDownViewResource(R.layout.spinner_item1_dropdown);
@@ -121,10 +129,19 @@ public class FragmentRule extends FragmentBase {
adapterTarget.setDropDownViewResource(R.layout.spinner_item1_dropdown);
spTarget.setAdapter(adapterTarget);
+ adapterIdentity = new ArrayAdapter<>(getContext(), R.layout.spinner_item1, android.R.id.text1, new ArrayList());
+ adapterIdentity.setDropDownViewResource(R.layout.spinner_item1_dropdown);
+ spIdent.setAdapter(adapterIdentity);
+
+ adapterAnswer = new ArrayAdapter<>(getContext(), R.layout.spinner_item1, android.R.id.text1, new ArrayList());
+ adapterAnswer.setDropDownViewResource(R.layout.spinner_item1_dropdown);
+ spAnswer.setAdapter(adapterAnswer);
+
List actions = new ArrayList<>();
actions.add(new Action(EntityRule.TYPE_SEEN, getString(R.string.title_seen)));
actions.add(new Action(EntityRule.TYPE_UNSEEN, getString(R.string.title_unseen)));
actions.add(new Action(EntityRule.TYPE_MOVE, getString(R.string.title_move)));
+ actions.add(new Action(EntityRule.TYPE_ANSWER, getString(R.string.menu_answers)));
adapterAction.addAll(actions);
spAction.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@@ -141,6 +158,7 @@ public class FragmentRule extends FragmentBase {
private void onActionSelected(int type) {
grpMove.setVisibility(type == EntityRule.TYPE_MOVE ? View.VISIBLE : View.GONE);
+ grpAnswer.setVisibility(type == EntityRule.TYPE_ANSWER ? View.VISIBLE : View.GONE);
new Handler().post(new Runnable() {
@Override
@@ -171,6 +189,7 @@ public class FragmentRule extends FragmentBase {
bottom_navigation.setVisibility(View.GONE);
grpReady.setVisibility(View.GONE);
grpMove.setVisibility(View.GONE);
+ grpAnswer.setVisibility(View.GONE);
pbWait.setVisibility(View.VISIBLE);
return view;
@@ -183,30 +202,39 @@ public class FragmentRule extends FragmentBase {
Bundle args = new Bundle();
args.putLong("account", account);
- new SimpleTask>() {
+ new SimpleTask() {
@Override
- protected List onExecute(Context context, Bundle args) {
+ protected RefData onExecute(Context context, Bundle args) {
long account = args.getLong("account");
+ RefData data = new RefData();
+
DB db = DB.getInstance(context);
- List folders = db.folder().getFolders(account);
+ data.folders = db.folder().getFolders(account);
- if (folders != null) {
- for (EntityFolder folder : folders)
- folder.display = folder.getDisplayName(context);
- EntityFolder.sort(context, folders);
- }
+ if (data.folders == null)
+ data.folders = new ArrayList<>();
+
+ for (EntityFolder folder : data.folders)
+ folder.display = folder.getDisplayName(context);
+ EntityFolder.sort(context, data.folders);
+
+ data.identities = db.identity().getIdentities(account);
+ data.answers = db.answer().getAnswers();
- return folders;
+ return data;
}
@Override
- protected void onExecuted(Bundle args, List folders) {
- if (folders == null)
- folders = new ArrayList<>();
-
+ protected void onExecuted(Bundle args, RefData data) {
adapterTarget.clear();
- adapterTarget.addAll(folders);
+ adapterTarget.addAll(data.folders);
+
+ adapterIdentity.clear();
+ adapterIdentity.addAll(data.identities);
+
+ adapterAnswer.clear();
+ adapterAnswer.addAll(data.answers);
Bundle rargs = new Bundle();
rargs.putLong("id", id);
@@ -232,33 +260,47 @@ public class FragmentRule extends FragmentBase {
etOrder.setText(rule == null ? null : Integer.toString(rule.order));
cbEnabled.setChecked(rule == null || rule.enabled);
cbStop.setChecked(rule != null && rule.stop);
- etSender.setText(jsender == null ? null : jsender.optString("value"));
- cbSender.setChecked(jsender != null && jsender.optBoolean("regex", false));
- etSubject.setText(jsubject == null ? null : jsubject.optString("value"));
- cbSubject.setChecked(jsubject != null && jsubject.optBoolean("regex", false));
- etHeader.setText(jheader == null ? null : jheader.optString("value"));
- cbHeader.setChecked(jheader != null && jheader.optBoolean("regex", false));
-
- int type = jaction.optInt("type", -1);
- for (int pos = 0; pos < adapterAction.getCount(); pos++)
- if (adapterAction.getItem(pos).type == type) {
- spAction.setSelection(pos);
- break;
+ etSender.setText(jsender == null ? null : jsender.getString("value"));
+ cbSender.setChecked(jsender != null && jsender.getBoolean("regex"));
+ etSubject.setText(jsubject == null ? null : jsubject.getString("value"));
+ cbSubject.setChecked(jsubject != null && jsubject.getBoolean("regex"));
+ etHeader.setText(jheader == null ? null : jheader.getString("value"));
+ cbHeader.setChecked(jheader != null && jheader.getBoolean("regex"));
+
+ if (rule != null) {
+ int type = jaction.getInt("type");
+ switch (type) {
+ case EntityRule.TYPE_MOVE:
+ long target = jaction.getLong("target");
+ for (int pos = 0; pos < adapterTarget.getCount(); pos++)
+ if (adapterTarget.getItem(pos).id.equals(target)) {
+ spTarget.setSelection(pos);
+ break;
+ }
+ break;
+
+ case EntityRule.TYPE_ANSWER:
+ long identity = jaction.getLong("identity");
+ for (int pos = 0; pos < adapterIdentity.getCount(); pos++)
+ if (adapterIdentity.getItem(pos).id.equals(identity)) {
+ spIdent.setSelection(pos);
+ break;
+ }
+
+ long answer = jaction.getLong("answer");
+ for (int pos = 0; pos < adapterAnswer.getCount(); pos++)
+ if (adapterAnswer.getItem(pos).id.equals(answer)) {
+ spAnswer.setSelection(pos);
+ break;
+ }
+ break;
}
- if (rule == null) {
- grpReady.setVisibility(View.VISIBLE);
- bottom_navigation.setVisibility(View.VISIBLE);
- pbWait.setVisibility(View.GONE);
- } else {
- if (type == EntityRule.TYPE_MOVE) {
- long target = jaction.optLong("target", -1);
- for (int pos = 0; pos < adapterTarget.getCount(); pos++)
- if (adapterTarget.getItem(pos).id.equals(target)) {
- spTarget.setSelection(pos);
- break;
- }
- }
+ for (int pos = 0; pos < adapterAction.getCount(); pos++)
+ if (adapterAction.getItem(pos).type == type) {
+ spAction.setSelection(pos);
+ break;
+ }
}
grpReady.setVisibility(View.VISIBLE);
@@ -362,9 +404,18 @@ public class FragmentRule extends FragmentBase {
Action action = (Action) spAction.getSelectedItem();
if (action != null) {
jaction.put("type", action.type);
- if (action.type == EntityRule.TYPE_MOVE) {
- EntityFolder target = (EntityFolder) spTarget.getSelectedItem();
- jaction.put("target", target.id);
+ switch (action.type) {
+ case EntityRule.TYPE_MOVE:
+ EntityFolder target = (EntityFolder) spTarget.getSelectedItem();
+ jaction.put("target", target.id);
+ break;
+
+ case EntityRule.TYPE_ANSWER:
+ EntityIdentity identity = (EntityIdentity) spIdent.getSelectedItem();
+ EntityAnswer answer = (EntityAnswer) spAnswer.getSelectedItem();
+ jaction.put("identity", identity.id);
+ jaction.put("answer", answer.id);
+ break;
}
}
@@ -458,6 +509,12 @@ public class FragmentRule extends FragmentBase {
}
}
+ private class RefData {
+ List folders;
+ List identities;
+ List answers;
+ }
+
private class Action {
int type;
String name;
diff --git a/app/src/main/res/layout/fragment_rule.xml b/app/src/main/res/layout/fragment_rule.xml
index 5e03351682..08ed34970b 100644
--- a/app/src/main/res/layout/fragment_rule.xml
+++ b/app/src/main/res/layout/fragment_rule.xml
@@ -12,6 +12,7 @@
android:layout_height="0dp"
android:layout_margin="12dp"
android:orientation="vertical"
+ android:scrollbarStyle="outsideOverlay"
app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
app:layout_constraintTop_toTopOf="parent">
@@ -221,14 +222,49 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvAction" />
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/spIdent" />
+ app:constraint_referenced_ids="tvTargetArguments,spTarget" />
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fcfd8763cd..3cd6c1a0c7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -390,6 +390,7 @@
Regex
AND
Action
+ Parameters
Rule name missing
Condition missing