From 4b832db3fcea0dec2ce5de36e5468ee031ab68fd Mon Sep 17 00:00:00 2001 From: M66B Date: Wed, 22 May 2019 09:01:25 +0200 Subject: [PATCH] Added rule check --- .../eu/faircode/email/AdapterRuleMatch.java | 189 ++++++++++++++++++ .../java/eu/faircode/email/EntityRule.java | 2 +- .../java/eu/faircode/email/FragmentRule.java | 87 ++++++++ app/src/main/res/layout/dialog_rule_match.xml | 36 ++++ app/src/main/res/layout/item_rule_match.xml | 39 ++++ app/src/main/res/menu/action_rule.xml | 5 + app/src/main/res/values/strings.xml | 5 + 7 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/eu/faircode/email/AdapterRuleMatch.java create mode 100644 app/src/main/res/layout/dialog_rule_match.xml create mode 100644 app/src/main/res/layout/item_rule_match.xml diff --git a/app/src/main/java/eu/faircode/email/AdapterRuleMatch.java b/app/src/main/java/eu/faircode/email/AdapterRuleMatch.java new file mode 100644 index 0000000000..a8fe5beedf --- /dev/null +++ b/app/src/main/java/eu/faircode/email/AdapterRuleMatch.java @@ -0,0 +1,189 @@ +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 . + + Copyright 2018-2019 by Marcel Bokhorst (M66B) +*/ + +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListUpdateCallback; +import androidx.recyclerview.widget.RecyclerView; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; + +public class AdapterRuleMatch extends RecyclerView.Adapter { + private Context context; + private LifecycleOwner owner; + private LayoutInflater inflater; + + private List items = new ArrayList<>(); + + private DateFormat DF = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.SHORT, SimpleDateFormat.SHORT); + + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + private View view; + private TextView tvTime; + private TextView tvSubject; + + ViewHolder(View itemView) { + super(itemView); + + view = itemView.findViewById(R.id.clItem); + tvTime = itemView.findViewById(R.id.tvTime); + tvSubject = itemView.findViewById(R.id.tvSubject); + } + + private void wire() { + view.setOnClickListener(this); + } + + private void unwire() { + view.setOnClickListener(null); + } + + private void bindTo(EntityMessage message) { + tvTime.setText(DF.format(message.received)); + tvSubject.setText(message.subject); + } + + @Override + public void onClick(View v) { + int pos = getAdapterPosition(); + if (pos == RecyclerView.NO_POSITION) + return; + + EntityMessage message = items.get(pos); + + LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); + lbm.sendBroadcast( + new Intent(ActivityView.ACTION_VIEW_THREAD) + .putExtra("account", message.account) + .putExtra("thread", message.thread) + .putExtra("id", message.id) + .putExtra("found", false)); + } + } + + AdapterRuleMatch(Context context, LifecycleOwner owner) { + this.context = context; + this.owner = owner; + this.inflater = LayoutInflater.from(context); + setHasStableIds(true); + } + + public void set(@NonNull List messages) { + Log.i("Set matched messages=" + messages.size()); + + DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new DiffCallback(items, messages), false); + + items = messages; + + diff.dispatchUpdatesTo(new ListUpdateCallback() { + @Override + public void onInserted(int position, int count) { + Log.i("Inserted @" + position + " #" + count); + } + + @Override + public void onRemoved(int position, int count) { + Log.i("Removed @" + position + " #" + count); + } + + @Override + public void onMoved(int fromPosition, int toPosition) { + Log.i("Moved " + fromPosition + ">" + toPosition); + } + + @Override + public void onChanged(int position, int count, Object payload) { + Log.i("Changed @" + position + " #" + count); + } + }); + diff.dispatchUpdatesTo(this); + } + + private class DiffCallback extends DiffUtil.Callback { + private List prev = new ArrayList<>(); + private List next = new ArrayList<>(); + + DiffCallback(List prev, List next) { + this.prev.addAll(prev); + this.next.addAll(next); + } + + @Override + public int getOldListSize() { + return prev.size(); + } + + @Override + public int getNewListSize() { + return next.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + EntityMessage m1 = prev.get(oldItemPosition); + EntityMessage m2 = next.get(newItemPosition); + return m1.id.equals(m2.id); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + EntityMessage m1 = prev.get(oldItemPosition); + EntityMessage m2 = next.get(newItemPosition); + return m1.id.equals(m2.id); + } + } + + @Override + public long getItemId(int position) { + return items.get(position).id; + } + + @Override + public int getItemCount() { + return items.size(); + } + + @Override + @NonNull + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(inflater.inflate(R.layout.item_rule_match, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.unwire(); + EntityMessage message = items.get(position); + holder.bindTo(message); + holder.wire(); + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/EntityRule.java b/app/src/main/java/eu/faircode/email/EntityRule.java index c47f0cea89..15752d6356 100644 --- a/app/src/main/java/eu/faircode/email/EntityRule.java +++ b/app/src/main/java/eu/faircode/email/EntityRule.java @@ -152,7 +152,7 @@ public class EntityRule { } JSONObject jheader = jcondition.optJSONObject("header"); - if (jheader != null) { + if (jheader != null && imessage != null) { String value = jheader.getString("value"); boolean regex = jheader.getBoolean("regex"); diff --git a/app/src/main/java/eu/faircode/email/FragmentRule.java b/app/src/main/java/eu/faircode/email/FragmentRule.java index cb5719b4c1..3fbc20c7e6 100644 --- a/app/src/main/java/eu/faircode/email/FragmentRule.java +++ b/app/src/main/java/eu/faircode/email/FragmentRule.java @@ -49,6 +49,8 @@ import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.Group; import androidx.fragment.app.FragmentTransaction; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import com.android.colorpicker.ColorPickerDialog; import com.android.colorpicker.ColorPickerSwatch; @@ -124,6 +126,8 @@ public class FragmentRule extends FragmentBase { private long folder = -1; private Integer color = null; + private final static int MAX_CHECK = 10; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -309,6 +313,9 @@ public class FragmentRule extends FragmentBase { case R.id.action_delete: onActionTrash(); return true; + case R.id.action_check: + onActionCheck(); + return true; case R.id.action_save: onActionSave(); return true; @@ -577,6 +584,86 @@ public class FragmentRule extends FragmentBase { .show(); } + private void onActionCheck() { + try { + JSONObject jcondition = getCondition(); + + JSONObject jheader = jcondition.optJSONObject("header"); + if (jheader != null) { + Snackbar.make(view, R.string.title_rule_no_headers, Snackbar.LENGTH_LONG).show(); + return; + } + + 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 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); + pbWait.setVisibility(View.VISIBLE); + + new DialogBuilderLifecycle(getContext(), getViewLifecycleOwner()) + .setTitle(R.string.title_rule_matched) + .setView(dview) + .show(); + + Bundle args = new Bundle(); + args.putLong("folder", folder); + args.putString("condition", jcondition.toString()); + + new SimpleTask>() { + @Override + protected List onExecute(Context context, Bundle args) throws Throwable { + long fid = args.getLong("folder"); + EntityRule rule = new EntityRule(); + rule.condition = args.getString("condition"); + + List matching = new ArrayList<>(); + + DB db = DB.getInstance(context); + List ids = db.message().getMessageIdsByFolder(fid); + for (long id : ids) { + EntityMessage message = db.message().getMessage(id); + + if (rule.matches(context, message, null)) + matching.add(message); + + if (matching.size() >= MAX_CHECK) + break; + } + + return matching; + } + + @Override + protected void onExecuted(Bundle args, List messages) { + adapter.set(messages); + + pbWait.setVisibility(View.GONE); + if (messages.size() > 0) + rvMessage.setVisibility(View.VISIBLE); + else + tvNoMessages.setVisibility(View.VISIBLE); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); + } + }.execute(this, args, "rule:check"); + } catch (JSONException ex) { + Log.e(ex); + } + } + private void onActionSave() { if (!Helper.isPro(getContext())) { FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); diff --git a/app/src/main/res/layout/dialog_rule_match.xml b/app/src/main/res/layout/dialog_rule_match.xml new file mode 100644 index 0000000000..42b6955d42 --- /dev/null +++ b/app/src/main/res/layout/dialog_rule_match.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_rule_match.xml b/app/src/main/res/layout/item_rule_match.xml new file mode 100644 index 0000000000..65a4a7baea --- /dev/null +++ b/app/src/main/res/layout/item_rule_match.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/action_rule.xml b/app/src/main/res/menu/action_rule.xml index 11299f84b7..3056c624ae 100644 --- a/app/src/main/res/menu/action_rule.xml +++ b/app/src/main/res/menu/action_rule.xml @@ -6,6 +6,11 @@ android:icon="@drawable/baseline_delete_24" android:title="@string/title_delete" /> + + Condition missing This will send the intent \'%1$s\' with the extras \'%2$s\' + Check + Header conditions cannot be checked + Matching messages + No matching messages + Synchronize Folders Messages