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.triggers.Trigger;
public class AutomationEvent {
public class AutomationEvent implements Cloneable {
private Trigger trigger;
private List<Action> 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;
}
}

View file

@ -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<EventListAdapter.ViewHolder> {
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.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();

View file

@ -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();
}

View file

@ -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<Integer> icon() {
return Optional.of(R.drawable.icon_cp_cgm_target);

View file

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

View file

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

View file

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

View file

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

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:background="@color/ribbonDefault">
<LinearLayout
android:id="@+id/iconLayout"
<ImageView
android:id="@+id/iconTrash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"/>
android:layout_alignParentRight="true"
android:src="@drawable/ic_trash_outline"
android:contentDescription="Remove"/>
<TextView
android:id="@+id/viewEventTitle"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@id/iconLayout"
app:layout_constraintTop_toTopOf="parent" />
android:layout_toLeftOf="@id/iconTrash"
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>

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