Work on automation plugin.

- Add MainApp.dpToPx() helper function
- Remove trigger layout resources
- Generate trigger layout programmatically
- Improve ux (when composing trigger logic)
- Refactoring
This commit is contained in:
Nico Schmitz 2018-10-15 23:20:06 +02:00
parent c60aae8630
commit f98d40f71b
12 changed files with 324 additions and 189 deletions

View file

@ -397,4 +397,9 @@ public class MainApp extends Application {
sDatabaseHelper = null;
}
}
public static int dpToPx(int dp) {
float scale = sResources.getDisplayMetrics().density;
return (int) (dp*scale + 0.5f);
}
}

View file

@ -1,6 +1,7 @@
package info.nightscout.androidaps.plugins.general.automation;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentManager;
@ -9,7 +10,10 @@ import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import java.util.ArrayList;
@ -18,10 +22,12 @@ import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.Common.SubscriberFragment;
import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog;
import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger;
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector;
public class AutomationFragment extends SubscriberFragment {
@ -110,35 +116,98 @@ public class AutomationFragment extends SubscriberFragment {
*/
public static class TriggerListAdapter {
private final LinearLayout mRootLayout;
private final LayoutInflater mInflater;
private final Context mContext;
private final List<Trigger> mTriggerList;
public TriggerListAdapter(LayoutInflater inflater, LinearLayout rootLayout, List<Trigger> triggers) {
public TriggerListAdapter(Context context, LinearLayout rootLayout, List<Trigger> triggers) {
mRootLayout = rootLayout;
mInflater = inflater;
mContext = context;
mTriggerList = triggers;
build();
}
public TriggerListAdapter(LayoutInflater inflater, LinearLayout rootLayout, Trigger trigger) {
public TriggerListAdapter(Context context, LinearLayout rootLayout, Trigger trigger) {
mRootLayout = rootLayout;
mInflater = inflater;
mContext = context;
mTriggerList = new ArrayList<>();
mTriggerList.add(trigger);
build();
}
public void destroy() {
mRootLayout.removeAllViews();
for(Trigger trigger : mTriggerList) {
trigger.destroyViewHolder();
}
}
private Spinner createSpinner() {
Spinner spinner = new Spinner(mContext);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(mContext, android.R.layout.simple_spinner_item, TriggerConnector.Type.labels());
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(spinnerArrayAdapter);
return spinner;
}
private void build() {
boolean isFirstItem = true;
for(Trigger trigger : mTriggerList) {
Trigger.ViewHolder viewHolder = trigger.createViewHolder(mInflater);
mRootLayout.addView(viewHolder.getView());
if (!isFirstItem) {
final TriggerConnector connector = trigger.getConnector();
final int initialPosition = connector.getConnectorType().ordinal();
Spinner spinner = createSpinner();
spinner.setSelection(initialPosition);
spinner.setBackgroundColor(MainApp.gc(R.color.black_overlay));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
params.setMargins(0, MainApp.dpToPx(8), 0, MainApp.dpToPx(8));
spinner.setLayoutParams(params);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position != initialPosition) {
// conector type changed
final TriggerConnector.Type newConnectorType = TriggerConnector.Type.values()[position];
if (connector.size() > 2) {
// split connector
int pos = connector.pos(trigger) - 1;
TriggerConnector newConnector = new TriggerConnector(newConnectorType);
{
Trigger t = connector.get(pos);
newConnector.add(t);
connector.remove(t);
}
TriggerConnector subConnector = new TriggerConnector(connector.getConnectorType());
int count = connector.size() - pos;
for (int i = 0; i < count; ++i) {
Trigger t = connector.get(pos);
subConnector.add(t);
connector.remove(t);
}
newConnector.add(subConnector);
connector.add(newConnector);
} else {
connector.changeConnectorType(newConnectorType);
}
connector.simplify().rebuildView();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
mRootLayout.addView(spinner);
} else {
isFirstItem = false;
}
mRootLayout.addView(trigger.createView(mContext));
}
}

View file

@ -17,6 +17,7 @@ import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.general.automation.AutomationEvent;
import info.nightscout.androidaps.plugins.general.automation.AutomationFragment;
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin;
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerBg;
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector;
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerTime;
@ -50,21 +51,19 @@ public class EditEventDialog extends DialogFragment {
// dummy initialization
TriggerConnector to = new TriggerConnector(TriggerConnector.Type.OR);
to.add(new TriggerBg());
to.add(new TriggerTime());
to.add(new TriggerTime());
TriggerConnector ta = new TriggerConnector();
ta.add(to);
mEvent.setTrigger(ta);
mEvent.setTrigger(to);
// display triggers
mTriggerListAdapter = new AutomationFragment.TriggerListAdapter(getLayoutInflater(), mLayoutTrigger, mEvent.getTrigger());
mTriggerListAdapter = new AutomationFragment.TriggerListAdapter(getContext(), mLayoutTrigger, mEvent.getTrigger());
return view;
}
@Override
public void onDestroyView() {
mTriggerListAdapter.destroy();
//mTriggerListAdapter.destroy();
mUnbinder.unbind();
super.onDestroyView();
}

View file

@ -1,15 +1,19 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.support.annotation.LayoutRes;
import android.content.Context;
import android.support.annotation.StringRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.json.JSONException;
import org.json.JSONObject;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
public abstract class Trigger {
@ -61,16 +65,22 @@ public abstract class Trigger {
return false;
}
}
}
protected ViewHolder viewHolder = null;
public static List<String> labels() {
List<String> list = new ArrayList<>();
for(Comparator c : values()) {
list.add(MainApp.gs(c.getStringRes()));
}
return list;
}
}
protected TriggerConnector connector = null;
Trigger() {
}
public Trigger getConnector() {
public TriggerConnector getConnector() {
return connector;
}
@ -99,31 +109,18 @@ public abstract class Trigger {
return null;
}
public abstract ViewHolder createViewHolder(LayoutInflater inflater);
public View createView(Context context) {
final int padding = MainApp.dpToPx(4);
public ViewHolder getViewHolder() {
return viewHolder;
}
LinearLayout root = new LinearLayout(context);
root.setPadding(padding, padding, padding, padding);
root.setOrientation(LinearLayout.VERTICAL);
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
public void destroyViewHolder() {
if (viewHolder != null) {
viewHolder.destroy();
}
}
TextView title = new TextView(context);
title.setText(friendlyName());
root.addView(title);
public static abstract class ViewHolder {
final View view;
final Unbinder unbinder;
public ViewHolder(LayoutInflater inflater, @LayoutRes int layout) {
view = inflater.inflate(layout, null);
unbinder = ButterKnife.bind(this, view);
}
public void destroy() {
unbinder.unbind();
}
public View getView() { return view; }
return root;
}
}

View file

@ -1,16 +1,28 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.view.LayoutInflater;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.DecimalFormat;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.GlucoseStatus;
import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.plugins.ConfigBuilder.ProfileFunctions;
import info.nightscout.utils.JsonHelper;
import info.nightscout.utils.NumberPicker;
public class TriggerBg extends Trigger {
@ -18,6 +30,21 @@ public class TriggerBg extends Trigger {
protected Comparator comparator = Comparator.IS_EQUAL;
protected String units = ProfileFunctions.getInstance().getProfileUnits();
final private TextWatcher textWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
// TODO: validate inputs
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
};
@Override
public synchronized boolean shouldRun() {
GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData();
@ -72,11 +99,6 @@ public class TriggerBg extends Trigger {
return MainApp.gs(R.string.glucosecompared, comparator.getStringRes(), threshold, units);
}
@Override
public ViewHolder createViewHolder(LayoutInflater inflater) {
return null;
}
TriggerBg threshold(double threshold) {
this.threshold = threshold;
return this;
@ -91,4 +113,48 @@ public class TriggerBg extends Trigger {
this.units = units;
return this;
}
@Override
public View createView(Context context) {
LinearLayout root = (LinearLayout) super.createView(context);
// spinner for comparator
Spinner spinner = new Spinner(context);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, Comparator.labels());
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(spinnerArrayAdapter);
LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
spinnerParams.setMargins(0, MainApp.dpToPx(4), 0, MainApp.dpToPx(4));
spinner.setLayoutParams(spinnerParams);
root.addView(spinner);
// horizontal layout
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.HORIZONTAL);
layout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
root.addView(layout);
// input filed for threshold
NumberPicker numberPicker = new NumberPicker(context, null);
numberPicker.setParams(0d, 0d, (double) 500, 1d, new DecimalFormat("0"), false, textWatcher);
numberPicker.setValue(100.0);
layout.addView(numberPicker);
// text view for unit
TextView tvUnits = new TextView(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.MATCH_PARENT
);
params.setMargins(MainApp.dpToPx(6), 0, 0, 0);
tvUnits.setLayoutParams(params);
tvUnits.setText(units);
tvUnits.setGravity(Gravity.CENTER_VERTICAL);
layout.addView(tvUnits);
return root;
}
}

View file

@ -1,10 +1,11 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.content.Context;
import android.support.annotation.StringRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.json.JSONArray;
import org.json.JSONException;
@ -13,8 +14,7 @@ import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.OnClick;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.general.automation.AutomationFragment;
import info.nightscout.utils.JsonHelper;
@ -22,7 +22,8 @@ import info.nightscout.utils.JsonHelper;
public class TriggerConnector extends Trigger {
public enum Type {
AND,
OR;
OR,
XOR;
public boolean apply(boolean a, boolean b) {
switch (this) {
@ -30,6 +31,8 @@ public class TriggerConnector extends Trigger {
return a && b;
case OR:
return a || b;
case XOR:
return a ^ b;
}
return false;
}
@ -38,12 +41,22 @@ public class TriggerConnector extends Trigger {
switch (this) {
case OR:
return R.string.or;
case XOR:
return R.string.xor;
default:
case AND:
return R.string.and;
}
}
public static List<String> labels() {
List<String> list = new ArrayList<>();
for(Type t : values()) {
list.add(MainApp.gs(t.getStringRes()));
}
return list;
}
}
protected List<Trigger> list = new ArrayList<>();
@ -78,6 +91,13 @@ public class TriggerConnector extends Trigger {
return list.get(i);
}
public int pos(Trigger trigger) {
for(int i = 0; i < list.size(); ++i) {
if (list.get(i) == trigger) return i;
}
return -1;
}
@Override
public synchronized boolean shouldRun() {
boolean result = false;
@ -139,67 +159,104 @@ public class TriggerConnector extends Trigger {
return result.toString();
}
@Override
public ViewHolder createViewHolder(LayoutInflater inflater) {
ViewHolder v = new ViewHolder(inflater);
viewHolder = v;
return v;
private AutomationFragment.TriggerListAdapter adapter;
public void rebuildView() {
if (adapter != null)
adapter.rebuild();
}
@Override
public View createView(Context context) {
final int padding = MainApp.dpToPx(5);
class ViewHolder extends Trigger.ViewHolder {
LinearLayout root = new LinearLayout(context);
root.setOrientation(LinearLayout.VERTICAL);
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
root.setPadding(padding,padding,padding,padding);
root.setBackgroundResource(R.drawable.border_automation_unit);
@BindView(R.id.triggerListLayout)
LinearLayout triggerListLayout;
LinearLayout triggerListLayout = new LinearLayout(context);
triggerListLayout.setOrientation(LinearLayout.VERTICAL);
triggerListLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
root.addView(triggerListLayout);
@BindView(R.id.title)
TextView titleView;
adapter = new AutomationFragment.TriggerListAdapter(context, triggerListLayout, list);
AutomationFragment.TriggerListAdapter adapter;
LinearLayout buttonLayout = new LinearLayout(context);
buttonLayout.setOrientation(LinearLayout.HORIZONTAL);
buttonLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
root.addView(buttonLayout);
public ViewHolder(LayoutInflater inflater) {
super(inflater, R.layout.automation_trigger_connector);
titleView.setText(friendlyName());
adapter = new AutomationFragment.TriggerListAdapter(inflater, triggerListLayout, list);
}
@OnClick(R.id.buttonRemove)
public void onButtonClickRemove(View view) {
Button buttonRemove = new Button(context);
buttonRemove.setText("-");
buttonRemove.setOnClickListener(v -> {
if (connector != null) {
connector.remove(TriggerConnector.this);
((TriggerConnector.ViewHolder)connector.getViewHolder()).adapter.rebuild();
connector.simplify();
connector.adapter.rebuild();
} else {
// no parent
list.clear();
simplify();
adapter.rebuild();
}
});
buttonLayout.addView(buttonRemove);
Button buttonAdd = new Button(context);
buttonAdd.setText("+");
buttonAdd.setOnClickListener(v -> {
addTrigger(adapter, new TriggerTime(), getConnectorType());
});
buttonLayout.addView(buttonAdd);
return root;
}
private void addTrigger(AutomationFragment.TriggerListAdapter adapter, Trigger trigger, Type connection) {
if (getConnectorType().equals(connection)) {
add(trigger);
} else {
TriggerConnector t = new TriggerConnector(connection);
t.add(trigger);
add(t);
}
adapter.rebuild();
}
public TriggerConnector simplify() {
// simplify children
for(int i = 0; i < size(); ++i) {
if (get(i) instanceof TriggerConnector) {
TriggerConnector t = (TriggerConnector) get(i);
t.simplify();
}
}
@OnClick(R.id.buttonAddAnd)
public void onButtonClickAnd(View view) {
addTrigger(new TriggerTime(), Type.AND);
}
@OnClick(R.id.buttonAddOr)
public void onButtonClickOr(View view) {
addTrigger(new TriggerTime(), Type.OR);
}
private void addTrigger(Trigger trigger, Type connection) {
if (getConnectorType().equals(connection)) {
add(trigger);
} else {
TriggerConnector t = new TriggerConnector(connection);
t.add(trigger);
// drop connector with only 1 element
if (size() == 1 && get(0) instanceof TriggerConnector) {
TriggerConnector c = (TriggerConnector) get(0);
remove(c);
changeConnectorType(c.getConnectorType());
for (Trigger t : c.list) {
add(t);
}
adapter.rebuild();
c.list.clear();
return simplify();
}
@Override
public void destroy() {
adapter.destroy();
super.destroy();
// merge connectors
if (connector != null && (connector.getConnectorType().equals(connectorType) || size() == 1)) {
connector.remove(this);
for (Trigger t : list) {
connector.add(t);
}
list.clear();
return connector.simplify();
}
return this;
}
}

View file

@ -1,7 +1,10 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.content.Context;
import android.support.annotation.StringRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.dpro.widgets.WeekdaysPicker;
@ -12,7 +15,6 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import butterknife.BindView;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.utils.DateUtil;
@ -97,7 +99,7 @@ public class TriggerTime extends Trigger {
public TriggerTime() {
for(DayOfWeek day : DayOfWeek.values()) {
set(day, true);
set(day, false);
}
}
@ -231,34 +233,27 @@ public class TriggerTime extends Trigger {
return this;
}
@Override
public ViewHolder createViewHolder(LayoutInflater inflater) {
ViewHolder v = new ViewHolder(inflater);
viewHolder = v;
return v;
}
class ViewHolder extends Trigger.ViewHolder {
@BindView(R.id.weekdays)
WeekdaysPicker weekdaysPicker;
public ViewHolder(LayoutInflater inflater) {
super(inflater, R.layout.automation_trigger_time);
List<Integer> selectedDays = new ArrayList<>();
for(int i = 0; i < weekdays.length; ++i) {
DayOfWeek day = DayOfWeek.values()[i];
boolean selected = weekdays[i];
if (selected) selectedDays.add(day.toCalendarInt());
}
weekdaysPicker.setSelectedDays(selectedDays);
weekdaysPicker.setOnWeekdaysChangeListener((view, i, list) -> {
set(DayOfWeek.fromCalendarInt(i), list.contains(i));
});
private List<Integer> getSelectedDays() {
List<Integer> selectedDays = new ArrayList<>();
for(int i = 0; i < weekdays.length; ++i) {
DayOfWeek day = DayOfWeek.values()[i];
boolean selected = weekdays[i];
if (selected) selectedDays.add(day.toCalendarInt());
}
return selectedDays;
}
@Override
public View createView(Context context) {
LinearLayout root = (LinearLayout) super.createView(context);
WeekdaysPicker weekdaysPicker = new WeekdaysPicker(context);
weekdaysPicker.setEditable(true);
weekdaysPicker.setSelectedDays(getSelectedDays());
weekdaysPicker.setOnWeekdaysChangeListener((view, i, list) -> set(DayOfWeek.fromCalendarInt(i), list.contains(i)));
weekdaysPicker.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
root.addView(weekdaysPicker);
return root;
}
}

View file

@ -2,6 +2,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="5dip"
android:width="3dip"
android:color="@android:color/white" />
</shape>

View file

@ -5,6 +5,10 @@
android:layout_height="match_parent"
tools:context="info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -28,4 +32,6 @@
</LinearLayout>
</ScrollView>
</FrameLayout>

View file

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="8dp"
android:background="@drawable/border_automation_unit">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:id="@+id/triggerListLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/buttonAddAnd"
android:text="@string/and"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/buttonAddOr"
android:text="@string/or"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/buttonRemove"
android:text="-"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<com.dpro.widgets.WeekdaysPicker
android:id="@+id/weekdays"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:sunday_first_day="false" />
</LinearLayout>

View file

@ -1224,6 +1224,7 @@
<string name="glucosecompared">Glucose %1$s %2$.2f %3$s</string>
<string name="and">And</string>
<string name="or">Or</string>
<string name="xor">Exclusive or</string>
<string name="atspecifiedtime">At %1$s</string>
<string name="use_network_location">Use network location</string>
<string name="use_gps_location">Use GPS location</string>