mirror of
https://github.com/M66B/FairEmail.git
synced 2026-04-14 13:03:13 +02:00
Improved composing with signature, replying/forwarding
This commit is contained in:
@@ -46,7 +46,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
|
||||
// https://developer.android.com/topic/libraries/architecture/room.html
|
||||
|
||||
@Database(
|
||||
version = 3,
|
||||
version = 4,
|
||||
entities = {
|
||||
EntityIdentity.class,
|
||||
EntityAccount.class,
|
||||
@@ -138,6 +138,15 @@ public abstract class DB extends RoomDatabase {
|
||||
" (SELECT account.signature FROM account WHERE account.id = identity.account)");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(3, 4) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `message` ADD COLUMN `forwarding` INTEGER" +
|
||||
" REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL");
|
||||
db.execSQL("CREATE INDEX `index_message_forwarding` ON `message` (`forwarding`)");
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ package eu.faircode.email;
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Html;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@@ -51,13 +52,15 @@ import static androidx.room.ForeignKey.SET_NULL;
|
||||
@ForeignKey(childColumns = "account", entity = EntityAccount.class, parentColumns = "id", onDelete = CASCADE),
|
||||
@ForeignKey(childColumns = "folder", entity = EntityFolder.class, parentColumns = "id", onDelete = CASCADE),
|
||||
@ForeignKey(childColumns = "identity", entity = EntityIdentity.class, parentColumns = "id", onDelete = SET_NULL),
|
||||
@ForeignKey(childColumns = "replying", entity = EntityMessage.class, parentColumns = "id", onDelete = SET_NULL)
|
||||
@ForeignKey(childColumns = "replying", entity = EntityMessage.class, parentColumns = "id", onDelete = SET_NULL),
|
||||
@ForeignKey(childColumns = "forwarding", entity = EntityMessage.class, parentColumns = "id", onDelete = SET_NULL)
|
||||
},
|
||||
indices = {
|
||||
@Index(value = {"account"}),
|
||||
@Index(value = {"folder"}),
|
||||
@Index(value = {"identity"}),
|
||||
@Index(value = {"replying"}),
|
||||
@Index(value = {"forwarding"}),
|
||||
@Index(value = {"folder", "uid", "ui_found"}, unique = true),
|
||||
@Index(value = {"msgid", "folder", "ui_found"}, unique = true),
|
||||
@Index(value = {"thread"}),
|
||||
@@ -80,6 +83,7 @@ public class EntityMessage implements Serializable {
|
||||
public Long identity;
|
||||
public String extra; // plus
|
||||
public Long replying;
|
||||
public Long forwarding;
|
||||
public Long uid; // compose = null
|
||||
public String msgid;
|
||||
public String references;
|
||||
@@ -178,6 +182,14 @@ public class EntityMessage implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
static String getQuote(Context context, long id) throws IOException {
|
||||
EntityMessage message = DB.getInstance(context).message().getMessage(id);
|
||||
return String.format("<p>%s %s:</p><blockquote>%s</blockquote>",
|
||||
Html.escapeHtml(new Date(message.sent == null ? message.received : message.sent).toString()),
|
||||
Html.escapeHtml(MessageHelper.getFormattedAddresses(message.from, true)),
|
||||
HtmlHelper.sanitize(EntityMessage.read(context, id)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EntityMessage) {
|
||||
@@ -186,6 +198,7 @@ public class EntityMessage implements Serializable {
|
||||
this.folder.equals(other.folder) &&
|
||||
(this.identity == null ? other.identity == null : this.identity.equals(other.identity)) &&
|
||||
(this.replying == null ? other.replying == null : this.replying.equals(other.replying)) &&
|
||||
(this.forwarding == null ? other.forwarding == null : this.replying.equals(other.forwarding)) &&
|
||||
(this.uid == null ? other.uid == null : this.uid.equals(other.uid)) &&
|
||||
(this.msgid == null ? other.msgid == null : this.msgid.equals(other.msgid)) &&
|
||||
(this.references == null ? other.references == null : this.references.equals(other.references)) &&
|
||||
|
||||
@@ -128,6 +128,8 @@ public class FragmentCompose extends FragmentEx {
|
||||
private EditText etSubject;
|
||||
private RecyclerView rvAttachment;
|
||||
private EditText etBody;
|
||||
private TextView tvSignature;
|
||||
private TextView tvReference;
|
||||
private BottomNavigationView edit_bar;
|
||||
private BottomNavigationView bottom_navigation;
|
||||
private ProgressBar pbWait;
|
||||
@@ -135,14 +137,23 @@ public class FragmentCompose extends FragmentEx {
|
||||
private Group grpExtra;
|
||||
private Group grpAddresses;
|
||||
private Group grpAttachments;
|
||||
private Group grpSignature;
|
||||
private Group grpReference;
|
||||
|
||||
private AdapterAttachment adapter;
|
||||
|
||||
private long working = -1;
|
||||
private boolean autosave = false;
|
||||
private boolean pro = false;
|
||||
|
||||
private OpenPgpServiceConnection pgpService;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
pro = Helper.isPro(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
@@ -164,6 +175,8 @@ public class FragmentCompose extends FragmentEx {
|
||||
etSubject = view.findViewById(R.id.etSubject);
|
||||
rvAttachment = view.findViewById(R.id.rvAttachment);
|
||||
etBody = view.findViewById(R.id.etBody);
|
||||
tvSignature = view.findViewById(R.id.tvSignature);
|
||||
tvReference = view.findViewById(R.id.tvReference);
|
||||
edit_bar = view.findViewById(R.id.edit_bar);
|
||||
bottom_navigation = view.findViewById(R.id.bottom_navigation);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
@@ -171,6 +184,8 @@ public class FragmentCompose extends FragmentEx {
|
||||
grpExtra = view.findViewById(R.id.grpExtra);
|
||||
grpAddresses = view.findViewById(R.id.grpAddresses);
|
||||
grpAttachments = view.findViewById(R.id.grpAttachments);
|
||||
grpSignature = view.findViewById(R.id.grpSignature);
|
||||
grpReference = view.findViewById(R.id.grpReference);
|
||||
|
||||
// Wire controls
|
||||
spIdentity.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@@ -180,17 +195,9 @@ public class FragmentCompose extends FragmentEx {
|
||||
int at = (identity == null ? -1 : identity.email.indexOf('@'));
|
||||
tvExtraPrefix.setText(at < 0 ? null : identity.email.substring(0, at) + "+");
|
||||
tvExtraSuffix.setText(at < 0 ? null : identity.email.substring(at));
|
||||
|
||||
String signature = (identity == null ? null : identity.signature);
|
||||
if (TextUtils.isEmpty(signature))
|
||||
signature = "‌";
|
||||
|
||||
String html = Html.toHtml(etBody.getText());
|
||||
int cstart = html.indexOf("<tt>");
|
||||
int cend = html.lastIndexOf("</tt>");
|
||||
if (cstart >= 0 && cend > cstart) {
|
||||
html = html.substring(0, cstart + 4) + signature + html.substring(cend);
|
||||
etBody.setText(Html.fromHtml(html));
|
||||
if (pro) {
|
||||
tvSignature.setText(identity == null ? null : Html.fromHtml(identity.signature));
|
||||
grpSignature.setVisibility(identity == null || TextUtils.isEmpty(identity.signature) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +205,8 @@ public class FragmentCompose extends FragmentEx {
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
tvExtraPrefix.setText(null);
|
||||
tvExtraSuffix.setText(null);
|
||||
tvSignature.setText(null);
|
||||
grpSignature.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -298,6 +307,8 @@ public class FragmentCompose extends FragmentEx {
|
||||
grpAddresses.setVisibility(View.GONE);
|
||||
grpAttachments.setVisibility(View.GONE);
|
||||
etBody.setVisibility(View.GONE);
|
||||
grpSignature.setVisibility(View.GONE);
|
||||
grpReference.setVisibility(View.GONE);
|
||||
edit_bar.setVisibility(View.GONE);
|
||||
bottom_navigation.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
@@ -640,7 +651,7 @@ public class FragmentCompose extends FragmentEx {
|
||||
Properties props = MessageHelper.getSessionProperties(Helper.AUTH_TYPE_PASSWORD, false);
|
||||
Session isession = Session.getInstance(props, null);
|
||||
MimeMessage imessage = new MimeMessage(isession);
|
||||
MessageHelper.build(context, message, attachments, imessage);
|
||||
MessageHelper.build(context, message, imessage);
|
||||
|
||||
// Serialize message
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
@@ -918,6 +929,7 @@ public class FragmentCompose extends FragmentEx {
|
||||
UnderlineSpan[] uspans = spannable.getSpans(0, spannable.length(), UnderlineSpan.class);
|
||||
for (UnderlineSpan uspan : uspans)
|
||||
spannable.removeSpan(uspan);
|
||||
|
||||
args.putString("body", Html.toHtml(spannable));
|
||||
|
||||
Log.i(Helper.TAG, "Run load id=" + working);
|
||||
@@ -1026,7 +1038,6 @@ public class FragmentCompose extends FragmentEx {
|
||||
long reference = args.getLong("reference", -1);
|
||||
boolean raw = args.getBoolean("raw", false);
|
||||
long answer = args.getLong("answer", -1);
|
||||
boolean pro = Helper.isPro(getContext());
|
||||
|
||||
Log.i(Helper.TAG, "Load draft action=" + action + " id=" + id + " reference=" + reference);
|
||||
|
||||
@@ -1140,9 +1151,6 @@ public class FragmentCompose extends FragmentEx {
|
||||
body = "";
|
||||
else
|
||||
body = body.replaceAll("\\r?\\n", "<br />");
|
||||
|
||||
if (pro)
|
||||
body += "<p>‌</p><p><tt>‌</tt></p>";
|
||||
} else {
|
||||
result.draft.thread = ref.thread;
|
||||
|
||||
@@ -1170,28 +1178,14 @@ public class FragmentCompose extends FragmentEx {
|
||||
}
|
||||
|
||||
} else if ("forward".equals(action)) {
|
||||
//msg.replying = ref.id;
|
||||
result.draft.forwarding = ref.id;
|
||||
result.draft.from = ref.to;
|
||||
}
|
||||
|
||||
long time = (ref.sent == null ? ref.received : ref.sent);
|
||||
|
||||
if ("reply".equals(action) || "reply_all".equals(action)) {
|
||||
if ("reply".equals(action) || "reply_all".equals(action))
|
||||
result.draft.subject = context.getString(R.string.title_subject_reply, ref.subject);
|
||||
body = String.format("<p>%s %s:</p><blockquote>%s</blockquote>",
|
||||
Html.escapeHtml(new Date(time).toString()),
|
||||
Html.escapeHtml(MessageHelper.getFormattedAddresses(result.draft.to, true)),
|
||||
HtmlHelper.sanitize(ref.read(context)));
|
||||
} else if ("forward".equals(action)) {
|
||||
else if ("forward".equals(action))
|
||||
result.draft.subject = context.getString(R.string.title_subject_forward, ref.subject);
|
||||
body = String.format("<p>%s %s:</p><blockquote>%s</blockquote>",
|
||||
Html.escapeHtml(new Date(time).toString()),
|
||||
Html.escapeHtml(MessageHelper.getFormattedAddresses(ref.from, true)),
|
||||
HtmlHelper.sanitize(ref.read(context)));
|
||||
}
|
||||
|
||||
if (pro)
|
||||
body = "<p><tt>‌</tt></p>" + body;
|
||||
|
||||
if (answer > 0 && ("reply".equals(action) || "reply_all".equals(action))) {
|
||||
String text = db.answer().getAnswer(answer).text;
|
||||
@@ -1206,8 +1200,7 @@ public class FragmentCompose extends FragmentEx {
|
||||
text = text.replace("$email$", email == null ? "" : email);
|
||||
|
||||
body = text + body;
|
||||
} else
|
||||
body = "<p>‌</p>" + body;
|
||||
}
|
||||
}
|
||||
|
||||
result.draft.content = true;
|
||||
@@ -1302,22 +1295,36 @@ public class FragmentCompose extends FragmentEx {
|
||||
|
||||
etBody.setText(null);
|
||||
|
||||
Bundle a = new Bundle();
|
||||
final Bundle a = new Bundle();
|
||||
a.putLong("id", result.draft.id);
|
||||
if (result.draft.replying != null)
|
||||
a.putLong("reference", result.draft.replying);
|
||||
else if (result.draft.forwarding != null)
|
||||
a.putLong("reference", result.draft.forwarding);
|
||||
|
||||
new SimpleTask<Spanned>() {
|
||||
new SimpleTask<Spanned[]>() {
|
||||
@Override
|
||||
protected Spanned onLoad(final Context context, Bundle args) throws Throwable {
|
||||
final long id = args.getLong("id");
|
||||
protected Spanned[] onLoad(final Context context, Bundle args) throws Throwable {
|
||||
long id = args.getLong("id");
|
||||
long reference = args.getLong("reference", -1);
|
||||
|
||||
String body = EntityMessage.read(context, id);
|
||||
return Html.fromHtml(body, cidGetter, null);
|
||||
String quote = (reference < 0 ? null : EntityMessage.getQuote(context, reference));
|
||||
|
||||
return new Spanned[]{
|
||||
Html.fromHtml(body, cidGetter, null),
|
||||
quote == null ? null : Html.fromHtml(quote)};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoaded(Bundle args, Spanned body) {
|
||||
protected void onLoaded(Bundle args, Spanned[] texts) {
|
||||
getActivity().invalidateOptionsMenu();
|
||||
etBody.setText(body);
|
||||
etBody.setText(texts[0]);
|
||||
etBody.setSelection(0);
|
||||
|
||||
tvReference.setText(texts[1]);
|
||||
grpReference.setVisibility(texts[1] == null ? View.GONE : View.VISIBLE);
|
||||
|
||||
new Handler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -173,14 +173,19 @@ public class MessageHelper {
|
||||
return props;
|
||||
}
|
||||
|
||||
static MimeMessageEx from(Context context, EntityMessage message, EntityMessage reply, List<EntityAttachment> attachments, Session isession) throws MessagingException, IOException {
|
||||
static MimeMessageEx from(Context context, EntityMessage message, Session isession) throws MessagingException, IOException {
|
||||
DB db = DB.getInstance(context);
|
||||
MimeMessageEx imessage = new MimeMessageEx(isession, message.msgid);
|
||||
|
||||
if (reply == null)
|
||||
EntityMessage replying = null;
|
||||
if (message.replying != null)
|
||||
replying = db.message().getMessage(message.replying);
|
||||
|
||||
if (replying == null)
|
||||
imessage.addHeader("References", message.msgid);
|
||||
else {
|
||||
imessage.addHeader("In-Reply-To", reply.msgid);
|
||||
imessage.addHeader("References", (reply.references == null ? "" : reply.references + " ") + reply.msgid);
|
||||
imessage.addHeader("In-Reply-To", replying.msgid);
|
||||
imessage.addHeader("References", (replying.references == null ? "" : replying.references + " ") + replying.msgid);
|
||||
}
|
||||
|
||||
imessage.setFlag(Flags.Flag.SEEN, message.seen);
|
||||
@@ -210,6 +215,8 @@ public class MessageHelper {
|
||||
|
||||
imessage.setSentDate(new Date());
|
||||
|
||||
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
|
||||
|
||||
if (message.from != null && message.from.length > 0)
|
||||
for (EntityAttachment attachment : attachments)
|
||||
if (attachment.available && "signature.asc".equals(attachment.name)) {
|
||||
@@ -265,14 +272,25 @@ public class MessageHelper {
|
||||
return imessage;
|
||||
}
|
||||
|
||||
build(context, message, attachments, imessage);
|
||||
build(context, message, imessage);
|
||||
|
||||
return imessage;
|
||||
}
|
||||
|
||||
static void build(Context context, EntityMessage message, List<EntityAttachment> attachments, MimeMessage imessage) throws IOException, MessagingException {
|
||||
static void build(Context context, EntityMessage message, MimeMessage imessage) throws IOException, MessagingException {
|
||||
DB db = DB.getInstance(context);
|
||||
|
||||
String body = message.read(context);
|
||||
|
||||
if (Helper.isPro(context) && message.identity != null) {
|
||||
EntityIdentity identity = db.identity().getIdentity(message.identity);
|
||||
if (!TextUtils.isEmpty(identity.signature))
|
||||
body += identity.signature;
|
||||
}
|
||||
|
||||
if (message.replying != null || message.forwarding != null)
|
||||
body += EntityMessage.getQuote(context, message.replying == null ? message.forwarding : message.replying);
|
||||
|
||||
BodyPart plain = new MimeBodyPart();
|
||||
plain.setContent(Jsoup.parse(body).text(), "text/plain; charset=" + Charset.defaultCharset().name());
|
||||
|
||||
@@ -283,6 +301,7 @@ public class MessageHelper {
|
||||
alternative.addBodyPart(plain);
|
||||
alternative.addBodyPart(html);
|
||||
|
||||
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
|
||||
if (attachments.size() == 0) {
|
||||
imessage.setContent(alternative);
|
||||
} else {
|
||||
|
||||
@@ -1398,8 +1398,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
|
||||
private void doAdd(EntityFolder folder, Session isession, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException, IOException {
|
||||
// Append message
|
||||
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
|
||||
MimeMessage imessage = MessageHelper.from(this, message, null, attachments, isession);
|
||||
MimeMessage imessage = MessageHelper.from(this, message, isession);
|
||||
AppendUID[] uid = ifolder.appendUIDMessages(new Message[]{imessage});
|
||||
db.message().setMessageUid(message.id, uid[0].uid);
|
||||
Log.i(Helper.TAG, "Appended uid=" + uid[0].uid);
|
||||
@@ -1432,14 +1431,12 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
} else {
|
||||
Log.w(Helper.TAG, "MOVE by DELETE/APPEND");
|
||||
|
||||
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
|
||||
|
||||
if (!EntityFolder.ARCHIVE.equals(folder.type)) {
|
||||
imessage.setFlag(Flags.Flag.DELETED, true);
|
||||
ifolder.expunge();
|
||||
}
|
||||
|
||||
MimeMessageEx icopy = MessageHelper.from(this, message, null, attachments, isession);
|
||||
MimeMessageEx icopy = MessageHelper.from(this, message, isession);
|
||||
Folder itarget = istore.getFolder(target.name);
|
||||
itarget.appendMessages(new Message[]{icopy});
|
||||
}
|
||||
@@ -1470,10 +1467,7 @@ public class ServiceSynchronize extends LifecycleService {
|
||||
final Session isession = Session.getInstance(props, null);
|
||||
|
||||
// Create message
|
||||
MimeMessage imessage;
|
||||
EntityMessage reply = (message.replying == null ? null : db.message().getMessage(message.replying));
|
||||
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
|
||||
imessage = MessageHelper.from(this, message, reply, attachments, isession);
|
||||
MimeMessage imessage = MessageHelper.from(this, message, isession);
|
||||
|
||||
if (ident.replyto != null)
|
||||
imessage.setReplyTo(new Address[]{new InternetAddress(ident.replyto)});
|
||||
|
||||
Reference in New Issue
Block a user