mirror of
https://github.com/M66B/FairEmail.git
synced 2025-01-01 12:44:42 +00:00
Rule execution
This commit is contained in:
parent
699d1be7ec
commit
97c4d39a0f
10 changed files with 174 additions and 85 deletions
|
@ -45,18 +45,20 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
|
|||
private LifecycleOwner owner;
|
||||
private LayoutInflater inflater;
|
||||
|
||||
private List<EntityRule> all = new ArrayList<>();
|
||||
private List<EntityRule> filtered = new ArrayList<>();
|
||||
private List<TupleRuleEx> all = new ArrayList<>();
|
||||
private List<TupleRuleEx> filtered = new ArrayList<>();
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
private View itemView;
|
||||
private TextView tvName;
|
||||
private TextView tvFolder;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.itemView = itemView.findViewById(R.id.clItem);
|
||||
tvName = itemView.findViewById(R.id.tvName);
|
||||
tvFolder = itemView.findViewById(R.id.tvFolder);
|
||||
}
|
||||
|
||||
private void wire() {
|
||||
|
@ -67,9 +69,10 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
|
|||
itemView.setOnClickListener(null);
|
||||
}
|
||||
|
||||
private void bindTo(EntityRule rule) {
|
||||
private void bindTo(TupleRuleEx rule) {
|
||||
itemView.setActivated(!rule.enabled);
|
||||
tvName.setText(rule.name);
|
||||
tvFolder.setText(rule.folderName + "/" + rule.accountName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -78,7 +81,7 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
|
|||
if (pos == RecyclerView.NO_POSITION)
|
||||
return;
|
||||
|
||||
EntityRule rule = filtered.get(pos);
|
||||
TupleRuleEx rule = filtered.get(pos);
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
|
@ -94,15 +97,24 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
|
|||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void set(@NonNull List<EntityRule> rules) {
|
||||
public void set(@NonNull List<TupleRuleEx> rules) {
|
||||
Log.i("Set rules=" + rules.size());
|
||||
|
||||
final Collator collator = Collator.getInstance(Locale.getDefault());
|
||||
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
|
||||
|
||||
Collections.sort(rules, new Comparator<EntityRule>() {
|
||||
Collections.sort(rules, new Comparator<TupleRuleEx>() {
|
||||
@Override
|
||||
public int compare(EntityRule r1, EntityRule r2) {
|
||||
public int compare(TupleRuleEx r1, TupleRuleEx r2) {
|
||||
int a = collator.compare(r1.accountName, r2.accountName);
|
||||
if (a != 0)
|
||||
return a;
|
||||
int f = collator.compare(r1.folderName, r2.folderName);
|
||||
if (f != 0)
|
||||
return f;
|
||||
int o = ((Integer) r1.order).compareTo(r2.order);
|
||||
if (o != 0)
|
||||
return 0;
|
||||
return collator.compare(r1.name, r2.name);
|
||||
}
|
||||
});
|
||||
|
@ -139,10 +151,10 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
|
|||
}
|
||||
|
||||
private class MessageDiffCallback extends DiffUtil.Callback {
|
||||
private List<EntityRule> prev;
|
||||
private List<EntityRule> next;
|
||||
private List<TupleRuleEx> prev;
|
||||
private List<TupleRuleEx> next;
|
||||
|
||||
MessageDiffCallback(List<EntityRule> prev, List<EntityRule> next) {
|
||||
MessageDiffCallback(List<TupleRuleEx> prev, List<TupleRuleEx> next) {
|
||||
this.prev = prev;
|
||||
this.next = next;
|
||||
}
|
||||
|
@ -159,15 +171,15 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
|
|||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
EntityRule r1 = prev.get(oldItemPosition);
|
||||
EntityRule r2 = next.get(newItemPosition);
|
||||
TupleRuleEx r1 = prev.get(oldItemPosition);
|
||||
TupleRuleEx r2 = next.get(newItemPosition);
|
||||
return r1.id.equals(r2.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
EntityRule r1 = prev.get(oldItemPosition);
|
||||
EntityRule r2 = next.get(newItemPosition);
|
||||
TupleRuleEx r1 = prev.get(oldItemPosition);
|
||||
TupleRuleEx r2 = next.get(newItemPosition);
|
||||
return r1.equals(r2);
|
||||
}
|
||||
}
|
||||
|
@ -191,7 +203,7 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
|
|||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.unwire();
|
||||
EntityRule rule = filtered.get(position);
|
||||
TupleRuleEx rule = filtered.get(position);
|
||||
holder.bindTo(rule);
|
||||
holder.wire();
|
||||
}
|
||||
|
|
|
@ -31,16 +31,20 @@ import androidx.room.Update;
|
|||
public interface DaoRule {
|
||||
@Query("SELECT * FROM rule" +
|
||||
" WHERE folder = :folder" +
|
||||
" AND enabled" +
|
||||
" ORDER BY `order`")
|
||||
List<EntityRule> getRules(long folder);
|
||||
List<EntityRule> getEnabledRules(long folder);
|
||||
|
||||
@Query("SELECT rule.*, folder.account FROM rule" +
|
||||
@Query("SELECT rule.*, folder.account, folder.name AS folderName, account.name AS accountName FROM rule" +
|
||||
" JOIN folder ON folder.id = rule.folder" +
|
||||
" JOIN account ON account.id = folder.account" +
|
||||
" WHERE rule.id = :id")
|
||||
TupleRuleEx getRule(long id);
|
||||
|
||||
@Query("SELECT * FROM rule ORDER BY `order`")
|
||||
LiveData<List<EntityRule>> liveRules();
|
||||
@Query("SELECT rule.*, folder.account, folder.name AS folderName, account.name AS accountName FROM rule" +
|
||||
" JOIN folder ON folder.id = rule.folder" +
|
||||
" JOIN account ON account.id = folder.account")
|
||||
LiveData<List<TupleRuleEx>> liveRules();
|
||||
|
||||
@Insert
|
||||
long insertRule(EntityRule rule);
|
||||
|
|
|
@ -67,28 +67,32 @@ public class EntityRule {
|
|||
static final int TYPE_UNSEEN = 2;
|
||||
static final int TYPE_MOVE = 3;
|
||||
|
||||
boolean matches(Context context, EntityMessage message) throws JSONException, IOException {
|
||||
JSONObject jcondition = new JSONObject(condition);
|
||||
String sender = jcondition.getString("sender");
|
||||
String subject = jcondition.getString("subject");
|
||||
String text = jcondition.getString("text");
|
||||
boolean regex = jcondition.getBoolean("regex");
|
||||
boolean matches(Context context, EntityMessage message) throws IOException {
|
||||
try {
|
||||
JSONObject jcondition = new JSONObject(condition);
|
||||
String sender = jcondition.optString("sender", null);
|
||||
String subject = jcondition.optString("subject", null);
|
||||
String text = jcondition.optString("text", null);
|
||||
boolean regex = jcondition.optBoolean("regex", false);
|
||||
|
||||
if (sender != null && message.from != null) {
|
||||
if (matches(sender, MessageHelper.getFormattedAddresses(message.from, true), regex))
|
||||
return true;
|
||||
}
|
||||
if (sender != null && message.from != null) {
|
||||
if (matches(sender, MessageHelper.getFormattedAddresses(message.from, true), regex))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (subject != null && message.subject != null) {
|
||||
if (matches(subject, message.subject, regex))
|
||||
return true;
|
||||
}
|
||||
if (subject != null && message.subject != null) {
|
||||
if (matches(subject, message.subject, regex))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (text != null && message.content) {
|
||||
String body = message.read(context);
|
||||
String santized = HtmlHelper.sanitize(body, true);
|
||||
if (matches(text, santized, regex))
|
||||
return true;
|
||||
if (text != null && message.content) {
|
||||
String body = message.read(context);
|
||||
String santized = HtmlHelper.sanitize(body, true);
|
||||
if (matches(text, santized, regex))
|
||||
return true;
|
||||
}
|
||||
} catch (JSONException ex) {
|
||||
Log.e(ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -102,30 +106,35 @@ public class EntityRule {
|
|||
return haystack.contains(needle);
|
||||
}
|
||||
|
||||
void execute(Context context, DB db, EntityMessage message) throws JSONException {
|
||||
JSONObject jargs = new JSONObject(action);
|
||||
switch (jargs.getInt("type")) {
|
||||
case TYPE_SEEN:
|
||||
onActionSeen(context, db, message, true);
|
||||
break;
|
||||
case TYPE_UNSEEN:
|
||||
onActionSeen(context, db, message, false);
|
||||
break;
|
||||
case TYPE_MOVE:
|
||||
onActionMove(context, db, message, jargs);
|
||||
break;
|
||||
void execute(Context context, DB db, EntityMessage message) {
|
||||
try {
|
||||
JSONObject jargs = new JSONObject(action);
|
||||
int type = jargs.getInt("type");
|
||||
Log.i("Executing rule=" + type + " message=" + message.id);
|
||||
|
||||
switch (type) {
|
||||
case TYPE_SEEN:
|
||||
onActionSeen(context, db, message, true);
|
||||
break;
|
||||
case TYPE_UNSEEN:
|
||||
onActionSeen(context, db, message, false);
|
||||
break;
|
||||
case TYPE_MOVE:
|
||||
onActionMove(context, db, message, jargs);
|
||||
break;
|
||||
}
|
||||
} catch (JSONException ex) {
|
||||
Log.e(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void onActionSeen(Context context, DB db, EntityMessage message, boolean seen) {
|
||||
EntityOperation.queue(context, db, message, EntityOperation.SEEN, seen);
|
||||
message.seen = seen;
|
||||
}
|
||||
|
||||
private void onActionMove(Context context, DB db, EntityMessage message, JSONObject jargs) throws JSONException {
|
||||
long target = jargs.getLong("target");
|
||||
boolean seen = jargs.getBoolean("seen");
|
||||
if (seen)
|
||||
EntityOperation.queue(context, db, message, EntityOperation.SEEN, true);
|
||||
EntityOperation.queue(context, db, message, EntityOperation.MOVE, target);
|
||||
}
|
||||
|
||||
|
|
|
@ -58,8 +58,7 @@ public class FragmentRule extends FragmentBase {
|
|||
private EditText etSubject;
|
||||
private EditText etText;
|
||||
private Spinner spAction;
|
||||
private Spinner spMove;
|
||||
private CheckBox cbMoveSeen;
|
||||
private Spinner spTarget;
|
||||
private BottomNavigationView bottom_navigation;
|
||||
private ContentLoadingProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
|
@ -68,6 +67,7 @@ public class FragmentRule extends FragmentBase {
|
|||
private ArrayAdapter<EntityAccount> adapterAccount;
|
||||
private ArrayAdapter<EntityFolder> adapterFolder;
|
||||
private ArrayAdapter<Action> adapterAction;
|
||||
private ArrayAdapter<EntityFolder> adapterTarget;
|
||||
|
||||
private long id = -1;
|
||||
|
||||
|
@ -95,8 +95,7 @@ public class FragmentRule extends FragmentBase {
|
|||
etSubject = view.findViewById(R.id.etSubject);
|
||||
etText = view.findViewById(R.id.etText);
|
||||
spAction = view.findViewById(R.id.spAction);
|
||||
spMove = view.findViewById(R.id.spMove);
|
||||
cbMoveSeen = view.findViewById(R.id.cbMoveSeen);
|
||||
spTarget = view.findViewById(R.id.spTarget);
|
||||
bottom_navigation = view.findViewById(R.id.bottom_navigation);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
|
@ -114,10 +113,14 @@ public class FragmentRule extends FragmentBase {
|
|||
adapterAction.setDropDownViewResource(R.layout.spinner_item1_dropdown);
|
||||
spAction.setAdapter(adapterAction);
|
||||
|
||||
adapterTarget = new ArrayAdapter<>(getContext(), R.layout.spinner_item1, android.R.id.text1, new ArrayList<EntityFolder>());
|
||||
adapterTarget.setDropDownViewResource(R.layout.spinner_item1_dropdown);
|
||||
spTarget.setAdapter(adapterTarget);
|
||||
|
||||
List<Action> actions = new ArrayList<>();
|
||||
actions.add(new Action(EntityRule.TYPE_SEEN, getString(R.string.title_seen)));
|
||||
actions.add(new Action(EntityRule.TYPE_UNSEEN, getString(R.string.title_unseen)));
|
||||
//actions.add(new Action(EntityRule.TYPE_MOVE, getString(R.string.title_move)));
|
||||
actions.add(new Action(EntityRule.TYPE_MOVE, getString(R.string.title_move)));
|
||||
adapterAction.addAll(actions);
|
||||
|
||||
spAccount.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
|
@ -133,6 +136,23 @@ public class FragmentRule extends FragmentBase {
|
|||
}
|
||||
});
|
||||
|
||||
spAction.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
Action action = (Action) adapterView.getAdapter().getItem(position);
|
||||
onActionSelected(action.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
onActionSelected(-1);
|
||||
}
|
||||
|
||||
private void onActionSelected(int type) {
|
||||
grpMove.setVisibility(type == EntityRule.TYPE_MOVE ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
bottom_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(MenuItem menuItem) {
|
||||
|
@ -223,6 +243,9 @@ public class FragmentRule extends FragmentBase {
|
|||
spAccount.setTag(rule.account);
|
||||
spFolder.setTag(rule.folder);
|
||||
|
||||
if (type == EntityRule.TYPE_MOVE)
|
||||
spTarget.setTag(jaction.getLong("target"));
|
||||
|
||||
for (int pos = 0; pos < adapterAccount.getCount(); pos++)
|
||||
if (adapterAccount.getItem(pos).id.equals(rule.account)) {
|
||||
spAccount.setSelection(pos);
|
||||
|
@ -265,6 +288,9 @@ public class FragmentRule extends FragmentBase {
|
|||
adapterFolder.clear();
|
||||
adapterFolder.addAll(folders);
|
||||
|
||||
adapterTarget.clear();
|
||||
adapterTarget.addAll(folders);
|
||||
|
||||
long account = args.getLong("account");
|
||||
if (account == (Long) spAccount.getTag()) {
|
||||
Long folder = (Long) spFolder.getTag();
|
||||
|
@ -273,8 +299,17 @@ public class FragmentRule extends FragmentBase {
|
|||
spFolder.setSelection(pos);
|
||||
break;
|
||||
}
|
||||
} else
|
||||
|
||||
Long target = (Long) spTarget.getTag();
|
||||
for (int pos = 0; pos < folders.size(); pos++)
|
||||
if (folders.get(pos).id.equals(target)) {
|
||||
spTarget.setSelection(pos);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
spFolder.setSelection(0);
|
||||
spTarget.setSelection(0);
|
||||
}
|
||||
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
bottom_navigation.setVisibility(View.VISIBLE);
|
||||
|
@ -352,8 +387,13 @@ public class FragmentRule extends FragmentBase {
|
|||
Action action = (Action) spAction.getSelectedItem();
|
||||
|
||||
JSONObject jaction = new JSONObject();
|
||||
if (action != null)
|
||||
if (action != null) {
|
||||
jaction.put("type", action.type);
|
||||
if (action.type == EntityRule.TYPE_MOVE) {
|
||||
EntityFolder target = (EntityFolder) spTarget.getSelectedItem();
|
||||
jaction.put("target", target.id);
|
||||
}
|
||||
}
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
|
|
|
@ -88,9 +88,9 @@ public class FragmentRules extends FragmentBase {
|
|||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
db.rule().liveRules().observe(getViewLifecycleOwner(), new Observer<List<EntityRule>>() {
|
||||
db.rule().liveRules().observe(getViewLifecycleOwner(), new Observer<List<TupleRuleEx>>() {
|
||||
@Override
|
||||
public void onChanged(List<EntityRule> rules) {
|
||||
public void onChanged(List<TupleRuleEx> rules) {
|
||||
if (rules == null)
|
||||
rules = new ArrayList<>();
|
||||
|
||||
|
|
|
@ -1037,7 +1037,9 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
db.beginTransaction();
|
||||
downloadMessage(ServiceSynchronize.this,
|
||||
folder, ifolder, (IMAPMessage) imessage,
|
||||
message.id, db.folder().getFolderDownload(folder.id));
|
||||
message.id,
|
||||
db.folder().getFolderDownload(folder.id),
|
||||
db.rule().getEnabledRules(folder.id));
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
@ -1124,7 +1126,9 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
db.beginTransaction();
|
||||
downloadMessage(ServiceSynchronize.this,
|
||||
folder, ifolder, (IMAPMessage) e.getMessage(),
|
||||
message.id, db.folder().getFolderDownload(folder.id));
|
||||
message.id,
|
||||
db.folder().getFolderDownload(folder.id),
|
||||
db.rule().getEnabledRules(folder.id));
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
@ -2300,6 +2304,8 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
|
||||
db.folder().setFolderSyncState(folder.id, "downloading");
|
||||
|
||||
List<EntityRule> rules = db.rule().getEnabledRules(folder.id);
|
||||
|
||||
//fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
|
||||
|
||||
// Download messages/attachments
|
||||
|
@ -2317,7 +2323,7 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
downloadMessage(
|
||||
this,
|
||||
folder, ifolder, (IMAPMessage) isub[j],
|
||||
ids[from + j], download);
|
||||
ids[from + j], download, rules);
|
||||
db.setTransactionSuccessful();
|
||||
} catch (FolderClosedException ex) {
|
||||
throw ex;
|
||||
|
@ -2575,7 +2581,7 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
static void downloadMessage(
|
||||
Context context,
|
||||
EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage,
|
||||
long id, boolean download) throws MessagingException, IOException {
|
||||
long id, boolean download, List<EntityRule> rules) throws MessagingException, IOException {
|
||||
DB db = DB.getInstance(context);
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
if (message == null)
|
||||
|
@ -2584,7 +2590,7 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
if (message.setContactInfo(context))
|
||||
db.message().updateMessage(message);
|
||||
|
||||
if (download) {
|
||||
if (download || rules.size() > 0) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
long maxSize = prefs.getInt("download", 32768);
|
||||
if (maxSize == 0)
|
||||
|
@ -2624,7 +2630,7 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
|
||||
MessageHelper.MessageParts parts = helper.getMessageParts();
|
||||
|
||||
if (!message.content)
|
||||
if (!message.content) {
|
||||
if (!metered || (message.size != null && message.size < maxSize)) {
|
||||
String body = parts.getHtml(context);
|
||||
message.write(context, body);
|
||||
|
@ -2633,6 +2639,11 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
Log.i(folder.name + " downloaded message id=" + message.id + " size=" + message.size);
|
||||
}
|
||||
|
||||
for (EntityRule rule : rules)
|
||||
if (rule.matches(context, message))
|
||||
rule.execute(context, db, message);
|
||||
}
|
||||
|
||||
for (int i = 0; i < attachments.size(); i++) {
|
||||
EntityAttachment attachment = attachments.get(i);
|
||||
if (!attachment.available)
|
||||
|
|
|
@ -21,13 +21,17 @@ package eu.faircode.email;
|
|||
|
||||
public class TupleRuleEx extends EntityRule {
|
||||
public long account;
|
||||
public String folderName;
|
||||
public String accountName;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof TupleRuleEx) {
|
||||
TupleRuleEx other = (TupleRuleEx) obj;
|
||||
return (super.equals(obj) &&
|
||||
this.account == other.account);
|
||||
this.account == other.account &&
|
||||
(this.folderName == null ? other.folderName == null : this.folderName.equals(other.folderName)) &&
|
||||
(this.accountName == null ? other.accountName == null : this.accountName.equals(other.accountName)));
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.sun.mail.imap.IMAPMessage;
|
|||
import com.sun.mail.imap.IMAPStore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
@ -230,7 +231,8 @@ public class ViewModelBrowse extends ViewModel {
|
|||
message = ServiceSynchronize.synchronizeMessage(state.context,
|
||||
folder, state.ifolder, (IMAPMessage) isub[j], true);
|
||||
ServiceSynchronize.downloadMessage(state.context,
|
||||
folder, state.ifolder, (IMAPMessage) isub[j], message.id, false);
|
||||
folder, state.ifolder, (IMAPMessage) isub[j], message.id,
|
||||
false, new ArrayList<EntityRule>());
|
||||
count++;
|
||||
}
|
||||
db.message().setMessageFound(message.account, message.thread);
|
||||
|
|
|
@ -198,7 +198,7 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/tvAction" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvMove"
|
||||
android:id="@+id/tvTarget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
|
@ -208,33 +208,24 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/spAction" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spMove"
|
||||
android:id="@+id/spTarget"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvMove" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbMoveSeen"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_rule_seen"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/spMove" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tvTarget" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpReady"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="tvName,etName,tvAccount,spAccount,tvFolder,spFolder,tvFolderRemark,tvOrder,etOrder,spFolder,tvSender,etSender,tvSubject,etSubject,tvText,etText,tvAction,spAction" />
|
||||
app:constraint_referenced_ids="tvName,etName,tvOrder,etOrder,cbEnabled,tvAccount,spAccount,tvFolder,spFolder,tvFolderRemark,tvSender,etSender,tvSubject,etSubject,tvText,etText,tvAction,spAction,tvTarget,spTarget" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpMove"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="tvMove,spMove,cbMoveSeen" />
|
||||
app:constraint_referenced_ids="tvTarget,spTarget" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
|
|
@ -25,6 +25,22 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFolder"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="end"
|
||||
android:maxLines="1"
|
||||
android:text="Name"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvName" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vSeparator"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -33,6 +49,6 @@
|
|||
android:layout_marginBottom="6dp"
|
||||
android:background="?attr/colorSeparator"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvName" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tvFolder" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
Loading…
Reference in a new issue