From 64d8504b7dcca1bcfa4d8f259dde2b606e69fb58 Mon Sep 17 00:00:00 2001 From: vanous Date: Mon, 12 Oct 2020 23:10:59 +0200 Subject: [PATCH] New item layout, icons, logic and defaults for daily activity list. --- app/src/main/assets/activity.svg | 83 +++++++ app/src/main/assets/ic_heartrate.svg | 85 +++++++ app/src/main/assets/ic_intensity.svg | 94 +++++++ app/src/main/assets/ic_shoe.svg | 74 ++++++ .../charts/ActivityListingAdapter.java | 78 ++++-- .../charts/ActivityListingChartFragment.java | 24 +- .../activities/charts/ChartsActivity.java | 3 +- .../activities/charts/ChartsHost.java | 2 + .../activities/charts/StepAnalysis.java | 140 +++++++---- .../AbstractActivityListingAdapter.java | 154 ++++++++++++ .../gadgetbridge/model/ActivityKind.java | 2 +- .../main/res/drawable/ic_activity_unknown.xml | 8 +- .../drawable/ic_activity_unknown_small.xml | 15 ++ app/src/main/res/drawable/ic_heart.xml | 10 + app/src/main/res/drawable/ic_heartrate.xml | 14 ++ app/src/main/res/drawable/ic_intensity.xml | 15 ++ app/src/main/res/drawable/ic_place.xml | 11 + app/src/main/res/drawable/ic_shoe.xml | 10 + app/src/main/res/drawable/ic_show_chart.xml | 11 + .../main/res/layout/activity_list_item.xml | 231 ++++++++++++++++++ app/src/main/res/xml/charts_preferences.xml | 2 +- 21 files changed, 975 insertions(+), 91 deletions(-) create mode 100644 app/src/main/assets/activity.svg create mode 100644 app/src/main/assets/ic_heartrate.svg create mode 100644 app/src/main/assets/ic_intensity.svg create mode 100644 app/src/main/assets/ic_shoe.svg create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractActivityListingAdapter.java create mode 100644 app/src/main/res/drawable/ic_activity_unknown_small.xml create mode 100644 app/src/main/res/drawable/ic_heart.xml create mode 100644 app/src/main/res/drawable/ic_heartrate.xml create mode 100644 app/src/main/res/drawable/ic_intensity.xml create mode 100644 app/src/main/res/drawable/ic_place.xml create mode 100644 app/src/main/res/drawable/ic_shoe.xml create mode 100644 app/src/main/res/drawable/ic_show_chart.xml create mode 100644 app/src/main/res/layout/activity_list_item.xml diff --git a/app/src/main/assets/activity.svg b/app/src/main/assets/activity.svg new file mode 100644 index 000000000..0d507b3e0 --- /dev/null +++ b/app/src/main/assets/activity.svg @@ -0,0 +1,83 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/ic_heartrate.svg b/app/src/main/assets/ic_heartrate.svg new file mode 100644 index 000000000..ee74236cb --- /dev/null +++ b/app/src/main/assets/ic_heartrate.svg @@ -0,0 +1,85 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/ic_intensity.svg b/app/src/main/assets/ic_intensity.svg new file mode 100644 index 000000000..4ea16fff8 --- /dev/null +++ b/app/src/main/assets/ic_intensity.svg @@ -0,0 +1,94 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/app/src/main/assets/ic_shoe.svg b/app/src/main/assets/ic_shoe.svg new file mode 100644 index 000000000..0a2397a09 --- /dev/null +++ b/app/src/main/assets/ic_shoe.svg @@ -0,0 +1,74 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java index ec648091e..0e072542e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java @@ -2,47 +2,73 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts; import android.content.Context; +import java.text.DecimalFormat; import java.util.Date; import java.util.concurrent.TimeUnit; -import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractItemAdapter; +import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractActivityListingAdapter; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; -public class ActivityListingAdapter extends AbstractItemAdapter { +public class ActivityListingAdapter extends AbstractActivityListingAdapter { public ActivityListingAdapter(Context context) { super(context); } @Override - protected String getName(StepAnalysis.StepSession item) { - int activityKind = item.getActivityKind(); - String activityKindLabel = ActivityKind.asString(activityKind, getContext()); - 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 + ")"; + protected String getTimeFrom(StepAnalysis.StepSession item) { + Date time = item.getStepStart(); + return DateTimeUtils.formatTime(time.getHours(), time.getMinutes()); } @Override - protected String getDetails(StepAnalysis.StepSession item) { - String heartRate = ""; - if (item.getActivityKind() == ActivityKind.TYPE_UNKNOWN) { - return getContext().getString(R.string.chart_get_active_and_synchronize); - } - if (item.getHeartRateAverage() > 50) { - heartRate = " ❤️ " + item.getHeartRateAverage(); - } + protected String getTimeTo(StepAnalysis.StepSession item) { + Date time = item.getStepEnd(); + return DateTimeUtils.formatTime(time.getHours(), time.getMinutes()); + } - 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 diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java index 593fe811a..d53fafe10 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java @@ -46,7 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; public class ActivityListingChartFragment extends AbstractChartFragment { protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingChartFragment.class); - int tsDataFrom; + int tsDateFrom; private View rootView; private List activitySamples; private ActivityListingAdapter stepListAdapter; @@ -61,7 +61,8 @@ public class ActivityListingChartFragment extends AbstractChartFragment { stepListAdapter = new ActivityListingAdapter(getContext()); stepsList.setAdapter(stepListAdapter); stepsDateView = rootView.findViewById(R.id.stepsDateView); - //refresh(); + refresh(); + return rootView; } @@ -76,7 +77,7 @@ public class ActivityListingChartFragment extends AbstractChartFragment { String action = intent.getAction(); if (action.equals(ChartsHost.REFRESH)) { // TODO: use LimitLines to visualize smart alarms? - //refresh(); + refresh(); } else { super.onReceive(context, intent); } @@ -94,13 +95,17 @@ public class ActivityListingChartFragment extends AbstractChartFragment { @Override protected void updateChartsnUIThread(ChartsData chartsData) { //top displays selected date - stepsDateView.setText(DateTimeUtils.formatDate(new Date(tsDataFrom * 1000L))); + stepsDateView.setText(DateTimeUtils.formatDate(new Date(tsDateFrom * 1000L))); //calculate active sessions StepAnalysis stepAnalysis = new StepAnalysis(); if (activitySamples != null) { List stepSessions = stepAnalysis.calculateStepSessions(activitySamples); if (stepSessions.toArray().length == 0) { 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 stepListAdapter.setItems(stepSessions, true); @@ -109,12 +114,10 @@ public class ActivityListingChartFragment extends AbstractChartFragment { @Override protected void renderCharts() { - } @Override protected void setupLegend(Chart chart) { - } @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.MINUTE, 0); day.set(Calendar.SECOND, 0); - tsFrom = (int) (day.getTimeInMillis() / 1000); tsTo = tsFrom + 24 * 60 * 60 - 1; - tsDataFrom = tsFrom; + tsDateFrom = tsFrom; return getAllSamples(db, device, tsFrom, tsTo); } private List create_empty_record() { //have an "Unknown Activity" in the list in case there are no active sessions List result = new ArrayList<>(); - int tsTo = tsDataFrom + 24 * 60 * 60 - 1; - result.add(new StepAnalysis.StepSession(new Date(tsDataFrom * 1000L), new Date(tsTo * 1000L), 0, 0, ActivityKind.TYPE_UNKNOWN)); + int tsTo = tsDateFrom + 24 * 60 * 60 - 1; + result.add(new StepAnalysis.StepSession(new Date(tsDateFrom * 1000L), new Date(tsTo * 1000L), 0, 0, 0, 0, ActivityKind.TYPE_UNKNOWN)); return result; } - - } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java index 0aea294d8..4512ad355 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java @@ -308,7 +308,8 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts return super.onOptionsItemSelected(item); } - private void enableSwipeRefresh(boolean enable) { + @Override + public void enableSwipeRefresh(boolean enable) { DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice); swipeLayout.setEnabled(enable && coordinator.allowFetchActivityData(mGBDevice)); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java index 59185e401..8f93350d6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java @@ -46,4 +46,6 @@ public interface ChartsHost { void setDateInfo(String dateInfo); ViewGroup getDateBar(); + + void enableSwipeRefresh(boolean enable); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java index 774798f0b..b117b4470 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java @@ -26,100 +26,137 @@ import java.util.List; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; public class StepAnalysis { 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 calculateStepSessions(List samples) { List result = new ArrayList<>(); 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 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; - Date stepStart = null; - Date stepEnd = null; - int activeSteps = 0; - int heartRateForAverage = 0; - int heartRateToAdd = 0; - int activeSamplesForAverage = 0; - int activeSamplesToAdd = 0; - int stepsBetweenActivities = 0; - int heartRateBetweenActivities = 0; + Date sessionStart = null; + Date sessionEnd = null; + int activeSteps = 0; //steps that we count + int stepsBetweenActivePeriods = 0; //steps during time when we maybe take a rest but then restart int durationSinceLastActiveStep = 0; int activityKind; - for (ActivitySample sample : samples) { - if (isStep(sample)) { //TODO we could improve/extend this to other activities as well, if in database + int heartRateForAverage = 0; + 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) { heartRateToAdd = sample.getHeartRate(); - activeSamplesToAdd = 1; + activeHrSamplesToAdd = 1; } else { heartRateToAdd = 0; - activeSamplesToAdd = 0; + activeHrSamplesToAdd = 0; } - if (stepStart == null) { - stepStart = getDateFromSample(sample); + if (sessionStart == null) { + sessionStart = getDateFromSample(sample); activeSteps = sample.getSteps(); + activeIntensity = (int) sample.getIntensity(); heartRateForAverage = heartRateToAdd; - activeSamplesForAverage = activeSamplesToAdd; + activeHrSamplesForAverage = activeHrSamplesToAdd; durationSinceLastActiveStep = 0; - stepsBetweenActivities = 0; - heartRateBetweenActivities = 0; + stepsBetweenActivePeriods = 0; + heartRateBetweenActivePeriods = 0; previousSample = null; } if (previousSample != null) { int durationSinceLastSample = sample.getTimestamp() - previousSample.getTimestamp(); - activeSamplesForAverage += activeSamplesToAdd; - if (sample.getSteps() > MIN_STEPS_PER_MINUTE) { - activeSteps += sample.getSteps() + stepsBetweenActivities; - heartRateForAverage += heartRateToAdd + heartRateBetweenActivities; - stepsBetweenActivities = 0; - heartRateBetweenActivities = 0; + activeHrSamplesForAverage += activeHrSamplesToAdd; + if (sample.getSteps() > MIN_STEPS_PER_MINUTE || //either some steps + (sample.getIntensity() > MIN_SESSION_INTENSITY && sample.getSteps() > 0)) { //or some intensity plus at least one step + activeSteps += sample.getSteps() + stepsBetweenActivePeriods; + activeIntensity += sample.getIntensity() + intensityBetweenActivePeriods; + heartRateForAverage += heartRateToAdd + heartRateBetweenActivePeriods; + stepsBetweenActivePeriods = 0; + heartRateBetweenActivePeriods = 0; + intensityBetweenActivePeriods = 0; durationSinceLastActiveStep = 0; - } else { - stepsBetweenActivities += sample.getSteps(); - heartRateBetweenActivities += heartRateToAdd; + + } else { //short break data to remember, we will add it to the rest later, if break not too long + stepsBetweenActivePeriods += sample.getSteps(); + heartRateBetweenActivePeriods += heartRateToAdd; 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 starting = (int) (stepStart.getTime() / 1000); + int starting = (int) (sessionStart.getTime() / 1000); int session_length = current - starting - durationSinceLastActiveStep; - int heartRateAverage = activeSamplesForAverage > 0 ? heartRateForAverage / activeSamplesForAverage : 0; - if (session_length >= MIN_SESSION_LENGTH) { - stepEnd = new Date((sample.getTimestamp() - durationSinceLastActiveStep) * 1000L); - activityKind = detect_activity(session_length, activeSteps, heartRateAverage); - result.add(new StepSession(stepStart, stepEnd, activeSteps, heartRateAverage, activityKind)); + if (session_length >= MIN_SESSION_LENGTH) { //valid activity session + int heartRateAverage = activeHrSamplesForAverage > 0 ? heartRateForAverage / activeHrSamplesForAverage : 0; + float distance = (float) (activeSteps * STEP_SIZE); + 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; } } //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 starting = (int) (stepStart.getTime() / 1000); + int starting = (int) (sessionStart.getTime() / 1000); int session_length = current - starting - durationSinceLastActiveStep; - int heartRateAverage = activeSamplesForAverage > 0 ? heartRateForAverage / activeSamplesForAverage : 0; - if (session_length > MIN_SESSION_LENGTH && activeSteps > MIN_SESSION_STEPS) { - stepEnd = getDateFromSample(previousSample); - activityKind = detect_activity(session_length, activeSteps, heartRateAverage); - result.add(new StepSession(stepStart, stepEnd, activeSteps, heartRateAverage, activityKind)); + if (session_length >= MIN_SESSION_LENGTH) { + int heartRateAverage = activeHrSamplesForAverage > 0 ? heartRateForAverage / activeHrSamplesForAverage : 0; + float distance = (float) (activeSteps * STEP_SIZE); + + 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; } - 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); int spm = (int) (activeSteps / (session_length / 60)); if (spm > MIN_STEPS_PER_MINUTE_FOR_RUN) { @@ -128,7 +165,7 @@ public class StepAnalysis { if (activeSteps > 200) { return ActivityKind.TYPE_WALKING; } - if (heartRateAverage > 90) { + if (heartRateAverage > 90 && intensity > 30) { //needs tuning return ActivityKind.TYPE_EXERCISE; } return ActivityKind.TYPE_ACTIVITY; @@ -147,15 +184,19 @@ public class StepAnalysis { private final Date stepEnd; private final int steps; private final int heartRateAverage; + private final float intensity; + private final float distance; private final int activityKind; StepSession(Date stepStart, Date stepEnd, - int steps, int heartRateAverage, int activityKind) { + int steps, int heartRateAverage, float intensity, float distance, int activityKind) { this.stepStart = stepStart; this.stepEnd = stepEnd; this.steps = steps; this.heartRateAverage = heartRateAverage; + this.intensity = intensity; + this.distance = distance; this.activityKind = activityKind; } @@ -179,5 +220,12 @@ public class StepAnalysis { return activityKind; } + public float getIntensity() { + return intensity; + } + + public float getDistance() { + return distance; + } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractActivityListingAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractActivityListingAdapter.java new file mode 100644 index 000000000..2a984a0b3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractActivityListingAdapter.java @@ -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 . */ +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 extends ArrayAdapter { + + private final Context context; + private final List items; + private int backgroundColor = 0; + private int alternateColor = 0; + + public AbstractActivityListingAdapter(Context context) { + this(context, new ArrayList()); + } + + public AbstractActivityListingAdapter(Context context, List 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 getItems() { + return items; + } + + public void loadItems() { + } + + public void setItems(List items, boolean notify) { + this.items.clear(); + this.items.addAll(items); + if (notify) { + notifyDataSetChanged(); + } + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java index 6afe19536..e443b6cc6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java @@ -207,7 +207,7 @@ public class ActivityKind { case TYPE_ACTIVITY: // fall through case TYPE_UNKNOWN: // fall through default: - return R.drawable.ic_activity_unknown; + return R.drawable.ic_activity_unknown_small; } } } diff --git a/app/src/main/res/drawable/ic_activity_unknown.xml b/app/src/main/res/drawable/ic_activity_unknown.xml index 270c6911f..bbe8bf87f 100644 --- a/app/src/main/res/drawable/ic_activity_unknown.xml +++ b/app/src/main/res/drawable/ic_activity_unknown.xml @@ -4,7 +4,7 @@ android:tint="#7E7E7E" android:viewportWidth="25" android:viewportHeight="25"> - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_activity_unknown_small.xml b/app/src/main/res/drawable/ic_activity_unknown_small.xml new file mode 100644 index 000000000..eef0e7d63 --- /dev/null +++ b/app/src/main/res/drawable/ic_activity_unknown_small.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/drawable/ic_heart.xml b/app/src/main/res/drawable/ic_heart.xml new file mode 100644 index 000000000..aa2379797 --- /dev/null +++ b/app/src/main/res/drawable/ic_heart.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_heartrate.xml b/app/src/main/res/drawable/ic_heartrate.xml new file mode 100644 index 000000000..4b61288b4 --- /dev/null +++ b/app/src/main/res/drawable/ic_heartrate.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_intensity.xml b/app/src/main/res/drawable/ic_intensity.xml new file mode 100644 index 000000000..0ec3b9953 --- /dev/null +++ b/app/src/main/res/drawable/ic_intensity.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/drawable/ic_place.xml b/app/src/main/res/drawable/ic_place.xml new file mode 100644 index 000000000..bb1f75229 --- /dev/null +++ b/app/src/main/res/drawable/ic_place.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_shoe.xml b/app/src/main/res/drawable/ic_shoe.xml new file mode 100644 index 000000000..eb4d7e539 --- /dev/null +++ b/app/src/main/res/drawable/ic_shoe.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_show_chart.xml b/app/src/main/res/drawable/ic_show_chart.xml new file mode 100644 index 000000000..0218c0f1c --- /dev/null +++ b/app/src/main/res/drawable/ic_show_chart.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/layout/activity_list_item.xml b/app/src/main/res/layout/activity_list_item.xml new file mode 100644 index 000000000..243b0e107 --- /dev/null +++ b/app/src/main/res/layout/activity_list_item.xml @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/charts_preferences.xml b/app/src/main/res/xml/charts_preferences.xml index 2c625fb51..429aa6e1e 100644 --- a/app/src/main/res/xml/charts_preferences.xml +++ b/app/src/main/res/xml/charts_preferences.xml @@ -73,7 +73,7 @@ android:title="@string/activity_prefs_chart_max_idle_phase_length" />