diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index 66af8f3ae..73cd6e2f8 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -54,7 +54,7 @@ public class GBDaoGenerator { public static void main(String[] args) throws Exception { - final Schema schema = new Schema(91, MAIN_PACKAGE + ".entities"); + final Schema schema = new Schema(92, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -131,6 +131,7 @@ public class GBDaoGenerator { addGarminHeartRateRestingSample(schema, user, device); addGarminRestingMetabolicRateSample(schema, user, device); addGarminSleepStatsSample(schema, user, device); + addGarminIntensityMinutesSample(schema, user, device); addPendingFile(schema, user, device); addWena3EnergySample(schema, user, device); addWena3BehaviorSample(schema, user, device); @@ -813,6 +814,7 @@ public class GBDaoGenerator { addHeartRateProperties(activitySample); activitySample.addIntProperty("distanceCm").notNull().codeBeforeGetterAndSetter(OVERRIDE); activitySample.addIntProperty("activeCalories").notNull().codeBeforeGetterAndSetter(OVERRIDE); + return activitySample; } @@ -903,6 +905,14 @@ public class GBDaoGenerator { return sample; } + private static Entity addGarminIntensityMinutesSample(Schema schema, Entity user, Entity device) { + Entity sample = addEntity(schema, "GarminIntensityMinutesSample"); + addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device); + sample.addIntProperty("moderate"); + sample.addIntProperty("vigorous"); + return sample; + } + private static Entity addPendingFile(Schema schema, Entity user, Entity device) { Entity pendingFile = addEntity(schema, "PendingFile"); pendingFile.setJavaDoc( diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PaiChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PaiChartFragment.java index c09049da3..0c4fbed42 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PaiChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PaiChartFragment.java @@ -17,10 +17,12 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts; import android.graphics.Color; +import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.LinearLayout; import android.widget.TextView; import androidx.core.content.ContextCompat; @@ -78,6 +80,9 @@ public class PaiChartFragment extends AbstractChartFragment= Build.VERSION_CODES.M) { + rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + getChartsHost().enableSwipeRefresh(scrollY == 0); + }); + } + mTodayPieChart = rootView.findViewById(R.id.pai_chart_today); mWeekChart = rootView.findViewById(R.id.pai_chart_week); mDateView = rootView.findViewById(R.id.pai_date_view); @@ -116,6 +127,9 @@ public class PaiChartFragment extends AbstractChartFragment entries = new ArrayList<>(); final ArrayList labels = new ArrayList<>(); @@ -297,7 +319,7 @@ public class PaiChartFragment extends AbstractChartFragment entries = new ArrayList<>(); - final int maxPai = Math.max(100, total); + final int maxPai = Math.max(getPaiTarget(), total); final String todayLabel = today != 0 ? requireContext().getString(R.string.pai_plus_num, today) : ""; entries.add(new PieEntry(total - today, "")); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java index 8f1b0d015..d0982a093 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java @@ -559,6 +559,16 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { return supportsPai(); } + @Override + public boolean supportsPaiLow() { + return supportsPai(); + } + + @Override + public int getPaiTarget() { + return 100; + } + @Override public boolean supportsRespiratoryRate() { return false; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java index 35faaabe4..5b1d7fee6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java @@ -272,6 +272,16 @@ public interface DeviceCoordinator { */ boolean supportsPaiTime(); + /** + * Returns true if the device is capable of providing the time contribution for light PAI type. + */ + boolean supportsPaiLow(); + + /** + * Returns the PAI target - usually 100. + */ + int getPaiTarget(); + /** * Indicates whether the device supports respiratory rate tracking. */ diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java index 7cd30a5a6..572f1d5f4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java @@ -44,6 +44,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; +import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample; import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample; import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample; @@ -150,6 +151,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator { return new GarminSpo2SampleProvider(device, session); } + @Override + public TimeSampleProvider getPaiSampleProvider(final GBDevice device, final DaoSession session) { + return new GarminPaiSampleProvider(device, session); + } + @Override public TimeSampleProvider getRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) { return new GarminRespiratoryRateSampleProvider(device, session); @@ -315,6 +321,32 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator { return true; } + @Override + public boolean supportsPai() { + // Intensity Minutes + return true; + } + + @Override + public int getPaiName() { + return R.string.garmin_intensity_minutes; + } + + @Override + public boolean supportsPaiTime() { + return true; + } + + @Override + public boolean supportsPaiLow() { + return false; + } + + @Override + public int getPaiTarget() { + return 150; + } + @Override public boolean supportsFindDevice() { return true; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminIntensityMinutesSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminIntensityMinutesSampleProvider.java new file mode 100644 index 000000000..8205ce45a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminIntensityMinutesSampleProvider.java @@ -0,0 +1,56 @@ +/* Copyright (C) 2024 José Rebelo + + 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.devices.garmin; + +import androidx.annotation.NonNull; + +import de.greenrobot.dao.AbstractDao; +import de.greenrobot.dao.Property; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.GarminIntensityMinutesSample; +import nodomain.freeyourgadget.gadgetbridge.entities.GarminIntensityMinutesSampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class GarminIntensityMinutesSampleProvider extends AbstractTimeSampleProvider { + public GarminIntensityMinutesSampleProvider(final GBDevice device, final DaoSession session) { + super(device, session); + } + + @NonNull + @Override + public AbstractDao getSampleDao() { + return getSession().getGarminIntensityMinutesSampleDao(); + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return GarminIntensityMinutesSampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return GarminIntensityMinutesSampleDao.Properties.DeviceId; + } + + @Override + public GarminIntensityMinutesSample createSample() { + return new GarminIntensityMinutesSample(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminPaiSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminPaiSampleProvider.java new file mode 100644 index 000000000..aa7d48b95 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminPaiSampleProvider.java @@ -0,0 +1,241 @@ +/* Copyright (C) 2024 José Rebelo + + 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.devices.garmin; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.time.DayOfWeek; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAdjusters; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.GarminIntensityMinutesSample; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; + +public class GarminPaiSampleProvider implements TimeSampleProvider { + private final GarminIntensityMinutesSampleProvider intensityMinutesSampleProvider; + + public GarminPaiSampleProvider(final GBDevice device, final DaoSession session) { + this.intensityMinutesSampleProvider = new GarminIntensityMinutesSampleProvider(device, session); + } + + @NonNull + @Override + public List getAllSamples(final long timestampFrom, final long timestampTo) { + // Intensity minutes reset every monday, so we need to go to the previous monday if we + // are not there yet + final ZoneId tz = ZoneId.systemDefault(); + ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestampFrom), tz); + final DayOfWeek dayOfWeek = zonedDateTime.getDayOfWeek(); + if (dayOfWeek != DayOfWeek.MONDAY) { + zonedDateTime = zonedDateTime.with(TemporalAdjusters.previous(DayOfWeek.MONDAY)); + } + zonedDateTime = zonedDateTime.truncatedTo(ChronoUnit.DAYS); + final List allSamples = intensityMinutesSampleProvider.getAllSamples( + zonedDateTime.toEpochSecond() * 1000L, + timestampTo + ); + if (allSamples.isEmpty()) { + return Collections.emptyList(); + } + + int totalModerate = 0; + int totalVigorous = 0; + int currentDay = 0; + + LocalDate lastSampleDate = LocalDate.ofInstant(Instant.ofEpochMilli(allSamples.get(0).getTimestamp()), tz); + final List ret = new ArrayList<>(allSamples.size()); + for (final GarminIntensityMinutesSample sample : allSamples) { + final LocalDate sampleDate = LocalDate.ofInstant(Instant.ofEpochMilli(sample.getTimestamp()), tz); + + // Since we only persist minute samples for days where there was activity, we need to fill any gaps + for (LocalDate d = lastSampleDate.plusDays(1); d.isBefore(sampleDate); d = d.plusDays(1)) { + if (lastSampleDate.getDayOfWeek() != d.getDayOfWeek()) { + currentDay = 0; + if (d.getDayOfWeek() == DayOfWeek.MONDAY) { + totalModerate = 0; + totalVigorous = 0; + } + } + + ret.add(new GarminPaiSample( + d.atStartOfDay(tz).toInstant().toEpochMilli(), + totalModerate, + totalVigorous, + currentDay + )); + + lastSampleDate = d; + } + + if (sampleDate.getDayOfWeek() != lastSampleDate.getDayOfWeek()) { + currentDay = 0; + if (sampleDate.getDayOfWeek() == DayOfWeek.MONDAY) { + totalModerate = 0; + totalVigorous = 0; + } + } + + totalModerate += sample.getModerate(); + totalVigorous += sample.getVigorous(); + currentDay += sample.getModerate() + sample.getVigorous() * 2; + + if (sample.getTimestamp() >= timestampFrom && sample.getTimestamp() <= timestampTo) { + ret.add(new GarminPaiSample( + sample.getTimestamp(), + totalModerate, + totalVigorous, + currentDay + )); + } + + lastSampleDate = sampleDate; + } + + // Finally, fill out from the last sample to the end of the timestampTo + final LocalDate endDate = LocalDate.ofInstant(Instant.ofEpochMilli(timestampTo), tz); + for (LocalDate d = lastSampleDate.plusDays(1); !d.isAfter(endDate); d = d.plusDays(1)) { + if (lastSampleDate.getDayOfWeek() != d.getDayOfWeek()) { + currentDay = 0; + if (d.getDayOfWeek() == DayOfWeek.MONDAY) { + totalModerate = 0; + totalVigorous = 0; + } + } + + ret.add(new GarminPaiSample( + d.atStartOfDay(tz).toInstant().toEpochMilli(), + totalModerate, + totalVigorous, + currentDay + )); + + lastSampleDate = d; + } + + return ret; + } + + @Override + public void addSample(final PaiSample timeSample) { + throw new UnsupportedOperationException("This sample provider is read-only!"); + } + + @Override + public void addSamples(final List timeSamples) { + throw new UnsupportedOperationException("This sample provider is read-only!"); + } + + @Override + public PaiSample createSample() { + throw new UnsupportedOperationException("This sample provider is read-only!"); + } + + @Nullable + @Override + public PaiSample getLatestSample() { + // TODO + return null; + } + + @Nullable + @Override + public PaiSample getLatestSample(long until) { + // TODO + return null; + } + + @Nullable + @Override + public PaiSample getFirstSample() { + // TODO + return null; + } + + + public static class GarminPaiSample implements PaiSample { + private final long timestamp; + private final int minutesModerate; + private final int minutesVigorous; + private final int today; + + public GarminPaiSample(final long timestamp, + final int moderate, + final int vigorous, + final int today) { + this.timestamp = timestamp; + this.minutesModerate = moderate; + this.minutesVigorous = vigorous; + this.today = today; + } + + @Override + public long getTimestamp() { + return timestamp; + } + + @Override + public float getPaiLow() { + return 0; + } + + @Override + public float getPaiModerate() { + return minutesModerate; + } + + @Override + public float getPaiHigh() { + return minutesVigorous * 2; + } + + @Override + public int getTimeLow() { + return 0; // not supported + } + + @Override + public int getTimeModerate() { + return minutesModerate; + } + + @Override + public int getTimeHigh() { + return minutesVigorous; + } + + @Override + public float getPaiToday() { + return today; + } + + @Override + public float getPaiTotal() { + return minutesModerate + 2 * minutesVigorous; + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java index 3ea2ba90e..e3dc7bc0f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java @@ -26,6 +26,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminEventSampleProv import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHeartRateRestingSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvSummarySampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvValueSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminIntensityMinutesSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRespiratoryRateSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRestingMetabolicRateSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSleepStatsSampleProvider; @@ -41,6 +42,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminBodyEnergySample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHeartRateRestingSample; +import nodomain.freeyourgadget.gadgetbridge.entities.GarminIntensityMinutesSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminRestingMetabolicRateSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSample; @@ -433,6 +435,7 @@ public class FitImporter { } final List activitySamples = new ArrayList<>(activitySamplesPerTimestamp.size()); + final List intensityMinutesSamples = new ArrayList<>(activitySamplesPerTimestamp.size()); // Garmin reports the cumulative data per activity, but not always, so we need to keep // track of the amounts for each activity, and set the sum of all on the sample @@ -472,9 +475,9 @@ public class FitImporter { sample.setDistanceCm(ActivitySample.NOT_MEASURED); sample.setActiveCalories(ActivitySample.NOT_MEASURED); - boolean hasSteps = false; - boolean hasDistance = false; - boolean hasCalories = false; + int minutesModerate = 0; + int minutesVigorous = 0; + for (final FitMonitoring record : Objects.requireNonNull(records)) { final Integer activityType = record.getComputedActivityType().orElse(ActivitySample.NOT_MEASURED); @@ -486,41 +489,48 @@ public class FitImporter { final Long steps = record.getCycles(); if (steps != null) { stepsPerActivity.put(activityType, steps); - hasSteps = true; } final Long distance = record.getDistance(); if (distance != null) { distancePerActivity.put(activityType, distance); - hasDistance = true; } final Integer calories = record.getActiveCalories(); if (calories != null) { caloriesPerActivity.put(activityType, calories); - hasCalories = true; } final Integer intensity = record.getComputedIntensity(); if (intensity != null) { sample.setRawIntensity(intensity); } + + final Integer recordMinutesModerate = record.getModerateActivityMinutes(); + if (recordMinutesModerate != null) { + minutesModerate += recordMinutesModerate; + } + + final Integer recordMinutesVigorous = record.getVigorousActivityMinutes(); + if (recordMinutesVigorous != null) { + minutesVigorous += recordMinutesVigorous; + } } - if (hasSteps) { + if (!stepsPerActivity.isEmpty()) { int sumSteps = 0; for (final Long steps : stepsPerActivity.values()) { sumSteps += steps; } sample.setSteps(sumSteps); } - if (hasDistance) { + if (!distancePerActivity.isEmpty()) { int sumDistance = 0; for (final Long distance : distancePerActivity.values()) { sumDistance += distance; } sample.setDistanceCm(sumDistance); } - if (hasCalories) { + if (!caloriesPerActivity.isEmpty()) { int sumCalories = 0; for (final Integer calories : caloriesPerActivity.values()) { sumCalories += calories; @@ -530,6 +540,14 @@ public class FitImporter { activitySamples.add(sample); + if (minutesModerate != 0 || minutesVigorous != 0) { + final GarminIntensityMinutesSample intensityMinutesSample = new GarminIntensityMinutesSample(); + intensityMinutesSample.setTimestamp(ts * 1000L); + intensityMinutesSample.setModerate(minutesModerate); + intensityMinutesSample.setVigorous(minutesVigorous); + intensityMinutesSamples.add(intensityMinutesSample); + } + prevActivityKind = sample.getRawKind(); prevTs = (int) ts; } @@ -551,6 +569,12 @@ public class FitImporter { } catch (final Exception e) { GB.toast(context, "Error saving activity samples", Toast.LENGTH_LONG, GB.ERROR, e); } + + try { + persistAbstractSamples(intensityMinutesSamples, new GarminIntensityMinutesSampleProvider(gbDevice, session)); + } catch (final Exception e) { + GB.toast(context, "Error saving intensity minutes samples", Toast.LENGTH_LONG, GB.ERROR, e); + } } /** diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java index becc0b71f..7468684b6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java @@ -208,6 +208,8 @@ public class GlobalFITMessage { new FieldDefinitionPrimitive(24, BaseType.BASE_TYPE_BYTE, "current_activity_type_intensity"), new FieldDefinitionPrimitive(26, BaseType.UINT16, "timestamp_16"), new FieldDefinitionPrimitive(27, BaseType.UINT8, "heart_rate"), + new FieldDefinitionPrimitive(33, BaseType.UINT16, "moderate_activity_minutes"), + new FieldDefinitionPrimitive(34, BaseType.UINT16, "vigorous_activity_minutes"), new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP) )); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java index 78653b2e3..1ffad4a4a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java @@ -68,6 +68,16 @@ public class FitMonitoring extends RecordData { return (Integer) getFieldByNumber(27); } + @Nullable + public Integer getModerateActivityMinutes() { + return (Integer) getFieldByNumber(33); + } + + @Nullable + public Integer getVigorousActivityMinutes() { + return (Integer) getFieldByNumber(34); + } + @Nullable public Long getTimestamp() { return (Long) getFieldByNumber(253); diff --git a/app/src/main/res/layout/fragment_pai_chart.xml b/app/src/main/res/layout/fragment_pai_chart.xml index 637104174..86a716336 100644 --- a/app/src/main/res/layout/fragment_pai_chart.xml +++ b/app/src/main/res/layout/fragment_pai_chart.xml @@ -93,6 +93,7 @@ @@ -120,6 +121,7 @@ @@ -147,8 +149,8 @@ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2f1a28c93..0c3e78c74 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3220,6 +3220,7 @@ Mi Watch Color Sport Pixoo Not set + Intensity Minutes Vitality Score 7-day progress Get a notification when your vitality score reaches 30, 60 or 100 in the past 7 days