Dialog refactoring

pull/212/head
M66B 2 years ago
parent b2c869597f
commit e8027bde38

@ -25,7 +25,6 @@ import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Dialog;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
@ -2487,65 +2486,6 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
} }
} }
public static class FragmentDialogFirst extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
LayoutInflater inflater = LayoutInflater.from(getContext());
View dview = inflater.inflate(R.layout.dialog_first, null);
ImageButton ibBatteryInfo = dview.findViewById(R.id.ibBatteryInfo);
ImageButton ibReformatInfo = dview.findViewById(R.id.ibReformatInfo);
ibBatteryInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 39);
}
});
ibReformatInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 35);
}
});
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("first", false).apply();
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
public static class FragmentDialogRate extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
return new AlertDialog.Builder(getContext())
.setMessage(R.string.title_issue)
.setPositiveButton(R.string.title_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Helper.viewFAQ(getContext(), 0);
}
})
.setNegativeButton(R.string.title_no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Helper.view(getContext(), Helper.getIntentRate(getContext()));
}
})
.create();
}
}
private final Consumer<WindowLayoutInfo> layoutStateChangeCallback = new Consumer<WindowLayoutInfo>() { private final Consumer<WindowLayoutInfo> layoutStateChangeCallback = new Consumer<WindowLayoutInfo>() {
@Override @Override
public void accept(WindowLayoutInfo info) { public void accept(WindowLayoutInfo info) {

@ -19,12 +19,10 @@ package eu.faircode.email;
Copyright 2018-2023 by Marcel Bokhorst (M66B) Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/ */
import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -32,7 +30,6 @@ import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
@ -40,8 +37,6 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.PopupMenu;
import androidx.core.app.ShareCompat; import androidx.core.app.ShareCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@ -51,12 +46,9 @@ import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent; import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.ListUpdateCallback; import androidx.recyclerview.widget.ListUpdateCallback;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.io.File;
import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -597,209 +589,4 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
holder.wire(); holder.wire();
} }
public static class FragmentDialogVirusTotal extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Bundle args = getArguments();
String apiKey = args.getString("apiKey");
String name = args.getString("name");
final Context context = getContext();
View view = LayoutInflater.from(context).inflate(R.layout.dialog_virus_total, null);
final TextView tvName = view.findViewById(R.id.tvName);
final TextView tvError = view.findViewById(R.id.tvError);
final TextView tvUnknown = view.findViewById(R.id.tvUnknown);
final TextView tvSummary = view.findViewById(R.id.tvSummary);
final TextView tvLabel = view.findViewById(R.id.tvLabel);
final TextView tvReport = view.findViewById(R.id.tvReport);
final RecyclerView rvScan = view.findViewById(R.id.rvScan);
final Button btnUpload = view.findViewById(R.id.btnUpload);
final ProgressBar pbUpload = view.findViewById(R.id.pbUpload);
final TextView tvAnalyzing = view.findViewById(R.id.tvAnalyzing);
final TextView tvPrivacy = view.findViewById(R.id.tvPrivacy);
final ProgressBar pbWait = view.findViewById(R.id.pbWait);
tvName.setText(name);
tvName.setVisibility(TextUtils.isEmpty(name) ? View.GONE : View.VISIBLE);
tvError.setVisibility(View.GONE);
tvUnknown.setVisibility(View.GONE);
tvSummary.setVisibility(View.GONE);
tvLabel.setVisibility(View.GONE);
tvReport.setVisibility(View.GONE);
tvReport.getPaint().setUnderlineText(true);
rvScan.setHasFixedSize(false);
LinearLayoutManager llm = new LinearLayoutManager(getContext());
rvScan.setLayoutManager(llm);
final AdapterVirusTotal adapter = new AdapterVirusTotal(getContext(), getViewLifecycleOwner());
rvScan.setAdapter(adapter);
rvScan.setVisibility(View.GONE);
btnUpload.setVisibility(View.GONE);
pbUpload.setVisibility(View.GONE);
tvAnalyzing.setVisibility(View.GONE);
tvPrivacy.setVisibility(View.GONE);
tvPrivacy.getPaint().setUnderlineText(true);
pbWait.setVisibility(View.GONE);
final SimpleTask<Bundle> taskLookup = new SimpleTask<Bundle>() {
@Override
protected void onPreExecute(Bundle args) {
tvError.setVisibility(View.GONE);
pbWait.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(Bundle args) {
pbWait.setVisibility(View.GONE);
}
@Override
protected Bundle onExecute(Context context, Bundle args) throws Throwable {
String apiKey = args.getString("apiKey");
File file = (File) args.getSerializable("file");
return VirusTotal.lookup(context, file, apiKey);
}
@Override
protected void onExecuted(Bundle args, Bundle result) {
List<VirusTotal.ScanResult> scans = result.getParcelableArrayList("scans");
String label = result.getString("label");
String analysis = args.getString("analysis");
int malicious = 0;
if (scans != null)
for (VirusTotal.ScanResult scan : scans)
if ("malicious".equals(scan.category))
malicious++;
NumberFormat NF = NumberFormat.getNumberInstance();
tvUnknown.setVisibility(scans == null ? View.VISIBLE : View.GONE);
tvSummary.setText(getString(R.string.title_vt_summary, NF.format(malicious)));
tvSummary.setTextColor(Helper.resolveColor(context,
malicious == 0 ? android.R.attr.textColorPrimary : R.attr.colorWarning));
tvSummary.setTypeface(malicious == 0 ? Typeface.DEFAULT : Typeface.DEFAULT_BOLD);
tvSummary.setVisibility(scans == null ? View.GONE : View.VISIBLE);
tvLabel.setText(label);
tvReport.setVisibility(scans == null ? View.GONE : View.VISIBLE);
adapter.set(scans == null ? new ArrayList<>() : scans);
rvScan.setVisibility(scans == null ? View.GONE : View.VISIBLE);
btnUpload.setVisibility(scans == null && !TextUtils.isEmpty(apiKey) ? View.VISIBLE : View.GONE);
tvPrivacy.setVisibility(btnUpload.getVisibility());
if (analysis != null && args.getBoolean("init")) {
args.remove("init");
btnUpload.callOnClick();
}
}
@Override
protected void onException(Bundle args, Throwable ex) {
tvError.setText(Log.formatThrowable(ex, false));
tvError.setVisibility(View.VISIBLE);
}
};
final SimpleTask<Void> taskUpload = new SimpleTask<Void>() {
@Override
protected void onPreExecute(Bundle args) {
btnUpload.setEnabled(false);
pbUpload.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(Bundle args) {
btnUpload.setEnabled(true);
tvAnalyzing.setVisibility(View.GONE);
pbUpload.setVisibility(View.GONE);
}
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
String apiKey = args.getString("apiKey");
File file = (File) args.getSerializable("file");
String analysis = args.getString("analysis");
if (analysis == null) {
analysis = VirusTotal.upload(context, file, apiKey);
args.putString("analysis", analysis);
}
postProgress(analysis);
VirusTotal.waitForAnalysis(context, analysis, apiKey);
return null;
}
@Override
protected void onProgress(CharSequence status, Bundle data) {
tvAnalyzing.setVisibility(View.VISIBLE);
}
@Override
protected void onExecuted(Bundle args, Void data) {
taskLookup.execute(FragmentDialogVirusTotal.this, args, "attachment:lookup");
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
};
final SimpleTask<String> taskUrl = new SimpleTask<String>() {
@Override
protected String onExecute(Context context, Bundle args) throws Throwable {
File file = (File) args.getSerializable("file");
return VirusTotal.getUrl(file);
}
@Override
protected void onExecuted(Bundle args, String uri) {
Helper.view(context, Uri.parse(uri), true);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
};
tvReport.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
taskUrl.execute(FragmentDialogVirusTotal.this, args, "attachment:report");
}
});
btnUpload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
taskUpload.execute(FragmentDialogVirusTotal.this, args, "attachment:upload");
}
});
tvPrivacy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.view(v.getContext(), Uri.parse(VirusTotal.URI_PRIVACY), true);
}
});
if (TextUtils.isEmpty(apiKey))
pbWait.setVisibility(View.GONE);
else {
args.putBoolean("init", true);
taskLookup.execute(this, args, "attachment:lookup");
}
return new AlertDialog.Builder(context)
.setView(view)
.setNegativeButton(R.string.title_setup_done, null)
.create();
}
}
} }

@ -325,7 +325,7 @@ public class AdapterContact extends RecyclerView.Adapter<AdapterContact.ViewHold
args.putString("name", contact.name); args.putString("name", contact.name);
args.putString("group", contact.group); args.putString("group", contact.group);
FragmentContacts.FragmentDialogEditContact fragment = new FragmentContacts.FragmentDialogEditContact(); FragmentDialogContactEdit fragment = new FragmentDialogContactEdit();
fragment.setArguments(args); fragment.setArguments(args);
fragment.setTargetFragment(parentFragment, FragmentContacts.REQUEST_EDIT_CONTACT); fragment.setTargetFragment(parentFragment, FragmentContacts.REQUEST_EDIT_CONTACT);
fragment.show(parentFragment.getParentFragmentManager(), "contact:edit"); fragment.show(parentFragment.getParentFragmentManager(), "contact:edit");

@ -19,16 +19,13 @@ package eu.faircode.email;
Copyright 2018-2023 by Marcel Bokhorst (M66B) Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/ */
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_FIRST_USER; import static android.app.Activity.RESULT_FIRST_USER;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static android.system.OsConstants.ENOSPC; import static android.system.OsConstants.ENOSPC;
import static android.view.inputmethod.EditorInfo.IME_FLAG_NO_FULLSCREEN; import static android.view.inputmethod.EditorInfo.IME_FLAG_NO_FULLSCREEN;
import static android.widget.AdapterView.INVALID_POSITION;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
@ -42,13 +39,11 @@ import android.content.pm.PackageManager;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.database.Cursor; import android.database.Cursor;
import android.database.MatrixCursor; import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.ImageDecoder; import android.graphics.ImageDecoder;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.GradientDrawable;
@ -96,7 +91,6 @@ import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.EditText; import android.widget.EditText;
@ -105,7 +99,6 @@ import android.widget.HorizontalScrollView;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.MultiAutoCompleteTextView; import android.widget.MultiAutoCompleteTextView;
import android.widget.RadioGroup;
import android.widget.ScrollView; import android.widget.ScrollView;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.SpinnerAdapter; import android.widget.SpinnerAdapter;
@ -118,7 +111,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.SwitchCompat;
import androidx.constraintlayout.widget.Group; import androidx.constraintlayout.widget.Group;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
@ -189,7 +181,6 @@ import java.security.cert.X509Certificate;
import java.text.Collator; import java.text.Collator;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -312,13 +303,10 @@ public class FragmentCompose extends FragmentBase {
private int searchIndex = 0; private int searchIndex = 0;
private static final int REDUCED_IMAGE_SIZE = 1440; // pixels static final int REDUCED_IMAGE_SIZE = 1440; // pixels
private static final int REDUCED_IMAGE_QUALITY = 90; // percent private static final int REDUCED_IMAGE_QUALITY = 90; // percent
// http://regex.info/blog/lightroom-goodies/jpeg-quality // http://regex.info/blog/lightroom-goodies/jpeg-quality
private static final int MAX_SHOW_RECIPIENTS = 5;
private static final int RECIPIENTS_WARNING = 10;
private static final int MAX_QUOTE_LEVEL = 5; private static final int MAX_QUOTE_LEVEL = 5;
private static final int MAX_OPENAI_LEN = 1000; // characters private static final int MAX_OPENAI_LEN = 1000; // characters
@ -6907,7 +6895,7 @@ public class FragmentCompose extends FragmentBase {
sent_missing || address_error != null || mx_error != null || sent_missing || address_error != null || mx_error != null ||
remind_dsn || remind_size || remind_pgp || remind_smime || remind_dsn || remind_size || remind_pgp || remind_smime ||
remind_to || remind_noreply || remind_external || remind_to || remind_noreply || remind_external ||
recipients > RECIPIENTS_WARNING || recipients > FragmentDialogSend.RECIPIENTS_WARNING ||
(styled && draft.isPlainOnly()) || (styled && draft.isPlainOnly()) ||
(send_reminders && (send_reminders &&
(remind_extra || remind_subject || remind_text || (remind_extra || remind_subject || remind_text ||
@ -7483,892 +7471,6 @@ public class FragmentCompose extends FragmentBase {
} }
}; };
public static class FragmentDialogContactGroup extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Bundle args = getArguments();
final long working = args.getLong("working");
int focussed = args.getInt("focussed");
final Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_contact_group, null);
final ImageButton ibInfo = dview.findViewById(R.id.ibInfo);
final Spinner spGroup = dview.findViewById(R.id.spGroup);
final Spinner spTarget = dview.findViewById(R.id.spTarget);
final Spinner spType = dview.findViewById(R.id.spType);
ibInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.view(v.getContext(), Uri.parse(Helper.URI_SUPPORT_CONTACT_GROUP), true);
}
});
new SimpleTask<Cursor>() {
@Override
protected Cursor onExecute(Context context, Bundle args) {
final String[] projection = new String[]{
ContactsContract.Groups._ID,
ContactsContract.Groups.TITLE,
ContactsContract.Groups.SUMMARY_COUNT,
ContactsContract.Groups.ACCOUNT_NAME,
ContactsContract.Groups.ACCOUNT_TYPE,
};
Cursor contacts = new MatrixCursor(projection);
if (Helper.hasPermission(context, Manifest.permission.READ_CONTACTS))
try {
ContentResolver resolver = context.getContentResolver();
contacts = resolver.query(
ContactsContract.Groups.CONTENT_SUMMARY_URI,
projection,
// ContactsContract.Groups.GROUP_VISIBLE + " = 1" + " AND " +
ContactsContract.Groups.DELETED + " = 0" +
" AND " + ContactsContract.Groups.SUMMARY_COUNT + " > 0",
null,
ContactsContract.Groups.TITLE
);
} catch (SecurityException ex) {
Log.w(ex);
}
DB db = DB.getInstance(context);
Cursor local = db.contact().getGroups(
null,
context.getString(R.string.app_name),
BuildConfig.APPLICATION_ID);
return new MergeCursor(new Cursor[]{contacts, local});
}
@Override
protected void onExecuted(Bundle args, Cursor cursor) {
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
context,
R.layout.spinner_contact_group,
cursor,
new String[]{ContactsContract.Groups.TITLE, ContactsContract.Groups.ACCOUNT_NAME},
new int[]{R.id.tvGroup, R.id.tvAccount},
0);
final NumberFormat NF = NumberFormat.getInstance();
adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
@Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
if (view.getId() == R.id.tvGroup) {
String title = cursor.getString(1);
if (TextUtils.isEmpty(title))
title = "-";
int count = cursor.getInt(2);
((TextView) view).setText(context.getString(R.string.title_name_count, title, NF.format(count)));
return true;
} else if (view.getId() == R.id.tvAccount) {
String account = cursor.getString(3);
String type = cursor.getString(4);
((TextView) view).setText(account + (BuildConfig.DEBUG ? "/" + type : ""));
return true;
} else
return false;
}
});
spGroup.setAdapter(adapter);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, new Bundle(), "compose:groups");
spTarget.setSelection(focussed);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
int target = spTarget.getSelectedItemPosition();
Cursor cursor = (Cursor) spGroup.getSelectedItem();
if (target != INVALID_POSITION &&
cursor != null && cursor.getCount() > 0) {
long group = cursor.getLong(0);
String name = cursor.getString(1);
Bundle args = getArguments();
args.putLong("id", working);
args.putInt("target", target);
args.putLong("group", group);
args.putString("name", name);
args.putInt("type", spType.getSelectedItemPosition());
sendResult(RESULT_OK);
} else
sendResult(RESULT_CANCELED);
} catch (Throwable ex) {
Log.e(ex);
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
public static class FragmentDialogAddImage extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
int title = getArguments().getInt("title");
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean add_inline = prefs.getBoolean("add_inline", true);
boolean resize_images = prefs.getBoolean("resize_images", true);
int resize = prefs.getInt("resize", FragmentCompose.REDUCED_IMAGE_SIZE);
boolean privacy_images = prefs.getBoolean("privacy_images", false);
boolean image_dialog = prefs.getBoolean("image_dialog", true);
final ViewGroup dview = (ViewGroup) LayoutInflater.from(getContext()).inflate(R.layout.dialog_add_image, null);
final ImageView ivType = dview.findViewById(R.id.ivType);
final RadioGroup rgAction = dview.findViewById(R.id.rgAction);
final ImageButton ibSettings = dview.findViewById(R.id.ibSettings);
final CheckBox cbResize = dview.findViewById(R.id.cbResize);
final ImageButton ibResize = dview.findViewById(R.id.ibResize);
final Spinner spResize = dview.findViewById(R.id.spResize);
final TextView tvResize = dview.findViewById(R.id.tvResize);
final CheckBox cbPrivacy = dview.findViewById(R.id.cbPrivacy);
final CheckBox cbNotAgain = dview.findViewById(R.id.cbNotAgain);
final TextView tvNotAgain = dview.findViewById(R.id.tvNotAgain);
ivType.setImageResource(title == R.string.title_attachment_photo
? R.drawable.twotone_photo_camera_24 : R.drawable.twotone_image_24);
rgAction.check(add_inline ? R.id.rbInline : R.id.rbAttach);
cbResize.setChecked(resize_images);
spResize.setEnabled(resize_images);
cbPrivacy.setChecked(privacy_images);
final int[] resizeValues = getResources().getIntArray(R.array.resizeValues);
for (int pos = 0; pos < resizeValues.length; pos++)
if (resizeValues[pos] == resize) {
spResize.setSelection(pos);
tvResize.setText(getString(R.string.title_add_resize_pixels, resizeValues[pos]));
break;
}
rgAction.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
prefs.edit().putBoolean("add_inline", checkedId == R.id.rbInline).apply();
}
});
// https://developer.android.com/reference/android/provider/MediaStore#ACTION_PICK_IMAGES_SETTINGS
PackageManager pm = getContext().getPackageManager();
Intent settings = new Intent(MediaStore.ACTION_PICK_IMAGES_SETTINGS);
ibSettings.setVisibility(settings.resolveActivity(pm) == null ? View.GONE : View.VISIBLE);
ibSettings.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
v.getContext().startActivity(settings);
}
});
cbResize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("resize_images", isChecked).apply();
spResize.setEnabled(isChecked);
}
});
ibResize.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 63);
}
});
spResize.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
prefs.edit().putInt("resize", resizeValues[position]).apply();
tvResize.setText(getString(R.string.title_add_resize_pixels, resizeValues[position]));
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
prefs.edit().remove("resize").apply();
}
});
cbPrivacy.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("privacy_images", isChecked).apply();
}
});
cbNotAgain.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("image_dialog", !isChecked).apply();
tvNotAgain.setVisibility(isChecked ? View.VISIBLE : View.GONE);
}
});
cbNotAgain.setChecked(!image_dialog);
tvNotAgain.setVisibility(cbNotAgain.isChecked() ? View.VISIBLE : View.GONE);
return new AlertDialog.Builder(getContext())
.setView(dview)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(title,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendResult(RESULT_OK);
}
})
.create();
}
}
public static class FragmentDialogSend extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Bundle args = getArguments();
long id = args.getLong("id");
final boolean sent_missing = args.getBoolean("sent_missing", false);
final String address_error = args.getString("address_error");
final String mx_error = args.getString("mx_error");
final boolean remind_dsn = args.getBoolean("remind_dsn", false);
final boolean remind_size = args.getBoolean("remind_size", false);
final boolean remind_pgp = args.getBoolean("remind_pgp", false);
final boolean remind_smime = args.getBoolean("remind_smime", false);
final boolean remind_to = args.getBoolean("remind_to", false);
final boolean remind_extra = args.getBoolean("remind_extra", false);
final boolean remind_noreply = args.getBoolean("remind_noreply", false);
final boolean remind_external = args.getBoolean("remind_external", false);
final boolean remind_subject = args.getBoolean("remind_subject", false);
final boolean remind_text = args.getBoolean("remind_text", false);
final boolean remind_attachment = args.getBoolean("remind_attachment", false);
final String remind_extension = args.getString("remind_extension");
final boolean remind_internet = args.getBoolean("remind_internet", false);
final boolean styled = args.getBoolean("styled", false);
final long size = args.getLong("size", -1);
final long max_size = args.getLong("max_size", -1);
final Context context = getContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final boolean send_reminders = prefs.getBoolean("send_reminders", true);
final int send_delayed = prefs.getInt("send_delayed", 0);
final boolean send_dialog = prefs.getBoolean("send_dialog", true);
final boolean send_archive = prefs.getBoolean("send_archive", false);
final MessageHelper.AddressFormat email_format = MessageHelper.getAddressFormat(getContext());
final int[] encryptValues = getResources().getIntArray(R.array.encryptValues);
final int[] sendDelayedValues = getResources().getIntArray(R.array.sendDelayedValues);
final String[] sendDelayedNames = getResources().getStringArray(R.array.sendDelayedNames);
final ViewGroup dview = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.dialog_send, null);
final Button btnFixSent = dview.findViewById(R.id.btnFixSent);
final TextView tvAddressError = dview.findViewById(R.id.tvAddressError);
final TextView tvRemindDsn = dview.findViewById(R.id.tvRemindDsn);
final TextView tvRemindSize = dview.findViewById(R.id.tvRemindSize);
final TextView tvRemindPgp = dview.findViewById(R.id.tvRemindPgp);
final TextView tvRemindSmime = dview.findViewById(R.id.tvRemindSmime);
final TextView tvRemindTo = dview.findViewById(R.id.tvRemindTo);
final TextView tvRemindExtra = dview.findViewById(R.id.tvRemindExtra);
final TextView tvRemindNoReply = dview.findViewById(R.id.tvRemindNoReply);
final TextView tvRemindExternal = dview.findViewById(R.id.tvRemindExternal);
final TextView tvRemindSubject = dview.findViewById(R.id.tvRemindSubject);
final TextView tvRemindText = dview.findViewById(R.id.tvRemindText);
final TextView tvRemindAttachment = dview.findViewById(R.id.tvRemindAttachment);
final TextView tvRemindExtension = dview.findViewById(R.id.tvRemindExtension);
final TextView tvRemindInternet = dview.findViewById(R.id.tvRemindInternet);
final SwitchCompat swSendReminders = dview.findViewById(R.id.swSendReminders);
final TextView tvSendRemindersHint = dview.findViewById(R.id.tvSendRemindersHint);
final TextView tvTo = dview.findViewById(R.id.tvTo);
final TextView tvViaTitle = dview.findViewById(R.id.tvViaTitle);
final TextView tvVia = dview.findViewById(R.id.tvVia);
final CheckBox cbPlainOnly = dview.findViewById(R.id.cbPlainOnly);
final TextView tvPlainHint = dview.findViewById(R.id.tvPlainHint);
final CheckBox cbReceipt = dview.findViewById(R.id.cbReceipt);
final TextView tvReceiptHint = dview.findViewById(R.id.tvReceiptHint);
final TextView tvEncrypt = dview.findViewById(R.id.tvEncrypt);
final Spinner spEncrypt = dview.findViewById(R.id.spEncrypt);
final ImageButton ibEncryption = dview.findViewById(R.id.ibEncryption);
final Spinner spPriority = dview.findViewById(R.id.spPriority);
final Spinner spSensitivity = dview.findViewById(R.id.spSensitivity);
final ImageButton ibSensitivity = dview.findViewById(R.id.ibSensitivity);
final TextView tvSendAt = dview.findViewById(R.id.tvSendAt);
final ImageButton ibSendAt = dview.findViewById(R.id.ibSendAt);
final CheckBox cbArchive = dview.findViewById(R.id.cbArchive);
final CheckBox cbNotAgain = dview.findViewById(R.id.cbNotAgain);
final TextView tvNotAgain = dview.findViewById(R.id.tvNotAgain);
final Group grpSentMissing = dview.findViewById(R.id.grpSentMissing);
final Group grpDsn = dview.findViewById(R.id.grpDsn);
btnFixSent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
v.getContext().startActivity(new Intent(v.getContext(), ActivitySetup.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
.putExtra("target", "accounts"));
}
});
grpSentMissing.setVisibility(sent_missing ? View.VISIBLE : View.GONE);
tvAddressError.setText(address_error == null ? mx_error : address_error);
tvAddressError.setVisibility(address_error == null && mx_error == null ? View.GONE : View.VISIBLE);
tvRemindDsn.setVisibility(remind_dsn ? View.VISIBLE : View.GONE);
tvRemindSize.setText(getString(R.string.title_size_reminder,
Helper.humanReadableByteCount(size),
Helper.humanReadableByteCount(max_size)));
tvRemindSize.setVisibility(remind_size ? View.VISIBLE : View.GONE);
tvRemindPgp.setVisibility(remind_pgp ? View.VISIBLE : View.GONE);
tvRemindSmime.setVisibility(remind_smime ? View.VISIBLE : View.GONE);
tvRemindTo.setVisibility(remind_to ? View.VISIBLE : View.GONE);
tvRemindExtra.setVisibility(send_reminders && remind_extra ? View.VISIBLE : View.GONE);
tvRemindNoReply.setVisibility(remind_noreply ? View.VISIBLE : View.GONE);
tvRemindExternal.setVisibility(remind_external ? View.VISIBLE : View.GONE);
tvRemindSubject.setVisibility(send_reminders && remind_subject ? View.VISIBLE : View.GONE);
tvRemindText.setVisibility(send_reminders && remind_text ? View.VISIBLE : View.GONE);
tvRemindAttachment.setVisibility(send_reminders && remind_attachment ? View.VISIBLE : View.GONE);
tvRemindExtension.setText(getString(R.string.title_attachment_warning, remind_extension));
tvRemindExtension.setVisibility(send_reminders && remind_extension != null ? View.VISIBLE : View.GONE);
tvRemindInternet.setVisibility(send_reminders && remind_internet ? View.VISIBLE : View.GONE);
tvTo.setText(null);
tvVia.setText(null);
tvPlainHint.setVisibility(View.GONE);
tvReceiptHint.setVisibility(View.GONE);
spEncrypt.setTag(0);
spEncrypt.setSelection(0);
spPriority.setTag(1);
spPriority.setSelection(1);
spSensitivity.setTag(0);
spSensitivity.setSelection(0);
tvSendAt.setText(null);
cbArchive.setEnabled(false);
cbNotAgain.setChecked(!send_dialog);
cbNotAgain.setVisibility(send_dialog ? View.VISIBLE : View.GONE);
tvNotAgain.setVisibility(cbNotAgain.isChecked() ? View.VISIBLE : View.GONE);
Helper.setViewsEnabled(dview, false);
boolean reminder = (remind_extra || remind_subject || remind_text ||
remind_attachment || remind_extension != null || remind_internet);
swSendReminders.setChecked(send_reminders);
swSendReminders.setVisibility(send_reminders && reminder ? View.VISIBLE : View.GONE);
tvSendRemindersHint.setVisibility(View.GONE);
swSendReminders.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("send_reminders", checked).apply();
tvRemindExtra.setVisibility(checked && remind_extra ? View.VISIBLE : View.GONE);
tvRemindSubject.setVisibility(checked && remind_subject ? View.VISIBLE : View.GONE);
tvRemindText.setVisibility(checked && remind_text ? View.VISIBLE : View.GONE);
tvRemindAttachment.setVisibility(checked && remind_attachment ? View.VISIBLE : View.GONE);
tvRemindExtension.setVisibility(checked && remind_extension != null ? View.VISIBLE : View.GONE);
tvRemindInternet.setVisibility(checked && remind_internet ? View.VISIBLE : View.GONE);
tvSendRemindersHint.setVisibility(checked ? View.GONE : View.VISIBLE);
}
});
cbNotAgain.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("send_dialog", !isChecked).apply();
tvNotAgain.setVisibility(isChecked ? View.VISIBLE : View.GONE);
}
});
cbPlainOnly.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
tvPlainHint.setVisibility(checked && styled ? View.VISIBLE : View.GONE);
Bundle args = new Bundle();
args.putLong("id", id);
args.putBoolean("plain_only", checked);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
boolean plain_only = args.getBoolean("plain_only");
DB db = DB.getInstance(context);
db.message().setMessagePlainOnly(id, plain_only ? 1 : 0);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.serial().execute(FragmentDialogSend.this, args, "compose:plain_only");
}
});
cbReceipt.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
tvReceiptHint.setVisibility(checked ? View.VISIBLE : View.GONE);
Bundle args = new Bundle();
args.putLong("id", id);
args.putBoolean("receipt", checked);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
boolean receipt = args.getBoolean("receipt");
DB db = DB.getInstance(context);
db.message().setMessageReceiptRequest(id, receipt);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.serial().execute(FragmentDialogSend.this, args, "compose:receipt");
}
});
spEncrypt.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
int last = (int) spEncrypt.getTag();
if (last != position) {
spEncrypt.setTag(position);
setEncrypt(encryptValues[position]);
if ((encryptValues[position] == EntityMessage.PGP_SIGNONLY ||
encryptValues[position] == EntityMessage.PGP_ENCRYPTONLY ||
encryptValues[position] == EntityMessage.PGP_SIGNENCRYPT) &&
Helper.isOpenKeychainInstalled(context)) {
tvEncrypt.setPaintFlags(tvEncrypt.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
tvEncrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String pkg = Helper.getOpenKeychainPackage(v.getContext());
PackageManager pm = v.getContext().getPackageManager();
v.getContext().startActivity(pm.getLaunchIntentForPackage(pkg));
}
});
} else {
tvEncrypt.setPaintFlags(tvEncrypt.getPaintFlags() & ~Paint.UNDERLINE_TEXT_FLAG);
tvEncrypt.setOnClickListener(null);
}
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
spEncrypt.setTag(0);
setEncrypt(encryptValues[0]);
}
private void setEncrypt(int encrypt) {
Bundle args = new Bundle();
args.putLong("id", id);
args.putInt("encrypt", encrypt);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
int encrypt = args.getInt("encrypt");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
db.message().setMessageUiEncrypt(message.id, encrypt);
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
if (attachments == null)
return null;
for (EntityAttachment attachment : attachments)
if (attachment.isEncryption())
db.attachment().deleteAttachment(attachment.id);
if (encrypt != EntityMessage.ENCRYPT_NONE &&
message.identity != null) {
int iencrypt =
(encrypt == EntityMessage.SMIME_SIGNONLY ||
encrypt == EntityMessage.SMIME_SIGNENCRYPT
? 1 : 0);
db.identity().setIdentityEncrypt(message.identity, iencrypt);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onExecuted(Bundle args, Void data) {
int encrypt = args.getInt("encrypt");
boolean none = EntityMessage.ENCRYPT_NONE.equals(encrypt);
tvRemindPgp.setVisibility(remind_pgp && none ? View.VISIBLE : View.GONE);
tvRemindSmime.setVisibility(remind_smime && none ? View.VISIBLE : View.GONE);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.serial().execute(FragmentDialogSend.this, args, "compose:encrypt");
}
});
ibEncryption.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 12);
}
});
spPriority.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
int last = (int) spPriority.getTag();
if (last != position) {
spPriority.setTag(position);
setPriority(position);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
spPriority.setTag(1);
setPriority(1);
}
private void setPriority(int priority) {
Bundle args = new Bundle();
args.putLong("id", id);
args.putInt("priority", priority);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
int priority = args.getInt("priority");
DB db = DB.getInstance(context);
db.message().setMessagePriority(id, priority);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.serial().execute(FragmentDialogSend.this, args, "compose:priority");
}
});
spSensitivity.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
int last = (int) spSensitivity.getTag();
if (last != position) {
spSensitivity.setTag(position);
setSensitivity(position);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
spSensitivity.setTag(0);
setSensitivity(0);
}
private void setSensitivity(int sensitivity) {
Bundle args = new Bundle();
args.putLong("id", id);
args.putInt("sensitivity", sensitivity);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
int sensitivity = args.getInt("sensitivity");
DB db = DB.getInstance(context);
db.message().setMessageSensitivity(id, sensitivity < 1 ? null : sensitivity);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.serial().execute(FragmentDialogSend.this, args, "compose:sensitivity");
}
});
ibSensitivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 177);
}
});
View.OnClickListener sendAt = new View.OnClickListener() {
@Override
public void onClick(View view) {
Bundle args = new Bundle();
args.putString("title", getString(R.string.title_send_at));
args.putLong("id", id);
FragmentDialogDuration fragment = new FragmentDialogDuration();
fragment.setArguments(args);
fragment.setTargetFragment(FragmentDialogSend.this, 1);
fragment.show(getParentFragmentManager(), "send:snooze");
}
};
tvSendAt.setOnClickListener(sendAt);
ibSendAt.setOnClickListener(sendAt);
cbArchive.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("send_archive", isChecked).apply();
}
});
DB db = DB.getInstance(context);
db.message().liveMessage(id).observe(getViewLifecycleOwner(), new Observer<TupleMessageEx>() {
@Override
public void onChanged(TupleMessageEx draft) {
if (draft == null) {
dismiss();
return;
}
boolean dsn = (draft.dsn != null && !EntityMessage.DSN_NONE.equals(draft.dsn));
int to = (draft.to == null ? 0 : draft.to.length);
int extra = (draft.cc == null ? 0 : draft.cc.length) + (draft.bcc == null ? 0 : draft.bcc.length);
List<Address> t = new ArrayList<>();
if (draft.to != null)
if (to <= MAX_SHOW_RECIPIENTS)
t.addAll(Arrays.asList(draft.to));
else {
t.addAll((Arrays.asList(Arrays.copyOf(draft.to, MAX_SHOW_RECIPIENTS))));
extra += draft.to.length - MAX_SHOW_RECIPIENTS;
}
Address[] tos = t.toArray(new Address[0]);
if (extra == 0)
tvTo.setText(MessageHelper.formatAddresses(tos, email_format, false));
else
tvTo.setText(getString(R.string.title_name_plus,
MessageHelper.formatAddresses(tos, email_format, false), extra));
tvTo.setTextColor(Helper.resolveColor(context,
to + extra > RECIPIENTS_WARNING ? R.attr.colorWarning : android.R.attr.textColorPrimary));
if (draft.identityColor != null && draft.identityColor != Color.TRANSPARENT)
tvViaTitle.setTextColor(draft.identityColor);
tvVia.setText(draft.identityEmail);
cbPlainOnly.setChecked(draft.isPlainOnly() && !dsn);
cbReceipt.setChecked(draft.receipt_request != null && draft.receipt_request && !dsn);
int encrypt = (draft.ui_encrypt == null || dsn ? EntityMessage.ENCRYPT_NONE : draft.ui_encrypt);
for (int i = 0; i < encryptValues.length; i++)
if (encryptValues[i] == encrypt) {
spEncrypt.setTag(i);
spEncrypt.setSelection(i);
break;
}
int priority = (draft.priority == null ? 1 : draft.priority);
spPriority.setTag(priority);
spPriority.setSelection(priority);
int sensitivity = (draft.sensitivity == null ? 0 : draft.sensitivity);
spSensitivity.setTag(sensitivity);
spSensitivity.setSelection(sensitivity);
if (draft.ui_snoozed == null) {
if (send_delayed == 0)
tvSendAt.setText(getString(R.string.title_now));
else
for (int pos = 0; pos < sendDelayedValues.length; pos++)
if (sendDelayedValues[pos] == send_delayed) {
tvSendAt.setText(getString(R.string.title_after, sendDelayedNames[pos]));
break;
}
} else {
DateFormat DTF = Helper.getDateTimeInstance(context, SimpleDateFormat.MEDIUM, SimpleDateFormat.SHORT);
DateFormat D = new SimpleDateFormat("E");
tvSendAt.setText(D.format(draft.ui_snoozed) + " " + DTF.format(draft.ui_snoozed));
}
grpDsn.setVisibility(dsn ? View.GONE : View.VISIBLE);
Helper.setViewsEnabled(dview, true);
}
});
Bundle aargs = new Bundle();
aargs.putLong("id", id);
new SimpleTask<Boolean>() {
@Override
protected @NonNull
Boolean onExecute(Context context, Bundle args) {
long id = args.getLong("id");
DB db = DB.getInstance(context);
EntityMessage draft = db.message().getMessage(id);
if (draft == null) {
args.putString("reason", "Draft gone");
return false;
}
if (TextUtils.isEmpty(draft.inreplyto)) {
args.putString("reason", "No in-reply-to");
return false;
}
EntityFolder archive = db.folder().getFolderByType(draft.account, EntityFolder.ARCHIVE);
if (archive == null) {
args.putString("reason", "No archive");
return false;
}
List<EntityMessage> messages = db.message().getMessagesByMsgId(draft.account, draft.inreplyto);
if (messages == null || messages.size() == 0) {
args.putString("reason", "In-reply-to gone");
return false;
}
for (EntityMessage message : messages) {
EntityFolder folder = db.folder().getFolder(message.folder);
if (folder == null)
continue;
if (EntityFolder.INBOX.equals(folder.type) || EntityFolder.USER.equals(folder.type))
return true;
}
args.putString("reason", "Not in inbox or unread");
return false;
}
@Override
protected void onExecuted(Bundle args, Boolean data) {
if (!data) {
String reason = args.getString("reason");
if (BuildConfig.DEBUG)
cbArchive.setText(reason);
else
Log.i("Auto archive reason=" + reason);
}
if (send_archive && data)
cbArchive.setChecked(true);
cbArchive.setEnabled(data);
}
@Override
protected void onException(Bundle args, Throwable ex) {
// Ignored
}
}.serial().execute(FragmentDialogSend.this, aargs, "send:archive");
AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setView(dview)
.setNegativeButton(android.R.string.cancel, null);
if (address_error == null && !remind_to && !remind_size) {
if (send_delayed != 0)
builder.setNeutralButton(R.string.title_send_now, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
getArguments().putBoolean("archive", cbArchive.isChecked());
sendResult(Activity.RESULT_FIRST_USER);
}
});
builder.setPositiveButton(R.string.title_send, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
getArguments().putBoolean("archive", cbArchive.isChecked());
sendResult(Activity.RESULT_OK);
}
});
}
return builder.create();
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_OK && intent != null) {
Bundle data = intent.getBundleExtra("args");
long id = data.getLong("id");
long duration = data.getLong("duration");
long time = data.getLong("time");
Bundle args = new Bundle();
args.putLong("id", id);
args.putLong("wakeup", duration == 0 ? -1 : time);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
long wakeup = args.getLong("wakeup");
DB db = DB.getInstance(context);
db.message().setMessageSnoozed(id, wakeup < 0 ? null : wakeup);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.serial().execute(this, args, "compose:snooze");
}
}
}
@NonNull @NonNull
private static UriInfo getInfo(Uri uri, Context context) { private static UriInfo getInfo(Uri uri, Context context) {
UriInfo result = new UriInfo(); UriInfo result = new UriInfo();

@ -21,10 +21,8 @@ package eu.faircode.email;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import android.app.Dialog;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
@ -36,15 +34,10 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.constraintlayout.widget.Group; import androidx.constraintlayout.widget.Group;
import androidx.lifecycle.Lifecycle; import androidx.lifecycle.Lifecycle;
@ -322,7 +315,7 @@ public class FragmentContacts extends FragmentBase {
args.putLong("account", account == null ? -1L : account); args.putLong("account", account == null ? -1L : account);
args.putBoolean("junk", junk); args.putBoolean("junk", junk);
FragmentDelete fragment = new FragmentDelete(); FragmentDialogContactDelete fragment = new FragmentDialogContactDelete();
fragment.setArguments(args); fragment.setArguments(args);
fragment.show(getParentFragmentManager(), "contacts:delete"); fragment.show(getParentFragmentManager(), "contacts:delete");
} }
@ -332,7 +325,7 @@ public class FragmentContacts extends FragmentBase {
args.putInt("type", junk ? EntityContact.TYPE_JUNK : EntityContact.TYPE_TO); args.putInt("type", junk ? EntityContact.TYPE_JUNK : EntityContact.TYPE_TO);
args.putLong("account", account); args.putLong("account", account);
FragmentDialogEditContact fragment = new FragmentDialogEditContact(); FragmentDialogContactEdit fragment = new FragmentDialogContactEdit();
fragment.setArguments(args); fragment.setArguments(args);
fragment.setTargetFragment(this, REQUEST_EDIT_CONTACT); fragment.setTargetFragment(this, REQUEST_EDIT_CONTACT);
fragment.show(getParentFragmentManager(), "contacts:add"); fragment.show(getParentFragmentManager(), "contacts:add");
@ -686,102 +679,4 @@ public class FragmentContacts extends FragmentBase {
} }
}.execute(this, args, "contacts:name"); }.execute(this, args, "contacts:name");
} }
public static class FragmentDelete extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
return new AlertDialog.Builder(getContext())
.setIcon(R.drawable.twotone_warning_24)
.setTitle(getString(R.string.title_delete_contacts))
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
Long account = args.getLong("account");
boolean junk = args.getBoolean("junk");
if (account < 0)
account = null;
int[] types = (junk
? new int[]{EntityContact.TYPE_JUNK, EntityContact.TYPE_NO_JUNK}
: new int[]{EntityContact.TYPE_FROM, EntityContact.TYPE_TO});
DB db = DB.getInstance(context);
int count = db.contact().clearContacts(account, types);
Log.i("Cleared contacts=" + count);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(getContext(), getActivity(), getArguments(), "contacts:delete");
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
public static class FragmentDialogEditContact extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Context context = getContext();
View view = LayoutInflater.from(context).inflate(R.layout.dialog_edit_contact, null);
final Spinner spType = view.findViewById(R.id.spType);
final EditText etEmail = view.findViewById(R.id.etEmail);
final EditText etName = view.findViewById(R.id.etName);
final EditText etGroup = view.findViewById(R.id.etGroup);
final Bundle args = getArguments();
int type = args.getInt("type");
boolean junk = (type == EntityContact.TYPE_JUNK || type == EntityContact.TYPE_NO_JUNK);
String[] values = getResources().getStringArray(R.array.contactTypes);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, R.layout.spinner_item1, android.R.id.text1, values) {
@Override
public boolean isEnabled(int position) {
if (junk)
return (position == EntityContact.TYPE_JUNK || position == EntityContact.TYPE_NO_JUNK);
else
return (position == EntityContact.TYPE_TO || position == EntityContact.TYPE_FROM);
}
@Override
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view = super.getDropDownView(position, convertView, parent);
TextView tv = view.findViewById(android.R.id.text1);
tv.setEnabled(isEnabled(position));
return view;
}
};
adapter.setDropDownViewResource(R.layout.spinner_item1_dropdown);
spType.setAdapter(adapter);
spType.setSelection(args.getInt("type"));
etEmail.setText(args.getString("email"));
etName.setText(args.getString("name"));
etGroup.setText(args.getString("group"));
return new AlertDialog.Builder(context)
.setView(view)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
args.putInt("type", spType.getSelectedItemPosition());
args.putString("email", etEmail.getText().toString().trim());
args.putString("name", etName.getText().toString());
args.putString("group", etGroup.getText().toString().trim());
sendResult(RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
} }

@ -0,0 +1,164 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import static android.app.Activity.RESULT_OK;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
public class FragmentDialogAddImage extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
int title = getArguments().getInt("title");
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean add_inline = prefs.getBoolean("add_inline", true);
boolean resize_images = prefs.getBoolean("resize_images", true);
int resize = prefs.getInt("resize", FragmentCompose.REDUCED_IMAGE_SIZE);
boolean privacy_images = prefs.getBoolean("privacy_images", false);
boolean image_dialog = prefs.getBoolean("image_dialog", true);
final ViewGroup dview = (ViewGroup) LayoutInflater.from(getContext()).inflate(R.layout.dialog_add_image, null);
final ImageView ivType = dview.findViewById(R.id.ivType);
final RadioGroup rgAction = dview.findViewById(R.id.rgAction);
final ImageButton ibSettings = dview.findViewById(R.id.ibSettings);
final CheckBox cbResize = dview.findViewById(R.id.cbResize);
final ImageButton ibResize = dview.findViewById(R.id.ibResize);
final Spinner spResize = dview.findViewById(R.id.spResize);
final TextView tvResize = dview.findViewById(R.id.tvResize);
final CheckBox cbPrivacy = dview.findViewById(R.id.cbPrivacy);
final CheckBox cbNotAgain = dview.findViewById(R.id.cbNotAgain);
final TextView tvNotAgain = dview.findViewById(R.id.tvNotAgain);
ivType.setImageResource(title == R.string.title_attachment_photo
? R.drawable.twotone_photo_camera_24 : R.drawable.twotone_image_24);
rgAction.check(add_inline ? R.id.rbInline : R.id.rbAttach);
cbResize.setChecked(resize_images);
spResize.setEnabled(resize_images);
cbPrivacy.setChecked(privacy_images);
final int[] resizeValues = getResources().getIntArray(R.array.resizeValues);
for (int pos = 0; pos < resizeValues.length; pos++)
if (resizeValues[pos] == resize) {
spResize.setSelection(pos);
tvResize.setText(getString(R.string.title_add_resize_pixels, resizeValues[pos]));
break;
}
rgAction.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
prefs.edit().putBoolean("add_inline", checkedId == R.id.rbInline).apply();
}
});
// https://developer.android.com/reference/android/provider/MediaStore#ACTION_PICK_IMAGES_SETTINGS
PackageManager pm = getContext().getPackageManager();
Intent settings = new Intent(MediaStore.ACTION_PICK_IMAGES_SETTINGS);
ibSettings.setVisibility(settings.resolveActivity(pm) == null ? View.GONE : View.VISIBLE);
ibSettings.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
v.getContext().startActivity(settings);
}
});
cbResize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("resize_images", isChecked).apply();
spResize.setEnabled(isChecked);
}
});
ibResize.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 63);
}
});
spResize.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
prefs.edit().putInt("resize", resizeValues[position]).apply();
tvResize.setText(getString(R.string.title_add_resize_pixels, resizeValues[position]));
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
prefs.edit().remove("resize").apply();
}
});
cbPrivacy.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("privacy_images", isChecked).apply();
}
});
cbNotAgain.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("image_dialog", !isChecked).apply();
tvNotAgain.setVisibility(isChecked ? View.VISIBLE : View.GONE);
}
});
cbNotAgain.setChecked(!image_dialog);
tvNotAgain.setVisibility(cbNotAgain.isChecked() ? View.VISIBLE : View.GONE);
return new AlertDialog.Builder(getContext())
.setView(dview)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(title,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendResult(RESULT_OK);
}
})
.create();
}
}

@ -0,0 +1,72 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
public class FragmentDialogAskSpam extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Bundle args = getArguments();
int count = args.getInt("count");
final Context context = getContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean block_sender = prefs.getBoolean("block_sender", true);
String text = getResources().getQuantityString(R.plurals.title_ask_spam, count, count);
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_ask_spam, null);
TextView tvMessage = dview.findViewById(R.id.tvMessage);
CheckBox cbBlockSender = dview.findViewById(R.id.cbBlockSender);
tvMessage.setText(text);
cbBlockSender.setChecked(block_sender);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
boolean block = cbBlockSender.isChecked();
prefs.edit().putBoolean("block_sender", block).apply();
getArguments().putBoolean("block", block);
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}

@ -0,0 +1,63 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
public class FragmentDialogBoundaryError extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
String error = getArguments().getString("error");
final Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_boundary_error, null);
TextView tvError = dview.findViewById(R.id.tvError);
tvError.setText(error);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(R.string.title_boundary_retry, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_CANCELED);
}
})
.create();
}
}

@ -0,0 +1,69 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
public class FragmentDialogContactDelete extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
return new AlertDialog.Builder(getContext())
.setIcon(R.drawable.twotone_warning_24)
.setTitle(getString(R.string.title_delete_contacts))
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
Long account = args.getLong("account");
boolean junk = args.getBoolean("junk");
if (account < 0)
account = null;
int[] types = (junk
? new int[]{EntityContact.TYPE_JUNK, EntityContact.TYPE_NO_JUNK}
: new int[]{EntityContact.TYPE_FROM, EntityContact.TYPE_TO});
DB db = DB.getInstance(context);
int count = db.contact().clearContacts(account, types);
Log.i("Cleared contacts=" + count);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(getContext(), getActivity(), getArguments(), "contacts:delete");
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}

@ -0,0 +1,96 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import static android.app.Activity.RESULT_OK;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
public class FragmentDialogContactEdit extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Context context = getContext();
View view = LayoutInflater.from(context).inflate(R.layout.dialog_edit_contact, null);
final Spinner spType = view.findViewById(R.id.spType);
final EditText etEmail = view.findViewById(R.id.etEmail);
final EditText etName = view.findViewById(R.id.etName);
final EditText etGroup = view.findViewById(R.id.etGroup);
final Bundle args = getArguments();
int type = args.getInt("type");
boolean junk = (type == EntityContact.TYPE_JUNK || type == EntityContact.TYPE_NO_JUNK);
String[] values = getResources().getStringArray(R.array.contactTypes);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, R.layout.spinner_item1, android.R.id.text1, values) {
@Override
public boolean isEnabled(int position) {
if (junk)
return (position == EntityContact.TYPE_JUNK || position == EntityContact.TYPE_NO_JUNK);
else
return (position == EntityContact.TYPE_TO || position == EntityContact.TYPE_FROM);
}
@Override
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view = super.getDropDownView(position, convertView, parent);
TextView tv = view.findViewById(android.R.id.text1);
tv.setEnabled(isEnabled(position));
return view;
}
};
adapter.setDropDownViewResource(R.layout.spinner_item1_dropdown);
spType.setAdapter(adapter);
spType.setSelection(args.getInt("type"));
etEmail.setText(args.getString("email"));
etName.setText(args.getString("name"));
etGroup.setText(args.getString("group"));
return new AlertDialog.Builder(context)
.setView(view)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
args.putInt("type", spType.getSelectedItemPosition());
args.putString("email", etEmail.getText().toString().trim());
args.putString("name", etName.getText().toString());
args.putString("group", etGroup.getText().toString().trim());
sendResult(RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}

@ -0,0 +1,184 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
import static android.widget.AdapterView.INVALID_POSITION;
import android.Manifest;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.cursoradapter.widget.SimpleCursorAdapter;
import java.text.NumberFormat;
public class FragmentDialogContactGroup extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Bundle args = getArguments();
final long working = args.getLong("working");
int focussed = args.getInt("focussed");
final Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_contact_group, null);
final ImageButton ibInfo = dview.findViewById(R.id.ibInfo);
final Spinner spGroup = dview.findViewById(R.id.spGroup);
final Spinner spTarget = dview.findViewById(R.id.spTarget);
final Spinner spType = dview.findViewById(R.id.spType);
ibInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.view(v.getContext(), Uri.parse(Helper.URI_SUPPORT_CONTACT_GROUP), true);
}
});
new SimpleTask<Cursor>() {
@Override
protected Cursor onExecute(Context context, Bundle args) {
final String[] projection = new String[]{
ContactsContract.Groups._ID,
ContactsContract.Groups.TITLE,
ContactsContract.Groups.SUMMARY_COUNT,
ContactsContract.Groups.ACCOUNT_NAME,
ContactsContract.Groups.ACCOUNT_TYPE,
};
Cursor contacts = new MatrixCursor(projection);
if (Helper.hasPermission(context, Manifest.permission.READ_CONTACTS))
try {
ContentResolver resolver = context.getContentResolver();
contacts = resolver.query(
ContactsContract.Groups.CONTENT_SUMMARY_URI,
projection,
// ContactsContract.Groups.GROUP_VISIBLE + " = 1" + " AND " +
ContactsContract.Groups.DELETED + " = 0" +
" AND " + ContactsContract.Groups.SUMMARY_COUNT + " > 0",
null,
ContactsContract.Groups.TITLE
);
} catch (SecurityException ex) {
Log.w(ex);
}
DB db = DB.getInstance(context);
Cursor local = db.contact().getGroups(
null,
context.getString(R.string.app_name),
BuildConfig.APPLICATION_ID);
return new MergeCursor(new Cursor[]{contacts, local});
}
@Override
protected void onExecuted(Bundle args, Cursor cursor) {
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
context,
R.layout.spinner_contact_group,
cursor,
new String[]{ContactsContract.Groups.TITLE, ContactsContract.Groups.ACCOUNT_NAME},
new int[]{R.id.tvGroup, R.id.tvAccount},
0);
final NumberFormat NF = NumberFormat.getInstance();
adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
@Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
if (view.getId() == R.id.tvGroup) {
String title = cursor.getString(1);
if (TextUtils.isEmpty(title))
title = "-";
int count = cursor.getInt(2);
((TextView) view).setText(context.getString(R.string.title_name_count, title, NF.format(count)));
return true;
} else if (view.getId() == R.id.tvAccount) {
String account = cursor.getString(3);
String type = cursor.getString(4);
((TextView) view).setText(account + (BuildConfig.DEBUG ? "/" + type : ""));
return true;
} else
return false;
}
});
spGroup.setAdapter(adapter);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, new Bundle(), "compose:groups");
spTarget.setSelection(focussed);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
int target = spTarget.getSelectedItemPosition();
Cursor cursor = (Cursor) spGroup.getSelectedItem();
if (target != INVALID_POSITION &&
cursor != null && cursor.getCount() > 0) {
long group = cursor.getLong(0);
String name = cursor.getString(1);
Bundle args = getArguments();
args.putLong("id", working);
args.putInt("target", target);
args.putLong("group", group);
args.putString("name", name);
args.putInt("type", spType.getSelectedItemPosition());
sendResult(RESULT_OK);
} else
sendResult(RESULT_CANCELED);
} catch (Throwable ex) {
Log.e(ex);
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}

@ -0,0 +1,53 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
public class FragmentDialogDoze extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
return new AlertDialog.Builder(getContext())
.setIcon(R.drawable.twotone_info_24)
.setTitle(R.string.title_setup_doze)
.setMessage(R.string.title_setup_doze_instructions)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
startActivity(new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS));
} catch (Throwable ex) {
Log.e(ex);
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}

@ -0,0 +1,136 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import static android.app.Activity.RESULT_OK;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
import com.google.android.material.textfield.TextInputLayout;
public class FragmentDialogExport extends FragmentDialogBase {
private TextInputLayout tilPassword1;
private TextInputLayout tilPassword2;
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putString("fair:password1", tilPassword1 == null ? null : tilPassword1.getEditText().getText().toString());
outState.putString("fair:password2", tilPassword2 == null ? null : tilPassword2.getEditText().getText().toString());
super.onSaveInstanceState(outState);
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_export, null);
tilPassword1 = dview.findViewById(R.id.tilPassword1);
tilPassword2 = dview.findViewById(R.id.tilPassword2);
if (savedInstanceState != null) {
tilPassword1.getEditText().setText(savedInstanceState.getString("fair:password1"));
tilPassword2.getEditText().setText(savedInstanceState.getString("fair:password2"));
}
Dialog dialog = new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(R.string.title_save_file, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ViewModelExport vme = new ViewModelProvider(getActivity()).get(ViewModelExport.class);
vme.setPassword(tilPassword1.getEditText().getText().toString());
sendResult(RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
return dialog;
}
@Override
public void onStart() {
super.onStart();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean debug = (BuildConfig.DEBUG || prefs.getBoolean("debug", false));
Button btnOk = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE);
TextWatcher w = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Do nothing
}
@Override
public void afterTextChanged(Editable s) {
String p1 = tilPassword1.getEditText().getText().toString();
String p2 = tilPassword2.getEditText().getText().toString();
btnOk.setEnabled((debug || !TextUtils.isEmpty(p1)) && p1.equals(p2));
tilPassword2.setHint(!TextUtils.isEmpty(p2) && !p2.equals(p1)
? R.string.title_setup_password_different
: R.string.title_setup_password_repeat);
}
};
tilPassword1.getEditText().addTextChangedListener(w);
tilPassword2.getEditText().addTextChangedListener(w);
w.afterTextChanged(null);
tilPassword2.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
btnOk.performClick();
return true;
} else
return false;
}
});
}
}

@ -0,0 +1,71 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
public class FragmentDialogFirst extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
LayoutInflater inflater = LayoutInflater.from(getContext());
View dview = inflater.inflate(R.layout.dialog_first, null);
ImageButton ibBatteryInfo = dview.findViewById(R.id.ibBatteryInfo);
ImageButton ibReformatInfo = dview.findViewById(R.id.ibReformatInfo);
ibBatteryInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 39);
}
});
ibReformatInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 35);
}
});
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("first", false).apply();
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}

@ -0,0 +1,149 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.RadioGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import java.util.List;
public class FragmentDialogFoldersApply extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_folder_all, null);
final RadioGroup rgSynchronize = view.findViewById(R.id.rgSynchronize);
final EditText etSyncDays = view.findViewById(R.id.etSyncDays);
final EditText etKeepDays = view.findViewById(R.id.etKeepDays);
final CheckBox cbKeepAll = view.findViewById(R.id.cbKeepAll);
final CheckBox cbPollSystem = view.findViewById(R.id.cbPollSystem);
final CheckBox cbPollUser = view.findViewById(R.id.cbPollUser);
cbKeepAll.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
etKeepDays.setEnabled(!isChecked);
}
});
return new AlertDialog.Builder(getContext())
.setView(view)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Bundle args = getArguments();
int optionId = rgSynchronize.getCheckedRadioButtonId();
if (optionId == R.id.rbEnable)
args.putBoolean("enable", true);
else if (optionId == R.id.rbDisable)
args.putBoolean("enable", false);
args.putString("sync", etSyncDays.getText().toString());
args.putString("keep", cbKeepAll.isChecked()
? Integer.toString(Integer.MAX_VALUE)
: etKeepDays.getText().toString());
args.putBoolean("system", cbPollSystem.isChecked());
args.putBoolean("user", cbPollUser.isChecked());
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
long aid = args.getLong("account");
Boolean enable = null;
if (args.containsKey("enable"))
enable = args.getBoolean("enable");
String sync = args.getString("sync");
String keep = args.getString("keep");
boolean system = args.getBoolean("system");
boolean user = args.getBoolean("user");
if (TextUtils.isEmpty(sync))
sync = "7";
if (TextUtils.isEmpty(keep))
keep = "30";
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityAccount account = db.account().getAccount(aid);
if (account == null)
return null;
if (system && account.poll_interval > 15)
db.account().setAccountKeepAliveInterval(account.id, 15);
List<EntityFolder> folders = db.folder().getFolders(aid, false, true);
if (folders == null)
return null;
for (EntityFolder folder : folders) {
if (EntityFolder.USER.equals(folder.type)) {
if (enable != null) {
folder.synchronize = enable;
db.folder().setFolderSynchronize(folder.id, folder.synchronize);
}
db.folder().setFolderProperties(
folder.id,
Integer.parseInt(sync),
Integer.parseInt(keep));
}
if (folder.synchronize && !folder.poll)
if (EntityFolder.USER.equals(folder.type)
? user
: system && !EntityFolder.INBOX.equals(folder.type))
db.folder().setFolderPoll(folder.id, true);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
ServiceSynchronize.reload(context, aid, false, "Apply");
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(FragmentDialogFoldersApply.this, args, "folders:all");
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}

@ -0,0 +1,136 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
import com.google.android.material.textfield.TextInputLayout;
public class FragmentDialogImport extends FragmentDialogBase {
private TextInputLayout tilPassword1;
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putString("fair:password1", tilPassword1 == null ? null : tilPassword1.getEditText().getText().toString());
super.onSaveInstanceState(outState);
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_import, null);
tilPassword1 = dview.findViewById(R.id.tilPassword1);
CheckBox cbAccounts = dview.findViewById(R.id.cbAccounts);
CheckBox cbDelete = dview.findViewById(R.id.cbDelete);
CheckBox cbRules = dview.findViewById(R.id.cbRules);
CheckBox cbContacts = dview.findViewById(R.id.cbContacts);
CheckBox cbAnswers = dview.findViewById(R.id.cbAnswers);
CheckBox cbSearches = dview.findViewById(R.id.cbSearches);
CheckBox cbSettings = dview.findViewById(R.id.cbSettings);
cbAccounts.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
cbRules.setEnabled(checked);
cbContacts.setEnabled(checked);
}
});
if (savedInstanceState != null)
tilPassword1.getEditText().setText(savedInstanceState.getString("fair:password1"));
Dialog dialog = new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(R.string.title_add_image_select, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password1 = tilPassword1.getEditText().getText().toString();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean debug = prefs.getBoolean("debug", false);
if (TextUtils.isEmpty(password1) && !(debug || BuildConfig.DEBUG)) {
ToastEx.makeText(context, R.string.title_setup_password_missing, Toast.LENGTH_LONG).show();
sendResult(RESULT_CANCELED);
} else {
ViewModelExport vme = new ViewModelProvider(getActivity()).get(ViewModelExport.class);
vme.setPassword(password1);
vme.setOptions("accounts", cbAccounts.isChecked());
vme.setOptions("delete", cbDelete.isChecked());
vme.setOptions("rules", cbRules.isChecked());
vme.setOptions("contacts", cbContacts.isChecked());
vme.setOptions("answers", cbAnswers.isChecked());
vme.setOptions("searches", cbSearches.isChecked());
vme.setOptions("settings", cbSettings.isChecked());
sendResult(RESULT_OK);
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
return dialog;
}
@Override
public void onStart() {
super.onStart();
Button btnOk = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE);
tilPassword1.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
btnOk.performClick();
return true;
} else
return false;
}
});
}
}

@ -0,0 +1,176 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import java.util.ArrayList;
import java.util.List;
public class FragmentDialogOperationsDelete extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Context context = getContext();
final View dview = LayoutInflater.from(context).inflate(R.layout.dialog_delete_operations, null);
final CheckBox cbError = dview.findViewById(R.id.cbError);
final CheckBox cbFetch = dview.findViewById(R.id.cbFetch);
final CheckBox cbMove = dview.findViewById(R.id.cbMove);
final CheckBox cbFlag = dview.findViewById(R.id.cbFlag);
final CheckBox cbDelete = dview.findViewById(R.id.cbDelete);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Bundle args = new Bundle();
args.putBoolean("error", cbError.isChecked());
args.putBoolean("fetch", cbFetch.isChecked());
args.putBoolean("move", cbMove.isChecked());
args.putBoolean("flag", cbFlag.isChecked());
args.putBoolean("delete", cbDelete.isChecked());
new SimpleTask<Integer>() {
private Toast toast = null;
@Override
protected void onPostExecute(Bundle args) {
toast = ToastEx.makeText(context, R.string.title_executing, Toast.LENGTH_LONG);
toast.show();
}
@Override
protected void onPreExecute(Bundle args) {
if (toast != null)
toast.cancel();
}
@Override
protected Integer onExecute(Context context, Bundle args) {
boolean error = args.getBoolean("error");
boolean fetch = args.getBoolean("fetch");
boolean move = args.getBoolean("move");
boolean flag = args.getBoolean("flag");
boolean delete = args.getBoolean("delete");
int deleted = 0;
DB db = DB.getInstance(context);
try {
db.beginTransaction();
List<EntityOperation> ops = new ArrayList<>();
// ADD, SEND, EXISTS, SUBSCRIBE
if (error)
addAll(ops, db.operation().getOperationsError());
if (fetch) {
addAll(ops, db.operation().getOperations(EntityOperation.FETCH));
addAll(ops, db.operation().getOperations(EntityOperation.DOWNLOAD));
addAll(ops, db.operation().getOperations(EntityOperation.RAW));
addAll(ops, db.operation().getOperations(EntityOperation.BODY));
addAll(ops, db.operation().getOperations(EntityOperation.ATTACHMENT));
addAll(ops, db.operation().getOperations(EntityOperation.HEADERS));
addAll(ops, db.operation().getOperations(EntityOperation.RULE));
addAll(ops, db.operation().getOperations(EntityOperation.SYNC));
}
if (move) {
addAll(ops, db.operation().getOperations(EntityOperation.MOVE));
addAll(ops, db.operation().getOperations(EntityOperation.COPY));
}
if (flag) {
addAll(ops, db.operation().getOperations(EntityOperation.SEEN));
addAll(ops, db.operation().getOperations(EntityOperation.ANSWERED));
addAll(ops, db.operation().getOperations(EntityOperation.FLAG));
addAll(ops, db.operation().getOperations(EntityOperation.KEYWORD));
addAll(ops, db.operation().getOperations(EntityOperation.LABEL));
addAll(ops, db.operation().getOperations(EntityOperation.REPORT));
}
if (delete) {
addAll(ops, db.operation().getOperations(EntityOperation.DELETE));
addAll(ops, db.operation().getOperations(EntityOperation.PURGE));
addAll(ops, db.operation().getOperations(EntityOperation.EXPUNGE));
}
for (EntityOperation op : ops) {
EntityLog.log(context, "Deleting operation=" + op.id + ":" + op.name + " error=" + op.error);
if (db.operation().deleteOperation(op.id) > 0) {
op.cleanup(context, false);
deleted++;
}
if (EntityOperation.SYNC.equals(op.name))
db.folder().setFolderSyncState(op.folder, null);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return deleted;
}
@Override
protected void onExecuted(Bundle args, Integer deleted) {
if (deleted == null)
deleted = -1;
Context context = getContext();
if (context == null)
return;
ToastEx.makeText(
context,
getString(R.string.title_delete_operation_deleted, deleted),
Toast.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
private void addAll(List<EntityOperation> list, List<EntityOperation> sublist) {
if (sublist != null)
list.addAll(sublist);
}
}.execute(context, getActivity(), args, "operations:delete");
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}

@ -0,0 +1,115 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.Lifecycle;
import androidx.preference.PreferenceManager;
public class FragmentDialogPin extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_pin_set, null);
final EditText etPin = dview.findViewById(R.id.etPin);
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String pin = etPin.getText().toString();
if (TextUtils.isEmpty(pin))
prefs.edit().remove("pin").apply();
else {
boolean pro = ActivityBilling.isPro(getContext());
if (pro) {
Helper.setAuthenticated(getContext());
prefs.edit()
.remove("biometrics")
.putString("pin", pin)
.apply();
} else
startActivity(new Intent(getContext(), ActivityBilling.class));
}
}
})
.setNegativeButton(android.R.string.cancel, null);
String pin = prefs.getString("pin", null);
if (!TextUtils.isEmpty(pin))
builder.setNeutralButton(R.string.title_reset, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().remove("pin").apply();
}
});
final Dialog dialog = builder.create();
etPin.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
((AlertDialog) getDialog()).getButton(DialogInterface.BUTTON_POSITIVE).performClick();
return true;
} else
return false;
}
});
etPin.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus)
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
});
ApplicationEx.getMainHandler().post(new Runnable() {
@Override
public void run() {
if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED))
return;
etPin.requestFocus();
}
});
return dialog;
}
}

@ -0,0 +1,118 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
public class FragmentDialogQuickActions extends FragmentDialogBase {
static final int MAX_QUICK_ACTIONS = 5;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Context context = getContext();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final View dview = LayoutInflater.from(context).inflate(R.layout.dialog_quick_actions, null);
final TextView tvHint = dview.findViewById(R.id.tvHint);
final CheckBox cbSeen = dview.findViewById(R.id.cbSeen);
final CheckBox cbUnseen = dview.findViewById(R.id.cbUnseen);
final CheckBox cbSnooze = dview.findViewById(R.id.cbSnooze);
final CheckBox cbHide = dview.findViewById(R.id.cbHide);
final CheckBox cbFlag = dview.findViewById(R.id.cbFlag);
final CheckBox cbFlagColor = dview.findViewById(R.id.cbFlagColor);
final CheckBox cbImportanceLow = dview.findViewById(R.id.cbImportanceLow);
final CheckBox cbImportanceNormal = dview.findViewById(R.id.cbImportanceNormal);
final CheckBox cbImportanceHigh = dview.findViewById(R.id.cbImportanceHigh);
final CheckBox cbInbox = dview.findViewById(R.id.cbInbox);
final CheckBox cbArchive = dview.findViewById(R.id.cbArchive);
final CheckBox cbJunk = dview.findViewById(R.id.cbJunk);
final CheckBox cbTrash = dview.findViewById(R.id.cbTrash);
final CheckBox cbDelete = dview.findViewById(R.id.cbDelete);
final CheckBox cbMove = dview.findViewById(R.id.cbMove);
final CheckBox cbClear = dview.findViewById(R.id.cbClear);
tvHint.setText(getString(R.string.title_quick_actions_hint, MAX_QUICK_ACTIONS));
cbSeen.setChecked(prefs.getBoolean("more_seen", true));
cbUnseen.setChecked(prefs.getBoolean("more_unseen", false));
cbSnooze.setChecked(prefs.getBoolean("more_snooze", false));
cbHide.setChecked(prefs.getBoolean("more_hide", false));
cbFlag.setChecked(prefs.getBoolean("more_flag", false));
cbFlagColor.setChecked(prefs.getBoolean("more_flag_color", false));
cbImportanceLow.setChecked(prefs.getBoolean("more_importance_low", false));
cbImportanceNormal.setChecked(prefs.getBoolean("more_importance_normal", false));
cbImportanceHigh.setChecked(prefs.getBoolean("more_importance_high", false));
cbInbox.setChecked(prefs.getBoolean("more_inbox", true));
cbArchive.setChecked(prefs.getBoolean("more_archive", true));
cbJunk.setChecked(prefs.getBoolean("more_junk", true));
cbTrash.setChecked(prefs.getBoolean("more_trash", true));
cbDelete.setChecked(prefs.getBoolean("more_delete", false));
cbMove.setChecked(prefs.getBoolean("more_move", true));
cbClear.setChecked(prefs.getBoolean("more_clear", true));
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("more_seen", cbSeen.isChecked());
editor.putBoolean("more_unseen", cbUnseen.isChecked());
editor.putBoolean("more_snooze", cbSnooze.isChecked());
editor.putBoolean("more_hide", cbHide.isChecked());
editor.putBoolean("more_flag", cbFlag.isChecked());
editor.putBoolean("more_flag_color", cbFlagColor.isChecked());
editor.putBoolean("more_importance_low", cbImportanceLow.isChecked());
editor.putBoolean("more_importance_normal", cbImportanceNormal.isChecked());
editor.putBoolean("more_importance_high", cbImportanceHigh.isChecked());
editor.putBoolean("more_inbox", cbInbox.isChecked());
editor.putBoolean("more_archive", cbArchive.isChecked());
editor.putBoolean("more_junk", cbJunk.isChecked());
editor.putBoolean("more_trash", cbTrash.isChecked());
editor.putBoolean("more_delete", cbDelete.isChecked());
editor.putBoolean("more_move", cbMove.isChecked());
editor.putBoolean("more_clear", cbClear.isChecked());
editor.apply();
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_CANCELED);
}
})
.create();
}
}

@ -0,0 +1,50 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
public class FragmentDialogRate extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
return new AlertDialog.Builder(getContext())
.setMessage(R.string.title_issue)
.setPositiveButton(R.string.title_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Helper.viewFAQ(getContext(), 0);
}
})
.setNegativeButton(R.string.title_no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Helper.view(getContext(), Helper.getIntentRate(getContext()));
}
})
.create();
}
}

@ -0,0 +1,68 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
public class FragmentDialogReporting extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_error_reporting, null);
Button btnInfo = dview.findViewById(R.id.btnInfo);
btnInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 104);
}
});
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("crash_reports", true).apply();
Log.setCrashReporting(true);
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("crash_reports_asked", true).apply();
}
})
.create();
}
}

@ -0,0 +1,93 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
import java.util.Date;
public class FragmentDialogReview extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_review, null);
TextView tvHelp = dview.findViewById(R.id.tvHelp);
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
Dialog dialog = new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().putBoolean("review_asked", true).apply();
startActivity(Helper.getIntentRate(getContext()));
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().putBoolean("review_asked", true).apply();
}
})
.setNeutralButton(R.string.title_later, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().putLong("review_later", new Date().getTime()).apply();
}
})
.create();
tvHelp.setPaintFlags(tvHelp.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
tvHelp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
prefs.edit().putLong("review_later", new Date().getTime()).apply();
startActivity(Helper.getIntentIssue(v.getContext(), "Review:issue"));
}
});
return dialog;
}
@Override
public void onCancel(@NonNull DialogInterface dialog) {
super.onCancel(dialog);
try {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("review_asked", true).apply();
} catch (Throwable ex) {
Log.e(ex);
}
}
}

@ -0,0 +1,213 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class FragmentDialogRuleCheck extends FragmentDialogBase {
private final static int MAX_CHECK = 10;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
long folder = getArguments().getLong("folder");
boolean daily = getArguments().getBoolean("daily");
String condition = getArguments().getString("condition");
String action = getArguments().getString("action");
final View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_rule_match, null);
final TextView tvNoMessages = dview.findViewById(R.id.tvNoMessages);
final RecyclerView rvMessage = dview.findViewById(R.id.rvMessage);
final Button btnExecute = dview.findViewById(R.id.btnExecute);
final ContentLoadingProgressBar pbWait = dview.findViewById(R.id.pbWait);
rvMessage.setHasFixedSize(false);
LinearLayoutManager llm = new LinearLayoutManager(getContext());
rvMessage.setLayoutManager(llm);
final AdapterRuleMatch adapter = new AdapterRuleMatch(getContext(), getViewLifecycleOwner());
rvMessage.setAdapter(adapter);
tvNoMessages.setVisibility(View.GONE);
rvMessage.setVisibility(View.GONE);
btnExecute.setVisibility(View.GONE);
final Bundle args = new Bundle();
args.putLong("folder", folder);
args.putBoolean("daily", daily);
args.putString("condition", condition);
args.putString("action", action);
btnExecute.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new SimpleTask<Integer>() {
private Toast toast = null;
@Override
protected void onPreExecute(Bundle args) {
toast = ToastEx.makeText(getContext(), R.string.title_executing, Toast.LENGTH_LONG);
toast.show();
}
@Override
protected void onPostExecute(Bundle args) {
if (toast != null)
toast.cancel();
}
@Override
protected Integer onExecute(Context context, Bundle args) throws Throwable {
EntityRule rule = new EntityRule();
rule.folder = args.getLong("folder");
rule.daily = args.getBoolean("daily");
rule.condition = args.getString("condition");
rule.action = args.getString("action");
int applied = 0;
DB db = DB.getInstance(context);
List<Long> ids =
db.message().getMessageIdsByFolder(rule.folder);
for (long mid : ids)
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(mid);
if (message == null || message.ui_hide)
continue;
if (rule.matches(context, message, null, null))
if (rule.execute(context, message))
applied++;
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (applied > 0)
ServiceSynchronize.eval(context, "rules/manual");
return applied;
}
@Override
protected void onExecuted(Bundle args, Integer applied) {
dismiss();
ToastEx.makeText(getContext(), getString(R.string.title_rule_applied, applied), Toast.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException)
ToastEx.makeText(getContext(), ex.getMessage(), Toast.LENGTH_LONG).show();
else
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(FragmentDialogRuleCheck.this, args, "rule:execute");
}
});
new SimpleTask<List<EntityMessage>>() {
@Override
protected void onPreExecute(Bundle args) {
pbWait.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(Bundle args) {
pbWait.setVisibility(View.GONE);
}
@Override
protected List<EntityMessage> onExecute(Context context, Bundle args) throws Throwable {
EntityRule rule = new EntityRule();
rule.folder = args.getLong("folder");
rule.daily = args.getBoolean("daily");
rule.condition = args.getString("condition");
rule.action = args.getString("action");
rule.validate(context);
List<EntityMessage> matching = new ArrayList<>();
DB db = DB.getInstance(context);
List<Long> ids =
db.message().getMessageIdsByFolder(rule.folder);
for (long id : ids) {
EntityMessage message = db.message().getMessage(id);
if (message == null)
continue;
if (rule.matches(context, message, null, null))
matching.add(message);
if (matching.size() >= MAX_CHECK)
break;
}
return matching;
}
@Override
protected void onExecuted(Bundle args, List<EntityMessage> messages) {
adapter.set(messages);
if (messages.size() > 0) {
rvMessage.setVisibility(View.VISIBLE);
btnExecute.setVisibility(View.VISIBLE);
} else
tvNoMessages.setVisibility(View.VISIBLE);
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException) {
tvNoMessages.setText(ex.getMessage());
tvNoMessages.setVisibility(View.VISIBLE);
} else
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "rule:check");
return new AlertDialog.Builder(getContext())
.setIcon(R.drawable.baseline_mail_outline_24)
.setTitle(R.string.title_rule_matched)
.setView(dview)
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}

@ -0,0 +1,125 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import static android.app.Activity.RESULT_OK;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
public class FragmentDialogSaveSearch extends FragmentDialogBase {
private ViewButtonColor btnColor;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Bundle args = getArguments();
BoundaryCallbackMessages.SearchCriteria criteria =
(BoundaryCallbackMessages.SearchCriteria) args.getSerializable("criteria");
if (criteria == null)
criteria = new BoundaryCallbackMessages.SearchCriteria();
final Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_save_search, null);
EditText etName = dview.findViewById(R.id.etName);
EditText etOrder = dview.findViewById(R.id.etOrder);
btnColor = dview.findViewById(R.id.btnColor);
btnColor.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.hideKeyboard(etName);
Bundle args = new Bundle();
args.putInt("color", btnColor.getColor());
args.putString("title", getString(R.string.title_color));
args.putBoolean("reset", true);
FragmentDialogColor fragment = new FragmentDialogColor();
fragment.setArguments(args);
fragment.setTargetFragment(FragmentDialogSaveSearch.this, 1234);
fragment.show(getParentFragmentManager(), "search:color");
}
});
etName.setText(criteria.name == null ? criteria.getTitle(context) : criteria.name);
etOrder.setText(criteria.order == null ? null : Integer.toString(criteria.order));
btnColor.setColor(criteria.color);
AlertDialog.Builder dialog = new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(R.string.title_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String order = etOrder.getText().toString();
args.putString("name", etName.getText().toString());
args.putInt("order",
!TextUtils.isEmpty(order) && TextUtils.isDigitsOnly(order)
? Integer.parseInt(order) : -1);
args.putInt("color", btnColor.getColor());
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_CANCELED);
}
});
if (criteria.id != null)
dialog.setNeutralButton(R.string.title_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_FIRST_USER);
}
});
return dialog.create();
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
try {
if (resultCode == RESULT_OK && data != null) {
Bundle args = data.getBundleExtra("args");
int color = args.getInt("color");
btnColor.setColor(color);
}
} catch (Throwable ex) {
Log.e(ex);
}
}
}

@ -0,0 +1,696 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import static android.app.Activity.RESULT_OK;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageButton;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SwitchCompat;
import androidx.constraintlayout.widget.Group;
import androidx.lifecycle.Observer;
import androidx.preference.PreferenceManager;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.mail.Address;
public class FragmentDialogSend extends FragmentDialogBase {
static final int MAX_SHOW_RECIPIENTS = 5;
static final int RECIPIENTS_WARNING = 10;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Bundle args = getArguments();
long id = args.getLong("id");
final boolean sent_missing = args.getBoolean("sent_missing", false);
final String address_error = args.getString("address_error");
final String mx_error = args.getString("mx_error");
final boolean remind_dsn = args.getBoolean("remind_dsn", false);
final boolean remind_size = args.getBoolean("remind_size", false);
final boolean remind_pgp = args.getBoolean("remind_pgp", false);
final boolean remind_smime = args.getBoolean("remind_smime", false);
final boolean remind_to = args.getBoolean("remind_to", false);
final boolean remind_extra = args.getBoolean("remind_extra", false);
final boolean remind_noreply = args.getBoolean("remind_noreply", false);
final boolean remind_external = args.getBoolean("remind_external", false);
final boolean remind_subject = args.getBoolean("remind_subject", false);
final boolean remind_text = args.getBoolean("remind_text", false);
final boolean remind_attachment = args.getBoolean("remind_attachment", false);
final String remind_extension = args.getString("remind_extension");
final boolean remind_internet = args.getBoolean("remind_internet", false);
final boolean styled = args.getBoolean("styled", false);
final long size = args.getLong("size", -1);
final long max_size = args.getLong("max_size", -1);
final Context context = getContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final boolean send_reminders = prefs.getBoolean("send_reminders", true);
final int send_delayed = prefs.getInt("send_delayed", 0);
final boolean send_dialog = prefs.getBoolean("send_dialog", true);
final boolean send_archive = prefs.getBoolean("send_archive", false);
final MessageHelper.AddressFormat email_format = MessageHelper.getAddressFormat(getContext());
final int[] encryptValues = getResources().getIntArray(R.array.encryptValues);
final int[] sendDelayedValues = getResources().getIntArray(R.array.sendDelayedValues);
final String[] sendDelayedNames = getResources().getStringArray(R.array.sendDelayedNames);
final ViewGroup dview = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.dialog_send, null);
final Button btnFixSent = dview.findViewById(R.id.btnFixSent);
final TextView tvAddressError = dview.findViewById(R.id.tvAddressError);
final TextView tvRemindDsn = dview.findViewById(R.id.tvRemindDsn);
final TextView tvRemindSize = dview.findViewById(R.id.tvRemindSize);
final TextView tvRemindPgp = dview.findViewById(R.id.tvRemindPgp);
final TextView tvRemindSmime = dview.findViewById(R.id.tvRemindSmime);
final TextView tvRemindTo = dview.findViewById(R.id.tvRemindTo);
final TextView tvRemindExtra = dview.findViewById(R.id.tvRemindExtra);
final TextView tvRemindNoReply = dview.findViewById(R.id.tvRemindNoReply);
final TextView tvRemindExternal = dview.findViewById(R.id.tvRemindExternal);
final TextView tvRemindSubject = dview.findViewById(R.id.tvRemindSubject);
final TextView tvRemindText = dview.findViewById(R.id.tvRemindText);
final TextView tvRemindAttachment = dview.findViewById(R.id.tvRemindAttachment);
final TextView tvRemindExtension = dview.findViewById(R.id.tvRemindExtension);
final TextView tvRemindInternet = dview.findViewById(R.id.tvRemindInternet);
final SwitchCompat swSendReminders = dview.findViewById(R.id.swSendReminders);
final TextView tvSendRemindersHint = dview.findViewById(R.id.tvSendRemindersHint);
final TextView tvTo = dview.findViewById(R.id.tvTo);
final TextView tvViaTitle = dview.findViewById(R.id.tvViaTitle);
final TextView tvVia = dview.findViewById(R.id.tvVia);
final CheckBox cbPlainOnly = dview.findViewById(R.id.cbPlainOnly);
final TextView tvPlainHint = dview.findViewById(R.id.tvPlainHint);
final CheckBox cbReceipt = dview.findViewById(R.id.cbReceipt);
final TextView tvReceiptHint = dview.findViewById(R.id.tvReceiptHint);
final TextView tvEncrypt = dview.findViewById(R.id.tvEncrypt);
final Spinner spEncrypt = dview.findViewById(R.id.spEncrypt);
final ImageButton ibEncryption = dview.findViewById(R.id.ibEncryption);
final Spinner spPriority = dview.findViewById(R.id.spPriority);
final Spinner spSensitivity = dview.findViewById(R.id.spSensitivity);
final ImageButton ibSensitivity = dview.findViewById(R.id.ibSensitivity);
final TextView tvSendAt = dview.findViewById(R.id.tvSendAt);
final ImageButton ibSendAt = dview.findViewById(R.id.ibSendAt);
final CheckBox cbArchive = dview.findViewById(R.id.cbArchive);
final CheckBox cbNotAgain = dview.findViewById(R.id.cbNotAgain);
final TextView tvNotAgain = dview.findViewById(R.id.tvNotAgain);
final Group grpSentMissing = dview.findViewById(R.id.grpSentMissing);
final Group grpDsn = dview.findViewById(R.id.grpDsn);
btnFixSent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
v.getContext().startActivity(new Intent(v.getContext(), ActivitySetup.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
.putExtra("target", "accounts"));
}
});
grpSentMissing.setVisibility(sent_missing ? View.VISIBLE : View.GONE);
tvAddressError.setText(address_error == null ? mx_error : address_error);
tvAddressError.setVisibility(address_error == null && mx_error == null ? View.GONE : View.VISIBLE);
tvRemindDsn.setVisibility(remind_dsn ? View.VISIBLE : View.GONE);
tvRemindSize.setText(getString(R.string.title_size_reminder,
Helper.humanReadableByteCount(size),
Helper.humanReadableByteCount(max_size)));
tvRemindSize.setVisibility(remind_size ? View.VISIBLE : View.GONE);
tvRemindPgp.setVisibility(remind_pgp ? View.VISIBLE : View.GONE);
tvRemindSmime.setVisibility(remind_smime ? View.VISIBLE : View.GONE);
tvRemindTo.setVisibility(remind_to ? View.VISIBLE : View.GONE);
tvRemindExtra.setVisibility(send_reminders && remind_extra ? View.VISIBLE : View.GONE);
tvRemindNoReply.setVisibility(remind_noreply ? View.VISIBLE : View.GONE);
tvRemindExternal.setVisibility(remind_external ? View.VISIBLE : View.GONE);
tvRemindSubject.setVisibility(send_reminders && remind_subject ? View.VISIBLE : View.GONE);
tvRemindText.setVisibility(send_reminders && remind_text ? View.VISIBLE : View.GONE);
tvRemindAttachment.setVisibility(send_reminders && remind_attachment ? View.VISIBLE : View.GONE);
tvRemindExtension.setText(getString(R.string.title_attachment_warning, remind_extension));
tvRemindExtension.setVisibility(send_reminders && remind_extension != null ? View.VISIBLE : View.GONE);
tvRemindInternet.setVisibility(send_reminders && remind_internet ? View.VISIBLE : View.GONE);
tvTo.setText(null);
tvVia.setText(null);
tvPlainHint.setVisibility(View.GONE);
tvReceiptHint.setVisibility(View.GONE);
spEncrypt.setTag(0);
spEncrypt.setSelection(0);
spPriority.setTag(1);
spPriority.setSelection(1);
spSensitivity.setTag(0);
spSensitivity.setSelection(0);
tvSendAt.setText(null);
cbArchive.setEnabled(false);
cbNotAgain.setChecked(!send_dialog);
cbNotAgain.setVisibility(send_dialog ? View.VISIBLE : View.GONE);
tvNotAgain.setVisibility(cbNotAgain.isChecked() ? View.VISIBLE : View.GONE);
Helper.setViewsEnabled(dview, false);
boolean reminder = (remind_extra || remind_subject || remind_text ||
remind_attachment || remind_extension != null || remind_internet);
swSendReminders.setChecked(send_reminders);
swSendReminders.setVisibility(send_reminders && reminder ? View.VISIBLE : View.GONE);
tvSendRemindersHint.setVisibility(View.GONE);
swSendReminders.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("send_reminders", checked).apply();
tvRemindExtra.setVisibility(checked && remind_extra ? View.VISIBLE : View.GONE);
tvRemindSubject.setVisibility(checked && remind_subject ? View.VISIBLE : View.GONE);
tvRemindText.setVisibility(checked && remind_text ? View.VISIBLE : View.GONE);
tvRemindAttachment.setVisibility(checked && remind_attachment ? View.VISIBLE : View.GONE);
tvRemindExtension.setVisibility(checked && remind_extension != null ? View.VISIBLE : View.GONE);
tvRemindInternet.setVisibility(checked && remind_internet ? View.VISIBLE : View.GONE);
tvSendRemindersHint.setVisibility(checked ? View.GONE : View.VISIBLE);
}
});
cbNotAgain.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("send_dialog", !isChecked).apply();
tvNotAgain.setVisibility(isChecked ? View.VISIBLE : View.GONE);
}
});
cbPlainOnly.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
tvPlainHint.setVisibility(checked && styled ? View.VISIBLE : View.GONE);
Bundle args = new Bundle();
args.putLong("id", id);
args.putBoolean("plain_only", checked);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
boolean plain_only = args.getBoolean("plain_only");
DB db = DB.getInstance(context);
db.message().setMessagePlainOnly(id, plain_only ? 1 : 0);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.serial().execute(FragmentDialogSend.this, args, "compose:plain_only");
}
});
cbReceipt.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
tvReceiptHint.setVisibility(checked ? View.VISIBLE : View.GONE);
Bundle args = new Bundle();
args.putLong("id", id);
args.putBoolean("receipt", checked);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
boolean receipt = args.getBoolean("receipt");
DB db = DB.getInstance(context);
db.message().setMessageReceiptRequest(id, receipt);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.serial().execute(FragmentDialogSend.this, args, "compose:receipt");
}
});
spEncrypt.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
int last = (int) spEncrypt.getTag();
if (last != position) {
spEncrypt.setTag(position);
setEncrypt(encryptValues[position]);
if ((encryptValues[position] == EntityMessage.PGP_SIGNONLY ||
encryptValues[position] == EntityMessage.PGP_ENCRYPTONLY ||
encryptValues[position] == EntityMessage.PGP_SIGNENCRYPT) &&
Helper.isOpenKeychainInstalled(context)) {
tvEncrypt.setPaintFlags(tvEncrypt.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
tvEncrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String pkg = Helper.getOpenKeychainPackage(v.getContext());
PackageManager pm = v.getContext().getPackageManager();
v.getContext().startActivity(pm.getLaunchIntentForPackage(pkg));
}
});
} else {
tvEncrypt.setPaintFlags(tvEncrypt.getPaintFlags() & ~Paint.UNDERLINE_TEXT_FLAG);
tvEncrypt.setOnClickListener(null);
}
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
spEncrypt.setTag(0);
setEncrypt(encryptValues[0]);
}
private void setEncrypt(int encrypt) {
Bundle args = new Bundle();
args.putLong("id", id);
args.putInt("encrypt", encrypt);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
int encrypt = args.getInt("encrypt");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
db.message().setMessageUiEncrypt(message.id, encrypt);
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
if (attachments == null)
return null;
for (EntityAttachment attachment : attachments)
if (attachment.isEncryption())
db.attachment().deleteAttachment(attachment.id);
if (encrypt != EntityMessage.ENCRYPT_NONE &&
message.identity != null) {
int iencrypt =
(encrypt == EntityMessage.SMIME_SIGNONLY ||
encrypt == EntityMessage.SMIME_SIGNENCRYPT
? 1 : 0);
db.identity().setIdentityEncrypt(message.identity, iencrypt);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onExecuted(Bundle args, Void data) {
int encrypt = args.getInt("encrypt");
boolean none = EntityMessage.ENCRYPT_NONE.equals(encrypt);
tvRemindPgp.setVisibility(remind_pgp && none ? View.VISIBLE : View.GONE);
tvRemindSmime.setVisibility(remind_smime && none ? View.VISIBLE : View.GONE);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.serial().execute(FragmentDialogSend.this, args, "compose:encrypt");
}
});
ibEncryption.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 12);
}
});
spPriority.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
int last = (int) spPriority.getTag();
if (last != position) {
spPriority.setTag(position);
setPriority(position);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
spPriority.setTag(1);
setPriority(1);
}
private void setPriority(int priority) {
Bundle args = new Bundle();
args.putLong("id", id);
args.putInt("priority", priority);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
int priority = args.getInt("priority");
DB db = DB.getInstance(context);
db.message().setMessagePriority(id, priority);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.serial().execute(FragmentDialogSend.this, args, "compose:priority");
}
});
spSensitivity.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
int last = (int) spSensitivity.getTag();
if (last != position) {
spSensitivity.setTag(position);
setSensitivity(position);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
spSensitivity.setTag(0);
setSensitivity(0);
}
private void setSensitivity(int sensitivity) {
Bundle args = new Bundle();
args.putLong("id", id);
args.putInt("sensitivity", sensitivity);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
int sensitivity = args.getInt("sensitivity");
DB db = DB.getInstance(context);
db.message().setMessageSensitivity(id, sensitivity < 1 ? null : sensitivity);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.serial().execute(FragmentDialogSend.this, args, "compose:sensitivity");
}
});
ibSensitivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 177);
}
});
View.OnClickListener sendAt = new View.OnClickListener() {
@Override
public void onClick(View view) {
Bundle args = new Bundle();
args.putString("title", getString(R.string.title_send_at));
args.putLong("id", id);
FragmentDialogDuration fragment = new FragmentDialogDuration();
fragment.setArguments(args);
fragment.setTargetFragment(FragmentDialogSend.this, 1);
fragment.show(getParentFragmentManager(), "send:snooze");
}
};
tvSendAt.setOnClickListener(sendAt);
ibSendAt.setOnClickListener(sendAt);
cbArchive.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("send_archive", isChecked).apply();
}
});
DB db = DB.getInstance(context);
db.message().liveMessage(id).observe(getViewLifecycleOwner(), new Observer<TupleMessageEx>() {
@Override
public void onChanged(TupleMessageEx draft) {
if (draft == null) {
dismiss();
return;
}
boolean dsn = (draft.dsn != null && !EntityMessage.DSN_NONE.equals(draft.dsn));
int to = (draft.to == null ? 0 : draft.to.length);
int extra = (draft.cc == null ? 0 : draft.cc.length) + (draft.bcc == null ? 0 : draft.bcc.length);
List<Address> t = new ArrayList<>();
if (draft.to != null)
if (to <= MAX_SHOW_RECIPIENTS)
t.addAll(Arrays.asList(draft.to));
else {
t.addAll((Arrays.asList(Arrays.copyOf(draft.to, MAX_SHOW_RECIPIENTS))));
extra += draft.to.length - MAX_SHOW_RECIPIENTS;
}
Address[] tos = t.toArray(new Address[0]);
if (extra == 0)
tvTo.setText(MessageHelper.formatAddresses(tos, email_format, false));
else
tvTo.setText(getString(R.string.title_name_plus,
MessageHelper.formatAddresses(tos, email_format, false), extra));
tvTo.setTextColor(Helper.resolveColor(context,
to + extra > RECIPIENTS_WARNING ? R.attr.colorWarning : android.R.attr.textColorPrimary));
if (draft.identityColor != null && draft.identityColor != Color.TRANSPARENT)
tvViaTitle.setTextColor(draft.identityColor);
tvVia.setText(draft.identityEmail);
cbPlainOnly.setChecked(draft.isPlainOnly() && !dsn);
cbReceipt.setChecked(draft.receipt_request != null && draft.receipt_request && !dsn);
int encrypt = (draft.ui_encrypt == null || dsn ? EntityMessage.ENCRYPT_NONE : draft.ui_encrypt);
for (int i = 0; i < encryptValues.length; i++)
if (encryptValues[i] == encrypt) {
spEncrypt.setTag(i);
spEncrypt.setSelection(i);
break;
}
int priority = (draft.priority == null ? 1 : draft.priority);
spPriority.setTag(priority);
spPriority.setSelection(priority);
int sensitivity = (draft.sensitivity == null ? 0 : draft.sensitivity);
spSensitivity.setTag(sensitivity);
spSensitivity.setSelection(sensitivity);
if (draft.ui_snoozed == null) {
if (send_delayed == 0)
tvSendAt.setText(getString(R.string.title_now));
else
for (int pos = 0; pos < sendDelayedValues.length; pos++)
if (sendDelayedValues[pos] == send_delayed) {
tvSendAt.setText(getString(R.string.title_after, sendDelayedNames[pos]));
break;
}
} else {
DateFormat DTF = Helper.getDateTimeInstance(context, SimpleDateFormat.MEDIUM, SimpleDateFormat.SHORT);
DateFormat D = new SimpleDateFormat("E");
tvSendAt.setText(D.format(draft.ui_snoozed) + " " + DTF.format(draft.ui_snoozed));
}
grpDsn.setVisibility(dsn ? View.GONE : View.VISIBLE);
Helper.setViewsEnabled(dview, true);
}
});
Bundle aargs = new Bundle();
aargs.putLong("id", id);
new SimpleTask<Boolean>() {
@Override
protected @NonNull
Boolean onExecute(Context context, Bundle args) {
long id = args.getLong("id");
DB db = DB.getInstance(context);
EntityMessage draft = db.message().getMessage(id);
if (draft == null) {
args.putString("reason", "Draft gone");
return false;
}
if (TextUtils.isEmpty(draft.inreplyto)) {
args.putString("reason", "No in-reply-to");
return false;
}
EntityFolder archive = db.folder().getFolderByType(draft.account, EntityFolder.ARCHIVE);
if (archive == null) {
args.putString("reason", "No archive");
return false;
}
List<EntityMessage> messages = db.message().getMessagesByMsgId(draft.account, draft.inreplyto);
if (messages == null || messages.size() == 0) {
args.putString("reason", "In-reply-to gone");
return false;
}
for (EntityMessage message : messages) {
EntityFolder folder = db.folder().getFolder(message.folder);
if (folder == null)
continue;
if (EntityFolder.INBOX.equals(folder.type) || EntityFolder.USER.equals(folder.type))
return true;
}
args.putString("reason", "Not in inbox or unread");
return false;
}
@Override
protected void onExecuted(Bundle args, Boolean data) {
if (!data) {
String reason = args.getString("reason");
if (BuildConfig.DEBUG)
cbArchive.setText(reason);
else
Log.i("Auto archive reason=" + reason);
}
if (send_archive && data)
cbArchive.setChecked(true);
cbArchive.setEnabled(data);
}
@Override
protected void onException(Bundle args, Throwable ex) {
// Ignored
}
}.serial().execute(FragmentDialogSend.this, aargs, "send:archive");
AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setView(dview)
.setNegativeButton(android.R.string.cancel, null);
if (address_error == null && !remind_to && !remind_size) {
if (send_delayed != 0)
builder.setNeutralButton(R.string.title_send_now, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
getArguments().putBoolean("archive", cbArchive.isChecked());
sendResult(Activity.RESULT_FIRST_USER);
}
});
builder.setPositiveButton(R.string.title_send, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
getArguments().putBoolean("archive", cbArchive.isChecked());
sendResult(Activity.RESULT_OK);
}
});
}
return builder.create();
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_OK && intent != null) {
Bundle data = intent.getBundleExtra("args");
long id = data.getLong("id");
long duration = data.getLong("duration");
long time = data.getLong("time");
Bundle args = new Bundle();
args.putLong("id", id);
args.putLong("wakeup", duration == 0 ? -1 : time);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
long wakeup = args.getLong("wakeup");
DB db = DB.getInstance(context);
db.message().setMessageSnoozed(id, wakeup < 0 ? null : wakeup);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.serial().execute(this, args, "compose:snooze");
}
}
}

@ -0,0 +1,158 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
import java.util.ArrayList;
import java.util.List;
public class FragmentDialogSwipes extends FragmentDialogBase {
private Spinner spLeft;
private Spinner spRight;
private ArrayAdapter<EntityFolder> adapter;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_swipes, null);
spLeft = dview.findViewById(R.id.spLeft);
spRight = dview.findViewById(R.id.spRight);
adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item1, android.R.id.text1, new ArrayList<EntityFolder>());
adapter.setDropDownViewResource(R.layout.spinner_item1_dropdown);
spLeft.setAdapter(adapter);
spRight.setAdapter(adapter);
List<EntityFolder> folders = FragmentAccount.getFolderActions(getContext());
EntityFolder trash = new EntityFolder();
trash.id = 2L;
trash.name = getString(R.string.title_trash);
folders.add(1, trash);
EntityFolder archive = new EntityFolder();
archive.id = 1L;
archive.name = getString(R.string.title_archive);
folders.add(1, archive);
adapter.addAll(folders);
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
int leftPos = prefs.getInt("swipe_left_default", 2); // Trash
int rightPos = prefs.getInt("swipe_right_default", 1); // Archive
spLeft.setSelection(leftPos);
spRight.setSelection(rightPos);
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit()
.putInt("swipe_left_default", spLeft.getSelectedItemPosition())
.putInt("swipe_right_default", spRight.getSelectedItemPosition())
.apply();
EntityFolder left = (EntityFolder) spLeft.getSelectedItem();
EntityFolder right = (EntityFolder) spRight.getSelectedItem();
if ((left != null && EntityMessage.SWIPE_ACTION_HIDE.equals(left.id)) ||
(right != null && EntityMessage.SWIPE_ACTION_HIDE.equals(right.id)))
prefs.edit()
.putBoolean("message_tools", true)
.putBoolean("button_hide", true)
.apply();
Bundle args = new Bundle();
args.putLong("left", left == null ? 0 : left.id);
args.putLong("right", right == null ? 0 : right.id);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long left = args.getLong("left");
long right = args.getLong("right");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
List<EntityAccount> accounts = db.account().getAccounts();
for (EntityAccount account : accounts)
if (account.protocol == EntityAccount.TYPE_IMAP)
db.account().setAccountSwipes(
account.id,
getAction(context, left, account.id),
getAction(context, right, account.id));
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onExecuted(Bundle args, Void data) {
ToastEx.makeText(getContext(), R.string.title_completed, Toast.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
private Long getAction(Context context, long selection, long account) {
if (selection < 0)
return selection;
else if (selection == 0)
return null;
else {
DB db = DB.getInstance(context);
String type = (selection == 2 ? EntityFolder.TRASH : EntityFolder.ARCHIVE);
EntityFolder archive = db.folder().getFolderByType(account, type);
return (archive == null ? null : archive.id);
}
}
}.execute(getContext(), getViewLifecycleOwner(), args, "dialog:swipe");
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}

@ -0,0 +1,248 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.Context;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.io.File;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
public class FragmentDialogVirusTotal extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Bundle args = getArguments();
String apiKey = args.getString("apiKey");
String name = args.getString("name");
final Context context = getContext();
View view = LayoutInflater.from(context).inflate(R.layout.dialog_virus_total, null);
final TextView tvName = view.findViewById(R.id.tvName);
final TextView tvError = view.findViewById(R.id.tvError);
final TextView tvUnknown = view.findViewById(R.id.tvUnknown);
final TextView tvSummary = view.findViewById(R.id.tvSummary);
final TextView tvLabel = view.findViewById(R.id.tvLabel);
final TextView tvReport = view.findViewById(R.id.tvReport);
final RecyclerView rvScan = view.findViewById(R.id.rvScan);
final Button btnUpload = view.findViewById(R.id.btnUpload);
final ProgressBar pbUpload = view.findViewById(R.id.pbUpload);
final TextView tvAnalyzing = view.findViewById(R.id.tvAnalyzing);
final TextView tvPrivacy = view.findViewById(R.id.tvPrivacy);
final ProgressBar pbWait = view.findViewById(R.id.pbWait);
tvName.setText(name);
tvName.setVisibility(TextUtils.isEmpty(name) ? View.GONE : View.VISIBLE);
tvError.setVisibility(View.GONE);
tvUnknown.setVisibility(View.GONE);
tvSummary.setVisibility(View.GONE);
tvLabel.setVisibility(View.GONE);
tvReport.setVisibility(View.GONE);
tvReport.getPaint().setUnderlineText(true);
rvScan.setHasFixedSize(false);
LinearLayoutManager llm = new LinearLayoutManager(getContext());
rvScan.setLayoutManager(llm);
final AdapterVirusTotal adapter = new AdapterVirusTotal(getContext(), getViewLifecycleOwner());
rvScan.setAdapter(adapter);
rvScan.setVisibility(View.GONE);
btnUpload.setVisibility(View.GONE);
pbUpload.setVisibility(View.GONE);
tvAnalyzing.setVisibility(View.GONE);
tvPrivacy.setVisibility(View.GONE);
tvPrivacy.getPaint().setUnderlineText(true);
pbWait.setVisibility(View.GONE);
final SimpleTask<Bundle> taskLookup = new SimpleTask<Bundle>() {
@Override
protected void onPreExecute(Bundle args) {
tvError.setVisibility(View.GONE);
pbWait.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(Bundle args) {
pbWait.setVisibility(View.GONE);
}
@Override
protected Bundle onExecute(Context context, Bundle args) throws Throwable {
String apiKey = args.getString("apiKey");
File file = (File) args.getSerializable("file");
return VirusTotal.lookup(context, file, apiKey);
}
@Override
protected void onExecuted(Bundle args, Bundle result) {
List<VirusTotal.ScanResult> scans = result.getParcelableArrayList("scans");
String label = result.getString("label");
String analysis = args.getString("analysis");
int malicious = 0;
if (scans != null)
for (VirusTotal.ScanResult scan : scans)
if ("malicious".equals(scan.category))
malicious++;
NumberFormat NF = NumberFormat.getNumberInstance();
tvUnknown.setVisibility(scans == null ? View.VISIBLE : View.GONE);
tvSummary.setText(getString(R.string.title_vt_summary, NF.format(malicious)));
tvSummary.setTextColor(Helper.resolveColor(context,
malicious == 0 ? android.R.attr.textColorPrimary : R.attr.colorWarning));
tvSummary.setTypeface(malicious == 0 ? Typeface.DEFAULT : Typeface.DEFAULT_BOLD);
tvSummary.setVisibility(scans == null ? View.GONE : View.VISIBLE);
tvLabel.setText(label);
tvReport.setVisibility(scans == null ? View.GONE : View.VISIBLE);
adapter.set(scans == null ? new ArrayList<>() : scans);
rvScan.setVisibility(scans == null ? View.GONE : View.VISIBLE);
btnUpload.setVisibility(scans == null && !TextUtils.isEmpty(apiKey) ? View.VISIBLE : View.GONE);
tvPrivacy.setVisibility(btnUpload.getVisibility());
if (analysis != null && args.getBoolean("init")) {
args.remove("init");
btnUpload.callOnClick();
}
}
@Override
protected void onException(Bundle args, Throwable ex) {
tvError.setText(Log.formatThrowable(ex, false));
tvError.setVisibility(View.VISIBLE);
}
};
final SimpleTask<Void> taskUpload = new SimpleTask<Void>() {
@Override
protected void onPreExecute(Bundle args) {
btnUpload.setEnabled(false);
pbUpload.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(Bundle args) {
btnUpload.setEnabled(true);
tvAnalyzing.setVisibility(View.GONE);
pbUpload.setVisibility(View.GONE);
}
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
String apiKey = args.getString("apiKey");
File file = (File) args.getSerializable("file");
String analysis = args.getString("analysis");
if (analysis == null) {
analysis = VirusTotal.upload(context, file, apiKey);
args.putString("analysis", analysis);
}
postProgress(analysis);
VirusTotal.waitForAnalysis(context, analysis, apiKey);
return null;
}
@Override
protected void onProgress(CharSequence status, Bundle data) {
tvAnalyzing.setVisibility(View.VISIBLE);
}
@Override
protected void onExecuted(Bundle args, Void data) {
taskLookup.execute(FragmentDialogVirusTotal.this, args, "attachment:lookup");
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
};
final SimpleTask<String> taskUrl = new SimpleTask<String>() {
@Override
protected String onExecute(Context context, Bundle args) throws Throwable {
File file = (File) args.getSerializable("file");
return VirusTotal.getUrl(file);
}
@Override
protected void onExecuted(Bundle args, String uri) {
Helper.view(context, Uri.parse(uri), true);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
};
tvReport.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
taskUrl.execute(FragmentDialogVirusTotal.this, args, "attachment:report");
}
});
btnUpload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
taskUpload.execute(FragmentDialogVirusTotal.this, args, "attachment:upload");
}
});
tvPrivacy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.view(v.getContext(), Uri.parse(VirusTotal.URI_PRIVACY), true);
}
});
if (TextUtils.isEmpty(apiKey))
pbWait.setVisibility(View.GONE);
else {
args.putBoolean("init", true);
taskLookup.execute(this, args, "attachment:lookup");
}
return new AlertDialog.Builder(context)
.setView(view)
.setNegativeButton(R.string.title_setup_done, null)
.create();
}
}

@ -0,0 +1,70 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
import java.text.DateFormatSymbols;
import java.util.Arrays;
import java.util.Calendar;
public class FragmentDialogWeekend extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
boolean[] days = new boolean[7];
final Context context = getContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String[] daynames = Arrays.copyOfRange(new DateFormatSymbols().getWeekdays(), 1, 8);
String weekend = prefs.getString("weekend", Calendar.SATURDAY + "," + Calendar.SUNDAY);
for (String day : weekend.split(","))
days[Integer.parseInt(day) - 1] = true;
return new AlertDialog.Builder(context)
.setTitle(R.string.title_advanced_schedule_weekend)
.setMultiChoiceItems(daynames, days, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < days.length; i++)
if (days[i]) {
if (sb.length() > 0)
sb.append(",");
sb.append(i + 1);
}
prefs.edit().putString("weekend", sb.toString()).apply();
}
})
.setNegativeButton(R.string.title_setup_done, null)
.create();
}
}

@ -22,12 +22,10 @@ package eu.faircode.email;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.app.Dialog;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Canvas; import android.graphics.Canvas;
@ -42,17 +40,12 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.RadioGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.constraintlayout.widget.Group; import androidx.constraintlayout.widget.Group;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
@ -889,7 +882,7 @@ public class FragmentFolders extends FragmentBase {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("account", account); args.putLong("account", account);
FragmentDialogApply fragment = new FragmentDialogApply(); FragmentDialogFoldersApply fragment = new FragmentDialogFoldersApply();
fragment.setArguments(args); fragment.setArguments(args);
fragment.show(getParentFragmentManager(), "folders:apply"); fragment.show(getParentFragmentManager(), "folders:apply");
} }
@ -1505,115 +1498,4 @@ public class FragmentFolders extends FragmentBase {
} }
}.execute(this, args, "edit:color"); }.execute(this, args, "edit:color");
} }
public static class FragmentDialogApply extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_folder_all, null);
final RadioGroup rgSynchronize = view.findViewById(R.id.rgSynchronize);
final EditText etSyncDays = view.findViewById(R.id.etSyncDays);
final EditText etKeepDays = view.findViewById(R.id.etKeepDays);
final CheckBox cbKeepAll = view.findViewById(R.id.cbKeepAll);
final CheckBox cbPollSystem = view.findViewById(R.id.cbPollSystem);
final CheckBox cbPollUser = view.findViewById(R.id.cbPollUser);
cbKeepAll.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
etKeepDays.setEnabled(!isChecked);
}
});
return new AlertDialog.Builder(getContext())
.setView(view)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Bundle args = getArguments();
int optionId = rgSynchronize.getCheckedRadioButtonId();
if (optionId == R.id.rbEnable)
args.putBoolean("enable", true);
else if (optionId == R.id.rbDisable)
args.putBoolean("enable", false);
args.putString("sync", etSyncDays.getText().toString());
args.putString("keep", cbKeepAll.isChecked()
? Integer.toString(Integer.MAX_VALUE)
: etKeepDays.getText().toString());
args.putBoolean("system", cbPollSystem.isChecked());
args.putBoolean("user", cbPollUser.isChecked());
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
long aid = args.getLong("account");
Boolean enable = null;
if (args.containsKey("enable"))
enable = args.getBoolean("enable");
String sync = args.getString("sync");
String keep = args.getString("keep");
boolean system = args.getBoolean("system");
boolean user = args.getBoolean("user");
if (TextUtils.isEmpty(sync))
sync = "7";
if (TextUtils.isEmpty(keep))
keep = "30";
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityAccount account = db.account().getAccount(aid);
if (account == null)
return null;
if (system && account.poll_interval > 15)
db.account().setAccountKeepAliveInterval(account.id, 15);
List<EntityFolder> folders = db.folder().getFolders(aid, false, true);
if (folders == null)
return null;
for (EntityFolder folder : folders) {
if (EntityFolder.USER.equals(folder.type)) {
if (enable != null) {
folder.synchronize = enable;
db.folder().setFolderSynchronize(folder.id, folder.synchronize);
}
db.folder().setFolderProperties(
folder.id,
Integer.parseInt(sync),
Integer.parseInt(keep));
}
if (folder.synchronize && !folder.poll)
if (EntityFolder.USER.equals(folder.type)
? user
: system && !EntityFolder.INBOX.equals(folder.type))
db.folder().setFolderPoll(folder.id, true);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
ServiceSynchronize.reload(context, aid, false, "Apply");
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(FragmentDialogApply.this, args, "folders:all");
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
} }

@ -37,8 +37,6 @@ import static me.everything.android.ui.overscroll.OverScrollBounceEffectDecorato
import android.Manifest; import android.Manifest;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.app.Activity;
import android.app.Dialog;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
@ -55,7 +53,6 @@ import android.content.res.ColorStateList;
import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteConstraintException;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.Typeface; import android.graphics.Typeface;
@ -117,10 +114,6 @@ import android.view.inputmethod.EditorInfo;
import android.webkit.WebSettings; import android.webkit.WebSettings;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
@ -418,7 +411,6 @@ public class FragmentMessages extends FragmentBase
private static final int SWIPE_DISABLE_SELECT_DURATION = 1500; // milliseconds private static final int SWIPE_DISABLE_SELECT_DURATION = 1500; // milliseconds
private static final float LUMINANCE_THRESHOLD = 0.7f; private static final float LUMINANCE_THRESHOLD = 0.7f;
private static final int ITEM_CACHE_SIZE = 10; // Default: 2 items private static final int ITEM_CACHE_SIZE = 10; // Default: 2 items
private static final int MAX_QUICK_ACTIONS = 5;
private static final int REQUEST_RAW = 1; private static final int REQUEST_RAW = 1;
private static final int REQUEST_OPENPGP = 4; private static final int REQUEST_OPENPGP = 4;
@ -6371,72 +6363,72 @@ public class FragmentMessages extends FragmentBase
int count = 0; int count = 0;
boolean move = (more_move && count < MAX_QUICK_ACTIONS && result.canMove()); boolean move = (more_move && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS && result.canMove());
if (move) if (move)
count++; count++;
boolean delete = (more_delete && count < MAX_QUICK_ACTIONS && result.canDelete()); boolean delete = (more_delete && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS && result.canDelete());
if (delete) if (delete)
count++; count++;
boolean trash = (more_trash && count < MAX_QUICK_ACTIONS && result.canTrash()); boolean trash = (more_trash && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS && result.canTrash());
if (trash) if (trash)
count++; count++;
if (!delete && !trash && (inTrash || inJunk) && if (!delete && !trash && (inTrash || inJunk) &&
more_trash && count < MAX_QUICK_ACTIONS && result.canDelete()) { more_trash && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS && result.canDelete()) {
delete = true; delete = true;
count++; count++;
} }
boolean junk = (more_junk && count < MAX_QUICK_ACTIONS && result.canJunk()); boolean junk = (more_junk && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS && result.canJunk());
if (junk) if (junk)
count++; count++;
boolean archive = (more_archive && count < MAX_QUICK_ACTIONS && result.canArchive()); boolean archive = (more_archive && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS && result.canArchive());
if (archive) if (archive)
count++; count++;
boolean inbox = ((more_inbox || (more_junk && inJunk)) && count < MAX_QUICK_ACTIONS && result.canInbox()); boolean inbox = ((more_inbox || (more_junk && inJunk)) && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS && result.canInbox());
if (inbox) if (inbox)
count++; count++;
boolean importance_high = (more_importance_high && count < MAX_QUICK_ACTIONS && boolean importance_high = (more_importance_high && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS &&
!EntityMessage.PRIORITIY_HIGH.equals(result.importance)); !EntityMessage.PRIORITIY_HIGH.equals(result.importance));
if (importance_high) if (importance_high)
count++; count++;
boolean importance_normal = (more_importance_normal && count < MAX_QUICK_ACTIONS && boolean importance_normal = (more_importance_normal && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS &&
!EntityMessage.PRIORITIY_NORMAL.equals(result.importance)); !EntityMessage.PRIORITIY_NORMAL.equals(result.importance));
if (importance_normal) if (importance_normal)
count++; count++;
boolean importance_low = (more_importance_low && count < MAX_QUICK_ACTIONS && boolean importance_low = (more_importance_low && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS &&
!EntityMessage.PRIORITIY_LOW.equals(result.importance)); !EntityMessage.PRIORITIY_LOW.equals(result.importance));
if (importance_low) if (importance_low)
count++; count++;
boolean flag = (more_flag && count < MAX_QUICK_ACTIONS && result.unflagged); boolean flag = (more_flag && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS && result.unflagged);
if (flag) if (flag)
count++; count++;
boolean flag_color = (more_flag_color && count < MAX_QUICK_ACTIONS && (result.unflagged || result.flagged)); boolean flag_color = (more_flag_color && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS && (result.unflagged || result.flagged));
if (flag_color) if (flag_color)
count++; count++;
boolean hide = (more_hide && count < MAX_QUICK_ACTIONS && result.visible); boolean hide = (more_hide && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS && result.visible);
if (hide) if (hide)
count++; count++;
boolean snooze = (more_snooze && count < MAX_QUICK_ACTIONS); boolean snooze = (more_snooze && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS);
if (snooze) if (snooze)
count++; count++;
boolean unseen = (more_unseen && count < MAX_QUICK_ACTIONS && result.seen); boolean unseen = (more_unseen && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS && result.seen);
if (unseen) if (unseen)
count++; count++;
boolean seen = (more_seen && count < MAX_QUICK_ACTIONS && result.unseen); boolean seen = (more_seen && count < FragmentDialogQuickActions.MAX_QUICK_ACTIONS && result.unseen);
if (seen) if (seen)
count++; count++;
@ -7432,7 +7424,7 @@ public class FragmentMessages extends FragmentBase
return; return;
} }
String title = getString(R.string.title_move_undo, getNames(result, true), result.size()); String title = getString(R.string.title_move_undo, FragmentMoveAsk.getNames(result, true), result.size());
((ActivityView) activity).undo(title, args, taskUndoMove, taskUndoShow); ((ActivityView) activity).undo(title, args, taskUndoMove, taskUndoShow);
if (viewType == AdapterMessage.ViewType.THREAD) { if (viewType == AdapterMessage.ViewType.THREAD) {
@ -7518,45 +7510,6 @@ public class FragmentMessages extends FragmentBase
} }
}; };
private static String getNames(ArrayList<MessageTarget> result, boolean dest) {
boolean across = false;
for (MessageTarget target : result)
if (target.isAcross())
across = true;
Map<String, Integer> nameCount = new HashMap<>();
for (MessageTarget target : result) {
String name = "";
if (across)
name += (dest ? target.targetAccount.name : target.sourceAccount.name) + "/";
name += (dest ? target.targetFolder.display : target.sourceFolder.display);
if (!nameCount.containsKey(name))
nameCount.put(name, 0);
nameCount.put(name, nameCount.get(name) + 1);
}
List<String> keys = new ArrayList(nameCount.keySet());
Collator collator = Collator.getInstance(Locale.getDefault());
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
Collections.sort(keys, collator);
NumberFormat NF = NumberFormat.getNumberInstance();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
if (i > 0)
sb.append(", ");
sb.append(keys.get(i));
if (!dest && keys.size() > 1) {
int count = nameCount.get(keys.get(i));
sb.append('(').append(NF.format(count)).append(')');
}
}
return sb.toString();
}
static String getSort(Context context, AdapterMessage.ViewType viewType, String type) { static String getSort(Context context, AdapterMessage.ViewType viewType, String type) {
if (viewType == AdapterMessage.ViewType.UNIFIED) if (viewType == AdapterMessage.ViewType.UNIFIED)
return "sort_unified"; return "sort_unified";
@ -10723,435 +10676,4 @@ public class FragmentMessages extends FragmentBase
} }
} }
} }
public static class FragmentDialogAskSpam extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Bundle args = getArguments();
int count = args.getInt("count");
final Context context = getContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean block_sender = prefs.getBoolean("block_sender", true);
String text = getResources().getQuantityString(R.plurals.title_ask_spam, count, count);
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_ask_spam, null);
TextView tvMessage = dview.findViewById(R.id.tvMessage);
CheckBox cbBlockSender = dview.findViewById(R.id.cbBlockSender);
tvMessage.setText(text);
cbBlockSender.setChecked(block_sender);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
boolean block = cbBlockSender.isChecked();
prefs.edit().putBoolean("block_sender", block).apply();
getArguments().putBoolean("block", block);
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
public static class FragmentDialogReporting extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_error_reporting, null);
Button btnInfo = dview.findViewById(R.id.btnInfo);
btnInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 104);
}
});
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("crash_reports", true).apply();
Log.setCrashReporting(true);
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("crash_reports_asked", true).apply();
}
})
.create();
}
}
public static class FragmentDialogReview extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_review, null);
TextView tvHelp = dview.findViewById(R.id.tvHelp);
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
Dialog dialog = new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().putBoolean("review_asked", true).apply();
startActivity(Helper.getIntentRate(getContext()));
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().putBoolean("review_asked", true).apply();
}
})
.setNeutralButton(R.string.title_later, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().putLong("review_later", new Date().getTime()).apply();
}
})
.create();
tvHelp.setPaintFlags(tvHelp.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
tvHelp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
prefs.edit().putLong("review_later", new Date().getTime()).apply();
startActivity(Helper.getIntentIssue(v.getContext(), "Review:issue"));
}
});
return dialog;
}
@Override
public void onCancel(@NonNull DialogInterface dialog) {
super.onCancel(dialog);
try {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("review_asked", true).apply();
} catch (Throwable ex) {
Log.e(ex);
}
}
}
public static class FragmentDialogBoundaryError extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
String error = getArguments().getString("error");
final Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_boundary_error, null);
TextView tvError = dview.findViewById(R.id.tvError);
tvError.setText(error);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(R.string.title_boundary_retry, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_CANCELED);
}
})
.create();
}
}
public static class FragmentMoveAsk extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
String notagain = getArguments().getString("notagain");
ArrayList<MessageTarget> result = getArguments().getParcelableArrayList("result");
final Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_ask_move, null);
TextView tvMessages = dview.findViewById(R.id.tvMessages);
TextView tvSourceFolders = dview.findViewById(R.id.tvSourceFolders);
TextView tvTargetFolders = dview.findViewById(R.id.tvTargetFolders);
CheckBox cbNotAgain = dview.findViewById(R.id.cbNotAgain);
TextView tvJunkLearn = dview.findViewById(R.id.tvJunkLearn);
String question = context.getResources()
.getQuantityString(R.plurals.title_moving_messages,
result.size(), result.size());
tvMessages.setText(question);
tvSourceFolders.setText(getNames(result, false));
tvTargetFolders.setText(getNames(result, true));
List<String> sources = new ArrayList<>();
List<String> targets = new ArrayList<>();
Integer sourceColor = null;
Integer targetColor = null;
boolean junk = false;
for (MessageTarget t : result) {
if (!sources.contains(t.sourceFolder.type))
sources.add(t.sourceFolder.type);
if (!targets.contains(t.targetFolder.type))
targets.add(t.targetFolder.type);
if (sourceColor == null)
sourceColor = t.sourceFolder.color;
if (targetColor == null)
targetColor = t.targetFolder.color;
if (!junk &&
(EntityFolder.JUNK.equals(t.sourceFolder.type) ||
EntityFolder.JUNK.equals(t.targetFolder.type)))
junk = true;
}
Drawable source = null;
if (sources.size() == 1) {
source = ContextCompat.getDrawable(context, EntityFolder.getIcon(sources.get(0)));
if (source != null)
source.setBounds(0, 0, source.getIntrinsicWidth(), source.getIntrinsicHeight());
if (sourceColor == null)
sourceColor = EntityFolder.getDefaultColor(sources.get(0), context);
} else {
source = ContextCompat.getDrawable(context, R.drawable.twotone_folders_24);
source.setBounds(0, 0, source.getIntrinsicWidth(), source.getIntrinsicHeight());
sourceColor = null;
}
Drawable target = null;
if (targets.size() == 1) {
target = ContextCompat.getDrawable(context, EntityFolder.getIcon(targets.get(0)));
if (target != null)
target.setBounds(0, 0, target.getIntrinsicWidth(), target.getIntrinsicHeight());
if (targetColor == null)
targetColor = EntityFolder.getDefaultColor(targets.get(0), context);
} else
targetColor = null;
tvSourceFolders.setCompoundDrawablesRelative(source, null, null, null);
tvTargetFolders.setCompoundDrawablesRelative(target, null, null, null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (sourceColor != null)
tvSourceFolders.setCompoundDrawableTintList(ColorStateList.valueOf(sourceColor));
if (targetColor != null)
tvTargetFolders.setCompoundDrawableTintList(ColorStateList.valueOf(targetColor));
}
if (notagain != null)
cbNotAgain.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(buttonView.getContext());
prefs.edit().putBoolean(notagain, isChecked).apply();
}
});
tvJunkLearn.setVisibility(junk ? View.VISIBLE : View.GONE);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_CANCELED);
}
})
.create();
}
}
public static class FragmentDialogSaveSearch extends FragmentDialogBase {
private ViewButtonColor btnColor;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Bundle args = getArguments();
BoundaryCallbackMessages.SearchCriteria criteria =
(BoundaryCallbackMessages.SearchCriteria) args.getSerializable("criteria");
if (criteria == null)
criteria = new BoundaryCallbackMessages.SearchCriteria();
final Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_save_search, null);
EditText etName = dview.findViewById(R.id.etName);
EditText etOrder = dview.findViewById(R.id.etOrder);
btnColor = dview.findViewById(R.id.btnColor);
btnColor.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.hideKeyboard(etName);
Bundle args = new Bundle();
args.putInt("color", btnColor.getColor());
args.putString("title", getString(R.string.title_color));
args.putBoolean("reset", true);
FragmentDialogColor fragment = new FragmentDialogColor();
fragment.setArguments(args);
fragment.setTargetFragment(FragmentDialogSaveSearch.this, 1234);
fragment.show(getParentFragmentManager(), "search:color");
}
});
etName.setText(criteria.name == null ? criteria.getTitle(context) : criteria.name);
etOrder.setText(criteria.order == null ? null : Integer.toString(criteria.order));
btnColor.setColor(criteria.color);
AlertDialog.Builder dialog = new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(R.string.title_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String order = etOrder.getText().toString();
args.putString("name", etName.getText().toString());
args.putInt("order",
!TextUtils.isEmpty(order) && TextUtils.isDigitsOnly(order)
? Integer.parseInt(order) : -1);
args.putInt("color", btnColor.getColor());
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_CANCELED);
}
});
if (criteria.id != null)
dialog.setNeutralButton(R.string.title_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_FIRST_USER);
}
});
return dialog.create();
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
try {
if (resultCode == RESULT_OK && data != null) {
Bundle args = data.getBundleExtra("args");
int color = args.getInt("color");
btnColor.setColor(color);
}
} catch (Throwable ex) {
Log.e(ex);
}
}
}
public static class FragmentDialogQuickActions extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Context context = getContext();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final View dview = LayoutInflater.from(context).inflate(R.layout.dialog_quick_actions, null);
final TextView tvHint = dview.findViewById(R.id.tvHint);
final CheckBox cbSeen = dview.findViewById(R.id.cbSeen);
final CheckBox cbUnseen = dview.findViewById(R.id.cbUnseen);
final CheckBox cbSnooze = dview.findViewById(R.id.cbSnooze);
final CheckBox cbHide = dview.findViewById(R.id.cbHide);
final CheckBox cbFlag = dview.findViewById(R.id.cbFlag);
final CheckBox cbFlagColor = dview.findViewById(R.id.cbFlagColor);
final CheckBox cbImportanceLow = dview.findViewById(R.id.cbImportanceLow);
final CheckBox cbImportanceNormal = dview.findViewById(R.id.cbImportanceNormal);
final CheckBox cbImportanceHigh = dview.findViewById(R.id.cbImportanceHigh);
final CheckBox cbInbox = dview.findViewById(R.id.cbInbox);
final CheckBox cbArchive = dview.findViewById(R.id.cbArchive);
final CheckBox cbJunk = dview.findViewById(R.id.cbJunk);
final CheckBox cbTrash = dview.findViewById(R.id.cbTrash);
final CheckBox cbDelete = dview.findViewById(R.id.cbDelete);
final CheckBox cbMove = dview.findViewById(R.id.cbMove);
final CheckBox cbClear = dview.findViewById(R.id.cbClear);
tvHint.setText(getString(R.string.title_quick_actions_hint, MAX_QUICK_ACTIONS));
cbSeen.setChecked(prefs.getBoolean("more_seen", true));
cbUnseen.setChecked(prefs.getBoolean("more_unseen", false));
cbSnooze.setChecked(prefs.getBoolean("more_snooze", false));
cbHide.setChecked(prefs.getBoolean("more_hide", false));
cbFlag.setChecked(prefs.getBoolean("more_flag", false));
cbFlagColor.setChecked(prefs.getBoolean("more_flag_color", false));
cbImportanceLow.setChecked(prefs.getBoolean("more_importance_low", false));
cbImportanceNormal.setChecked(prefs.getBoolean("more_importance_normal", false));
cbImportanceHigh.setChecked(prefs.getBoolean("more_importance_high", false));
cbInbox.setChecked(prefs.getBoolean("more_inbox", true));
cbArchive.setChecked(prefs.getBoolean("more_archive", true));
cbJunk.setChecked(prefs.getBoolean("more_junk", true));
cbTrash.setChecked(prefs.getBoolean("more_trash", true));
cbDelete.setChecked(prefs.getBoolean("more_delete", false));
cbMove.setChecked(prefs.getBoolean("more_move", true));
cbClear.setChecked(prefs.getBoolean("more_clear", true));
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("more_seen", cbSeen.isChecked());
editor.putBoolean("more_unseen", cbUnseen.isChecked());
editor.putBoolean("more_snooze", cbSnooze.isChecked());
editor.putBoolean("more_hide", cbHide.isChecked());
editor.putBoolean("more_flag", cbFlag.isChecked());
editor.putBoolean("more_flag_color", cbFlagColor.isChecked());
editor.putBoolean("more_importance_low", cbImportanceLow.isChecked());
editor.putBoolean("more_importance_normal", cbImportanceNormal.isChecked());
editor.putBoolean("more_importance_high", cbImportanceHigh.isChecked());
editor.putBoolean("more_inbox", cbInbox.isChecked());
editor.putBoolean("more_archive", cbArchive.isChecked());
editor.putBoolean("more_junk", cbJunk.isChecked());
editor.putBoolean("more_trash", cbTrash.isChecked());
editor.putBoolean("more_delete", cbDelete.isChecked());
editor.putBoolean("more_move", cbMove.isChecked());
editor.putBoolean("more_clear", cbClear.isChecked());
editor.apply();
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_CANCELED);
}
})
.create();
}
}
} }

@ -0,0 +1,194 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import java.text.Collator;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class FragmentMoveAsk extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
String notagain = getArguments().getString("notagain");
ArrayList<FragmentMessages.MessageTarget> result = getArguments().getParcelableArrayList("result");
final Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_ask_move, null);
TextView tvMessages = dview.findViewById(R.id.tvMessages);
TextView tvSourceFolders = dview.findViewById(R.id.tvSourceFolders);
TextView tvTargetFolders = dview.findViewById(R.id.tvTargetFolders);
CheckBox cbNotAgain = dview.findViewById(R.id.cbNotAgain);
TextView tvJunkLearn = dview.findViewById(R.id.tvJunkLearn);
String question = context.getResources()
.getQuantityString(R.plurals.title_moving_messages,
result.size(), result.size());
tvMessages.setText(question);
tvSourceFolders.setText(getNames(result, false));
tvTargetFolders.setText(getNames(result, true));
List<String> sources = new ArrayList<>();
List<String> targets = new ArrayList<>();
Integer sourceColor = null;
Integer targetColor = null;
boolean junk = false;
for (FragmentMessages.MessageTarget t : result) {
if (!sources.contains(t.sourceFolder.type))
sources.add(t.sourceFolder.type);
if (!targets.contains(t.targetFolder.type))
targets.add(t.targetFolder.type);
if (sourceColor == null)
sourceColor = t.sourceFolder.color;
if (targetColor == null)
targetColor = t.targetFolder.color;
if (!junk &&
(EntityFolder.JUNK.equals(t.sourceFolder.type) ||
EntityFolder.JUNK.equals(t.targetFolder.type)))
junk = true;
}
Drawable source = null;
if (sources.size() == 1) {
source = ContextCompat.getDrawable(context, EntityFolder.getIcon(sources.get(0)));
if (source != null)
source.setBounds(0, 0, source.getIntrinsicWidth(), source.getIntrinsicHeight());
if (sourceColor == null)
sourceColor = EntityFolder.getDefaultColor(sources.get(0), context);
} else {
source = ContextCompat.getDrawable(context, R.drawable.twotone_folders_24);
source.setBounds(0, 0, source.getIntrinsicWidth(), source.getIntrinsicHeight());
sourceColor = null;
}
Drawable target = null;
if (targets.size() == 1) {
target = ContextCompat.getDrawable(context, EntityFolder.getIcon(targets.get(0)));
if (target != null)
target.setBounds(0, 0, target.getIntrinsicWidth(), target.getIntrinsicHeight());
if (targetColor == null)
targetColor = EntityFolder.getDefaultColor(targets.get(0), context);
} else
targetColor = null;
tvSourceFolders.setCompoundDrawablesRelative(source, null, null, null);
tvTargetFolders.setCompoundDrawablesRelative(target, null, null, null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (sourceColor != null)
tvSourceFolders.setCompoundDrawableTintList(ColorStateList.valueOf(sourceColor));
if (targetColor != null)
tvTargetFolders.setCompoundDrawableTintList(ColorStateList.valueOf(targetColor));
}
if (notagain != null)
cbNotAgain.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(buttonView.getContext());
prefs.edit().putBoolean(notagain, isChecked).apply();
}
});
tvJunkLearn.setVisibility(junk ? View.VISIBLE : View.GONE);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_CANCELED);
}
})
.create();
}
static String getNames(ArrayList<FragmentMessages.MessageTarget> result, boolean dest) {
boolean across = false;
for (FragmentMessages.MessageTarget target : result)
if (target.isAcross())
across = true;
Map<String, Integer> nameCount = new HashMap<>();
for (FragmentMessages.MessageTarget target : result) {
String name = "";
if (across)
name += (dest ? target.targetAccount.name : target.sourceAccount.name) + "/";
name += (dest ? target.targetFolder.display : target.sourceFolder.display);
if (!nameCount.containsKey(name))
nameCount.put(name, 0);
nameCount.put(name, nameCount.get(name) + 1);
}
List<String> keys = new ArrayList(nameCount.keySet());
Collator collator = Collator.getInstance(Locale.getDefault());
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
Collections.sort(keys, collator);
NumberFormat NF = NumberFormat.getNumberInstance();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
if (i > 0)
sb.append(", ");
sb.append(keys.get(i));
if (!dest && keys.size() > 1) {
int count = nameCount.get(keys.get(i));
sb.append('(').append(NF.format(count)).append(')');
}
}
return sb.toString();
}
}

@ -19,9 +19,6 @@ package eu.faircode.email;
Copyright 2018-2023 by Marcel Bokhorst (M66B) Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/ */
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -29,13 +26,10 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.widget.Group; import androidx.constraintlayout.widget.Group;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
@ -117,7 +111,7 @@ public class FragmentOperations extends FragmentBase {
onMenuHelp(); onMenuHelp();
return true; return true;
} else if (itemId == R.id.menu_delete) { } else if (itemId == R.id.menu_delete) {
new FragmentDialogDelete().show(getParentFragmentManager(), "operations:delete"); new FragmentDialogOperationsDelete().show(getParentFragmentManager(), "operations:delete");
return true; return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
@ -126,144 +120,4 @@ public class FragmentOperations extends FragmentBase {
private void onMenuHelp() { private void onMenuHelp() {
Helper.viewFAQ(getContext(), 3); Helper.viewFAQ(getContext(), 3);
} }
public static class FragmentDialogDelete extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Context context = getContext();
final View dview = LayoutInflater.from(context).inflate(R.layout.dialog_delete_operations, null);
final CheckBox cbError = dview.findViewById(R.id.cbError);
final CheckBox cbFetch = dview.findViewById(R.id.cbFetch);
final CheckBox cbMove = dview.findViewById(R.id.cbMove);
final CheckBox cbFlag = dview.findViewById(R.id.cbFlag);
final CheckBox cbDelete = dview.findViewById(R.id.cbDelete);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Bundle args = new Bundle();
args.putBoolean("error", cbError.isChecked());
args.putBoolean("fetch", cbFetch.isChecked());
args.putBoolean("move", cbMove.isChecked());
args.putBoolean("flag", cbFlag.isChecked());
args.putBoolean("delete", cbDelete.isChecked());
new SimpleTask<Integer>() {
private Toast toast = null;
@Override
protected void onPostExecute(Bundle args) {
toast = ToastEx.makeText(context, R.string.title_executing, Toast.LENGTH_LONG);
toast.show();
}
@Override
protected void onPreExecute(Bundle args) {
if (toast != null)
toast.cancel();
}
@Override
protected Integer onExecute(Context context, Bundle args) {
boolean error = args.getBoolean("error");
boolean fetch = args.getBoolean("fetch");
boolean move = args.getBoolean("move");
boolean flag = args.getBoolean("flag");
boolean delete = args.getBoolean("delete");
int deleted = 0;
DB db = DB.getInstance(context);
try {
db.beginTransaction();
List<EntityOperation> ops = new ArrayList<>();
// ADD, SEND, EXISTS, SUBSCRIBE
if (error)
addAll(ops, db.operation().getOperationsError());
if (fetch) {
addAll(ops, db.operation().getOperations(EntityOperation.FETCH));
addAll(ops, db.operation().getOperations(EntityOperation.DOWNLOAD));
addAll(ops, db.operation().getOperations(EntityOperation.RAW));
addAll(ops, db.operation().getOperations(EntityOperation.BODY));
addAll(ops, db.operation().getOperations(EntityOperation.ATTACHMENT));
addAll(ops, db.operation().getOperations(EntityOperation.HEADERS));
addAll(ops, db.operation().getOperations(EntityOperation.RULE));
addAll(ops, db.operation().getOperations(EntityOperation.SYNC));
}
if (move) {
addAll(ops, db.operation().getOperations(EntityOperation.MOVE));
addAll(ops, db.operation().getOperations(EntityOperation.COPY));
}
if (flag) {
addAll(ops, db.operation().getOperations(EntityOperation.SEEN));
addAll(ops, db.operation().getOperations(EntityOperation.ANSWERED));
addAll(ops, db.operation().getOperations(EntityOperation.FLAG));
addAll(ops, db.operation().getOperations(EntityOperation.KEYWORD));
addAll(ops, db.operation().getOperations(EntityOperation.LABEL));
addAll(ops, db.operation().getOperations(EntityOperation.REPORT));
}
if (delete) {
addAll(ops, db.operation().getOperations(EntityOperation.DELETE));
addAll(ops, db.operation().getOperations(EntityOperation.PURGE));
addAll(ops, db.operation().getOperations(EntityOperation.EXPUNGE));
}
for (EntityOperation op : ops) {
EntityLog.log(context, "Deleting operation=" + op.id + ":" + op.name + " error=" + op.error);
if (db.operation().deleteOperation(op.id) > 0) {
op.cleanup(context, false);
deleted++;
}
if (EntityOperation.SYNC.equals(op.name))
db.folder().setFolderSyncState(op.folder, null);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return deleted;
}
@Override
protected void onExecuted(Bundle args, Integer deleted) {
if (deleted == null)
deleted = -1;
Context context = getContext();
if (context == null)
return;
ToastEx.makeText(
context,
getString(R.string.title_delete_operation_deleted, deleted),
Toast.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
private void addAll(List<EntityOperation> list, List<EntityOperation> sublist) {
if (sublist != null)
list.addAll(sublist);
}
}.execute(context, getActivity(), args, "operations:delete");
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
} }

@ -19,11 +19,9 @@ package eu.faircode.email;
Copyright 2018-2023 by Marcel Bokhorst (M66B) Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/ */
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_GMAIL; import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_GMAIL;
import android.app.Dialog;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationChannelGroup; import android.app.NotificationChannelGroup;
import android.app.NotificationManager; import android.app.NotificationManager;
@ -38,19 +36,14 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.OperationCanceledException; import android.os.OperationCanceledException;
import android.text.Editable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.util.Pair; import android.util.Pair;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.Button; import android.widget.Button;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.CompoundButton; import android.widget.CompoundButton;
@ -1642,182 +1635,4 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere
} }
}.execute(FragmentOptionsBackup.this, args, "cloud"); }.execute(FragmentOptionsBackup.this, args, "cloud");
} }
public static class FragmentDialogExport extends FragmentDialogBase {
private TextInputLayout tilPassword1;
private TextInputLayout tilPassword2;
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putString("fair:password1", tilPassword1 == null ? null : tilPassword1.getEditText().getText().toString());
outState.putString("fair:password2", tilPassword2 == null ? null : tilPassword2.getEditText().getText().toString());
super.onSaveInstanceState(outState);
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_export, null);
tilPassword1 = dview.findViewById(R.id.tilPassword1);
tilPassword2 = dview.findViewById(R.id.tilPassword2);
if (savedInstanceState != null) {
tilPassword1.getEditText().setText(savedInstanceState.getString("fair:password1"));
tilPassword2.getEditText().setText(savedInstanceState.getString("fair:password2"));
}
Dialog dialog = new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(R.string.title_save_file, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ViewModelExport vme = new ViewModelProvider(getActivity()).get(ViewModelExport.class);
vme.setPassword(tilPassword1.getEditText().getText().toString());
sendResult(RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
return dialog;
}
@Override
public void onStart() {
super.onStart();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean debug = (BuildConfig.DEBUG || prefs.getBoolean("debug", false));
Button btnOk = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE);
TextWatcher w = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Do nothing
}
@Override
public void afterTextChanged(Editable s) {
String p1 = tilPassword1.getEditText().getText().toString();
String p2 = tilPassword2.getEditText().getText().toString();
btnOk.setEnabled((debug || !TextUtils.isEmpty(p1)) && p1.equals(p2));
tilPassword2.setHint(!TextUtils.isEmpty(p2) && !p2.equals(p1)
? R.string.title_setup_password_different
: R.string.title_setup_password_repeat);
}
};
tilPassword1.getEditText().addTextChangedListener(w);
tilPassword2.getEditText().addTextChangedListener(w);
w.afterTextChanged(null);
tilPassword2.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
btnOk.performClick();
return true;
} else
return false;
}
});
}
}
public static class FragmentDialogImport extends FragmentDialogBase {
private TextInputLayout tilPassword1;
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putString("fair:password1", tilPassword1 == null ? null : tilPassword1.getEditText().getText().toString());
super.onSaveInstanceState(outState);
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_import, null);
tilPassword1 = dview.findViewById(R.id.tilPassword1);
CheckBox cbAccounts = dview.findViewById(R.id.cbAccounts);
CheckBox cbDelete = dview.findViewById(R.id.cbDelete);
CheckBox cbRules = dview.findViewById(R.id.cbRules);
CheckBox cbContacts = dview.findViewById(R.id.cbContacts);
CheckBox cbAnswers = dview.findViewById(R.id.cbAnswers);
CheckBox cbSearches = dview.findViewById(R.id.cbSearches);
CheckBox cbSettings = dview.findViewById(R.id.cbSettings);
cbAccounts.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
cbRules.setEnabled(checked);
cbContacts.setEnabled(checked);
}
});
if (savedInstanceState != null)
tilPassword1.getEditText().setText(savedInstanceState.getString("fair:password1"));
Dialog dialog = new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(R.string.title_add_image_select, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password1 = tilPassword1.getEditText().getText().toString();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean debug = prefs.getBoolean("debug", false);
if (TextUtils.isEmpty(password1) && !(debug || BuildConfig.DEBUG)) {
ToastEx.makeText(context, R.string.title_setup_password_missing, Toast.LENGTH_LONG).show();
sendResult(RESULT_CANCELED);
} else {
ViewModelExport vme = new ViewModelProvider(getActivity()).get(ViewModelExport.class);
vme.setPassword(password1);
vme.setOptions("accounts", cbAccounts.isChecked());
vme.setOptions("delete", cbDelete.isChecked());
vme.setOptions("rules", cbRules.isChecked());
vme.setOptions("contacts", cbContacts.isChecked());
vme.setOptions("answers", cbAnswers.isChecked());
vme.setOptions("searches", cbSearches.isChecked());
vme.setOptions("settings", cbSettings.isChecked());
sendResult(RESULT_OK);
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
return dialog;
}
@Override
public void onStart() {
super.onStart();
Button btnOk = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE);
tilPassword1.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
btnOk.performClick();
return true;
} else
return false;
}
});
}
}
} }

@ -21,9 +21,6 @@ package eu.faircode.email;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -40,7 +37,6 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.EditText; import android.widget.EditText;
@ -48,18 +44,13 @@ import android.widget.ImageButton;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.SwitchCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import java.util.ArrayList;
import java.util.List;
public class FragmentOptionsBehavior extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener { public class FragmentOptionsBehavior extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener {
private View view; private View view;
private ImageButton ibHelp; private ImageButton ibHelp;
@ -730,123 +721,4 @@ public class FragmentOptionsBehavior extends FragmentBase implements SharedPrefe
else else
prefs.edit().putString("default_folder", uri.toString()).apply(); prefs.edit().putString("default_folder", uri.toString()).apply();
} }
public static class FragmentDialogSwipes extends FragmentDialogBase {
private Spinner spLeft;
private Spinner spRight;
private ArrayAdapter<EntityFolder> adapter;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_swipes, null);
spLeft = dview.findViewById(R.id.spLeft);
spRight = dview.findViewById(R.id.spRight);
adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item1, android.R.id.text1, new ArrayList<EntityFolder>());
adapter.setDropDownViewResource(R.layout.spinner_item1_dropdown);
spLeft.setAdapter(adapter);
spRight.setAdapter(adapter);
List<EntityFolder> folders = FragmentAccount.getFolderActions(getContext());
EntityFolder trash = new EntityFolder();
trash.id = 2L;
trash.name = getString(R.string.title_trash);
folders.add(1, trash);
EntityFolder archive = new EntityFolder();
archive.id = 1L;
archive.name = getString(R.string.title_archive);
folders.add(1, archive);
adapter.addAll(folders);
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
int leftPos = prefs.getInt("swipe_left_default", 2); // Trash
int rightPos = prefs.getInt("swipe_right_default", 1); // Archive
spLeft.setSelection(leftPos);
spRight.setSelection(rightPos);
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit()
.putInt("swipe_left_default", spLeft.getSelectedItemPosition())
.putInt("swipe_right_default", spRight.getSelectedItemPosition())
.apply();
EntityFolder left = (EntityFolder) spLeft.getSelectedItem();
EntityFolder right = (EntityFolder) spRight.getSelectedItem();
if ((left != null && EntityMessage.SWIPE_ACTION_HIDE.equals(left.id)) ||
(right != null && EntityMessage.SWIPE_ACTION_HIDE.equals(right.id)))
prefs.edit()
.putBoolean("message_tools", true)
.putBoolean("button_hide", true)
.apply();
Bundle args = new Bundle();
args.putLong("left", left == null ? 0 : left.id);
args.putLong("right", right == null ? 0 : right.id);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long left = args.getLong("left");
long right = args.getLong("right");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
List<EntityAccount> accounts = db.account().getAccounts();
for (EntityAccount account : accounts)
if (account.protocol == EntityAccount.TYPE_IMAP)
db.account().setAccountSwipes(
account.id,
getAction(context, left, account.id),
getAction(context, right, account.id));
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onExecuted(Bundle args, Void data) {
ToastEx.makeText(getContext(), R.string.title_completed, Toast.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
private Long getAction(Context context, long selection, long account) {
if (selection < 0)
return selection;
else if (selection == 0)
return null;
else {
DB db = DB.getInstance(context);
String type = (selection == 2 ? EntityFolder.TRASH : EntityFolder.ARCHIVE);
EntityFolder archive = db.folder().getFolderByType(account, type);
return (archive == null ? null : archive.id);
}
}
}.execute(getContext(), getViewLifecycleOwner(), args, "dialog:swipe");
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
} }

@ -19,30 +19,24 @@ package eu.faircode.email;
Copyright 2018-2023 by Marcel Bokhorst (M66B) Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/ */
import android.app.Dialog;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.Button; import android.widget.Button;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
@ -50,10 +44,8 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.SwitchCompat;
import androidx.constraintlayout.widget.Group; import androidx.constraintlayout.widget.Group;
import androidx.lifecycle.Lifecycle;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.webkit.WebViewFeature; import androidx.webkit.WebViewFeature;
@ -622,79 +614,4 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
swMnemonic.setChecked(mnemonic != null); swMnemonic.setChecked(mnemonic != null);
tvMnemonic.setText(mnemonic); tvMnemonic.setText(mnemonic);
} }
public static class FragmentDialogPin extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_pin_set, null);
final EditText etPin = dview.findViewById(R.id.etPin);
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String pin = etPin.getText().toString();
if (TextUtils.isEmpty(pin))
prefs.edit().remove("pin").apply();
else {
boolean pro = ActivityBilling.isPro(getContext());
if (pro) {
Helper.setAuthenticated(getContext());
prefs.edit()
.remove("biometrics")
.putString("pin", pin)
.apply();
} else
startActivity(new Intent(getContext(), ActivityBilling.class));
}
}
})
.setNegativeButton(android.R.string.cancel, null);
String pin = prefs.getString("pin", null);
if (!TextUtils.isEmpty(pin))
builder.setNeutralButton(R.string.title_reset, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().remove("pin").apply();
}
});
final Dialog dialog = builder.create();
etPin.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
((AlertDialog) getDialog()).getButton(DialogInterface.BUTTON_POSITIVE).performClick();
return true;
} else
return false;
}
});
etPin.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus)
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
});
ApplicationEx.getMainHandler().post(new Runnable() {
@Override
public void run() {
if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED))
return;
etPin.requestFocus();
}
});
return dialog;
}
}
} }

@ -22,7 +22,6 @@ package eu.faircode.email;
import android.app.Dialog; import android.app.Dialog;
import android.app.TimePickerDialog; import android.app.TimePickerDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Typeface; import android.graphics.Typeface;
@ -46,7 +45,6 @@ import android.widget.TimePicker;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.SwitchCompat;
import androidx.constraintlayout.widget.Group; import androidx.constraintlayout.widget.Group;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
@ -60,7 +58,6 @@ import androidx.recyclerview.widget.RecyclerView;
import java.text.DateFormatSymbols; import java.text.DateFormatSymbols;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -704,41 +701,6 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr
} }
} }
public static class FragmentDialogWeekend extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
boolean[] days = new boolean[7];
final Context context = getContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String[] daynames = Arrays.copyOfRange(new DateFormatSymbols().getWeekdays(), 1, 8);
String weekend = prefs.getString("weekend", Calendar.SATURDAY + "," + Calendar.SUNDAY);
for (String day : weekend.split(","))
days[Integer.parseInt(day) - 1] = true;
return new AlertDialog.Builder(context)
.setTitle(R.string.title_advanced_schedule_weekend)
.setMultiChoiceItems(daynames, days, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < days.length; i++)
if (days[i]) {
if (sb.length() > 0)
sb.append(",");
sb.append(i + 1);
}
prefs.edit().putString("weekend", sb.toString()).apply();
}
})
.setNegativeButton(R.string.title_setup_done, null)
.create();
}
}
public static class AdapterAccountExempted extends RecyclerView.Adapter<AdapterAccountExempted.ViewHolder> { public static class AdapterAccountExempted extends RecyclerView.Adapter<AdapterAccountExempted.ViewHolder> {
private Context context; private Context context;
private LifecycleOwner owner; private LifecycleOwner owner;

@ -58,14 +58,11 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.PopupMenu;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Group; import androidx.constraintlayout.widget.Group;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.Lifecycle; import androidx.lifecycle.Lifecycle;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
@ -201,8 +198,6 @@ public class FragmentRule extends FragmentBase {
private DateFormat DF; private DateFormat DF;
private final static int MAX_CHECK = 10;
private static final int REQUEST_SENDER = 1; private static final int REQUEST_SENDER = 1;
private static final int REQUEST_RECIPIENT = 2; private static final int REQUEST_RECIPIENT = 2;
private static final int REQUEST_COLOR = 3; private static final int REQUEST_COLOR = 3;
@ -1380,7 +1375,7 @@ public class FragmentRule extends FragmentBase {
args.putString("condition", jcondition.toString()); args.putString("condition", jcondition.toString());
args.putString("action", jaction.toString()); args.putString("action", jaction.toString());
FragmentDialogCheck fragment = new FragmentDialogCheck(); FragmentDialogRuleCheck fragment = new FragmentDialogRuleCheck();
fragment.setArguments(args); fragment.setArguments(args);
fragment.show(getParentFragmentManager(), "rule:check"); fragment.show(getParentFragmentManager(), "rule:check");
@ -1731,177 +1726,4 @@ public class FragmentRule extends FragmentBase {
sendResult(RESULT_OK); sendResult(RESULT_OK);
} }
} }
public static class FragmentDialogCheck extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
long folder = getArguments().getLong("folder");
boolean daily = getArguments().getBoolean("daily");
String condition = getArguments().getString("condition");
String action = getArguments().getString("action");
final View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_rule_match, null);
final TextView tvNoMessages = dview.findViewById(R.id.tvNoMessages);
final RecyclerView rvMessage = dview.findViewById(R.id.rvMessage);
final Button btnExecute = dview.findViewById(R.id.btnExecute);
final ContentLoadingProgressBar pbWait = dview.findViewById(R.id.pbWait);
rvMessage.setHasFixedSize(false);
LinearLayoutManager llm = new LinearLayoutManager(getContext());
rvMessage.setLayoutManager(llm);
final AdapterRuleMatch adapter = new AdapterRuleMatch(getContext(), getViewLifecycleOwner());
rvMessage.setAdapter(adapter);
tvNoMessages.setVisibility(View.GONE);
rvMessage.setVisibility(View.GONE);
btnExecute.setVisibility(View.GONE);
final Bundle args = new Bundle();
args.putLong("folder", folder);
args.putBoolean("daily", daily);
args.putString("condition", condition);
args.putString("action", action);
btnExecute.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new SimpleTask<Integer>() {
private Toast toast = null;
@Override
protected void onPreExecute(Bundle args) {
toast = ToastEx.makeText(getContext(), R.string.title_executing, Toast.LENGTH_LONG);
toast.show();
}
@Override
protected void onPostExecute(Bundle args) {
if (toast != null)
toast.cancel();
}
@Override
protected Integer onExecute(Context context, Bundle args) throws Throwable {
EntityRule rule = new EntityRule();
rule.folder = args.getLong("folder");
rule.daily = args.getBoolean("daily");
rule.condition = args.getString("condition");
rule.action = args.getString("action");
int applied = 0;
DB db = DB.getInstance(context);
List<Long> ids =
db.message().getMessageIdsByFolder(rule.folder);
for (long mid : ids)
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(mid);
if (message == null || message.ui_hide)
continue;
if (rule.matches(context, message, null, null))
if (rule.execute(context, message))
applied++;
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (applied > 0)
ServiceSynchronize.eval(context, "rules/manual");
return applied;
}
@Override
protected void onExecuted(Bundle args, Integer applied) {
dismiss();
ToastEx.makeText(getContext(), getString(R.string.title_rule_applied, applied), Toast.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException)
ToastEx.makeText(getContext(), ex.getMessage(), Toast.LENGTH_LONG).show();
else
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(FragmentDialogCheck.this, args, "rule:execute");
}
});
new SimpleTask<List<EntityMessage>>() {
@Override
protected void onPreExecute(Bundle args) {
pbWait.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(Bundle args) {
pbWait.setVisibility(View.GONE);
}
@Override
protected List<EntityMessage> onExecute(Context context, Bundle args) throws Throwable {
EntityRule rule = new EntityRule();
rule.folder = args.getLong("folder");
rule.daily = args.getBoolean("daily");
rule.condition = args.getString("condition");
rule.action = args.getString("action");
rule.validate(context);
List<EntityMessage> matching = new ArrayList<>();
DB db = DB.getInstance(context);
List<Long> ids =
db.message().getMessageIdsByFolder(rule.folder);
for (long id : ids) {
EntityMessage message = db.message().getMessage(id);
if (message == null)
continue;
if (rule.matches(context, message, null, null))
matching.add(message);
if (matching.size() >= MAX_CHECK)
break;
}
return matching;
}
@Override
protected void onExecuted(Bundle args, List<EntityMessage> messages) {
adapter.set(messages);
if (messages.size() > 0) {
rvMessage.setVisibility(View.VISIBLE);
btnExecute.setVisibility(View.VISIBLE);
} else
tvNoMessages.setVisibility(View.VISIBLE);
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException) {
tvNoMessages.setText(ex.getMessage());
tvNoMessages.setVisibility(View.VISIBLE);
} else
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "rule:check");
return new AlertDialog.Builder(getContext())
.setIcon(R.drawable.baseline_mail_outline_24)
.setTitle(R.string.title_rule_matched)
.setView(dview)
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
} }

@ -24,7 +24,6 @@ import static android.app.Activity.RESULT_OK;
import android.Manifest; import android.Manifest;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
@ -1236,27 +1235,4 @@ public class FragmentSetup extends FragmentBase implements SharedPreferences.OnS
tvNoInternet.setVisibility(available ? View.GONE : View.VISIBLE); tvNoInternet.setVisibility(available ? View.GONE : View.VISIBLE);
} }
}; };
public static class FragmentDialogDoze extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
return new AlertDialog.Builder(getContext())
.setIcon(R.drawable.twotone_info_24)
.setTitle(R.string.title_setup_doze)
.setMessage(R.string.title_setup_doze_instructions)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
startActivity(new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS));
} catch (Throwable ex) {
Log.e(ex);
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
} }

Loading…
Cancel
Save