diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java
index 448882de6d..d7cb86ae9e 100644
--- a/app/src/main/java/eu/faircode/email/FragmentCompose.java
+++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java
@@ -50,8 +50,10 @@ import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.LocaleList;
import android.os.Looper;
import android.provider.ContactsContract;
import android.provider.MediaStore;
@@ -203,6 +205,7 @@ public class FragmentCompose extends FragmentBase {
private long working = -1;
private State state = State.NONE;
private boolean show_images = false;
+ private boolean reminded = false;
private boolean autosave = false;
private boolean busy = false;
@@ -234,6 +237,7 @@ public class FragmentCompose extends FragmentBase {
private static final int REQUEST_LINK = 15;
private static final int REQUEST_DISCARD = 16;
private static final int REQUEST_SEND = 17;
+ private static final int REQUEST_REMIND = 18;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -756,6 +760,7 @@ public class FragmentCompose extends FragmentBase {
public void onSaveInstanceState(Bundle outState) {
outState.putLong("fair:working", working);
outState.putBoolean("fair:show_images", show_images);
+ outState.putBoolean("fair:reminded", reminded);
outState.putParcelable("fair:photo", photoURI);
super.onSaveInstanceState(outState);
}
@@ -804,6 +809,7 @@ public class FragmentCompose extends FragmentBase {
} else {
working = savedInstanceState.getLong("fair:working");
show_images = savedInstanceState.getBoolean("fair:show_images");
+ reminded = savedInstanceState.getBoolean("fair:reminded");
photoURI = savedInstanceState.getParcelable("fair:photo");
Bundle args = new Bundle();
@@ -1218,6 +1224,11 @@ public class FragmentCompose extends FragmentBase {
onAction(R.id.action_send);
}
+ private void onActionReminded() {
+ reminded = true;
+ onAction(R.id.action_send);
+ }
+
private void onEncrypt() {
if (pgpService.isBound())
try {
@@ -1327,6 +1338,10 @@ public class FragmentCompose extends FragmentBase {
if (resultCode == RESULT_OK)
onActionSendConfirmed();
break;
+ case REQUEST_REMIND:
+ if (resultCode == RESULT_OK)
+ onActionReminded();
+ break;
}
} catch (Throwable ex) {
Log.e(ex);
@@ -1932,6 +1947,7 @@ public class FragmentCompose extends FragmentBase {
args.putBoolean("plain_only", plain_only);
args.putBoolean("encrypt", encrypt);
args.putBoolean("empty", isEmpty());
+ args.putBoolean("reminded", reminded);
Log.i("Run execute id=" + working);
actionLoader.execute(this, args, "compose:action:" + action);
@@ -2663,6 +2679,7 @@ public class FragmentCompose extends FragmentBase {
boolean plain_only = args.getBoolean("plain_only");
boolean encrypt = args.getBoolean("encrypt");
boolean empty = args.getBoolean("empty");
+ boolean reminded = args.getBoolean("reminded");
EntityMessage draft;
@@ -2932,11 +2949,38 @@ public class FragmentCompose extends FragmentBase {
if (draft.to == null && draft.cc == null && draft.bcc == null)
throw new IllegalArgumentException(context.getString(R.string.title_to_missing));
- // Save attachments
+ // Check attachments
for (EntityAttachment attachment : attachments)
if (!attachment.available)
throw new IllegalArgumentException(context.getString(R.string.title_attachments_missing));
+ boolean check_attachments = prefs.getBoolean("check_attachments", true);
+ if (check_attachments && !reminded && attachments.size() == 0) {
+ List keywords = new ArrayList<>();
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ String[] k = context.getString(R.string.title_attachment_keywords).split(",");
+ keywords.addAll(Arrays.asList(k));
+ } else {
+ Configuration config = context.getResources().getConfiguration();
+ LocaleList ll = context.getResources().getConfiguration().getLocales();
+ for (int i = 0; i < ll.size(); i++) {
+ Configuration lconf = new Configuration(config);
+ lconf.setLocale(ll.get(i));
+ Context lcontext = context.createConfigurationContext(lconf);
+ String[] k = lcontext.getString(R.string.title_attachment_keywords).split(",");
+ keywords.addAll(Arrays.asList(k));
+ }
+ }
+
+ String plain = HtmlHelper.getText(body);
+ for (String keyword : keywords)
+ if (plain.matches("(?si).*\\b" + Pattern.quote(keyword.trim()) + "\\b.*")) {
+ args.putBoolean("remind", true);
+ return draft;
+ }
+ }
+
// Delete draft (cannot move to outbox)
EntityOperation.queue(context, draft, EntityOperation.DELETE);
@@ -3025,8 +3069,14 @@ public class FragmentCompose extends FragmentBase {
onEncrypt();
} else if (action == R.id.action_send) {
- autosave = false;
- finish();
+ if (args.getBoolean("remind", false)) {
+ FragmentDialogRemind remind = new FragmentDialogRemind();
+ remind.setTargetFragment(FragmentCompose.this, FragmentCompose.REQUEST_REMIND);
+ remind.show(getFragmentManager(), "compose:remind");
+ } else {
+ autosave = false;
+ finish();
+ }
}
}
@@ -3688,6 +3738,40 @@ public class FragmentCompose extends FragmentBase {
}
}
+ public static class FragmentDialogRemind extends FragmentDialogEx {
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_ask_again, null);
+ TextView tvMessage = dview.findViewById(R.id.tvMessage);
+ final CheckBox cbNotAgain = dview.findViewById(R.id.cbNotAgain);
+
+ tvMessage.setText(R.string.title_attachment_reminder);
+
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+
+ return new AlertDialog.Builder(getContext())
+ .setView(dview)
+ .setPositiveButton(R.string.title_yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (cbNotAgain.isChecked())
+ prefs.edit().putBoolean("check_attachments", false).apply();
+ sendResult(Activity.RESULT_CANCELED);
+ }
+ })
+ .setNegativeButton(R.string.title_no, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ if (cbNotAgain.isChecked())
+ prefs.edit().putBoolean("check_attachments", false).apply();
+ sendResult(Activity.RESULT_OK);
+ }
+ })
+ .create();
+ }
+ }
+
private class DraftData {
private EntityMessage draft;
private List identities;
diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsSend.java b/app/src/main/java/eu/faircode/email/FragmentOptionsSend.java
index ff37df5a7c..434546b892 100644
--- a/app/src/main/java/eu/faircode/email/FragmentOptionsSend.java
+++ b/app/src/main/java/eu/faircode/email/FragmentOptionsSend.java
@@ -50,10 +50,12 @@ public class FragmentOptionsSend extends FragmentBase implements SharedPreferenc
private TextView tvAutoResize;
private SwitchCompat swLookupMx;
private SwitchCompat swAutoSend;
+ private SwitchCompat swCheckAttachments;
private Spinner spSendDelayed;
private final static String[] RESET_OPTIONS = new String[]{
- "keyboard", "suggest_local", "prefix_once", "plain_only", "usenet_signature", "autoresize", "resize", "lookup_mx", "autosend", "send_delayed"
+ "keyboard", "suggest_local", "prefix_once", "plain_only", "usenet_signature",
+ "autoresize", "resize", "lookup_mx", "autosend", "check_attachments", "send_delayed"
};
@Override
@@ -76,6 +78,7 @@ public class FragmentOptionsSend extends FragmentBase implements SharedPreferenc
tvAutoResize = view.findViewById(R.id.tvAutoResize);
swLookupMx = view.findViewById(R.id.swLookupMx);
swAutoSend = view.findViewById(R.id.swAutoSend);
+ swCheckAttachments = view.findViewById(R.id.swCheckAttachments);
spSendDelayed = view.findViewById(R.id.spSendDelayed);
setOptions();
@@ -155,6 +158,13 @@ public class FragmentOptionsSend extends FragmentBase implements SharedPreferenc
}
});
+ swCheckAttachments.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
+ prefs.edit().putBoolean("check_attachments", checked).apply();
+ }
+ });
+
spSendDelayed.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView> adapterView, View view, int position, long id) {
@@ -234,6 +244,7 @@ public class FragmentOptionsSend extends FragmentBase implements SharedPreferenc
swLookupMx.setChecked(prefs.getBoolean("lookup_mx", false));
swAutoSend.setChecked(!prefs.getBoolean("autosend", false));
+ swCheckAttachments.setChecked(prefs.getBoolean("check_attachments", true));
int send_delayed = prefs.getInt("send_delayed", 0);
int[] sendDelayedValues = getResources().getIntArray(R.array.sendDelayedValues);
diff --git a/app/src/main/res/layout/fragment_options_send.xml b/app/src/main/res/layout/fragment_options_send.xml
index 0420d46d78..4c48ca4f56 100644
--- a/app/src/main/res/layout/fragment_options_send.xml
+++ b/app/src/main/res/layout/fragment_options_send.xml
@@ -153,6 +153,18 @@
app:layout_constraintTop_toBottomOf="@id/tvLookupMxHint"
app:switchPadding="12dp" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/swCheckAttachments" />
< %1$d pixels
Check recipient email addresses before sending
Confirm sending messages
+ Check for missing attachments
Delay sending messages
Use metered connections