AndroidAPS/wear/src/main/java/info/nightscout/androidaps/watchfaces/BgGraphBuilder.java

490 lines
19 KiB
Java
Raw Normal View History

2017-02-09 20:54:41 +01:00
package info.nightscout.androidaps.watchfaces;
2016-11-17 15:17:02 +01:00
import android.content.Context;
2018-02-21 14:23:52 +01:00
import android.graphics.Color;
2016-11-18 01:01:42 +01:00
import android.graphics.DashPathEffect;
2016-11-20 02:31:15 +01:00
import android.preference.PreferenceManager;
2016-11-17 15:17:02 +01:00
import android.text.format.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
2017-02-09 20:54:41 +01:00
import info.nightscout.androidaps.data.BasalWatchData;
import info.nightscout.androidaps.data.BgWatchData;
2018-02-20 14:14:27 +01:00
import info.nightscout.androidaps.data.BolusWatchData;
2017-02-09 20:54:41 +01:00
import info.nightscout.androidaps.data.TempWatchData;
2016-11-17 15:17:02 +01:00
import lecho.lib.hellocharts.model.Axis;
import lecho.lib.hellocharts.model.AxisValue;
import lecho.lib.hellocharts.model.Line;
import lecho.lib.hellocharts.model.LineChartData;
import lecho.lib.hellocharts.model.PointValue;
import lecho.lib.hellocharts.model.Viewport;
/**
2016-11-22 17:23:46 +01:00
* Created by emmablack on 11/15/14.
2016-11-17 15:17:02 +01:00
*/
public class BgGraphBuilder {
2018-02-21 14:23:52 +01:00
public static final double MAX_PREDICTION__TIME_RATIO = (3d / 5);
2018-02-21 14:32:48 +01:00
private long predictionEndTime;
2018-02-21 14:23:52 +01:00
private List<BgWatchData> predictionsList;
2018-02-20 14:14:27 +01:00
private ArrayList<BolusWatchData> bolusWatchDataList;
2016-11-18 02:31:29 +01:00
private ArrayList<BasalWatchData> basalWatchDataList;
2016-11-18 01:01:42 +01:00
public List<TempWatchData> tempWatchDataList;
2016-11-17 15:17:02 +01:00
private int timespan;
2017-12-04 18:40:13 +01:00
public long end_time;
public long start_time;
2016-11-17 15:17:02 +01:00
public double fuzzyTimeDenom = (1000 * 60 * 1);
public Context context;
public double highMark;
public double lowMark;
public List<BgWatchData> bgDataList = new ArrayList<BgWatchData>();
public int pointSize;
public int highColor;
public int lowColor;
public int midColor;
2016-11-27 03:48:03 +01:00
public int gridColour;
2016-11-27 04:14:46 +01:00
public int basalCenterColor;
public int basalBackgroundColor;
2018-03-04 14:06:19 +01:00
private int bolusInvalidColor;
2018-03-04 15:37:55 +01:00
private int carbsColor;
2016-11-17 15:17:02 +01:00
public boolean singleLine = false;
private List<PointValue> inRangeValues = new ArrayList<PointValue>();
private List<PointValue> highValues = new ArrayList<PointValue>();
private List<PointValue> lowValues = new ArrayList<PointValue>();
public Viewport viewport;
2016-11-26 20:53:00 +01:00
2016-11-27 06:12:20 +01:00
//used for low resolution screen.
2018-03-04 15:37:55 +01:00
public BgGraphBuilder(Context context, List<BgWatchData> aBgList, List<BgWatchData> predictionsList, List<TempWatchData> tempWatchDataList, ArrayList<BasalWatchData> basalWatchDataList, ArrayList<BolusWatchData> bolusWatchDataList, int aPointSize, int aMidColor, int gridColour, int basalBackgroundColor, int basalCenterColor, int bolusInvalidColor, int carbsColor, int timespan) {
2018-03-04 12:50:06 +01:00
this.start_time = System.currentTimeMillis() - (1000 * 60 * 60 * timespan); //timespan hours ago
2016-11-17 15:17:02 +01:00
this.bgDataList = aBgList;
2018-02-21 14:23:52 +01:00
this.predictionsList = predictionsList;
2016-11-17 15:17:02 +01:00
this.context = context;
this.highMark = aBgList.get(aBgList.size() - 1).high;
this.lowMark = aBgList.get(aBgList.size() - 1).low;
this.pointSize = aPointSize;
this.singleLine = false;
2016-11-17 15:17:02 +01:00
this.midColor = aMidColor;
this.lowColor = aMidColor;
this.highColor = aMidColor;
this.timespan = timespan;
2016-11-18 01:01:42 +01:00
this.tempWatchDataList = tempWatchDataList;
2016-11-18 02:31:29 +01:00
this.basalWatchDataList = basalWatchDataList;
2018-02-20 14:14:27 +01:00
this.bolusWatchDataList = (bolusWatchDataList!=null)?bolusWatchDataList:new ArrayList<BolusWatchData>();
2016-11-27 03:48:03 +01:00
this.gridColour = gridColour;
2016-11-27 04:14:46 +01:00
this.basalCenterColor = basalCenterColor;
this.basalBackgroundColor = basalBackgroundColor;
2018-03-04 14:06:19 +01:00
this.bolusInvalidColor = bolusInvalidColor;
2018-03-04 15:37:55 +01:00
this.carbsColor = carbsColor;
2018-02-21 14:32:48 +01:00
this.end_time = System.currentTimeMillis() + (1000 * 60 * 6 * timespan); //Now plus 30 minutes padding (for 5 hours. Less if less.)
2018-03-04 12:50:06 +01:00
this.predictionEndTime = getPredictionEndTime();
this.end_time = (predictionEndTime>end_time)?predictionEndTime:end_time;
2016-11-17 15:17:02 +01:00
}
2018-03-04 15:37:55 +01:00
public BgGraphBuilder(Context context, List<BgWatchData> aBgList, List<BgWatchData> predictionsList, List<TempWatchData> tempWatchDataList, ArrayList<BasalWatchData> basalWatchDataList, ArrayList<BolusWatchData> bolusWatchDataList, int aPointSize, int aHighColor, int aLowColor, int aMidColor, int gridColour, int basalBackgroundColor, int basalCenterColor, int bolusInvalidColor, int carbsColor, int timespan) {
2018-03-04 12:50:06 +01:00
this.start_time = System.currentTimeMillis() - (1000 * 60 * 60 * timespan); //timespan hours ago
2016-11-17 15:17:02 +01:00
this.bgDataList = aBgList;
2018-02-21 14:23:52 +01:00
this.predictionsList = predictionsList;
2016-11-17 15:17:02 +01:00
this.context = context;
this.highMark = aBgList.get(aBgList.size() - 1).high;
this.lowMark = aBgList.get(aBgList.size() - 1).low;
this.pointSize = aPointSize;
this.highColor = aHighColor;
this.lowColor = aLowColor;
this.midColor = aMidColor;
this.timespan = timespan;
2016-11-18 01:01:42 +01:00
this.tempWatchDataList = tempWatchDataList;
2016-11-18 02:31:29 +01:00
this.basalWatchDataList = basalWatchDataList;
2018-02-20 14:14:27 +01:00
this.bolusWatchDataList = (bolusWatchDataList!=null)?bolusWatchDataList:new ArrayList<BolusWatchData>();
2016-11-27 03:48:03 +01:00
this.gridColour = gridColour;
2016-11-27 04:14:46 +01:00
this.basalCenterColor = basalCenterColor;
this.basalBackgroundColor = basalBackgroundColor;
2018-03-04 14:06:19 +01:00
this.bolusInvalidColor = bolusInvalidColor;
2018-03-04 15:37:55 +01:00
this.carbsColor = carbsColor;
2018-02-21 14:32:48 +01:00
this.end_time = System.currentTimeMillis() + (1000 * 60 * 6 * timespan); //Now plus 30 minutes padding (for 5 hours. Less if less.)
this.predictionEndTime = getPredictionEndTime();
this.end_time = (predictionEndTime>end_time)?predictionEndTime:end_time;
2016-11-17 15:17:02 +01:00
}
public LineChartData lineData() {
LineChartData lineData = new LineChartData(defaultLines());
lineData.setAxisYLeft(yAxis());
lineData.setAxisXBottom(xAxis());
return lineData;
}
public List<Line> defaultLines() {
2018-02-21 14:23:52 +01:00
2016-11-17 15:17:02 +01:00
addBgReadingValues();
List<Line> lines = new ArrayList<Line>();
lines.add(highLine());
lines.add(lowLine());
lines.add(inRangeValuesLine());
lines.add(lowValuesLine());
lines.add(highValuesLine());
2016-11-18 01:01:42 +01:00
double minChart = lowMark;
double maxChart = highMark;
for ( BgWatchData bgd:bgDataList) {
if(bgd.sgv > maxChart){
maxChart = bgd.sgv;
}
if(bgd.sgv < minChart){
minChart = bgd.sgv;
}
}
2016-11-18 02:31:29 +01:00
double maxBasal = 0.1;
for (BasalWatchData bwd: basalWatchDataList) {
if(bwd.amount > maxBasal){
maxBasal = bwd.amount;
}
}
2016-11-18 01:01:42 +01:00
double maxTemp = maxBasal;
for (TempWatchData twd: tempWatchDataList) {
if(twd.amount > maxTemp){
maxTemp = twd.amount;
}
}
double factor = (maxChart-minChart)/maxTemp;
// in case basal is the highest, don't paint it totally at the top.
2016-11-18 03:21:38 +01:00
factor = Math.min(factor, ((maxChart-minChart)/maxBasal)*(2/3d));
2016-11-20 02:31:15 +01:00
boolean highlight = PreferenceManager
.getDefaultSharedPreferences(context)
.getBoolean("highlight_basals", false);
2016-11-18 01:35:46 +01:00
for (TempWatchData twd: tempWatchDataList) {
if(twd.endTime > start_time) {
lines.add(tempValuesLine(twd, (float) minChart, factor, false, highlight?(pointSize+1):pointSize));
2016-11-20 02:31:15 +01:00
if(highlight){
lines.add(tempValuesLine(twd, (float) minChart, factor, true, 1));
}
2016-11-18 01:35:46 +01:00
}
}
2016-11-18 01:01:42 +01:00
2016-11-20 02:31:15 +01:00
lines.add(basalLine((float) minChart, factor, highlight));
2018-02-21 01:56:06 +01:00
lines.add(bolusLine((float) minChart));
2018-03-04 14:06:19 +01:00
lines.add(bolusInvalidLine((float) minChart));
2018-03-04 15:37:55 +01:00
lines.add(carbsLine((float) minChart));
2018-02-21 01:56:06 +01:00
lines.add(smbLine((float) minChart));
2018-02-21 14:23:52 +01:00
lines.add(predictionLine());
2018-02-21 01:56:06 +01:00
2016-11-18 02:31:29 +01:00
2016-11-17 15:17:02 +01:00
return lines;
}
2016-11-20 02:31:15 +01:00
private Line basalLine(float offset, double factor, boolean highlight) {
2016-11-18 02:31:29 +01:00
List<PointValue> pointValues = new ArrayList<PointValue>();
for (BasalWatchData bwd: basalWatchDataList) {
if(bwd.endTime > start_time) {
long begin = (long) Math.max(start_time, bwd.startTime);
pointValues.add(new PointValue(fuzz(begin), offset + (float) (factor * bwd.amount)));
pointValues.add(new PointValue(fuzz(bwd.endTime), offset + (float) (factor * bwd.amount)));
}
}
Line basalLine = new Line(pointValues);
basalLine.setHasPoints(false);
2016-11-27 04:14:46 +01:00
basalLine.setColor(basalCenterColor);
2016-11-18 02:31:29 +01:00
basalLine.setPathEffect(new DashPathEffect(new float[]{4f, 3f}, 4f));
2016-11-20 02:31:15 +01:00
basalLine.setStrokeWidth(highlight?2:1);
2016-11-18 02:31:29 +01:00
return basalLine;
}
2018-02-21 01:56:06 +01:00
private Line bolusLine(float offset) {
2018-02-20 15:39:02 +01:00
List<PointValue> pointValues = new ArrayList<PointValue>();
for (BolusWatchData bwd: bolusWatchDataList) {
2018-03-04 14:47:30 +01:00
if(bwd.date > start_time && bwd.date <= end_time && !bwd.isSMB && bwd.isValid && bwd.bolus > 0) {
2018-02-21 01:56:06 +01:00
pointValues.add(new PointValue(fuzz(bwd.date), (float) offset-2));
2018-02-20 15:39:02 +01:00
}
}
2018-02-21 01:56:06 +01:00
Line line = new Line(pointValues);
2018-03-04 14:47:30 +01:00
line.setColor(basalCenterColor);
2018-02-21 01:56:06 +01:00
line.setHasLines(false);
2018-02-24 18:10:59 +01:00
line.setPointRadius(pointSize*2);
2018-02-21 01:56:06 +01:00
line.setHasPoints(true);
return line;
}
2018-02-20 15:39:02 +01:00
2018-02-21 01:56:06 +01:00
private Line smbLine(float offset) {
2018-02-20 15:39:02 +01:00
2018-02-21 01:56:06 +01:00
List<PointValue> pointValues = new ArrayList<PointValue>();
for (BolusWatchData bwd: bolusWatchDataList) {
2018-03-04 14:47:30 +01:00
if(bwd.date > start_time && bwd.date <= end_time && bwd.isSMB && bwd.isValid && bwd.bolus > 0) {
2018-02-21 01:56:06 +01:00
pointValues.add(new PointValue(fuzz(bwd.date), (float) offset-2));
}
}
2018-02-20 15:39:02 +01:00
Line line = new Line(pointValues);
line.setColor(basalCenterColor);
line.setHasLines(false);
line.setPointRadius(pointSize);
line.setHasPoints(true);
return line;
}
2018-03-04 14:06:19 +01:00
private Line bolusInvalidLine(float offset) {
List<PointValue> pointValues = new ArrayList<PointValue>();
for (BolusWatchData bwd: bolusWatchDataList) {
2018-03-04 15:09:59 +01:00
if(bwd.date > start_time && bwd.date <= end_time && !(bwd.isValid && (bwd.bolus > 0 || bwd.carbs > 0))) {
2018-03-04 14:06:19 +01:00
pointValues.add(new PointValue(fuzz(bwd.date), (float) offset-2));
}
}
Line line = new Line(pointValues);
line.setColor(bolusInvalidColor);
line.setHasLines(false);
line.setPointRadius(pointSize);
line.setHasPoints(true);
return line;
}
2018-03-04 15:37:55 +01:00
private Line carbsLine(float offset) {
List<PointValue> pointValues = new ArrayList<PointValue>();
for (BolusWatchData bwd: bolusWatchDataList) {
if(bwd.date > start_time && bwd.date <= end_time && !bwd.isSMB && bwd.isValid && bwd.carbs > 0) {
pointValues.add(new PointValue(fuzz(bwd.date), (float) offset+2));
}
}
Line line = new Line(pointValues);
2018-03-06 20:32:53 +01:00
line.setColor(carbsColor);
2018-03-04 15:37:55 +01:00
line.setHasLines(false);
line.setPointRadius(pointSize*2);
line.setHasPoints(true);
return line;
}
2018-03-04 14:06:19 +01:00
2018-02-21 14:23:52 +01:00
private Line predictionLine() {
List<PointValue> pointValues = new ArrayList<PointValue>();
long endTime = getPredictionEndTime();
for (BgWatchData bwd: predictionsList) {
if(bwd.timestamp <= endTime) {
pointValues.add(new PointValue(fuzz(bwd.timestamp), (float) bwd.sgv));
}
}
Line line = new Line(pointValues);
line.setColor(Color.MAGENTA);
line.setHasLines(false);
2018-02-21 15:11:38 +01:00
int size = pointSize/2;
size = (size>0)?size:1;
line.setPointRadius(size);
2018-02-21 14:23:52 +01:00
line.setHasPoints(true);
return line;
}
2018-02-20 15:39:02 +01:00
2016-11-17 15:17:02 +01:00
public Line highValuesLine() {
Line highValuesLine = new Line(highValues);
highValuesLine.setColor(highColor);
highValuesLine.setHasLines(false);
highValuesLine.setPointRadius(pointSize);
highValuesLine.setHasPoints(true);
return highValuesLine;
}
public Line lowValuesLine() {
Line lowValuesLine = new Line(lowValues);
lowValuesLine.setColor(lowColor);
lowValuesLine.setHasLines(false);
lowValuesLine.setPointRadius(pointSize);
lowValuesLine.setHasPoints(true);
return lowValuesLine;
}
public Line inRangeValuesLine() {
Line inRangeValuesLine = new Line(inRangeValues);
inRangeValuesLine.setColor(midColor);
if(singleLine) {
inRangeValuesLine.setHasLines(true);
inRangeValuesLine.setHasPoints(false);
inRangeValuesLine.setStrokeWidth(pointSize);
} else {
inRangeValuesLine.setPointRadius(pointSize);
inRangeValuesLine.setHasPoints(true);
inRangeValuesLine.setHasLines(false);
}
return inRangeValuesLine;
}
2016-11-18 01:01:42 +01:00
2016-11-20 02:31:15 +01:00
public Line tempValuesLine(TempWatchData twd, float offset, double factor, boolean isHighlightLine, int strokeWidth) {
2016-11-18 01:01:42 +01:00
List<PointValue> lineValues = new ArrayList<PointValue>();
2016-11-18 02:31:29 +01:00
long begin = (long) Math.max(start_time, twd.startTime);
lineValues.add(new PointValue(fuzz(begin), offset + (float) (factor * twd.startBasal)));
lineValues.add(new PointValue(fuzz(begin), offset + (float) (factor * twd.amount)));
lineValues.add(new PointValue(fuzz(twd.endTime), offset + (float) (factor * twd.amount)));
lineValues.add(new PointValue(fuzz(twd.endTime), offset + (float) (factor * twd.endBasal)));
2016-11-18 01:01:42 +01:00
Line valueLine = new Line(lineValues);
valueLine.setHasPoints(false);
2016-11-20 02:31:15 +01:00
if (isHighlightLine){
2016-11-27 04:14:46 +01:00
valueLine.setColor(basalCenterColor);
2016-11-20 02:31:15 +01:00
valueLine.setStrokeWidth(1);
}else {
2016-11-27 04:14:46 +01:00
valueLine.setColor(basalBackgroundColor);
2016-11-20 02:31:15 +01:00
valueLine.setStrokeWidth(strokeWidth);
}
2016-11-18 01:01:42 +01:00
return valueLine;
}
2016-11-17 15:17:02 +01:00
private void addBgReadingValues() {
if(singleLine) {
for (BgWatchData bgReading : bgDataList) {
if(bgReading.timestamp > start_time) {
if (bgReading.sgv >= 400) {
inRangeValues.add(new PointValue(fuzz(bgReading.timestamp), (float) 400));
} else if (bgReading.sgv >= highMark) {
inRangeValues.add(new PointValue(fuzz(bgReading.timestamp), (float) bgReading.sgv));
} else if (bgReading.sgv >= lowMark) {
inRangeValues.add(new PointValue(fuzz(bgReading.timestamp), (float) bgReading.sgv));
} else if (bgReading.sgv >= 40) {
inRangeValues.add(new PointValue(fuzz(bgReading.timestamp), (float) bgReading.sgv));
} else if (bgReading.sgv >= 11) {
inRangeValues.add(new PointValue(fuzz(bgReading.timestamp), (float) 40));
}
}
}
} else {
for (BgWatchData bgReading : bgDataList) {
if (bgReading.timestamp > start_time) {
if (bgReading.sgv >= 400) {
highValues.add(new PointValue(fuzz(bgReading.timestamp), (float) 400));
} else if (bgReading.sgv >= highMark) {
highValues.add(new PointValue(fuzz(bgReading.timestamp), (float) bgReading.sgv));
} else if (bgReading.sgv >= lowMark) {
inRangeValues.add(new PointValue(fuzz(bgReading.timestamp), (float) bgReading.sgv));
} else if (bgReading.sgv >= 40) {
lowValues.add(new PointValue(fuzz(bgReading.timestamp), (float) bgReading.sgv));
} else if (bgReading.sgv >= 11) {
lowValues.add(new PointValue(fuzz(bgReading.timestamp), (float) 40));
}
}
}
}
}
public Line highLine() {
List<PointValue> highLineValues = new ArrayList<PointValue>();
highLineValues.add(new PointValue(fuzz(start_time), (float) highMark));
highLineValues.add(new PointValue(fuzz(end_time), (float) highMark));
Line highLine = new Line(highLineValues);
highLine.setHasPoints(false);
highLine.setStrokeWidth(1);
highLine.setColor(highColor);
return highLine;
}
public Line lowLine() {
List<PointValue> lowLineValues = new ArrayList<PointValue>();
lowLineValues.add(new PointValue(fuzz(start_time), (float) lowMark));
lowLineValues.add(new PointValue(fuzz(end_time), (float) lowMark));
Line lowLine = new Line(lowLineValues);
lowLine.setHasPoints(false);
lowLine.setColor(lowColor);
lowLine.setStrokeWidth(1);
return lowLine;
}
/////////AXIS RELATED//////////////
2016-11-26 20:53:00 +01:00
2016-11-17 15:17:02 +01:00
public Axis yAxis() {
Axis yAxis = new Axis();
yAxis.setAutoGenerated(true);
List<AxisValue> axisValues = new ArrayList<AxisValue>();
yAxis.setValues(axisValues);
yAxis.setHasLines(false);
2016-11-27 03:48:03 +01:00
yAxis.setLineColor(gridColour);
2016-11-17 15:17:02 +01:00
return yAxis;
}
public Axis xAxis() {
final boolean is24 = DateFormat.is24HourFormat(context);
SimpleDateFormat timeFormat = new SimpleDateFormat(is24? "HH" : "h a");
timeFormat.setTimeZone(TimeZone.getDefault());
2017-12-04 18:40:13 +01:00
long timeNow = System.currentTimeMillis();
2018-02-21 14:23:52 +01:00
2018-03-04 13:35:17 +01:00
Axis xAxis = new Axis();
xAxis.setAutoGenerated(false);
List<AxisValue> xAxisValues = new ArrayList<AxisValue>();
//get the time-tick at the full hour after start_time
GregorianCalendar startGC = new GregorianCalendar();
startGC.setTimeInMillis(start_time);
startGC.set(Calendar.MILLISECOND, 0);
startGC.set(Calendar.SECOND, 0);
startGC.set(Calendar.MINUTE, 0);
startGC.add(Calendar.HOUR, 1);
long start_hour = startGC.getTimeInMillis();
2018-02-21 14:23:52 +01:00
2016-11-17 15:17:02 +01:00
//Display current time on the graph
SimpleDateFormat longTimeFormat = new SimpleDateFormat(is24? "HH:mm" : "h:mm a");
xAxisValues.add(new AxisValue(fuzz(timeNow)).setLabel((longTimeFormat.format(timeNow))));
2016-11-17 15:17:02 +01:00
2018-03-04 13:35:17 +01:00
long hourTick = start_hour;
// add all full hours within the timeframe
while (hourTick < end_time){
if(Math.abs(hourTick - timeNow) > (1000 * 60 * 8 * timespan)){
xAxisValues.add(new AxisValue(fuzz(hourTick)).setLabel(timeFormat.format(hourTick)));
2018-03-04 13:35:17 +01:00
} else {
//don't print hour label if too close to now to avoid overlaps
2018-03-04 15:41:32 +01:00
xAxisValues.add(new AxisValue(fuzz(hourTick)).setLabel(""));
2016-11-17 15:17:02 +01:00
}
2018-03-04 13:35:17 +01:00
//increment by one hour
hourTick += 60*60*1000;
2016-11-17 15:17:02 +01:00
}
2018-03-04 13:35:17 +01:00
2016-11-17 15:17:02 +01:00
xAxis.setValues(xAxisValues);
xAxis.setTextSize(10);
xAxis.setHasLines(true);
2016-11-27 03:48:03 +01:00
xAxis.setLineColor(gridColour);
xAxis.setTextColor(gridColour);
2016-11-17 15:17:02 +01:00
return xAxis;
}
2018-02-21 14:23:52 +01:00
public long getPredictionEndTime() {
long maxPredictionDate = System.currentTimeMillis();
for (BgWatchData prediction :
predictionsList) {
if (maxPredictionDate < prediction.timestamp) {
maxPredictionDate = prediction.timestamp;
}
}
return (long) Math.min(maxPredictionDate, System.currentTimeMillis() + MAX_PREDICTION__TIME_RATIO *timespan*1000*60*60);
}
2017-12-04 18:40:13 +01:00
public float fuzz(long value) {
return (float) Math.round(value / fuzzyTimeDenom);
2016-11-17 15:17:02 +01:00
}
}