1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-01 06:22:55 +01:00

Merge branch 'master' of https://codeberg.org/Freeyourgadget/Gadgetbridge into fossil-hybrid-new-firmware

This commit is contained in:
Daniel Dakhno 2020-10-14 02:02:00 +02:00
commit 7ecb5543f7
21 changed files with 975 additions and 91 deletions

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22.620001"
height="22.620001"
tint="#7E7E7E"
viewportWidth="25"
viewportHeight="25"
version="1.1"
id="svg1967"
sodipodi:docname="activity.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
<metadata
id="metadata1973">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs1971" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1042"
id="namedview1969"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="20.08"
inkscape:cx="22.086921"
inkscape:cy="16.68033"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="svg1967"
inkscape:document-rotation="0">
<sodipodi:guide
position="22.62,162.31"
orientation="0,-1"
id="guide2551" />
<sodipodi:guide
position="11.17728,160.50592"
orientation="1,0"
id="guide2553" />
</sodipodi:namedview>
<g
id="g5608"
transform="matrix(0.6107681,0,0,0.6107681,3.9807832,3.9807832)"
style="stroke-width:1.63728">
<path
fill="#000000"
d="m 12,18 a 6,6 0 0 1 -6,-6 6,6 0 0 1 6,-6 6,6 0 0 1 6,6 6,6 0 0 1 -6,6 M 20,15.31 23.31,12 20,8.69 V 4 H 15.31 L 12,0.69 8.69,4 H 4 V 8.69 L 0.69,12 4,15.31 V 20 H 8.69 L 12,23.31 15.31,20 H 20 Z"
id="path1965"
style="stroke-width:2.82858" />
<path
fill="currentColor"
d="m 4.4762317,12.388718 h 3.561751 l 2.0837443,-5.0286723 1.932068,5.4893343 1.823648,-2.726785 h 6.48905 l -0.488068,1.38717 H 14.61543 l -2.938854,4.509156 -1.60202,-4.54569 -0.9505983,2.403348 -3.506487,-4.01e-4 z"
id="path2361"
sodipodi:nodetypes="ccccccccccccc"
style="stroke-width:1.63728"
inkscape:transform-center-x="1.0957646"
inkscape:transform-center-y="-1.4792654" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="24"
viewBox="0 0 24 24"
width="24"
version="1.1"
id="svg6"
sodipodi:docname="ic_heartrate.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1042"
id="namedview8"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="22.185475"
inkscape:cx="15.252703"
inkscape:cy="16.702097"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="svg6">
<sodipodi:guide
position="13.81042,14.118536"
orientation="0,-1"
id="guide843" />
<sodipodi:guide
position="12.237736,12.731366"
orientation="0,-1"
id="guide845" />
<sodipodi:guide
position="10.840426,11.852413"
orientation="0,-1"
id="guide847" />
<sodipodi:guide
position="10.569979,10.364953"
orientation="0,-1"
id="guide849" />
</sodipodi:namedview>
<path
d="M0 0h24v24H0V0z"
fill="none"
id="path2" />
<path
d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"
id="path4" />
<path
fill="currentColor"
d="m 4.4092095,12.147587 3.5617512,0 2.0837433,-5.0286723 1.932068,5.4893343 1.823648,-2.726785 h 6.48905 l -0.488067,1.38717 -5.262995,0 -2.938855,4.509156 -1.60202,-4.54569 -0.9505981,2.403348 -3.5064865,-4.01e-4 z"
id="path2361"
sodipodi:nodetypes="ccccccccccccc"
style="stroke-width:1"
inkscape:transform-center-x="1.0957646"
inkscape:transform-center-y="-1.4792654" />
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="20.465673"
viewBox="0 0 12.461885 20.465673"
width="12.461885"
version="1.1"
id="svg6"
sodipodi:docname="ic_intensity2.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
<g
id="g852"
transform="matrix(1.2633472,0,0,1.2633472,-7.0842384,-0.67734805)"
style="stroke-width:0.791548">
<path
fill="currentColor"
d="M 5.607515,11.686925 H 8.1252115 L 9.465446,6.8235586 10.96387,12.147587 12.471175,3.5240992 15.471696,11.686925 15.378398,14.515508 12.813693,7.3561261 11.311914,16.735717 9.4890299,10.441173 8.8928532,13.174786 6.4142216,13.174385 Z"
id="path2361"
sodipodi:nodetypes="ccccccccccccc"
style="stroke-width:0.791543"
inkscape:transform-center-x="0.77456355"
inkscape:transform-center-y="-1.4792654" />
<path
d="M 9.1454321,2.0091519 C 9.5324975,1.9556103 9.8035425,1.5818286 9.7477555,1.1785257 9.6919675,0.77522288 9.3296334,0.48905438 8.942568,0.54259588 8.5555035,0.59613728 8.2844579,0.96991921 8.3402456,1.3732221 8.3960336,1.776525 8.7583676,2.0626932 9.1454321,2.0091519 Z M 8.7316404,7.2832811 8.8161724,5.6413122 10.05796,5.9645671 10.13763,7.1563547 10.958841,7.1254797 10.779661,5.3602183 9.3400311,4.9714687 9.3990101,3.8423473 c 0.5335143,0.4866822 1.2879859,0.755973 2.0621139,0.64889 L 11.359694,3.7579588 C 10.691129,3.8504397 10.077409,3.5616792 9.7249045,3.087324 L 9.291882,2.5493755 C 9.1207016,2.3488618 8.8892885,2.2314106 8.6429739,2.2654826 8.537411,2.2800847 8.4721073,2.3264834 8.3665441,2.3410857 L 6.6483576,3.4007966 6.8867229,5.124 7.5904774,5.0266518 7.4180429,3.7800791 8.0159208,3.4358185 7.869675,6.8781207 6.7695976,8.2657413 7.1039661,9.0398223 Z"
id="path4"
style="stroke-width:0.791547"
sodipodi:nodetypes="sssssccccccccccccscccccccccc" />
</g>
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1042"
id="namedview8"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="22.185475"
inkscape:cx="7.4963196"
inkscape:cy="6.3791957"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="svg6"
inkscape:document-rotation="0"
inkscape:snap-others="false">
<sodipodi:guide
position="13.511677,10.539359"
orientation="0,-1"
id="guide843" />
<sodipodi:guide
position="6.4686785,10.964203"
orientation="0,-1"
id="guide845" />
<sodipodi:guide
position="5.0713686,10.08525"
orientation="0,-1"
id="guide847" />
<sodipodi:guide
position="4.8009216,8.5977896"
orientation="0,-1"
id="guide849" />
</sodipodi:namedview>
<path
d="M -5.7690573,-1.7671634 H 18.230943 V 22.232837 H -5.7690573 Z"
fill="none"
id="path2" />
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
enable-background="new 0 0 24 24"
height="24"
viewBox="0 0 24 24"
width="24"
version="1.1"
id="svg1685"
sodipodi:docname="ic_shoe.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
<metadata
id="metadata1691">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs1689" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1042"
id="namedview1687"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="22.185475"
inkscape:cx="24.454686"
inkscape:cy="8.5363032"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="g1683">
<sodipodi:guide
position="6.9189413,13.542708"
orientation="1,0"
id="guide2254" />
<sodipodi:guide
position="7.4147612,14.252632"
orientation="0,-1"
id="guide2256" />
</sodipodi:namedview>
<g
id="g1683">
<rect
fill="none"
height="24"
width="24"
id="rect1679" />
<path
d="M 18.51,15.68 17.1,14.27 21.58,9.79 23,11.2 Z M 14.185167,13.058927 18.05,9.08 13.8,4.82 9.9251666,8.7989268 l -1.42,-1.42 L 13.8,2 v 0 l 7.08,7.08 -5.274833,5.388927 z M 15.24,15.26 13.83,16.67 v 0 L 10.5,20 H 1 V 17.37 C 1,16.53 1.52,15.8 2.3,15.49 2.88,15.26 3.58,14.93 4.27,14.47 l 1.38,1.38 C 5.74,15.95 5.87,16 6,16 c 0.13,0 0.26,-0.05 0.36,-0.15 0.2,-0.2 0.2,-0.51 0,-0.71 L 5.08,13.86 c 0.27,-0.24 0.53,-0.51 0.77,-0.8 l 1.27,1.27 c 0.09,0.1 0.23,0.15 0.35,0.15 0.12,0 0.25,-0.05 0.35,-0.15 0.2,-0.2 0.2,-0.51 0,-0.71 l -1.4,-1.4 C 6.61,11.88 6.76,11.5 6.87,11.1 l 1.71,1.72 c 0.09,0.1 0.23,0.15 0.35,0.15 0.12,0 0.25,-0.05 0.35,-0.15 0.19,-0.2 0.19,-0.5 0.01,-0.7 l -2.3710587,-2.372632 1.42,-1.41 z m -2.82,0 -1.67,-1.68 -3.33,3.32 C 6.64,17.68 5.37,17.68 4.59,16.89 L 4.4,16.72 3.93,16.96 C 3.64,17.1 3.34,17.23 3.04,17.35 L 3.03,18 h 6.64 z"
id="path1681"
sodipodi:nodetypes="ccccccccccccccccccccscccssscccssscccsccccccccccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -2,47 +2,73 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.content.Context; import android.content.Context;
import java.text.DecimalFormat;
import java.util.Date; import java.util.Date;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractActivityListingAdapter;
import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractItemAdapter;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
public class ActivityListingAdapter extends AbstractItemAdapter<StepAnalysis.StepSession> { public class ActivityListingAdapter extends AbstractActivityListingAdapter<StepAnalysis.StepSession> {
public ActivityListingAdapter(Context context) { public ActivityListingAdapter(Context context) {
super(context); super(context);
} }
@Override @Override
protected String getName(StepAnalysis.StepSession item) { protected String getTimeFrom(StepAnalysis.StepSession item) {
int activityKind = item.getActivityKind(); Date time = item.getStepStart();
String activityKindLabel = ActivityKind.asString(activityKind, getContext()); return DateTimeUtils.formatTime(time.getHours(), time.getMinutes());
Date startTime = item.getStepStart();
Date endTime = item.getStepEnd();
String fromTime = DateTimeUtils.formatTime(startTime.getHours(), startTime.getMinutes());
String toTime = DateTimeUtils.formatTime(endTime.getHours(), endTime.getMinutes());
String duration = DateTimeUtils.formatDurationHoursMinutes(endTime.getTime() - startTime.getTime(), TimeUnit.MILLISECONDS);
if (activityKind == ActivityKind.TYPE_UNKNOWN) {
return getContext().getString(R.string.chart_no_active_data);
}
return activityKindLabel + " " + duration + " (" + fromTime + " - " + toTime + ")";
} }
@Override @Override
protected String getDetails(StepAnalysis.StepSession item) { protected String getTimeTo(StepAnalysis.StepSession item) {
String heartRate = ""; Date time = item.getStepEnd();
if (item.getActivityKind() == ActivityKind.TYPE_UNKNOWN) { return DateTimeUtils.formatTime(time.getHours(), time.getMinutes());
return getContext().getString(R.string.chart_get_active_and_synchronize); }
}
if (item.getHeartRateAverage() > 50) {
heartRate = " ❤️ " + item.getHeartRateAverage();
}
return "👣 " + item.getSteps() + heartRate; @Override
protected String getActivityName(StepAnalysis.StepSession item) {
return ActivityKind.asString(item.getActivityKind(), getContext());
}
@Override
protected String getStepLabel(StepAnalysis.StepSession item) {
return String.valueOf(item.getSteps());
}
@Override
protected String getDistanceLabel(StepAnalysis.StepSession item) {
DecimalFormat df = new DecimalFormat("###m");
//DecimalFormatSymbols symbols = df.getDecimalFormatSymbols();
//symbols.setGroupingSeparator(' ');
return df.format(item.getDistance());
}
@Override
protected String getHrLabel(StepAnalysis.StepSession item) {
return String.valueOf(item.getHeartRateAverage());
}
@Override
protected String getIntensityLabel(StepAnalysis.StepSession item) {
DecimalFormat df = new DecimalFormat("###.#");
return df.format(item.getIntensity());
}
@Override
protected String getDurationLabel(StepAnalysis.StepSession item) {
long duration = item.getStepEnd().getTime() - item.getStepStart().getTime();
return DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS);
}
@Override
public Boolean hasHR(StepAnalysis.StepSession item) {
if (item.getHeartRateAverage() > 0) {
return true;
} else {
return false;
}
} }
@Override @Override

View File

@ -46,7 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
public class ActivityListingChartFragment extends AbstractChartFragment { public class ActivityListingChartFragment extends AbstractChartFragment {
protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingChartFragment.class); protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingChartFragment.class);
int tsDataFrom; int tsDateFrom;
private View rootView; private View rootView;
private List<? extends ActivitySample> activitySamples; private List<? extends ActivitySample> activitySamples;
private ActivityListingAdapter stepListAdapter; private ActivityListingAdapter stepListAdapter;
@ -61,7 +61,8 @@ public class ActivityListingChartFragment extends AbstractChartFragment {
stepListAdapter = new ActivityListingAdapter(getContext()); stepListAdapter = new ActivityListingAdapter(getContext());
stepsList.setAdapter(stepListAdapter); stepsList.setAdapter(stepListAdapter);
stepsDateView = rootView.findViewById(R.id.stepsDateView); stepsDateView = rootView.findViewById(R.id.stepsDateView);
//refresh(); refresh();
return rootView; return rootView;
} }
@ -76,7 +77,7 @@ public class ActivityListingChartFragment extends AbstractChartFragment {
String action = intent.getAction(); String action = intent.getAction();
if (action.equals(ChartsHost.REFRESH)) { if (action.equals(ChartsHost.REFRESH)) {
// TODO: use LimitLines to visualize smart alarms? // TODO: use LimitLines to visualize smart alarms?
//refresh(); refresh();
} else { } else {
super.onReceive(context, intent); super.onReceive(context, intent);
} }
@ -94,13 +95,17 @@ public class ActivityListingChartFragment extends AbstractChartFragment {
@Override @Override
protected void updateChartsnUIThread(ChartsData chartsData) { protected void updateChartsnUIThread(ChartsData chartsData) {
//top displays selected date //top displays selected date
stepsDateView.setText(DateTimeUtils.formatDate(new Date(tsDataFrom * 1000L))); stepsDateView.setText(DateTimeUtils.formatDate(new Date(tsDateFrom * 1000L)));
//calculate active sessions //calculate active sessions
StepAnalysis stepAnalysis = new StepAnalysis(); StepAnalysis stepAnalysis = new StepAnalysis();
if (activitySamples != null) { if (activitySamples != null) {
List<StepAnalysis.StepSession> stepSessions = stepAnalysis.calculateStepSessions(activitySamples); List<StepAnalysis.StepSession> stepSessions = stepAnalysis.calculateStepSessions(activitySamples);
if (stepSessions.toArray().length == 0) { if (stepSessions.toArray().length == 0) {
stepSessions = create_empty_record(); stepSessions = create_empty_record();
getChartsHost().enableSwipeRefresh(true); //try to enable pull to refresh, might be needed
} else {
getChartsHost().enableSwipeRefresh(false); //disable pull to refresh as it collides with swipable view
// this still provides one pull to refresh on the start, in case it is needed
} }
//push to the adapter //push to the adapter
stepListAdapter.setItems(stepSessions, true); stepListAdapter.setItems(stepSessions, true);
@ -109,12 +114,10 @@ public class ActivityListingChartFragment extends AbstractChartFragment {
@Override @Override
protected void renderCharts() { protected void renderCharts() {
} }
@Override @Override
protected void setupLegend(Chart chart) { protected void setupLegend(Chart chart) {
} }
@Override @Override
@ -124,20 +127,17 @@ public class ActivityListingChartFragment extends AbstractChartFragment {
day.set(Calendar.HOUR_OF_DAY, 0); //and we set time for the start and end of the same day day.set(Calendar.HOUR_OF_DAY, 0); //and we set time for the start and end of the same day
day.set(Calendar.MINUTE, 0); day.set(Calendar.MINUTE, 0);
day.set(Calendar.SECOND, 0); day.set(Calendar.SECOND, 0);
tsFrom = (int) (day.getTimeInMillis() / 1000); tsFrom = (int) (day.getTimeInMillis() / 1000);
tsTo = tsFrom + 24 * 60 * 60 - 1; tsTo = tsFrom + 24 * 60 * 60 - 1;
tsDataFrom = tsFrom; tsDateFrom = tsFrom;
return getAllSamples(db, device, tsFrom, tsTo); return getAllSamples(db, device, tsFrom, tsTo);
} }
private List<StepAnalysis.StepSession> create_empty_record() { private List<StepAnalysis.StepSession> create_empty_record() {
//have an "Unknown Activity" in the list in case there are no active sessions //have an "Unknown Activity" in the list in case there are no active sessions
List<StepAnalysis.StepSession> result = new ArrayList<>(); List<StepAnalysis.StepSession> result = new ArrayList<>();
int tsTo = tsDataFrom + 24 * 60 * 60 - 1; int tsTo = tsDateFrom + 24 * 60 * 60 - 1;
result.add(new StepAnalysis.StepSession(new Date(tsDataFrom * 1000L), new Date(tsTo * 1000L), 0, 0, ActivityKind.TYPE_UNKNOWN)); result.add(new StepAnalysis.StepSession(new Date(tsDateFrom * 1000L), new Date(tsTo * 1000L), 0, 0, 0, 0, ActivityKind.TYPE_UNKNOWN));
return result; return result;
} }
} }

View File

@ -308,7 +308,8 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
private void enableSwipeRefresh(boolean enable) { @Override
public void enableSwipeRefresh(boolean enable) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice); DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice);
swipeLayout.setEnabled(enable && coordinator.allowFetchActivityData(mGBDevice)); swipeLayout.setEnabled(enable && coordinator.allowFetchActivityData(mGBDevice));
} }

View File

@ -46,4 +46,6 @@ public interface ChartsHost {
void setDateInfo(String dateInfo); void setDateInfo(String dateInfo);
ViewGroup getDateBar(); ViewGroup getDateBar();
void enableSwipeRefresh(boolean enable);
} }

View File

@ -26,100 +26,137 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
public class StepAnalysis { public class StepAnalysis {
protected static final Logger LOG = LoggerFactory.getLogger(StepAnalysis.class); protected static final Logger LOG = LoggerFactory.getLogger(StepAnalysis.class);
private final int MIN_SESSION_STEPS = 100; final double MULTIPLIER_FEMALE = 0.44; //constants to calculate steps from height
final double MULTIPLIER_OTHER = 0.45; //thes feel too small though
final double MULTIPLIER_MALE = 0.46;
private final double MIN_SESSION_INTENSITY = 0.4; //needs tuning
private double STEP_SIZE = 1;
public List<StepSession> calculateStepSessions(List<? extends ActivitySample> samples) { public List<StepSession> calculateStepSessions(List<? extends ActivitySample> samples) {
List<StepSession> result = new ArrayList<>(); List<StepSession> result = new ArrayList<>();
final int MIN_SESSION_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_min_session_length", 5); final int MIN_SESSION_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_min_session_length", 5);
final int MAX_IDLE_PHASE_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_max_idle_phase_length", 5); final int MAX_IDLE_PHASE_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_max_idle_phase_length", 5);
final int MIN_STEPS_PER_MINUTE = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute", 40); final int MIN_STEPS_PER_MINUTE = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute", 40);
final int GENDER = GBApplication.getPrefs().getInt("activity_user_gender", 2);
final int HEIGHT = GBApplication.getPrefs().getInt("activity_user_height_cm", 170);
STEP_SIZE = calculate_step_size(GENDER, HEIGHT);
ActivitySample previousSample = null; ActivitySample previousSample = null;
Date stepStart = null; Date sessionStart = null;
Date stepEnd = null; Date sessionEnd = null;
int activeSteps = 0; int activeSteps = 0; //steps that we count
int heartRateForAverage = 0; int stepsBetweenActivePeriods = 0; //steps during time when we maybe take a rest but then restart
int heartRateToAdd = 0;
int activeSamplesForAverage = 0;
int activeSamplesToAdd = 0;
int stepsBetweenActivities = 0;
int heartRateBetweenActivities = 0;
int durationSinceLastActiveStep = 0; int durationSinceLastActiveStep = 0;
int activityKind; int activityKind;
for (ActivitySample sample : samples) { int heartRateForAverage = 0;
if (isStep(sample)) { //TODO we could improve/extend this to other activities as well, if in database int heartRateToAdd = 0;
int heartRateBetweenActivePeriods = 0;
int activeHrSamplesForAverage = 0;
int activeHrSamplesToAdd = 0;
float activeIntensity = 0;
float intensityBetweenActivePeriods = 0;
for (ActivitySample sample : samples) {
if (sample.getKind() != ActivityKind.TYPE_SLEEP) { //anything but sleep counts
if (sample.getHeartRate() != 255 && sample.getHeartRate() != -1) { if (sample.getHeartRate() != 255 && sample.getHeartRate() != -1) {
heartRateToAdd = sample.getHeartRate(); heartRateToAdd = sample.getHeartRate();
activeSamplesToAdd = 1; activeHrSamplesToAdd = 1;
} else { } else {
heartRateToAdd = 0; heartRateToAdd = 0;
activeSamplesToAdd = 0; activeHrSamplesToAdd = 0;
} }
if (stepStart == null) { if (sessionStart == null) {
stepStart = getDateFromSample(sample); sessionStart = getDateFromSample(sample);
activeSteps = sample.getSteps(); activeSteps = sample.getSteps();
activeIntensity = (int) sample.getIntensity();
heartRateForAverage = heartRateToAdd; heartRateForAverage = heartRateToAdd;
activeSamplesForAverage = activeSamplesToAdd; activeHrSamplesForAverage = activeHrSamplesToAdd;
durationSinceLastActiveStep = 0; durationSinceLastActiveStep = 0;
stepsBetweenActivities = 0; stepsBetweenActivePeriods = 0;
heartRateBetweenActivities = 0; heartRateBetweenActivePeriods = 0;
previousSample = null; previousSample = null;
} }
if (previousSample != null) { if (previousSample != null) {
int durationSinceLastSample = sample.getTimestamp() - previousSample.getTimestamp(); int durationSinceLastSample = sample.getTimestamp() - previousSample.getTimestamp();
activeSamplesForAverage += activeSamplesToAdd; activeHrSamplesForAverage += activeHrSamplesToAdd;
if (sample.getSteps() > MIN_STEPS_PER_MINUTE) { if (sample.getSteps() > MIN_STEPS_PER_MINUTE || //either some steps
activeSteps += sample.getSteps() + stepsBetweenActivities; (sample.getIntensity() > MIN_SESSION_INTENSITY && sample.getSteps() > 0)) { //or some intensity plus at least one step
heartRateForAverage += heartRateToAdd + heartRateBetweenActivities; activeSteps += sample.getSteps() + stepsBetweenActivePeriods;
stepsBetweenActivities = 0; activeIntensity += sample.getIntensity() + intensityBetweenActivePeriods;
heartRateBetweenActivities = 0; heartRateForAverage += heartRateToAdd + heartRateBetweenActivePeriods;
stepsBetweenActivePeriods = 0;
heartRateBetweenActivePeriods = 0;
intensityBetweenActivePeriods = 0;
durationSinceLastActiveStep = 0; durationSinceLastActiveStep = 0;
} else {
stepsBetweenActivities += sample.getSteps(); } else { //short break data to remember, we will add it to the rest later, if break not too long
heartRateBetweenActivities += heartRateToAdd; stepsBetweenActivePeriods += sample.getSteps();
heartRateBetweenActivePeriods += heartRateToAdd;
durationSinceLastActiveStep += durationSinceLastSample; durationSinceLastActiveStep += durationSinceLastSample;
intensityBetweenActivePeriods += sample.getIntensity();
} }
if (durationSinceLastActiveStep >= MAX_IDLE_PHASE_LENGTH) { if (durationSinceLastActiveStep >= MAX_IDLE_PHASE_LENGTH) { //break too long, we split here
int current = sample.getTimestamp(); int current = sample.getTimestamp();
int starting = (int) (stepStart.getTime() / 1000); int starting = (int) (sessionStart.getTime() / 1000);
int session_length = current - starting - durationSinceLastActiveStep; int session_length = current - starting - durationSinceLastActiveStep;
int heartRateAverage = activeSamplesForAverage > 0 ? heartRateForAverage / activeSamplesForAverage : 0;
if (session_length >= MIN_SESSION_LENGTH) { if (session_length >= MIN_SESSION_LENGTH) { //valid activity session
stepEnd = new Date((sample.getTimestamp() - durationSinceLastActiveStep) * 1000L); int heartRateAverage = activeHrSamplesForAverage > 0 ? heartRateForAverage / activeHrSamplesForAverage : 0;
activityKind = detect_activity(session_length, activeSteps, heartRateAverage); float distance = (float) (activeSteps * STEP_SIZE);
result.add(new StepSession(stepStart, stepEnd, activeSteps, heartRateAverage, activityKind)); sessionEnd = new Date((sample.getTimestamp() - durationSinceLastActiveStep) * 1000L);
activityKind = detect_activity_kind(session_length, activeSteps, heartRateAverage, activeIntensity);
result.add(new StepSession(sessionStart, sessionEnd, activeSteps, heartRateAverage, activeIntensity, distance, activityKind));
} }
stepStart = null; sessionStart = null;
} }
} }
previousSample = sample; previousSample = sample;
} }
} }
//make sure we show the last portion of the data as well in case no further activity is recorded yet //make sure we show the last portion of the data as well in case no further activity is recorded yet
if (stepStart != null && previousSample != null) { if (sessionStart != null && previousSample != null) {
int current = previousSample.getTimestamp(); int current = previousSample.getTimestamp();
int starting = (int) (stepStart.getTime() / 1000); int starting = (int) (sessionStart.getTime() / 1000);
int session_length = current - starting - durationSinceLastActiveStep; int session_length = current - starting - durationSinceLastActiveStep;
int heartRateAverage = activeSamplesForAverage > 0 ? heartRateForAverage / activeSamplesForAverage : 0;
if (session_length > MIN_SESSION_LENGTH && activeSteps > MIN_SESSION_STEPS) { if (session_length >= MIN_SESSION_LENGTH) {
stepEnd = getDateFromSample(previousSample); int heartRateAverage = activeHrSamplesForAverage > 0 ? heartRateForAverage / activeHrSamplesForAverage : 0;
activityKind = detect_activity(session_length, activeSteps, heartRateAverage); float distance = (float) (activeSteps * STEP_SIZE);
result.add(new StepSession(stepStart, stepEnd, activeSteps, heartRateAverage, activityKind));
sessionEnd = getDateFromSample(previousSample);
activityKind = detect_activity_kind(session_length, activeSteps, heartRateAverage, activeIntensity);
result.add(new StepSession(sessionStart, sessionEnd, activeSteps, heartRateAverage, activeIntensity, distance, activityKind));
} }
} }
return result; return result;
} }
private int detect_activity(int session_length, int activeSteps, int heartRateAverage) { private double calculate_step_size(int gender, int height) {
double multiplier = 0;
switch (gender) {
case ActivityUser.GENDER_MALE:
multiplier = MULTIPLIER_MALE;
break;
case ActivityUser.GENDER_FEMALE:
multiplier = MULTIPLIER_FEMALE;
break;
case ActivityUser.GENDER_OTHER:
multiplier = MULTIPLIER_OTHER;
break;
}
return height * multiplier / 100;
}
private int detect_activity_kind(int session_length, int activeSteps, int heartRateAverage, float intensity) {
final int MIN_STEPS_PER_MINUTE_FOR_RUN = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute_for_run", 120); final int MIN_STEPS_PER_MINUTE_FOR_RUN = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute_for_run", 120);
int spm = (int) (activeSteps / (session_length / 60)); int spm = (int) (activeSteps / (session_length / 60));
if (spm > MIN_STEPS_PER_MINUTE_FOR_RUN) { if (spm > MIN_STEPS_PER_MINUTE_FOR_RUN) {
@ -128,7 +165,7 @@ public class StepAnalysis {
if (activeSteps > 200) { if (activeSteps > 200) {
return ActivityKind.TYPE_WALKING; return ActivityKind.TYPE_WALKING;
} }
if (heartRateAverage > 90) { if (heartRateAverage > 90 && intensity > 30) { //needs tuning
return ActivityKind.TYPE_EXERCISE; return ActivityKind.TYPE_EXERCISE;
} }
return ActivityKind.TYPE_ACTIVITY; return ActivityKind.TYPE_ACTIVITY;
@ -147,15 +184,19 @@ public class StepAnalysis {
private final Date stepEnd; private final Date stepEnd;
private final int steps; private final int steps;
private final int heartRateAverage; private final int heartRateAverage;
private final float intensity;
private final float distance;
private final int activityKind; private final int activityKind;
StepSession(Date stepStart, StepSession(Date stepStart,
Date stepEnd, Date stepEnd,
int steps, int heartRateAverage, int activityKind) { int steps, int heartRateAverage, float intensity, float distance, int activityKind) {
this.stepStart = stepStart; this.stepStart = stepStart;
this.stepEnd = stepEnd; this.stepEnd = stepEnd;
this.steps = steps; this.steps = steps;
this.heartRateAverage = heartRateAverage; this.heartRateAverage = heartRateAverage;
this.intensity = intensity;
this.distance = distance;
this.activityKind = activityKind; this.activityKind = activityKind;
} }
@ -179,5 +220,12 @@ public class StepAnalysis {
return activityKind; return activityKind;
} }
public float getIntensity() {
return intensity;
}
public float getDistance() {
return distance;
}
} }
} }

View File

@ -0,0 +1,154 @@
/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.content.Context;
import android.content.res.Resources;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import static nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityAnalysis.LOG;
/**
* Adapter for displaying generic ItemWithDetails instances.
*/
public abstract class AbstractActivityListingAdapter<T> extends ArrayAdapter<T> {
private final Context context;
private final List<T> items;
private int backgroundColor = 0;
private int alternateColor = 0;
public AbstractActivityListingAdapter(Context context) {
this(context, new ArrayList<T>());
}
public AbstractActivityListingAdapter(Context context, List<T> items) {
super(context, 0, items);
this.context = context;
this.items = items;
alternateColor = getAlternateColor(context);
}
public static int getAlternateColor(Context context) {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = context.getTheme();
theme.resolveAttribute(R.attr.alternate_row_background, typedValue, true);
return typedValue.data;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
T item = getItem(position);
view = null; //this is ugly (probably we get no recycling), but it is required to keep the layout nice. We have only few items, so this should be OK.
if (view == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.activity_list_item, parent, false);
}
TextView timeFrom = view.findViewById(R.id.line_layout_timeFrom);
TextView timeTo = view.findViewById(R.id.line_layout_timeTo);
TextView activityName = view.findViewById(R.id.line_layout_activityName);
TextView stepLabel = view.findViewById(R.id.line_layout_step_label);
TextView distanceLabel = view.findViewById(R.id.line_layout_distance_label);
TextView hrLabel = view.findViewById(R.id.line_layout_hr_label);
TextView intensityLabel = view.findViewById(R.id.line_layout_intensity_label);
TextView durationLabel = view.findViewById(R.id.line_layout_duration_labe);
LinearLayout hrLayout = view.findViewById(R.id.line_layout_hr);
LinearLayout stepLayout = view.findViewById(R.id.line_layout_step);
LinearLayout intensityLayout = view.findViewById(R.id.line_layout_intensity);
RelativeLayout parentLayout = view.findViewById(R.id.list_item_parent_layout);
ImageView activityIcon = view.findViewById(R.id.line_layout_activityIcon);
timeFrom.setText(getTimeFrom(item));
timeTo.setText(getTimeTo(item));
activityName.setText(getActivityName(item));
stepLabel.setText(getStepLabel(item));
distanceLabel.setText(getDistanceLabel(item));
hrLabel.setText(getHrLabel(item));
intensityLabel.setText(getIntensityLabel(item));
durationLabel.setText(getDurationLabel(item));
if (!hasHR(item)) {
hrLayout.setVisibility(View.GONE);
} else {
hrLayout.setVisibility(View.VISIBLE);
}
activityIcon.setImageResource(getIcon(item));
if (position % 2 == 0) {parentLayout.setBackgroundColor(alternateColor);}
return view;
}
protected abstract String getTimeFrom(T item);
protected abstract String getTimeTo(T item);
protected abstract String getActivityName(T item);
protected abstract String getStepLabel(T item);
protected abstract String getDistanceLabel(T item);
protected abstract String getHrLabel(T item);
protected abstract String getIntensityLabel(T item);
protected abstract String getDurationLabel(T item);
protected abstract Boolean hasHR(T item);
@DrawableRes
protected abstract int getIcon(T item);
public List<T> getItems() {
return items;
}
public void loadItems() {
}
public void setItems(List<T> items, boolean notify) {
this.items.clear();
this.items.addAll(items);
if (notify) {
notifyDataSetChanged();
}
}
}

View File

@ -207,7 +207,7 @@ public class ActivityKind {
case TYPE_ACTIVITY: // fall through case TYPE_ACTIVITY: // fall through
case TYPE_UNKNOWN: // fall through case TYPE_UNKNOWN: // fall through
default: default:
return R.drawable.ic_activity_unknown; return R.drawable.ic_activity_unknown_small;
} }
} }
} }

View File

@ -4,7 +4,7 @@
android:tint="#7E7E7E" android:tint="#7E7E7E"
android:viewportWidth="25" android:viewportWidth="25"
android:viewportHeight="25"> android:viewportHeight="25">
<path <path
android:fillColor="#000000" android:fillColor="#000000"
android:pathData="M12 18a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-2.69L23.31 12 20 8.69V4h-4.69L12 0.69 8.69 4H4v4.69L0.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69z" /> android:pathData="M12 18a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-2.69L23.31 12 20 8.69V4h-4.69L12 0.69 8.69 4H4v4.69L0.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69z" />
</vector> </vector>

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22.62dp"
android:height="22.62dp"
android:tint="#7E7E7E"
android:viewportWidth="22.62"
android:viewportHeight="22.62">
<path
android:pathData="m11.31,14.9746a3.6646,3.6646 0,0 1,-3.6646 -3.6646,3.6646 3.6646,0 0,1 3.6646,-3.6646 3.6646,3.6646 0,0 1,3.6646 3.6646,3.6646 3.6646,0 0,1 -3.6646,3.6646M16.1961,13.3316 L18.2178,11.31 16.1961,9.2884L16.1961,6.4239L13.3316,6.4239L11.31,4.4022 9.2884,6.4239L6.4239,6.4239L6.4239,9.2884L4.4022,11.31 6.4239,13.3316L6.4239,16.1961L9.2884,16.1961L11.31,18.2178 13.3316,16.1961L16.1961,16.1961Z"
android:strokeWidth="2.82858"
android:fillColor="#000000"/>
<path
android:pathData="m6.7147,11.5474l2.1754,0l1.2727,-3.0714 1.18,3.3527 1.1138,-1.6654l3.9633,0l-0.2981,0.8472L12.9074,11.0106l-1.795,2.754 -0.9785,-2.7764 -0.5806,1.4679 -2.1417,-0.0002z"
android:strokeWidth="1.63728"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:tint="#7E7E7E"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:tint="#7E7E7E"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"/>
<path
android:pathData="m4.4092,12.1476 l3.5618,0 2.0837,-5.0287 1.9321,5.4893 1.8236,-2.7268h6.489l-0.4881,1.3872 -5.263,0 -2.9389,4.5092 -1.602,-4.5457 -0.9506,2.4033 -3.5065,-0.0004z"
android:strokeWidth="1"
android:fillColor="#FF000000"/>
</vector>

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12.461885dp"
android:height="20.465673dp"
android:tint="#7E7E7E"
android:viewportWidth="12.461885"
android:viewportHeight="20.465673">
<path
android:pathData="M-0,14.0873L3.1807,14.0873L4.8739,7.9432 6.7669,14.6693 8.6712,3.7748 12.4619,14.0873 12.344,17.6608 9.1039,8.616 7.2066,20.4657 4.9037,12.5135 4.1505,15.967 1.0192,15.9665Z"
android:strokeWidth="0.791543"
android:fillColor="#FF000000"/>
<path
android:fillColor="#FF000000"
android:pathData="M4.4696,1.8609C4.9586,1.7933 5.301,1.3211 5.2306,0.8115 5.1601,0.302 4.7023,-0.0595 4.2133,0.0081 3.7243,0.0758 3.3819,0.548 3.4524,1.0575 3.5229,1.567 3.9806,1.9285 4.4696,1.8609ZM3.9469,8.524 L4.0536,6.4496 5.6225,6.858 5.7231,8.3636 6.7606,8.3246 6.5342,6.0945 4.7155,5.6033 4.79,4.1769c0.674,0.6148 1.6272,0.9551 2.6052,0.8198L7.267,4.0703C6.4224,4.1871 5.647,3.8223 5.2017,3.223L4.6546,2.5434C4.4384,2.2901 4.146,2.1417 3.8348,2.1847 3.7015,2.2032 3.619,2.2618 3.4856,2.2803L1.3149,3.619 1.6161,5.796 2.5052,5.6731 2.2873,4.0982 3.0427,3.6633 2.8579,8.0121 1.4681,9.7652 1.8905,10.7431Z"
android:strokeWidth="0.791547"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#7E7E7E"
>
<path
android:fillColor="@android:color/white"
android:pathData="M12,12c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM18,10.2C18,6.57 15.35,4 12,4s-6,2.57 -6,6.2c0,2.34 1.95,5.44 6,9.14 4.05,-3.7 6,-6.8 6,-9.14zM12,2c4.2,0 8,3.22 8,8.2 0,3.32 -2.67,7.25 -8,11.8 -5.33,-4.55 -8,-8.48 -8,-11.8C4,5.22 7.8,2 12,2z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:tint="#7E7E7E"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M18.51,15.68 L17.1,14.27 21.58,9.79 23,11.2ZM14.1852,13.0589 L18.05,9.08 13.8,4.82 9.9252,8.7989l-1.42,-1.42L13.8,2v0l7.08,7.08 -5.2748,5.3889zM15.24,15.26 L13.83,16.67v0L10.5,20L1,20L1,17.37C1,16.53 1.52,15.8 2.3,15.49 2.88,15.26 3.58,14.93 4.27,14.47l1.38,1.38C5.74,15.95 5.87,16 6,16c0.13,0 0.26,-0.05 0.36,-0.15 0.2,-0.2 0.2,-0.51 0,-0.71L5.08,13.86c0.27,-0.24 0.53,-0.51 0.77,-0.8l1.27,1.27c0.09,0.1 0.23,0.15 0.35,0.15 0.12,0 0.25,-0.05 0.35,-0.15 0.2,-0.2 0.2,-0.51 0,-0.71l-1.4,-1.4C6.61,11.88 6.76,11.5 6.87,11.1l1.71,1.72c0.09,0.1 0.23,0.15 0.35,0.15 0.12,0 0.25,-0.05 0.35,-0.15 0.19,-0.2 0.19,-0.5 0.01,-0.7l-2.3711,-2.3726 1.42,-1.41zM12.42,15.26 L10.75,13.58 7.42,16.9C6.64,17.68 5.37,17.68 4.59,16.89L4.4,16.72 3.93,16.96C3.64,17.1 3.34,17.23 3.04,17.35L3.03,18h6.64z"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
>
<path
android:fillColor="@android:color/white"
android:pathData="M3.5,18.49l6,-6.01 4,4L22,6.92l-1.41,-1.41 -7.09,7.97 -4,-4L2,16.99l1.5,1.5z"/>
</vector>

View File

@ -0,0 +1,231 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/list_item_parent_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/activatedBackgroundIndicator">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="5dp"
android:layout_marginBottom="5dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/line_layout_timeFrom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="14:30" />
<TextView
android:id="@+id/line_layout_timeTo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_weight="1"
android:gravity="bottom"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="16:30" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/line_layout_activityIcon"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:contentDescription="@string/candidate_item_device_image"
android:padding="8dp"
app:srcCompat="@drawable/ic_activity_running" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/line_layout_activityName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="Running"
android:textStyle="bold" />
<TextView
android:id="@+id/line_layout_duration_labe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:layout_weight="1"
android:gravity="end"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="25min" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/line_layout_step"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:gravity="start"
android:orientation="horizontal">
<ImageView
android:id="@+id/line_layout_step_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="start"
android:contentDescription="@string/candidate_item_device_image"
app:srcCompat="@drawable/ic_shoe" />
<TextView
android:id="@+id/line_layout_step_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:layout_weight="1"
android:gravity="start"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="2000" />
</LinearLayout>
<LinearLayout
android:id="@+id/line_layout_intensity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:gravity="start"
android:orientation="horizontal">
<ImageView
android:id="@+id/line_layout_intensity_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="start"
android:contentDescription="@string/candidate_item_device_image"
app:srcCompat="@drawable/ic_intensity" />
<TextView
android:id="@+id/line_layout_intensity_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:layout_weight="1"
android:gravity="start"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="200" />
</LinearLayout>
<LinearLayout
android:id="@+id/line_layout_distance"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:gravity="start"
android:orientation="horizontal">
<ImageView
android:id="@+id/line_layout_distance_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="start"
android:contentDescription="@string/candidate_item_device_image"
app:srcCompat="@drawable/ic_place" />
<TextView
android:id="@+id/line_layout_distance_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:layout_weight="1"
android:gravity="start"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="2.5km" />
</LinearLayout>
<LinearLayout
android:id="@+id/line_layout_hr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:gravity="start"
android:orientation="horizontal">
<ImageView
android:id="@+id/line_layout_hr_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="start"
android:contentDescription="@string/candidate_item_device_image"
app:srcCompat="@drawable/ic_heartrate" />
<TextView
android:id="@+id/line_layout_hr_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:layout_weight="1"
android:gravity="start"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="2.5km" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</RelativeLayout>

View File

@ -73,7 +73,7 @@
android:title="@string/activity_prefs_chart_max_idle_phase_length" /> android:title="@string/activity_prefs_chart_max_idle_phase_length" />
<EditTextPreference <EditTextPreference
android:defaultValue="40" android:defaultValue="20"
android:inputType="number" android:inputType="number"
android:key="chart_list_min_steps_per_minute" android:key="chart_list_min_steps_per_minute"
android:maxLength="3" android:maxLength="3"