Xiaomi: Add Vitality Score (PAI-like metric)

This commit is contained in:
José Rebelo 2023-12-12 20:27:15 +00:00
parent bddec00de1
commit 372cf563ea
9 changed files with 258 additions and 8 deletions

View File

@ -172,7 +172,7 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
case "stress":
return getString(R.string.menuitem_stress);
case "pai":
return getString(R.string.menuitem_pai);
return getString(getDevice().getDeviceCoordinator().getPaiName());
case "stepsweek":
return getStepsTitle();
case "speedzones":

View File

@ -58,7 +58,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.Optional;
public class PaiChartFragment extends AbstractChartFragment<PaiChartFragment.PaiChartsData> {
@ -118,6 +117,12 @@ public class PaiChartFragment extends AbstractChartFragment<PaiChartFragment.Pai
mLineHighInc = rootView.findViewById(R.id.pai_line_high_inc);
mLineHighTime = rootView.findViewById(R.id.pai_line_high_time);
if (!getChartsHost().getDevice().getDeviceCoordinator().supportsPaiTime()) {
mLineLowTime.setVisibility(View.GONE);
mLineModerateTime.setVisibility(View.GONE);
mLineHighTime.setVisibility(View.GONE);
}
setupWeekChart();
setupTodayPieChart();

View File

@ -956,6 +956,20 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
}
}
// Replace the PAI with the device-specific name
if (chartsTabsOrderSelection != null) {
final ListPreference chartsTabsListPref = (ListPreference) chartsTabsOrderSelection;
final CharSequence[] entries = chartsTabsListPref.getEntries();
final CharSequence[] entryValues = chartsTabsListPref.getEntryValues();
for (int i = 0; i < entries.length; i++) {
if ("pai".equals(entryValues[i].toString())) {
entries[i] = getString(coordinator.getPaiName());
break;
}
}
chartsTabsListPref.setEntries(entries);
}
final Preference loyaltyCards = findPreference(LoyaltyCardsSettingsConst.PREF_KEY_LOYALTY_CARDS);
if (loyaltyCards != null) {
loyaltyCards.setOnPreferenceClickListener(preference -> {

View File

@ -398,6 +398,16 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return false;
}
@Override
public int getPaiName() {
return R.string.menuitem_pai;
}
@Override
public boolean supportsPaiTime() {
return supportsPai();
}
@Override
public boolean supportsSleepRespiratoryRate() {
return false;

View File

@ -209,6 +209,18 @@ public interface DeviceCoordinator {
*/
boolean supportsPai();
/**
* Returns the device-specific name for PAI (eg. Vitality Score).
*/
@StringRes
int getPaiName();
/**
* Returns true if the device is capable of providing the time contribution for each PAI type
* (light, moderate, high).
*/
boolean supportsPaiTime();
/**
* Returns true if sleep respiratory rate measurement and fetching is supported by
* the device (with this coordinator).

View File

@ -140,8 +140,7 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public TimeSampleProvider<? extends PaiSample> getPaiSampleProvider(final GBDevice device, final DaoSession session) {
// TODO XiaomiPaiSampleProvider
return super.getPaiSampleProvider(device, session);
return new XiaomiPaiSampleProvider(device, session);
}
@Override
@ -245,13 +244,23 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public boolean supportsHeartRateStats() {
// TODO it does - see DailySummaryParser
// TODO it does, and they're persisted - see DailySummaryParser
return false;
}
@Override
public boolean supportsPai() {
// TODO it does - vitality score
// Vitality Score
return true;
}
@Override
public int getPaiName() {
return R.string.pref_vitality_score_title;
}
@Override
public boolean supportsPaiTime() {
return false;
}

View File

@ -0,0 +1,56 @@
/* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
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.XiaomiDailySummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiDailySummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class XiaomiDailySummarySampleProvider extends AbstractTimeSampleProvider<XiaomiDailySummarySample> {
public XiaomiDailySummarySampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public AbstractDao<XiaomiDailySummarySample, ?> getSampleDao() {
return getSession().getXiaomiDailySummarySampleDao();
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return XiaomiDailySummarySampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return XiaomiDailySummarySampleDao.Properties.DeviceId;
}
@Override
public XiaomiDailySummarySample createSample() {
return new XiaomiDailySummarySample();
}
}

View File

@ -0,0 +1,144 @@
/* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiDailySummarySample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
public class XiaomiPaiSampleProvider implements TimeSampleProvider<PaiSample> {
private final XiaomiDailySummarySampleProvider dailySummarySampleProvider;
public XiaomiPaiSampleProvider(final GBDevice device, final DaoSession session) {
this.dailySummarySampleProvider = new XiaomiDailySummarySampleProvider(device, session);
}
@NonNull
@Override
public List<PaiSample> getAllSamples(final long timestampFrom, final long timestampTo) {
final List<XiaomiDailySummarySample> allSamples = dailySummarySampleProvider.getAllSamples(timestampFrom, timestampTo);
final List<PaiSample> ret = new ArrayList<>(allSamples.size());
for (final XiaomiDailySummarySample sample : allSamples) {
ret.add(new XiaomiPaiSample(sample));
}
return ret;
}
@Override
public void addSample(final PaiSample timeSample) {
throw new UnsupportedOperationException("This sample provider is read-only!");
}
@Override
public void addSamples(final List<PaiSample> 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() {
final XiaomiDailySummarySample sample = dailySummarySampleProvider.getLatestSample();
if (sample != null) {
return new XiaomiPaiSample(sample);
}
return null;
}
@Nullable
@Override
public PaiSample getFirstSample() {
final XiaomiDailySummarySample sample = dailySummarySampleProvider.getFirstSample();
if (sample != null) {
return new XiaomiPaiSample(sample);
}
return null;
}
public static class XiaomiPaiSample implements PaiSample {
private final long timestamp;
private final int paiLow;
private final int paiModerate;
private final int paiHigh;
private final int paiTotal;
public XiaomiPaiSample(final XiaomiDailySummarySample sample) {
this.timestamp = sample.getTimestamp();
this.paiLow = sample.getVitalityIncreaseLight();
this.paiModerate = sample.getVitalityIncreaseModerate();
this.paiHigh = sample.getVitalityIncreaseHigh();
this.paiTotal = sample.getVitalityCurrent();
}
@Override
public long getTimestamp() {
return timestamp;
}
@Override
public float getPaiLow() {
return paiLow;
}
@Override
public float getPaiModerate() {
return paiModerate;
}
@Override
public float getPaiHigh() {
return paiHigh;
}
@Override
public int getTimeLow() {
return 0; // not supported
}
@Override
public int getTimeModerate() {
return 0; // not supported
}
@Override
public int getTimeHigh() {
return 0; // not supported
}
@Override
public float getPaiToday() {
return paiLow + paiModerate + paiHigh;
}
@Override
public float getPaiTotal() {
return paiTotal;
}
}
}

View File

@ -2043,8 +2043,8 @@
<string name="stress_mild">Mild</string>
<string name="stress_moderate">Moderate</string>
<string name="stress_high">High</string>
<string name="pai_total">PAI Total</string>
<string name="pai_day">Day PAI increase</string>
<string name="pai_total">Total</string>
<string name="pai_day">Day increase</string>
<string name="sony_ambient_sound">Mode</string>
<string name="sony_ambient_sound_off">Off</string>
<string name="sony_ambient_sound_noise_cancelling">Noise Cancelling</string>