diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationEvent.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationEvent.java index c6d104f61c..d868bfda62 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationEvent.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationEvent.java @@ -10,7 +10,7 @@ import java.util.List; import info.nightscout.androidaps.plugins.general.automation.actions.Action; import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger; -public class AutomationEvent { +public class AutomationEvent implements Cloneable { private Trigger trigger; private List actions = new ArrayList<>(); @@ -57,17 +57,41 @@ public class AutomationEvent { try { JSONObject d = new JSONObject(data); // title - title = d.getString("title"); + title = d.optString("title", ""); // trigger trigger = Trigger.instantiate(d.getString("trigger")); // actions JSONArray array = d.getJSONArray("actions"); for (int i = 0; i < array.length(); i++) { - actions.add(Action.instantiate(array.getJSONObject(i))); + actions.add(Action.instantiate(new JSONObject(array.getString(i)))); } } catch (JSONException e) { e.printStackTrace(); } return this; } + + public void apply(AutomationEvent event) { + trigger = event.trigger; + actions = event.actions; + title = event.title; + } + + @Override + public AutomationEvent clone() throws CloneNotSupportedException { + AutomationEvent e = (AutomationEvent) super.clone(); + e.title = title; + + // clone actions + e.actions = new ArrayList<>(); + for(Action a : actions) { + e.actions.add(a.clone()); + } + + // clone triggers + if (trigger != null) { + e.trigger = trigger.clone(); + } + return e; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.java index 359ddfd813..ec207659ba 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.java @@ -51,14 +51,11 @@ public class AutomationFragment extends SubscriberFragment { unbinder = ButterKnife.bind(this, view); final AutomationPlugin plugin = AutomationPlugin.getPlugin(); - mEventListAdapter = new EventListAdapter(plugin.getAutomationEvents()); + mEventListAdapter = new EventListAdapter(plugin.getAutomationEvents(), getFragmentManager()); mEventListView.setLayoutManager(new LinearLayoutManager(getContext())); mEventListView.setAdapter(mEventListAdapter); - EditEventDialog.setOnClickListener(event -> { - plugin.getAutomationEvents().add(event); - mEventListAdapter.notifyDataSetChanged(); - }); + EditEventDialog.setOnClickListener(event -> mEventListAdapter.notifyDataSetChanged()); updateGUI(); @@ -74,7 +71,7 @@ public class AutomationFragment extends SubscriberFragment { @OnClick(R.id.fabAddEvent) void onClickAddEvent(View v) { - EditEventDialog dialog = EditEventDialog.newInstance(new AutomationEvent()); + EditEventDialog dialog = EditEventDialog.newInstance(new AutomationEvent(), true); dialog.show(getFragmentManager(), "EditEventDialog"); } @@ -83,9 +80,11 @@ public class AutomationFragment extends SubscriberFragment { */ public static class EventListAdapter extends RecyclerView.Adapter { private final List mEventList; + private final FragmentManager mFragmentManager; - public EventListAdapter(List events) { + public EventListAdapter(List events, FragmentManager fragmentManager) { this.mEventList = events; + this.mFragmentManager = fragmentManager; } @NonNull @@ -132,8 +131,17 @@ public class AutomationFragment extends SubscriberFragment { addImage(res, holder.context, holder.iconLayout); } - // TODO: check null - //holder.eventDescription.setText(event.getTrigger().friendlyDescription()); + // action: remove + holder.iconTrash.setOnClickListener(v -> { + mEventList.remove(event); + notifyDataSetChanged(); + }); + + // action: edit + holder.rootLayout.setOnClickListener(v -> { + EditEventDialog dialog = EditEventDialog.newInstance(event, false); + dialog.show(mFragmentManager, "EditEventDialog"); + }); } @Override @@ -146,6 +154,7 @@ public class AutomationFragment extends SubscriberFragment { final LinearLayout iconLayout; final TextView eventTitle; final Context context; + final ImageView iconTrash; public ViewHolder(View view, Context context) { super(view); @@ -153,6 +162,7 @@ public class AutomationFragment extends SubscriberFragment { eventTitle = view.findViewById(R.id.viewEventTitle); rootLayout = view.findViewById(R.id.rootLayout); iconLayout = view.findViewById(R.id.iconLayout); + iconTrash = view.findViewById(R.id.iconTrash); } } } @@ -224,6 +234,17 @@ public class AutomationFragment extends SubscriberFragment { build(); } + public Context getContext() { + return mContext; + } + + public LinearLayout getRootLayout() { + return mRootLayout; + } + + public FragmentManager getFragmentManager() { + return mFragmentManager; + } public void destroy() { mRootLayout.removeAllViews(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java index d1522640f3..7400fae27d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java @@ -9,7 +9,7 @@ import org.json.JSONObject; import info.nightscout.androidaps.queue.Callback; -public abstract class Action { +public abstract class Action implements Cloneable { public abstract int friendlyName(); @@ -33,6 +33,11 @@ public abstract class Action { public void copy(Action action) { } + @Override + public Action clone() throws CloneNotSupportedException { + return (Action) super.clone(); + } + /*package*/ Action fromJSON(String data) { return this; } @@ -40,9 +45,9 @@ public abstract class Action { public static Action instantiate(JSONObject object) { try { String type = object.getString("type"); - JSONObject data = object.getJSONObject("data"); + JSONObject data = object.optJSONObject("data"); Class clazz = Class.forName(type); - return ((Action) clazz.newInstance()).fromJSON(data.toString()); + return ((Action) clazz.newInstance()).fromJSON(data != null ? data.toString() : ""); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | JSONException e) { e.printStackTrace(); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStartTempTarget.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStartTempTarget.java index f7afe1d06c..d34fcb8725 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStartTempTarget.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStartTempTarget.java @@ -23,7 +23,6 @@ import info.nightscout.androidaps.utils.JsonHelper; public class ActionStartTempTarget extends Action { private String reason; - private InputBg value; private InputDuration duration = new InputDuration(0, InputDuration.TimeUnit.MINUTES); @@ -103,6 +102,15 @@ public class ActionStartTempTarget extends Action { } } + @Override + public ActionStartTempTarget clone() throws CloneNotSupportedException { + ActionStartTempTarget a = (ActionStartTempTarget) super.clone(); + a.reason = reason; + a.value = value; + a.duration = duration; + return a; + } + @Override public Optional icon() { return Optional.of(R.drawable.icon_cp_cgm_target); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditActionDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditActionDialog.java index 696ccd6f8e..da83a4ebf1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditActionDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditActionDialog.java @@ -36,7 +36,13 @@ public class EditActionDialog extends DialogFragment { Bundle args = new Bundle(); EditActionDialog fragment = new EditActionDialog(); fragment.setArguments(args); - resultAction = action; + + // clone action to static object + try { + resultAction = action.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } return fragment; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditEventDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditEventDialog.java index 2289b0dd18..a455aa08d2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditEventDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditEventDialog.java @@ -28,7 +28,7 @@ public class EditEventDialog extends DialogFragment { } private static OnClickListener mClickListener = null; - private static AutomationEvent mEvent; + private static AutomationEvent staticEvent; public static void setOnClickListener(OnClickListener clickListener) { mClickListener = clickListener; @@ -51,14 +51,22 @@ public class EditEventDialog extends DialogFragment { private Unbinder mUnbinder; private AutomationFragment.ActionListAdapter mActionListAdapter; + private AutomationEvent mEvent; + private boolean mAddNew; - public static EditEventDialog newInstance(AutomationEvent event) { - mEvent = event; // FIXME + public static EditEventDialog newInstance(AutomationEvent event, boolean addNew) { + staticEvent = event; Bundle args = new Bundle(); EditEventDialog fragment = new EditEventDialog(); fragment.setArguments(args); - + // clone event + try { + fragment.mEvent = event.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + fragment.mAddNew = addNew; return fragment; } @@ -70,11 +78,15 @@ public class EditEventDialog extends DialogFragment { // load data from bundle if (savedInstanceState != null) { String eventData = savedInstanceState.getString("event"); - if (eventData != null) mEvent.fromJSON(eventData); - } else { + if (eventData != null) mEvent = new AutomationEvent().fromJSON(eventData); + mAddNew = savedInstanceState.getBoolean("addNew"); + } else if (mAddNew) { mEvent.setTrigger(new TriggerConnector(TriggerConnector.Type.OR)); } + // event title + mEditEventTitle.setText(mEvent.getTitle()); + // display root trigger mTriggerDescription.setText(mEvent.getTrigger().friendlyDescription()); @@ -136,6 +148,15 @@ public class EditEventDialog extends DialogFragment { return; } + // apply changes + staticEvent.apply(mEvent); + + // add new + if (mAddNew) { + final AutomationPlugin plugin = AutomationPlugin.getPlugin(); + plugin.getAutomationEvents().add(mEvent); + } + if (mClickListener != null) mClickListener.onClick(mEvent); dismiss(); } @@ -148,6 +169,7 @@ public class EditEventDialog extends DialogFragment { @Override public void onSaveInstanceState(Bundle bundle) { bundle.putString("event", mEvent.toJSON()); + bundle.putBoolean("addNew", mAddNew); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/Trigger.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/Trigger.java index 2f766a14c8..62f8bad785 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/Trigger.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/Trigger.java @@ -19,7 +19,7 @@ import java.util.List; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -public abstract class Trigger { +public abstract class Trigger implements Cloneable { public enum Comparator { IS_LOWER, @@ -139,4 +139,11 @@ public abstract class Trigger { return root; } + + @Override + public Trigger clone() throws CloneNotSupportedException { + Trigger t = (Trigger) super.clone(); + t.connector = connector; // parent should already be cloned + return t; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnector.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnector.java index db4541f373..d67b0dc05c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnector.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnector.java @@ -259,4 +259,16 @@ public class TriggerConnector extends Trigger { return this; } + + @Override + public TriggerConnector clone() throws CloneNotSupportedException { + TriggerConnector tc = (TriggerConnector) super.clone(); + tc.list = new ArrayList<>(); + for(Trigger t : list) { + tc.list.add(t.clone()); + } + if (adapter != null) + tc.adapter = new AutomationFragment.TriggerListAdapter(adapter.getContext(), adapter.getFragmentManager(), adapter.getRootLayout(), tc); + return tc; + } } diff --git a/app/src/main/res/drawable/ic_trash_outline.xml b/app/src/main/res/drawable/ic_trash_outline.xml new file mode 100644 index 0000000000..a411c394e0 --- /dev/null +++ b/app/src/main/res/drawable/ic_trash_outline.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/automation_event_item.xml b/app/src/main/res/layout/automation_event_item.xml index a481c39755..7c845d93bb 100644 --- a/app/src/main/res/layout/automation_event_item.xml +++ b/app/src/main/res/layout/automation_event_item.xml @@ -12,21 +12,36 @@ android:padding="8dp" android:background="@color/ribbonDefault"> - + android:layout_alignParentRight="true" + android:src="@drawable/ic_trash_outline" + android:contentDescription="Remove"/> - + android:layout_toLeftOf="@id/iconTrash" + app:layout_constraintTop_toTopOf="parent"> + + + + + + \ No newline at end of file diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/AutomationEventTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/AutomationEventTest.java new file mode 100644 index 0000000000..4766644412 --- /dev/null +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/AutomationEventTest.java @@ -0,0 +1,41 @@ +package info.nightscout.androidaps.plugins.general.automation; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import info.nightscout.androidaps.plugins.general.automation.actions.ActionLoopEnable; +import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger; +import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnectorTest; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({}) +public class AutomationEventTest { + @Test + public void testCloneEvent() throws CloneNotSupportedException { + // create test object + AutomationEvent event = new AutomationEvent(); + event.setTitle("Test"); + event.setTrigger(Trigger.instantiate(TriggerConnectorTest.oneItem)); + event.addAction(new ActionLoopEnable()); + + // clone + AutomationEvent clone = event.clone(); + + // check title + Assert.assertEquals(event.getTitle(), clone.getTitle()); + + // check trigger + Assert.assertNotNull(clone.getTrigger()); + Assert.assertFalse(event.getTrigger() == clone.getTrigger()); // not the same object reference + Assert.assertEquals(event.getTrigger().getClass(), clone.getTrigger().getClass()); + // TODO: check trigger details + + // check action + Assert.assertEquals(1, clone.getActions().size()); + Assert.assertFalse(event.getActions() == clone.getActions()); // not the same object reference + // TODO: check action details + } +}