/* 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; } } }