work on automation plugin

- implement clone() methods for actions and triggers
- add AutomationEventTest
- improve ui
This commit is contained in:
Nico Schmitz 2019-03-26 00:05:20 +01:00
parent 4d98c44d74
commit 97a7a75c86
11 changed files with 199 additions and 31 deletions

View file

@ -10,7 +10,7 @@ import java.util.List;
import info.nightscout.androidaps.plugins.general.automation.actions.Action; import info.nightscout.androidaps.plugins.general.automation.actions.Action;
import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger; import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger;
public class AutomationEvent { public class AutomationEvent implements Cloneable {
private Trigger trigger; private Trigger trigger;
private List<Action> actions = new ArrayList<>(); private List<Action> actions = new ArrayList<>();
@ -57,17 +57,41 @@ public class AutomationEvent {
try { try {
JSONObject d = new JSONObject(data); JSONObject d = new JSONObject(data);
// title // title
title = d.getString("title"); title = d.optString("title", "");
// trigger // trigger
trigger = Trigger.instantiate(d.getString("trigger")); trigger = Trigger.instantiate(d.getString("trigger"));
// actions // actions
JSONArray array = d.getJSONArray("actions"); JSONArray array = d.getJSONArray("actions");
for (int i = 0; i < array.length(); i++) { 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) { } catch (JSONException e) {
e.printStackTrace(); e.printStackTrace();
} }
return this; 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;
}
} }

View file

@ -51,14 +51,11 @@ public class AutomationFragment extends SubscriberFragment {
unbinder = ButterKnife.bind(this, view); unbinder = ButterKnife.bind(this, view);
final AutomationPlugin plugin = AutomationPlugin.getPlugin(); final AutomationPlugin plugin = AutomationPlugin.getPlugin();
mEventListAdapter = new EventListAdapter(plugin.getAutomationEvents()); mEventListAdapter = new EventListAdapter(plugin.getAutomationEvents(), getFragmentManager());
mEventListView.setLayoutManager(new LinearLayoutManager(getContext())); mEventListView.setLayoutManager(new LinearLayoutManager(getContext()));
mEventListView.setAdapter(mEventListAdapter); mEventListView.setAdapter(mEventListAdapter);
EditEventDialog.setOnClickListener(event -> { EditEventDialog.setOnClickListener(event -> mEventListAdapter.notifyDataSetChanged());
plugin.getAutomationEvents().add(event);
mEventListAdapter.notifyDataSetChanged();
});
updateGUI(); updateGUI();
@ -74,7 +71,7 @@ public class AutomationFragment extends SubscriberFragment {
@OnClick(R.id.fabAddEvent) @OnClick(R.id.fabAddEvent)
void onClickAddEvent(View v) { void onClickAddEvent(View v) {
EditEventDialog dialog = EditEventDialog.newInstance(new AutomationEvent()); EditEventDialog dialog = EditEventDialog.newInstance(new AutomationEvent(), true);
dialog.show(getFragmentManager(), "EditEventDialog"); dialog.show(getFragmentManager(), "EditEventDialog");
} }
@ -83,9 +80,11 @@ public class AutomationFragment extends SubscriberFragment {
*/ */
public static class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder> { public static class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder> {
private final List<AutomationEvent> mEventList; private final List<AutomationEvent> mEventList;
private final FragmentManager mFragmentManager;
public EventListAdapter(List<AutomationEvent> events) { public EventListAdapter(List<AutomationEvent> events, FragmentManager fragmentManager) {
this.mEventList = events; this.mEventList = events;
this.mFragmentManager = fragmentManager;
} }
@NonNull @NonNull
@ -132,8 +131,17 @@ public class AutomationFragment extends SubscriberFragment {
addImage(res, holder.context, holder.iconLayout); addImage(res, holder.context, holder.iconLayout);
} }
// TODO: check null // action: remove
//holder.eventDescription.setText(event.getTrigger().friendlyDescription()); 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 @Override
@ -146,6 +154,7 @@ public class AutomationFragment extends SubscriberFragment {
final LinearLayout iconLayout; final LinearLayout iconLayout;
final TextView eventTitle; final TextView eventTitle;
final Context context; final Context context;
final ImageView iconTrash;
public ViewHolder(View view, Context context) { public ViewHolder(View view, Context context) {
super(view); super(view);
@ -153,6 +162,7 @@ public class AutomationFragment extends SubscriberFragment {
eventTitle = view.findViewById(R.id.viewEventTitle); eventTitle = view.findViewById(R.id.viewEventTitle);
rootLayout = view.findViewById(R.id.rootLayout); rootLayout = view.findViewById(R.id.rootLayout);
iconLayout = view.findViewById(R.id.iconLayout); iconLayout = view.findViewById(R.id.iconLayout);
iconTrash = view.findViewById(R.id.iconTrash);
} }
} }
} }
@ -224,6 +234,17 @@ public class AutomationFragment extends SubscriberFragment {
build(); build();
} }
public Context getContext() {
return mContext;
}
public LinearLayout getRootLayout() {
return mRootLayout;
}
public FragmentManager getFragmentManager() {
return mFragmentManager;
}
public void destroy() { public void destroy() {
mRootLayout.removeAllViews(); mRootLayout.removeAllViews();

View file

@ -9,7 +9,7 @@ import org.json.JSONObject;
import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.queue.Callback;
public abstract class Action { public abstract class Action implements Cloneable {
public abstract int friendlyName(); public abstract int friendlyName();
@ -33,6 +33,11 @@ public abstract class Action {
public void copy(Action action) { } public void copy(Action action) { }
@Override
public Action clone() throws CloneNotSupportedException {
return (Action) super.clone();
}
/*package*/ Action fromJSON(String data) { /*package*/ Action fromJSON(String data) {
return this; return this;
} }
@ -40,9 +45,9 @@ public abstract class Action {
public static Action instantiate(JSONObject object) { public static Action instantiate(JSONObject object) {
try { try {
String type = object.getString("type"); String type = object.getString("type");
JSONObject data = object.getJSONObject("data"); JSONObject data = object.optJSONObject("data");
Class clazz = Class.forName(type); 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) { } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | JSONException e) {
e.printStackTrace(); e.printStackTrace();
} }

View file

@ -23,7 +23,6 @@ import info.nightscout.androidaps.utils.JsonHelper;
public class ActionStartTempTarget extends Action { public class ActionStartTempTarget extends Action {
private String reason; private String reason;
private InputBg value; private InputBg value;
private InputDuration duration = new InputDuration(0, InputDuration.TimeUnit.MINUTES); 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 @Override
public Optional<Integer> icon() { public Optional<Integer> icon() {
return Optional.of(R.drawable.icon_cp_cgm_target); return Optional.of(R.drawable.icon_cp_cgm_target);

View file

@ -36,7 +36,13 @@ public class EditActionDialog extends DialogFragment {
Bundle args = new Bundle(); Bundle args = new Bundle();
EditActionDialog fragment = new EditActionDialog(); EditActionDialog fragment = new EditActionDialog();
fragment.setArguments(args); fragment.setArguments(args);
resultAction = action;
// clone action to static object
try {
resultAction = action.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return fragment; return fragment;
} }

View file

@ -28,7 +28,7 @@ public class EditEventDialog extends DialogFragment {
} }
private static OnClickListener mClickListener = null; private static OnClickListener mClickListener = null;
private static AutomationEvent mEvent; private static AutomationEvent staticEvent;
public static void setOnClickListener(OnClickListener clickListener) { public static void setOnClickListener(OnClickListener clickListener) {
mClickListener = clickListener; mClickListener = clickListener;
@ -51,14 +51,22 @@ public class EditEventDialog extends DialogFragment {
private Unbinder mUnbinder; private Unbinder mUnbinder;
private AutomationFragment.ActionListAdapter mActionListAdapter; private AutomationFragment.ActionListAdapter mActionListAdapter;
private AutomationEvent mEvent;
private boolean mAddNew;
public static EditEventDialog newInstance(AutomationEvent event) { public static EditEventDialog newInstance(AutomationEvent event, boolean addNew) {
mEvent = event; // FIXME staticEvent = event;
Bundle args = new Bundle(); Bundle args = new Bundle();
EditEventDialog fragment = new EditEventDialog(); EditEventDialog fragment = new EditEventDialog();
fragment.setArguments(args); fragment.setArguments(args);
// clone event
try {
fragment.mEvent = event.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
fragment.mAddNew = addNew;
return fragment; return fragment;
} }
@ -70,11 +78,15 @@ public class EditEventDialog extends DialogFragment {
// load data from bundle // load data from bundle
if (savedInstanceState != null) { if (savedInstanceState != null) {
String eventData = savedInstanceState.getString("event"); String eventData = savedInstanceState.getString("event");
if (eventData != null) mEvent.fromJSON(eventData); if (eventData != null) mEvent = new AutomationEvent().fromJSON(eventData);
} else { mAddNew = savedInstanceState.getBoolean("addNew");
} else if (mAddNew) {
mEvent.setTrigger(new TriggerConnector(TriggerConnector.Type.OR)); mEvent.setTrigger(new TriggerConnector(TriggerConnector.Type.OR));
} }
// event title
mEditEventTitle.setText(mEvent.getTitle());
// display root trigger // display root trigger
mTriggerDescription.setText(mEvent.getTrigger().friendlyDescription()); mTriggerDescription.setText(mEvent.getTrigger().friendlyDescription());
@ -136,6 +148,15 @@ public class EditEventDialog extends DialogFragment {
return; 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); if (mClickListener != null) mClickListener.onClick(mEvent);
dismiss(); dismiss();
} }
@ -148,6 +169,7 @@ public class EditEventDialog extends DialogFragment {
@Override @Override
public void onSaveInstanceState(Bundle bundle) { public void onSaveInstanceState(Bundle bundle) {
bundle.putString("event", mEvent.toJSON()); bundle.putString("event", mEvent.toJSON());
bundle.putBoolean("addNew", mAddNew);
} }
} }

View file

@ -19,7 +19,7 @@ import java.util.List;
import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R; import info.nightscout.androidaps.R;
public abstract class Trigger { public abstract class Trigger implements Cloneable {
public enum Comparator { public enum Comparator {
IS_LOWER, IS_LOWER,
@ -139,4 +139,11 @@ public abstract class Trigger {
return root; return root;
} }
@Override
public Trigger clone() throws CloneNotSupportedException {
Trigger t = (Trigger) super.clone();
t.connector = connector; // parent should already be cloned
return t;
}
} }

View file

@ -259,4 +259,16 @@ public class TriggerConnector extends Trigger {
return this; 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;
}
} }

View file

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="@color/defaulttext" android:pathData="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z" />
</vector>

View file

@ -12,21 +12,36 @@
android:padding="8dp" android:padding="8dp"
android:background="@color/ribbonDefault"> android:background="@color/ribbonDefault">
<LinearLayout <ImageView
android:id="@+id/iconLayout" android:id="@+id/iconTrash"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignParentRight="true"/> android:layout_alignParentRight="true"
android:src="@drawable/ic_trash_outline"
android:contentDescription="Remove"/>
<TextView <LinearLayout
android:id="@+id/viewEventTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_toLeftOf="@id/iconLayout" android:layout_toLeftOf="@id/iconTrash"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/viewEventTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:id="@+id/iconLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" />
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -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
}
}