diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 86c3f2d43..f8f2b6ef2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -118,7 +118,7 @@ public class GBApplication extends Application { private static SharedPreferences sharedPrefs; private static final String PREFS_VERSION = "shared_preferences_version"; //if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version - private static final int CURRENT_PREFS_VERSION = 20; + private static final int CURRENT_PREFS_VERSION = 21; private static LimitedQueue mIDSenderLookup = new LimitedQueue(16); private static Prefs prefs; @@ -1236,6 +1236,36 @@ public class GBApplication extends Application { } } + if (oldVersion < 21) { + // Add the new PAI tab to all devices + try (DBHandler db = acquireDB()) { + final DaoSession daoSession = db.getDaoSession(); + final List activeDevices = DBHelper.getActiveDevices(daoSession); + + for (final Device dbDevice : activeDevices) { + final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier()); + + final String chartsTabsValue = deviceSharedPrefs.getString("charts_tabs", null); + if (chartsTabsValue == null) { + continue; + } + + final String newPrefValue; + if (!StringUtils.isBlank(chartsTabsValue)) { + newPrefValue = chartsTabsValue + ",pai"; + } else { + newPrefValue = "pai"; + } + + final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit(); + deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue); + deviceSharedPrefsEdit.apply(); + } + } catch (Exception e) { + Log.w(TAG, "error acquiring DB lock"); + } + } + editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION)); editor.apply(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java index c0cf26e8b..67ebd6be6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java @@ -95,6 +95,7 @@ public abstract class AbstractActivityChartFragment extend protected String HEARTRATE_LABEL; protected String HEARTRATE_AVERAGE_LABEL; + @Override protected void init() { Prefs prefs = GBApplication.getPrefs(); TypedValue runningColor = new TypedValue(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityChartsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityChartsActivity.java index 1c8b25703..2b9bbeb12 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityChartsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityChartsActivity.java @@ -86,6 +86,9 @@ public class ActivityChartsActivity extends AbstractChartsActivity { if (!coordinator.supportsStressMeasurement()) { tabList.remove("stress"); } + if (!coordinator.supportsPai()) { + tabList.remove("pai"); + } return tabList; } @@ -117,6 +120,8 @@ public class ActivityChartsActivity extends AbstractChartsActivity { return new WeekSleepChartFragment(); case "stress": return new StressChartFragment(); + case "pai": + return new PaiChartFragment(); case "stepsweek": return new WeekStepsChartFragment(); case "speedzones": @@ -161,6 +166,8 @@ public class ActivityChartsActivity extends AbstractChartsActivity { return getSleepTitle(); case "stress": return getString(R.string.menuitem_stress); + case "pai": + return getString(R.string.menuitem_pai); case "stepsweek": return getStepsTitle(); case "speedzones": 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 0b30f4eb0..fd96c69a7 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 @@ -36,7 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils; public class ActivityListingAdapter extends AbstractActivityListingAdapter { public static final String CHART_COLOR_START = "#e74c3c"; public static final String CHART_COLOR_END = "#2ecc71"; - protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class); + protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingAdapter.class); protected final int ANIM_TIME = 250; private final int SESSION_SUMMARY = ActivitySession.SESSION_SUMMARY; private final int SESSION_EMPTY = ActivitySession.SESSION_EMPTY; 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 new file mode 100644 index 000000000..81dd82af3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PaiChartFragment.java @@ -0,0 +1,472 @@ +/* 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 . */ +package nodomain.freeyourgadget.gadgetbridge.activities.charts; + +import android.graphics.Color; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.core.content.ContextCompat; + +import com.github.mikephil.charting.charts.BarChart; +import com.github.mikephil.charting.charts.Chart; +import com.github.mikephil.charting.charts.PieChart; +import com.github.mikephil.charting.components.Legend; +import com.github.mikephil.charting.components.LegendEntry; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.BarData; +import com.github.mikephil.charting.data.BarDataSet; +import com.github.mikephil.charting.data.BarEntry; +import com.github.mikephil.charting.data.ChartData; +import com.github.mikephil.charting.data.PieData; +import com.github.mikephil.charting.data.PieDataSet; +import com.github.mikephil.charting.data.PieEntry; +import com.github.mikephil.charting.formatter.ValueFormatter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; +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 { + protected static final Logger LOG = LoggerFactory.getLogger(PaiChartFragment.class); + + protected final int TOTAL_DAYS = getRangeDays(); + + protected Locale mLocale; + + protected PieChart mTodayPieChart; + protected BarChart mWeekChart; + protected TextView mDateView; + protected TextView mLineToday; + protected TextView mLineTotal; + protected TextView mLineLowInc; + protected TextView mLineLowTime; + protected TextView mLineModerateInc; + protected TextView mLineModerateTime; + protected TextView mLineHighInc; + protected TextView mLineHighTime; + + protected int BACKGROUND_COLOR; + protected int DESCRIPTION_COLOR; + protected int CHART_TEXT_COLOR; + protected int LEGEND_TEXT_COLOR; + + protected int PAI_TOTAL_COLOR; + protected int PAI_DAY_COLOR; + + @Override + protected void init() { + BACKGROUND_COLOR = GBApplication.getBackgroundColor(requireContext()); + LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(requireContext()); + CHART_TEXT_COLOR = ContextCompat.getColor(requireContext(), R.color.secondarytext); + + PAI_TOTAL_COLOR = ContextCompat.getColor(requireContext(), R.color.chart_deep_sleep_light); + PAI_DAY_COLOR = ContextCompat.getColor(requireContext(), R.color.chart_activity_dark); + } + + @Override + public View onCreateView(final LayoutInflater inflater, + final ViewGroup container, + final Bundle savedInstanceState) { + mLocale = getResources().getConfiguration().locale; + + final View rootView = inflater.inflate(R.layout.fragment_pai_chart, container, false); + + mTodayPieChart = rootView.findViewById(R.id.pai_chart_today); + mWeekChart = rootView.findViewById(R.id.pai_chart_week); + mDateView = rootView.findViewById(R.id.pai_date_view); + mLineToday = rootView.findViewById(R.id.pai_line_today); + mLineTotal = rootView.findViewById(R.id.pai_line_total); + mLineLowInc = rootView.findViewById(R.id.pai_line_low_inc); + mLineLowTime = rootView.findViewById(R.id.pai_line_low_time); + mLineModerateInc = rootView.findViewById(R.id.pai_line_moderate_inc); + mLineModerateTime = rootView.findViewById(R.id.pai_line_moderate_time); + mLineHighInc = rootView.findViewById(R.id.pai_line_high_inc); + mLineHighTime = rootView.findViewById(R.id.pai_line_high_time); + + setupWeekChart(); + setupTodayPieChart(); + + // refresh immediately instead of use refreshIfVisible(), for perceived performance + refresh(); + + return rootView; + } + + private void setupTodayPieChart() { + mTodayPieChart.setBackgroundColor(BACKGROUND_COLOR); + mTodayPieChart.getDescription().setTextColor(DESCRIPTION_COLOR); + mTodayPieChart.setEntryLabelColor(DESCRIPTION_COLOR); + mTodayPieChart.getDescription().setText(""); + mTodayPieChart.setNoDataText(""); + mTodayPieChart.getLegend().setEnabled(false); + } + + private void setupWeekChart() { + mWeekChart.setBackgroundColor(BACKGROUND_COLOR); + mWeekChart.getDescription().setTextColor(DESCRIPTION_COLOR); + mWeekChart.getDescription().setText(""); + mWeekChart.setFitBars(true); + + configureBarLineChartDefaults(mWeekChart); + + final XAxis x = mWeekChart.getXAxis(); + x.setDrawLabels(true); + x.setDrawGridLines(false); + x.setEnabled(true); + x.setTextColor(CHART_TEXT_COLOR); + x.setDrawLimitLinesBehindData(true); + x.setPosition(XAxis.XAxisPosition.BOTTOM); + + final YAxis y = mWeekChart.getAxisLeft(); + y.setDrawGridLines(true); + y.setDrawTopYLabelEntry(true); + y.setTextColor(CHART_TEXT_COLOR); + y.setDrawZeroLine(true); + y.setSpaceBottom(0); + y.setAxisMinimum(0); + y.setAxisMaximum(100); + y.setValueFormatter(getRoundFormatter()); + y.setEnabled(true); + + final YAxis yAxisRight = mWeekChart.getAxisRight(); + yAxisRight.setDrawGridLines(false); + yAxisRight.setEnabled(false); + yAxisRight.setDrawLabels(false); + yAxisRight.setDrawTopYLabelEntry(false); + yAxisRight.setTextColor(CHART_TEXT_COLOR); + } + + private int getRangeDays() { + if (GBApplication.getPrefs().getBoolean("charts_range", true)) { + return 30; + } else { + return 7; + } + } + + @Override + public String getTitle() { + if (GBApplication.getPrefs().getBoolean("charts_range", true)) { + return getString(R.string.pai_chart_per_month); + } else { + return getString(R.string.pai_chart_per_week); + } + } + + @Override + protected PaiChartsData refreshInBackground(final ChartsHost chartsHost, final DBHandler db, final GBDevice device) { + final Calendar day = Calendar.getInstance(); + day.setTime(chartsHost.getEndDate()); + //NB: we could have omitted the day, but this way we can move things to the past easily + final DayData dayData = refreshDayData(db, day, device); + final WeekChartsData weekBeforeData = refreshWeekBeforeData(db, mWeekChart, day, device); + + return new PaiChartsData(dayData, weekBeforeData); + } + + @Override + protected void updateChartsnUIThread(final PaiChartsData pcd) { + setupLegend(mWeekChart); + mTodayPieChart.setCenterText(""); + mTodayPieChart.setData(pcd.getDayData().getData()); + + // set custom renderer for 30days bar charts + if (GBApplication.getPrefs().getBoolean("charts_range", true)) { + mWeekChart.setRenderer(new AngledLabelsChartRenderer(mWeekChart, mWeekChart.getAnimator(), mWeekChart.getViewPortHandler())); + } + + mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317 + mWeekChart.setData(pcd.getWeekBeforeData().getData()); + mWeekChart.getXAxis().setValueFormatter(pcd.getWeekBeforeData().getXValueFormatter()); + + mDateView.setText(DateTimeUtils.formatDate(pcd.getDayData().day.getTime())); + mLineToday.setText(requireContext().getString(R.string.pai_plus_num, pcd.getDayData().today)); + mLineTotal.setText(String.valueOf(pcd.getDayData().total)); + mLineLowInc.setText(String.valueOf(pcd.getDayData().paiLow)); + mLineLowTime.setText(requireContext().getString(R.string.num_min, pcd.getDayData().minutesLow)); + mLineModerateInc.setText(String.valueOf(pcd.getDayData().paiModerate)); + mLineModerateTime.setText(requireContext().getString(R.string.num_min, pcd.getDayData().minutesModerate)); + mLineHighInc.setText(String.valueOf(pcd.getDayData().paiHigh)); + mLineHighTime.setText(requireContext().getString(R.string.num_min, pcd.getDayData().minutesHigh)); + } + + @Override + protected void renderCharts() { + mWeekChart.invalidate(); + mTodayPieChart.invalidate(); + } + + protected String getWeeksChartsLabel(final Calendar day) { + if (GBApplication.getPrefs().getBoolean("charts_range", true)) { + // month, show day date + return String.valueOf(day.get(Calendar.DAY_OF_MONTH)); + } else { + // week, show short day name + return day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale); + } + } + + protected WeekChartsData refreshWeekBeforeData(final DBHandler db, + final BarChart barChart, + Calendar day, + final GBDevice device) { + day = (Calendar) day.clone(); // do not modify the caller's argument + day.add(Calendar.DATE, -TOTAL_DAYS); + + List entries = new ArrayList<>(); + final ArrayList labels = new ArrayList<>(); + + int maxPai = -1; + + for (int counter = 0; counter < TOTAL_DAYS; counter++) { + final Optional sampleOpt = getSamplePaiForDay(db, device, day); + + if (sampleOpt.isPresent()) { + final PaiSample sample = sampleOpt.get(); + final int paiToday = Math.round(sample.getPaiToday()); + final int paiTotal = Math.round(sample.getPaiTotal()); + + if (paiTotal > maxPai) { + maxPai = paiTotal; + } + + final float[] paiBar = new float[]{ + paiTotal - paiToday, + paiToday + }; + + entries.add(new BarEntry(counter, paiBar)); + } else { + entries.add(new BarEntry(counter, new float[]{0.0f, 0.0f})); + } + labels.add(getWeeksChartsLabel(day)); + day.add(Calendar.DATE, 1); + } + + BarDataSet set = new BarDataSet(entries, ""); + set.setColors(PAI_TOTAL_COLOR, PAI_DAY_COLOR); + set.setValueFormatter(getRoundFormatter()); + + BarData barData = new BarData(set); + barData.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false); + barData.setValueTextSize(10f); + + barChart.getAxisLeft().setAxisMaximum(Math.max(maxPai, 100)); + + //LimitLine target = new LimitLine(mTargetValue); + //barChart.getAxisLeft().removeAllLimitLines(); + //barChart.getAxisLeft().addLimitLine(target); + + //float average = 0; + //if (TOTAL_DAYS_FOR_AVERAGE > 0) { + // average = Math.abs(balance / TOTAL_DAYS_FOR_AVERAGE); + //} + //LimitLine average_line = new LimitLine(average); + //average_line.setLabel(getString(R.string.average, getAverage(average))); +// + //if (average > (mTargetValue)) { + // average_line.setLineColor(Color.GREEN); + // average_line.setTextColor(Color.GREEN); + //} else { + // average_line.setLineColor(Color.RED); + // average_line.setTextColor(Color.RED); + //} + //if (average > 0) { + // if (GBApplication.getPrefs().getBoolean("charts_show_average", true)) { + // barChart.getAxisLeft().addLimitLine(average_line); + // } + //} + + return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels)); + } + + protected DayData refreshDayData(final DBHandler db, + final Calendar day, + final GBDevice device) { + final Optional sampleOpt = getSamplePaiForDay(db, device, day); + + final int today; + final int total; + final int paiLow; + final int paiModerate; + final int paiHigh; + final int minutesLow; + final int minutesModerate; + final int minutesHigh; + + if (sampleOpt.isPresent()) { + final PaiSample sample = sampleOpt.get(); + today = Math.round(sample.getPaiToday()); + total = Math.round(sample.getPaiTotal()); + paiLow = Math.round(sample.getPaiLow()); + paiModerate = Math.round(sample.getPaiModerate()); + paiHigh = Math.round(sample.getPaiHigh()); + minutesLow = sample.getTimeLow(); + minutesModerate = sample.getTimeModerate(); + minutesHigh = sample.getTimeHigh(); + } else { + today = 0; + total = 0; + paiLow = 0; + paiModerate = 0; + paiHigh = 0; + minutesLow = 0; + minutesModerate = 0; + minutesHigh = 0; + } + + final PieData data = new PieData(); + final List entries = new ArrayList<>(); + + final int maxPai = Math.max(100, total); + final String todayLabel = today != 0 ? requireContext().getString(R.string.pai_plus_num, today) : ""; + + entries.add(new PieEntry(today, todayLabel)); + entries.add(new PieEntry(total - today, "")); + entries.add(new PieEntry(maxPai - total, "")); + + final PieDataSet pieDataSet = new PieDataSet(entries, ""); + pieDataSet.setColors(PAI_DAY_COLOR, PAI_TOTAL_COLOR, 0x0); + data.setDataSet(pieDataSet); + data.setDrawValues(false); + + return new DayData(day, data, today, total, paiLow, paiModerate, paiHigh, minutesLow, minutesModerate, minutesHigh); + } + + protected ValueFormatter getRoundFormatter() { + return new ValueFormatter() { + @Override + public String getFormattedValue(final float value) { + return String.valueOf(Math.round(value)); + } + }; + } + + @Override + protected void setupLegend(Chart chart) { + List legendEntries = new ArrayList<>(2); + + LegendEntry lightSleepEntry = new LegendEntry(); + lightSleepEntry.label = requireContext().getString(R.string.pai_total); + lightSleepEntry.formColor = PAI_TOTAL_COLOR; + legendEntries.add(lightSleepEntry); + + LegendEntry deepSleepEntry = new LegendEntry(); + deepSleepEntry.label = requireContext().getString(R.string.pai_day); + deepSleepEntry.formColor = PAI_DAY_COLOR; + legendEntries.add(deepSleepEntry); + + chart.getLegend().setCustom(legendEntries); + chart.getLegend().setTextColor(LEGEND_TEXT_COLOR); + chart.getLegend().setWordWrapEnabled(true); + chart.getLegend().setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER); + } + + private Optional getSamplePaiForDay(final DBHandler db, final GBDevice device, final Calendar day) { + final Date dayStart = DateTimeUtils.dayStart(day.getTime()); + final Date dayEnd = DateTimeUtils.dayEnd(day.getTime()); + final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); + final TimeSampleProvider sampleProvider = coordinator.getPaiSampleProvider(device, db.getDaoSession()); + final List daySamples = sampleProvider.getAllSamples(dayStart.getTime(), dayEnd.getTime()); + return Optional.ofNullable(daySamples.isEmpty() ? null : daySamples.get(daySamples.size() - 1)); + } + + protected static class DayData { + private final Calendar day; + private final PieData data; + private final int today; + private final int total; + private final int paiLow; + private final int paiModerate; + private final int paiHigh; + private final int minutesLow; + private final int minutesModerate; + private final int minutesHigh; + + DayData(final Calendar day, + final PieData data, + final int today, + final int total, + final int paiLow, + final int paiModerate, + final int paiHigh, + final int minutesLow, + final int minutesModerate, + final int minutesHigh) { + this.day = day; + this.data = data; + this.today = today; + this.total = total; + this.paiLow = paiLow; + this.paiModerate = paiModerate; + this.paiHigh = paiHigh; + this.minutesLow = minutesLow; + this.minutesModerate = minutesModerate; + this.minutesHigh = minutesHigh; + } + + public PieData getData() { + return data; + } + } + + protected static class WeekChartsData> extends DefaultChartsData { + public WeekChartsData(final T data, + final PreformattedXIndexLabelFormatter xIndexLabelFormatter) { + super(data, xIndexLabelFormatter); + } + } + + protected static class PaiChartsData extends ChartsData { + private final WeekChartsData weekBeforeData; + private final DayData dayData; + + PaiChartsData(final DayData dayData, final WeekChartsData weekBeforeData) { + this.dayData = dayData; + this.weekBeforeData = weekBeforeData; + } + + public DayData getDayData() { + return dayData; + } + + public WeekChartsData getWeekBeforeData() { + return weekBeforeData; + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java index 5b1faed1d..53adc1f09 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java @@ -1674,6 +1674,10 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements this.fetchOperationQueue.add(new FetchStressManualOperation(this)); } + if ((dataTypes & RecordedDataTypes.TYPE_PAI) != 0 && coordinator.supportsPai()) { + this.fetchOperationQueue.add(new FetchPaiOperation(this)); + } + if (Huami2021Coordinator.experimentalFeatures(getDevice())) { if ((dataTypes & RecordedDataTypes.TYPE_SPO2) != 0 && coordinator.supportsSpo2()) { this.fetchOperationQueue.add(new FetchSpo2NormalOperation(this)); @@ -1685,10 +1689,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements this.fetchOperationQueue.add(new FetchHeartRateRestingOperation(this)); } - if ((dataTypes & RecordedDataTypes.TYPE_PAI) != 0 && coordinator.supportsPai()) { - this.fetchOperationQueue.add(new FetchPaiOperation(this)); - } - if ((dataTypes & RecordedDataTypes.TYPE_SLEEP_RESPIRATORY_RATE) != 0 && coordinator.supportsSleepRespiratoryRate()) { this.fetchOperationQueue.add(new FetchSleepRespiratoryRateOperation(this)); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java index b514f2d87..4625afc65 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java @@ -119,6 +119,26 @@ public class DateTimeUtils { return newDate; } + public static Date dayStart(final Date date) { + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } + + public static Date dayEnd(final Date date) { + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 999); + return calendar.getTime(); + } + public static Date parseTimeStamp(int timestamp) { GregorianCalendar cal = (GregorianCalendar) GregorianCalendar.getInstance(); cal.setTimeInMillis(timestamp * 1000L); // make sure it's converted to long diff --git a/app/src/main/res/layout/fragment_pai_chart.xml b/app/src/main/res/layout/fragment_pai_chart.xml new file mode 100644 index 000000000..9c20e36e2 --- /dev/null +++ b/app/src/main/res/layout/fragment_pai_chart.xml @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index a9f8355eb..0802bd968 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -2460,6 +2460,7 @@ @string/weeksleepchart_sleep_a_month @string/weekstepschart_steps_a_month @string/menuitem_stress + @string/menuitem_pai @string/stats_title @string/liveactivity_live_activity @@ -2471,6 +2472,7 @@ @string/p_sleep_week @string/p_steps_week @string/p_stress + @string/p_pai @string/p_speed_zones @string/p_live_stats @@ -2482,6 +2484,7 @@ @string/p_sleep_week @string/p_steps_week @string/p_stress + @string/p_pai @string/p_speed_zones @string/p_live_stats diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d3fdce966..7b6e434ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -766,6 +766,10 @@ Sleep per week Sleep today, target: %1$s Steps per week + PAI per week + PAI per month + +%d + %d min Activity Activity list Active steps @@ -1900,6 +1904,8 @@ Mild Moderate High + PAI Total + Day PAI increase Mode Off Noise Cancelling diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index a0eda9aa3..24814b418 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -102,6 +102,7 @@ sleepweek stepsweek stress + pai speedzones livestats