From 3185166d3271fd6317ecb86b03a9d896a9a44639 Mon Sep 17 00:00:00 2001 From: M66B Date: Sun, 4 Aug 2019 19:14:53 +0200 Subject: [PATCH] Added time condition to rule --- .../java/eu/faircode/email/AdapterRule.java | 2 + .../java/eu/faircode/email/EntityRule.java | 27 +++- .../java/eu/faircode/email/FragmentRule.java | 144 +++++++++++++++++- app/src/main/res/layout/fragment_rule.xml | 78 +++++++++- app/src/main/res/values/strings.xml | 2 + 5 files changed, 247 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/AdapterRule.java b/app/src/main/java/eu/faircode/email/AdapterRule.java index 160947ad15..58eeb5f181 100644 --- a/app/src/main/java/eu/faircode/email/AdapterRule.java +++ b/app/src/main/java/eu/faircode/email/AdapterRule.java @@ -110,6 +110,8 @@ public class AdapterRule extends RecyclerView.Adapter { condition.add(context.getString(R.string.title_rule_subject)); if (jcondition.has("header")) condition.add(context.getString(R.string.title_rule_header)); + if (jcondition.has("schedule")) + condition.add(context.getString(R.string.title_rule_time)); tvCondition.setText(TextUtils.join(" & ", condition)); } catch (Throwable ex) { tvCondition.setText(ex.getMessage()); diff --git a/app/src/main/java/eu/faircode/email/EntityRule.java b/app/src/main/java/eu/faircode/email/EntityRule.java index 45bfa8ffd6..6485dcf0fc 100644 --- a/app/src/main/java/eu/faircode/email/EntityRule.java +++ b/app/src/main/java/eu/faircode/email/EntityRule.java @@ -35,6 +35,7 @@ import org.json.JSONObject; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.Date; import java.util.Enumeration; import java.util.List; @@ -143,6 +144,7 @@ public class EntityRule { return false; } + // Subject JSONObject jsubject = jcondition.optJSONObject("subject"); if (jsubject != null) { String value = jsubject.getString("value"); @@ -152,6 +154,7 @@ public class EntityRule { return false; } + // Header JSONObject jheader = jcondition.optJSONObject("header"); if (jheader != null && imessage != null) { String value = jheader.getString("value"); @@ -171,8 +174,30 @@ public class EntityRule { return false; } + // Schedule + JSONObject jschedule = jcondition.optJSONObject("schedule"); + if (jschedule != null) { + int day = jschedule.optInt("day", -1) + 1; + int start = jschedule.optInt("start", 0); + int end = jschedule.optInt("end", 0); + if (end <= start) + end += 24 * 60; + + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date(message.received)); + int mday = cal.get(Calendar.DAY_OF_WEEK); + int mhour = cal.get(Calendar.HOUR_OF_DAY); + int mminute = cal.get(Calendar.MINUTE); + int minutes = mhour * 60 + mminute; + + if (day > 0 && mday != day) + return false; + if (minutes < start || minutes > end) + return false; + } + // Safeguard - if (jsender == null && jrecipient == null && jsubject == null && jheader == null) + if (jsender == null && jrecipient == null && jsubject == null && jheader == null && jschedule == null) return false; } catch (JSONException ex) { Log.e(ex); diff --git a/app/src/main/java/eu/faircode/email/FragmentRule.java b/app/src/main/java/eu/faircode/email/FragmentRule.java index 311d82ae7f..cac61ec538 100644 --- a/app/src/main/java/eu/faircode/email/FragmentRule.java +++ b/app/src/main/java/eu/faircode/email/FragmentRule.java @@ -20,6 +20,7 @@ package eu.faircode.email; */ import android.app.Dialog; +import android.app.TimePickerDialog; import android.content.Context; import android.content.Intent; import android.database.Cursor; @@ -30,6 +31,7 @@ import android.os.Bundle; import android.os.Handler; import android.provider.ContactsContract; import android.text.TextUtils; +import android.text.format.DateFormat; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -45,12 +47,15 @@ import android.widget.NumberPicker; import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; +import android.widget.TimePicker; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.Group; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -61,7 +66,10 @@ import com.google.android.material.snackbar.Snackbar; import org.json.JSONException; import org.json.JSONObject; +import java.text.DateFormatSymbols; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; import java.util.List; @@ -92,6 +100,10 @@ public class FragmentRule extends FragmentBase { private EditText etHeader; private CheckBox cbHeader; + private Spinner spScheduleDay; + private TextView tvScheduleStart; + private TextView tvScheduleEnd; + private Spinner spAction; private TextView tvActionRemark; @@ -123,6 +135,7 @@ public class FragmentRule extends FragmentBase { private Group grpAnswer; private Group grpAutomation; + private ArrayAdapter adapterDay; private ArrayAdapter adapterAction; private ArrayAdapter adapterTarget; private ArrayAdapter adapterIdentity; @@ -139,6 +152,8 @@ public class FragmentRule extends FragmentBase { private static final int REQUEST_RECIPIENT = 2; private static final int REQUEST_COLOR = 3; private final static int REQUEST_DELETE = 4; + private final static int REQUEST_SCHEDULE_START = 5; + private final static int REQUEST_SCHEDULE_END = 6; @Override public void onCreate(Bundle savedInstanceState) { @@ -182,12 +197,14 @@ public class FragmentRule extends FragmentBase { etHeader = view.findViewById(R.id.etHeader); cbHeader = view.findViewById(R.id.cbHeader); + spScheduleDay = view.findViewById(R.id.spScheduleDay); + tvScheduleStart = view.findViewById(R.id.tvScheduleStart); + tvScheduleEnd = view.findViewById(R.id.tvScheduleEnd); + spAction = view.findViewById(R.id.spAction); tvActionRemark = view.findViewById(R.id.tvActionRemark); npDuration = view.findViewById(R.id.npDuration); - npDuration.setMinValue(1); - npDuration.setMaxValue(99); btnColor = view.findViewById(R.id.btnColor); vwColor = view.findViewById(R.id.vwColor); @@ -238,6 +255,10 @@ public class FragmentRule extends FragmentBase { } }); + adapterDay = new ArrayAdapter<>(getContext(), R.layout.spinner_item1, android.R.id.text1, new ArrayList()); + adapterDay.setDropDownViewResource(R.layout.spinner_item1_dropdown); + spScheduleDay.setAdapter(adapterDay); + adapterAction = new ArrayAdapter<>(getContext(), R.layout.spinner_item1, android.R.id.text1, new ArrayList()); adapterAction.setDropDownViewResource(R.layout.spinner_item1_dropdown); spAction.setAdapter(adapterAction); @@ -254,6 +275,37 @@ public class FragmentRule extends FragmentBase { adapterAnswer.setDropDownViewResource(R.layout.spinner_item1_dropdown); spAnswer.setAdapter(adapterAnswer); + adapterDay.add(getString(R.string.title_any)); + String[] dayNames = DateFormatSymbols.getInstance().getWeekdays(); + for (int day = Calendar.SUNDAY; day <= Calendar.SATURDAY; day++) + adapterDay.add(dayNames[day]); + + tvScheduleStart.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Object time = v.getTag(); + Bundle args = new Bundle(); + args.putLong("minutes", time == null ? 0 : (int) time); + DialogFragment timePicker = new TimePickerFragment(); + timePicker.setArguments(args); + timePicker.setTargetFragment(FragmentRule.this, REQUEST_SCHEDULE_START); + timePicker.show(getFragmentManager(), "timePicker"); + } + }); + + tvScheduleEnd.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Object time = v.getTag(); + Bundle args = new Bundle(); + args.putLong("minutes", time == null ? 0 : (int) time); + DialogFragment timePicker = new TimePickerFragment(); + timePicker.setArguments(args); + timePicker.setTargetFragment(FragmentRule.this, REQUEST_SCHEDULE_END); + timePicker.show(getFragmentManager(), "timePicker"); + } + }); + List actions = new ArrayList<>(); actions.add(new Action(EntityRule.TYPE_SEEN, getString(R.string.title_rule_seen))); actions.add(new Action(EntityRule.TYPE_UNSEEN, getString(R.string.title_rule_unseen))); @@ -293,6 +345,9 @@ public class FragmentRule extends FragmentBase { } }); + npDuration.setMinValue(1); + npDuration.setMaxValue(99); + tvActionRemark.setVisibility(View.GONE); onSelectColor(color); @@ -443,6 +498,14 @@ public class FragmentRule extends FragmentBase { if (resultCode == RESULT_OK) onDelete(); break; + case REQUEST_SCHEDULE_START: + if (resultCode == RESULT_OK) + onScheduleStart(data); + break; + case REQUEST_SCHEDULE_END: + if (resultCode == RESULT_OK) + onScheduleEnd(data); + break; } } catch (Throwable ex) { Log.e(ex); @@ -511,6 +574,18 @@ public class FragmentRule extends FragmentBase { }.execute(FragmentRule.this, args, "rule:delete"); } + private void onScheduleStart(Intent data) { + int minutes = data.getIntExtra("minutes", 0); + tvScheduleStart.setTag(minutes); + tvScheduleStart.setText(formatHour(getContext(), minutes)); + } + + private void onScheduleEnd(Intent data) { + int minutes = data.getIntExtra("minutes", 0); + tvScheduleEnd.setTag(minutes); + tvScheduleEnd.setText(formatHour(getContext(), minutes)); + } + private void loadRule() { Bundle rargs = new Bundle(); rargs.putLong("id", id); @@ -535,6 +610,7 @@ public class FragmentRule extends FragmentBase { JSONObject jrecipient = jcondition.optJSONObject("recipient"); JSONObject jsubject = jcondition.optJSONObject("subject"); JSONObject jheader = jcondition.optJSONObject("header"); + JSONObject jschedule = jcondition.optJSONObject("schedule"); etName.setText(rule == null ? args.getString("subject") : rule.name); etOrder.setText(rule == null ? null : Integer.toString(rule.order)); @@ -553,6 +629,15 @@ public class FragmentRule extends FragmentBase { etHeader.setText(jheader == null ? null : jheader.getString("value")); cbHeader.setChecked(jheader != null && jheader.getBoolean("regex")); + if (jschedule != null && jschedule.has("day")) + spScheduleDay.setSelection(jschedule.getInt("day") + 1); + int start = (jschedule != null && jschedule.has("start") ? jschedule.getInt("start") : 0); + tvScheduleStart.setTag(start); + tvScheduleStart.setText(formatHour(getContext(), start)); + int end = (jschedule != null && jschedule.has("end") ? jschedule.getInt("end") : 0); + tvScheduleEnd.setTag(end); + tvScheduleEnd.setText(formatHour(getContext(), end)); + if (rule == null) { for (int pos = 0; pos < adapterIdentity.getCount(); pos++) if (adapterIdentity.getItem(pos).primary) { @@ -721,8 +806,9 @@ public class FragmentRule extends FragmentBase { JSONObject jrecipient = jcondition.optJSONObject("recipient"); JSONObject jsubject = jcondition.optJSONObject("subject"); JSONObject jheader = jcondition.optJSONObject("header"); + JSONObject jschedule = jcondition.optJSONObject("schedule"); - if (jsender == null && jrecipient == null && jsubject == null && jheader == null) + if (jsender == null && jrecipient == null && jsubject == null && jheader == null && jschedule == null) throw new IllegalArgumentException(context.getString(R.string.title_rule_condition_missing)); if (TextUtils.isEmpty(order)) @@ -807,6 +893,20 @@ public class FragmentRule extends FragmentBase { jcondition.put("header", jheader); } + Object start = tvScheduleStart.getTag(); + Object end = tvScheduleEnd.getTag(); + if (start == null) + start = 0; + if (end == null) + end = 0; + if (!start.equals(end)) { + JSONObject jschedule = new JSONObject(); + jschedule.put("day", spScheduleDay.getSelectedItemPosition() - 1); + jschedule.put("start", (int) start); + jschedule.put("end", (int) end); + jcondition.put("schedule", jschedule); + } + return jcondition; } @@ -871,6 +971,44 @@ public class FragmentRule extends FragmentBase { } } + private String formatHour(Context context, int minutes) { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, minutes / 60); + cal.set(Calendar.MINUTE, minutes % 60); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return Helper.getTimeInstance(context, SimpleDateFormat.SHORT).format(cal.getTime()); + } + + public static class TimePickerFragment extends FragmentDialogEx implements TimePickerDialog.OnTimeSetListener { + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Bundle args = getArguments(); + int minutes = args.getInt("minutes"); + + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, minutes / 60); + cal.set(Calendar.MINUTE, minutes % 60); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + return new TimePickerDialog(getContext(), this, + cal.get(Calendar.HOUR_OF_DAY), + cal.get(Calendar.MINUTE), + DateFormat.is24HourFormat(getContext())); + } + + public void onTimeSet(TimePicker view, int hour, int minute) { + Fragment target = getTargetFragment(); + if (target != null) { + Intent data = new Intent(); + data.putExtra("minutes", hour * 60 + minute); + target.onActivityResult(getTargetRequestCode(), RESULT_OK, data); + } + } + } + public static class FragmentDialogCheck extends FragmentDialogEx { @NonNull @Override diff --git a/app/src/main/res/layout/fragment_rule.xml b/app/src/main/res/layout/fragment_rule.xml index 0545d25bd8..741c0e9987 100644 --- a/app/src/main/res/layout/fragment_rule.xml +++ b/app/src/main/res/layout/fragment_rule.xml @@ -324,6 +324,79 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/cbHeader" /> + + + + + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/tvScheduleStart" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cb259fc9eb..396cef09de 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -614,6 +614,7 @@ Recipient contains Subject contains Header contains + Time between Regex AND Action @@ -755,6 +756,7 @@ Report Fix Enable + Any Executing Completed Ask what to do