mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-27 19:15:50 +01:00
Add PAI charts
This commit is contained in:
parent
0f7fa75931
commit
e95c8a3775
@ -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<Device> 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();
|
||||
}
|
||||
|
@ -95,6 +95,7 @@ public abstract class AbstractActivityChartFragment<D extends ChartsData> extend
|
||||
protected String HEARTRATE_LABEL;
|
||||
protected String HEARTRATE_AVERAGE_LABEL;
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
TypedValue runningColor = new TypedValue();
|
||||
|
@ -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":
|
||||
|
@ -36,7 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
|
||||
public class ActivityListingAdapter extends AbstractActivityListingAdapter<ActivitySession> {
|
||||
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;
|
||||
|
@ -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 <http://www.gnu.org/licenses/>. */
|
||||
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<PaiChartFragment.PaiChartsData> {
|
||||
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<BarData> 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<BarData> 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<BarEntry> entries = new ArrayList<>();
|
||||
final ArrayList<String> labels = new ArrayList<>();
|
||||
|
||||
int maxPai = -1;
|
||||
|
||||
for (int counter = 0; counter < TOTAL_DAYS; counter++) {
|
||||
final Optional<? extends PaiSample> 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<? extends PaiSample> 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<PieEntry> 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<LegendEntry> 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<? extends PaiSample> 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<? extends PaiSample> sampleProvider = coordinator.getPaiSampleProvider(device, db.getDaoSession());
|
||||
final List<? extends PaiSample> 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<T extends ChartData<?>> extends DefaultChartsData<T> {
|
||||
public WeekChartsData(final T data,
|
||||
final PreformattedXIndexLabelFormatter xIndexLabelFormatter) {
|
||||
super(data, xIndexLabelFormatter);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class PaiChartsData extends ChartsData {
|
||||
private final WeekChartsData<BarData> weekBeforeData;
|
||||
private final DayData dayData;
|
||||
|
||||
PaiChartsData(final DayData dayData, final WeekChartsData<BarData> weekBeforeData) {
|
||||
this.dayData = dayData;
|
||||
this.weekBeforeData = weekBeforeData;
|
||||
}
|
||||
|
||||
public DayData getDayData() {
|
||||
return dayData;
|
||||
}
|
||||
|
||||
public WeekChartsData<BarData> getWeekBeforeData() {
|
||||
return weekBeforeData;
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
@ -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
|
||||
|
293
app/src/main/res/layout/fragment_pai_chart.xml
Normal file
293
app/src/main/res/layout/fragment_pai_chart.xml
Normal file
@ -0,0 +1,293 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pai_date_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.github.mikephil.charting.charts.PieChart
|
||||
android:id="@+id/pai_chart_today"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="210dp"
|
||||
android:layout_gravity="bottom|center"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="bottom|center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="60dp"
|
||||
android:paddingRight="60dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/activity_summary_today"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pai_line_today"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="+10"
|
||||
android:textSize="24sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/step_streak_total"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pai_line_total"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="100"
|
||||
android:textSize="24sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/sony_speak_to_chat_sensitivity_low"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pai_line_low_inc"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="5"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pai_line_low_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="10 min"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/stress_moderate"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pai_line_moderate_inc"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="5"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pai_line_moderate_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="10 min"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/prefs_active_noise_cancelling_level_low"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pai_line_high_inc"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="5"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pai_line_high_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="10 min"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.github.mikephil.charting.charts.BarChart
|
||||
android:id="@+id/pai_chart_week"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="20" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
@ -2460,6 +2460,7 @@
|
||||
<item>@string/weeksleepchart_sleep_a_month</item>
|
||||
<item>@string/weekstepschart_steps_a_month</item>
|
||||
<item>@string/menuitem_stress</item>
|
||||
<item>@string/menuitem_pai</item>
|
||||
<item>@string/stats_title</item>
|
||||
<item>@string/liveactivity_live_activity</item>
|
||||
</string-array>
|
||||
@ -2471,6 +2472,7 @@
|
||||
<item>@string/p_sleep_week</item>
|
||||
<item>@string/p_steps_week</item>
|
||||
<item>@string/p_stress</item>
|
||||
<item>@string/p_pai</item>
|
||||
<item>@string/p_speed_zones</item>
|
||||
<item>@string/p_live_stats</item>
|
||||
</string-array>
|
||||
@ -2482,6 +2484,7 @@
|
||||
<item>@string/p_sleep_week</item>
|
||||
<item>@string/p_steps_week</item>
|
||||
<item>@string/p_stress</item>
|
||||
<item>@string/p_pai</item>
|
||||
<item>@string/p_speed_zones</item>
|
||||
<item>@string/p_live_stats</item>
|
||||
</string-array>
|
||||
|
@ -766,6 +766,10 @@
|
||||
<string name="weeksleepchart_sleep_a_week">Sleep per week</string>
|
||||
<string name="weeksleepchart_today_sleep_description">Sleep today, target: %1$s</string>
|
||||
<string name="weekstepschart_steps_a_week">Steps per week</string>
|
||||
<string name="pai_chart_per_week">PAI per week</string>
|
||||
<string name="pai_chart_per_month">PAI per month</string>
|
||||
<string name="pai_plus_num">+%d</string>
|
||||
<string name="num_min">%d min</string>
|
||||
<string name="activity_sleepchart_activity_and_sleep">Activity</string>
|
||||
<string name="charts_activity_list">Activity list</string>
|
||||
<string name="activity_list_summary_active_steps">Active steps</string>
|
||||
@ -1900,6 +1904,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="sony_ambient_sound">Mode</string>
|
||||
<string name="sony_ambient_sound_off">Off</string>
|
||||
<string name="sony_ambient_sound_noise_cancelling">Noise Cancelling</string>
|
||||
|
@ -102,6 +102,7 @@
|
||||
<item name="p_sleep_week" type="string">sleepweek</item>
|
||||
<item name="p_steps_week" type="string">stepsweek</item>
|
||||
<item name="p_stress" type="string">stress</item>
|
||||
<item name="p_pai" type="string">pai</item>
|
||||
<item name="p_speed_zones" type="string">speedzones</item>
|
||||
<item name="p_live_stats" type="string">livestats</item>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user