diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java
deleted file mode 100644
index a2c37297fe..0000000000
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/* Copyright (C) 2017-2024 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
- Daniele Gobbetti, José Rebelo, Pavel Elagin, Petr Vaněk, a0z
-
- 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.app.Activity;
-import android.graphics.Color;
-
-import com.github.mikephil.charting.charts.BarChart;
-import com.github.mikephil.charting.components.LimitLine;
-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.Entry;
-import com.github.mikephil.charting.data.LineData;
-import com.github.mikephil.charting.data.LineDataSet;
-import com.github.mikephil.charting.formatter.ValueFormatter;
-import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-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.TimeSampleProvider;
-import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
-import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
-import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
-import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
-import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
-
-
-public abstract class AbstractWeekChartFragment extends AbstractActivityChartFragment {
- protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class);
- protected int TOTAL_DAYS = getRangeDays();
- protected int TOTAL_DAYS_FOR_AVERAGE = 0;
-
- protected Locale mLocale;
- protected int mTargetValue = 0;
-
- protected BarChart mWeekChart;
-
- private final int mOffsetHours = getOffsetHours();
-
- protected String getWeeksChartsLabel(Calendar day){
- if (TOTAL_DAYS > 7) {
- //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(DBHandler db, BarChart barChart, Calendar day, GBDevice device) {
- day = (Calendar) day.clone(); // do not modify the caller's argument
- day.add(Calendar.DATE, -TOTAL_DAYS + 1);
- List entries = new ArrayList<>();
- ArrayList labels = new ArrayList();
-
- long balance = 0;
- long daily_balance = 0;
- TOTAL_DAYS_FOR_AVERAGE=0;
- List sleepScoreEntities = new ArrayList<>();
- final List sleepScoreDataSets = new ArrayList<>();
- for (int counter = 0; counter < TOTAL_DAYS; counter++) {
- // Sleep stages
- ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
- daily_balance=calculateBalance(amounts);
- if (daily_balance > 0) {
- TOTAL_DAYS_FOR_AVERAGE++;
- }
- balance += daily_balance;
- entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts)));
- labels.add(getWeeksChartsLabel(day));
- // Sleep score
- if (supportsSleepScore()) {
- List extends SleepScoreSample> sleepScoreSamples = getSleepScoreSamples(db, device, day);
- if (!sleepScoreSamples.isEmpty() && sleepScoreSamples.get(0).getSleepScore() > 0) {
- sleepScoreEntities.add(new Entry(counter, sleepScoreSamples.get(0).getSleepScore()));
- } else {
- if (!sleepScoreEntities.isEmpty()) {
- List clone = new ArrayList<>(sleepScoreEntities.size());
- clone.addAll(sleepScoreEntities);
- sleepScoreDataSets.add(createSleepScoreDataSet(clone));
- sleepScoreEntities.clear();
- }
- }
- }
- day.add(Calendar.DATE, 1);
- }
- if (!sleepScoreEntities.isEmpty()) {
- sleepScoreDataSets.add(createSleepScoreDataSet(sleepScoreEntities));
- }
- final LineData sleepScoreLineData = new LineData(sleepScoreDataSets);
- sleepScoreLineData.setHighlightEnabled(false);
-
- BarDataSet set = new BarDataSet(entries, "");
- set.setColors(getColors());
- set.setValueFormatter(getBarValueFormatter());
-
- 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(set.getYMax(), mTargetValue) + 60);
-
- LimitLine target = new LimitLine(mTargetValue);
- target.setLineWidth(1.5f);
- target.enableDashedLine(15f, 10f, 0f);
- target.setLineColor(getResources().getColor(R.color.chart_deep_sleep_dark));
- 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.setLineWidth(1.5f);
- average_line.enableDashedLine(15f, 10f, 0f);
- 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);
- }
- }
-
- if (supportsSleepScore()) {
- return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue), sleepScoreLineData);
- }
- return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue));
- }
-
- protected List getSleepScoreSamples(DBHandler db, GBDevice device, Calendar day) {
- int startTs;
- int endTs;
-
- day = (Calendar) day.clone(); // do not modify the caller's argument
- day.set(Calendar.HOUR_OF_DAY, 0);
- day.set(Calendar.MINUTE, 0);
- day.set(Calendar.SECOND, 0);
- day.add(Calendar.HOUR, 0);
- startTs = (int) (day.getTimeInMillis() / 1000);
- endTs = startTs + 24 * 60 * 60 - 1;
-
- TimeSampleProvider extends SleepScoreSample> provider = device.getDeviceCoordinator().getSleepScoreProvider(device, db.getDaoSession());
- return (List) provider.getAllSamples(startTs * 1000L, endTs * 1000L);
- }
-
- protected LineDataSet createSleepScoreDataSet(final List values) {
- final LineDataSet lineDataSet = new LineDataSet(values, getString(R.string.sleep_score));
- lineDataSet.setColor(getResources().getColor(R.color.chart_light_sleep_light));
- lineDataSet.setDrawCircles(false);
- lineDataSet.setLineWidth(2f);
- lineDataSet.setFillAlpha(255);
- lineDataSet.setCircleRadius(5f);
- lineDataSet.setDrawCircles(true);
- lineDataSet.setDrawCircleHole(true);
- lineDataSet.setCircleColor(getResources().getColor(R.color.chart_light_sleep_light));
- lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
- lineDataSet.setDrawValues(true);
- lineDataSet.setValueTextSize(10f);
- lineDataSet.setValueTextColor(CHART_TEXT_COLOR);
- lineDataSet.setValueFormatter(new ValueFormatter() {
- @Override
- public String getFormattedValue(float value) {
- return String.format(Locale.ROOT, "%d", (int) value);
- }
- });
- return lineDataSet;
- };
-
- protected void setupWeekChart() {
- mWeekChart.setBackgroundColor(BACKGROUND_COLOR);
- mWeekChart.getDescription().setTextColor(DESCRIPTION_COLOR);
- mWeekChart.getDescription().setText("");
- mWeekChart.setFitBars(true);
-
- configureBarLineChartDefaults(mWeekChart);
-
- 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);
-
- YAxis y = mWeekChart.getAxisLeft();
- y.setDrawGridLines(false);
- y.setDrawTopYLabelEntry(false);
- y.setTextColor(CHART_TEXT_COLOR);
- y.setDrawZeroLine(true);
- y.setSpaceBottom(0);
- y.setAxisMinimum(0);
- y.setValueFormatter(getYAxisFormatter());
- y.setEnabled(true);
-
- YAxis yAxisRight = mWeekChart.getAxisRight();
- yAxisRight.setDrawGridLines(false);
- yAxisRight.setEnabled(false);
- yAxisRight.setDrawLabels(false);
- yAxisRight.setDrawTopYLabelEntry(false);
- yAxisRight.setTextColor(CHART_TEXT_COLOR);
- }
-
- private List extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, int offsetHours, GBDevice device) {
- int startTs;
- int endTs;
-
- day = (Calendar) day.clone(); // do not modify the caller's argument
- day.set(Calendar.HOUR_OF_DAY, 0);
- day.set(Calendar.MINUTE, 0);
- day.set(Calendar.SECOND, 0);
- day.add(Calendar.HOUR, offsetHours);
-
- startTs = (int) (day.getTimeInMillis() / 1000);
- endTs = startTs + 24 * 60 * 60 - 1;
-
- return getSamples(db, device, startTs, endTs);
- }
-
- @Override
- protected List extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
- return super.getAllSamples(db, device, tsFrom, tsTo);
- }
-
- protected static class MyChartsData extends ChartsData {
- private final WeekChartsData weekBeforeData;
-
- MyChartsData(WeekChartsData weekBeforeData) {
- this.weekBeforeData = weekBeforeData;
- }
-
- WeekChartsData getWeekBeforeData() {
- return weekBeforeData;
- }
- }
-
- protected ActivityAmounts getActivityAmountsForDay(DBHandler db, Calendar day, GBDevice device) {
-
- LimitedQueue activityAmountCache = null;
- ActivityAmounts amounts = null;
-
- Activity activity = getActivity();
- int key = (int) (day.getTimeInMillis() / 1000) + (mOffsetHours * 3600);
- if (activity != null) {
- activityAmountCache = ((ActivityChartsActivity) activity).mActivityAmountCache;
- amounts = activityAmountCache.lookup(key);
- }
-
- if (amounts == null) {
- ActivityAnalysis analysis = new ActivityAnalysis();
- amounts = analysis.calculateActivityAmounts(getSamplesOfDay(db, day, mOffsetHours, device));
- if (activityAmountCache != null) {
- activityAmountCache.add(key, amounts);
- }
- }
-
- return amounts;
- }
-
- private int getRangeDays(){
- if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
- return 30;}
- else{
- return 7;
- }
- }
-
- public boolean supportsSleepScore() {
- final GBDevice device = getChartsHost().getDevice();
- return device.getDeviceCoordinator().supportsSleepScore(device);
- }
-
- abstract String getAverage(float value);
-
- abstract int getGoal();
-
- abstract int getOffsetHours();
-
- abstract float[] getTotalsForActivityAmounts(ActivityAmounts activityAmounts);
-
- abstract String formatPieValue(long value);
-
- abstract String[] getPieLabels();
-
- abstract ValueFormatter getPieValueFormatter();
-
- abstract ValueFormatter getBarValueFormatter();
-
- abstract ValueFormatter getYAxisFormatter();
-
- abstract int[] getColors();
-
- abstract String getPieDescription(int targetValue);
-
- protected abstract long calculateBalance(ActivityAmounts amounts);
-
- protected abstract String getBalanceMessage(long balance, int targetValue);
-
- protected class WeekChartsData> extends DefaultChartsData {
- private final String balanceMessage;
- private LineData sleepScoresLineData;
-
- public WeekChartsData(T data, PreformattedXIndexLabelFormatter xIndexLabelFormatter, String balanceMessage) {
- super(data, xIndexLabelFormatter);
- this.balanceMessage = balanceMessage;
- }
-
- public WeekChartsData(T data, PreformattedXIndexLabelFormatter xIndexLabelFormatter, String balanceMessage, LineData sleepScores) {
- super(data, xIndexLabelFormatter);
- this.balanceMessage = balanceMessage;
- this.sleepScoresLineData = sleepScores;
- }
-
- public String getBalanceMessage() {
- return balanceMessage;
- }
-
- public LineData getSleepScoreData() { return sleepScoresLineData; }
- }
-}
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 05f66dc8d8..4c934ebe6a 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
@@ -177,7 +177,7 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
case "vo2max":
return new VO2MaxFragment();
case "stress":
- return new StressChartFragment();
+ return StressCollectionFragment.newInstance(enabledTabsList.size() == 1);
case "pai":
return new PaiChartFragment();
case "stepsweek":
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/Spo2ChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/Spo2ChartFragment.java
index f235cab9c0..3a08b8902c 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/Spo2ChartFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/Spo2ChartFragment.java
@@ -53,7 +53,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
-// Based on StressChartFragment
+// Based on StressDailyFragment
public class Spo2ChartFragment extends AbstractChartFragment {
protected static final Logger LOG = LoggerFactory.getLogger(Spo2ChartFragment.class);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressCollectionFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressCollectionFragment.java
new file mode 100644
index 0000000000..e3568a12c8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressCollectionFragment.java
@@ -0,0 +1,44 @@
+/* Copyright (C) 2024 a0z, 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.os.Bundle;
+
+import androidx.fragment.app.FragmentManager;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
+import nodomain.freeyourgadget.gadgetbridge.adapter.NestedFragmentAdapter;
+import nodomain.freeyourgadget.gadgetbridge.adapter.StressFragmentAdapter;
+
+public class StressCollectionFragment extends AbstractCollectionFragment {
+ public StressCollectionFragment() {
+
+ }
+
+ public static StressCollectionFragment newInstance(final boolean allowSwipe) {
+ final StressCollectionFragment fragment = new StressCollectionFragment();
+ final Bundle args = new Bundle();
+ args.putBoolean(ARG_ALLOW_SWIPE, allowSwipe);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public NestedFragmentAdapter getNestedFragmentAdapter(AbstractGBFragment fragment, FragmentManager childFragmentManager) {
+ return new StressFragmentAdapter(this, getChildFragmentManager());
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressDailyFragment.java
similarity index 82%
rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressChartFragment.java
rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressDailyFragment.java
index 64ad312132..c20b94d80d 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressChartFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressDailyFragment.java
@@ -16,7 +16,6 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
-import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
@@ -29,8 +28,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-import androidx.core.content.ContextCompat;
-
import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.LineChart;
@@ -64,15 +61,13 @@ import java.util.concurrent.TimeUnit;
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.StressSample;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
-public class StressChartFragment extends AbstractChartFragment {
- protected static final Logger LOG = LoggerFactory.getLogger(StressChartFragment.class);
+public class StressDailyFragment extends StressFragment {
+ protected static final Logger LOG = LoggerFactory.getLogger(StressDailyFragment.class);
private LineChart mStressChart;
private PieChart mStressLevelsPieChart;
@@ -82,27 +77,17 @@ public class StressChartFragment extends AbstractChartFragment samples = getSamples(db, device, tsStart, tsEnd);
+ final List extends StressSample> samples = getStressSamples(db, device, tsStart, tsEnd);
LOG.info("Got {} stress samples", samples.size());
@@ -196,11 +181,6 @@ public class StressChartFragment extends AbstractChartFragment chart) {
- final List legendEntries = new ArrayList<>(StressType.values().length + 1);
-
- for (final StressType stressType : StressType.values()) {
- final LegendEntry entry = new LegendEntry();
- entry.label = stressType.getLabel(requireContext());
- entry.formColor = stressType.getColor(requireContext());
- legendEntries.add(entry);
- }
+ final List legendEntries = createLegendEntries(chart);
if (!CHARTS_SLEEP_RANGE_24H && SHOW_CHARTS_AVERAGE) {
final LegendEntry averageEntry = new LegendEntry();
@@ -303,54 +276,6 @@ public class StressChartFragment extends AbstractChartFragment getSamples(final DBHandler db, final GBDevice device, int tsStart, int tsEnd) {
- final DeviceCoordinator coordinator = device.getDeviceCoordinator();
- final TimeSampleProvider extends StressSample> sampleProvider = coordinator.getStressSampleProvider(device, db.getDaoSession());
- return sampleProvider.getAllSamples(tsStart * 1000L, tsEnd * 1000L);
- }
-
- protected void ensureStartAndEndSamples(final List samples, int tsStart, int tsEnd) {
- if (samples == null || samples.isEmpty()) {
- return;
- }
-
- final long tsEndMillis = tsEnd * 1000L;
- final long tsStartMillis = tsStart * 1000L;
-
- final StressSample lastSample = samples.get(samples.size() - 1);
- if (lastSample.getTimestamp() < tsEndMillis) {
- samples.add(new EmptyStressSample(tsEndMillis));
- }
-
- final StressSample firstSample = samples.get(0);
- if (firstSample.getTimestamp() > tsStartMillis) {
- samples.add(0, new EmptyStressSample(tsStartMillis));
- }
- }
-
- protected static final class EmptyStressSample implements StressSample {
- private final long ts;
-
- public EmptyStressSample(final long ts) {
- this.ts = ts;
- }
-
- @Override
- public Type getType() {
- return Type.AUTOMATIC;
- }
-
- @Override
- public int getStress() {
- return -1;
- }
-
- @Override
- public long getTimestamp() {
- return ts;
- }
- }
-
protected class StressChartsDataBuilder {
private static final int UNKNOWN_VAL = 2;
@@ -407,8 +332,6 @@ public class StressChartFragment extends AbstractChartFragment sampleRate * 10) {
// More than 15 minutes since last sample
@@ -567,43 +488,4 @@ public class StressChartFragment extends AbstractChartFragment extends AbstractChartFragment {
+
+ protected int BACKGROUND_COLOR;
+ protected int DESCRIPTION_COLOR;
+ protected int CHART_TEXT_COLOR;
+ protected int LEGEND_TEXT_COLOR;
+
+ @Override
+ public String getTitle() {
+ return getString(R.string.menuitem_stress);
+ }
+
+ @Override
+ protected void init() {
+ BACKGROUND_COLOR = GBApplication.getBackgroundColor(requireContext());
+ LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(requireContext());
+ CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+ }
+
+ protected List extends StressSample> getStressSamples(DBHandler db, GBDevice device, int tsStart, int tsEnd) {
+ final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+ final TimeSampleProvider extends StressSample> sampleProvider = coordinator.getStressSampleProvider(device, db.getDaoSession());
+ return sampleProvider.getAllSamples(tsStart * 1000L, tsEnd * 1000L);
+ }
+
+ protected void ensureStartAndEndSamples(final List samples, int tsStart, int tsEnd) {
+ if (samples == null || samples.isEmpty()) {
+ return;
+ }
+
+ final long tsEndMillis = tsEnd * 1000L;
+ final long tsStartMillis = tsStart * 1000L;
+
+ final StressSample lastSample = samples.get(samples.size() - 1);
+ if (lastSample.getTimestamp() < tsEndMillis) {
+ samples.add(new EmptyStressSample(tsEndMillis));
+ }
+
+ final StressSample firstSample = samples.get(0);
+ if (firstSample.getTimestamp() > tsStartMillis) {
+ samples.add(0, new EmptyStressSample(tsStartMillis));
+ }
+ }
+
+ protected List createLegendEntries(Chart> chart) {
+ List legendEntries = new ArrayList<>(StressType.values().length);
+
+ for (final StressType stressType : StressType.values()) {
+ final LegendEntry entry = new LegendEntry();
+ entry.label = stressType.getLabel(requireContext());
+ entry.formColor = stressType.getColor(requireContext());
+ legendEntries.add(entry);
+ }
+
+ return legendEntries;
+ }
+
+ protected Map calculateStressTotals(List extends StressSample> samples, int[] stressRanges, int sampleRate) {
+ Map result = new HashMap<>();
+ for (StressType type : StressType.values()) {
+ result.put(type, 0);
+ }
+
+ StressSample prevSample = null;
+ for (StressSample sample : samples) {
+ if (prevSample != null && sample.getStress() >= 0) {
+ long durationSinceLastSample = sample.getTimestamp() - prevSample.getTimestamp();
+ durationSinceLastSample = Math.min(durationSinceLastSample, sampleRate * 10 * 1000); // Cap at 10x sample rate
+
+ StressType type = StressType.fromStress(prevSample.getStress(), stressRanges);
+ if (type != StressType.UNKNOWN) {
+ int seconds = (int) (durationSinceLastSample / 1000);
+ result.put(type, result.get(type) + seconds);
+ }
+ }
+ prevSample = sample;
+ }
+
+ return result;
+ }
+
+ protected int calculateAverageStress(List extends StressSample> samples) {
+ long sum = 0;
+ int count = 0;
+
+ for (StressSample sample : samples) {
+ if (sample.getStress() > 0) {
+ sum += sample.getStress();
+ count++;
+ }
+ }
+
+ return count > 0 ? Math.round((float) sum / count) : 0;
+ }
+
+ protected static final class EmptyStressSample implements StressSample {
+ private final long ts;
+
+ public EmptyStressSample(final long ts) {
+ this.ts = ts;
+ }
+
+ @Override
+ public Type getType() {
+ return Type.AUTOMATIC;
+ }
+
+ @Override
+ public int getStress() {
+ return -1;
+ }
+
+ @Override
+ public long getTimestamp() {
+ return ts;
+ }
+ }
+
+ public enum StressType {
+ UNKNOWN(R.string.unknown, R.color.chart_stress_unknown),
+ RELAXED(R.string.stress_relaxed, R.color.chart_stress_relaxed),
+ MILD(R.string.stress_mild, R.color.chart_stress_mild),
+ MODERATE(R.string.stress_moderate, R.color.chart_stress_moderate),
+ HIGH(R.string.stress_high, R.color.chart_stress_high);
+
+ private final int labelId;
+ private final int colorId;
+
+ StressType(final int labelId, final int colorId) {
+ this.labelId = labelId;
+ this.colorId = colorId;
+ }
+
+ public String getLabel(final Context context) {
+ return context.getString(labelId);
+ }
+
+ public int getColor(final Context context) {
+ return ContextCompat.getColor(context, colorId);
+ }
+
+ public static StressType fromStress(final int stress, final int[] stressRanges) {
+ if (stress < stressRanges[0]) {
+ return StressType.UNKNOWN;
+ } else if (stress < stressRanges[1]) {
+ return StressType.RELAXED;
+ } else if (stress < stressRanges[2]) {
+ return StressType.MILD;
+ } else if (stress < stressRanges[3]) {
+ return StressType.MODERATE;
+ } else {
+ return StressType.HIGH;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressPeriodFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressPeriodFragment.java
new file mode 100644
index 0000000000..dd0b7916d8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressPeriodFragment.java
@@ -0,0 +1,513 @@
+/* Copyright (C) 2023-2024
+
+ 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.os.Build;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.RelativeSizeSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+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.PieData;
+import com.github.mikephil.charting.data.PieDataSet;
+import com.github.mikephil.charting.data.PieEntry;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+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.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public class StressPeriodFragment extends StressFragment {
+ protected static final Logger LOG = LoggerFactory.getLogger(StressPeriodFragment.class);
+
+ protected int TOTAL_DAYS = getRangeDays();
+ protected int TOTAL_DAYS_FOR_AVERAGE = 0;
+
+ private TextView relaxedStressTimeText;
+ private TextView mildStressTimeText;
+ private TextView moderateStressTimeText;
+ private TextView highStressTimeText;
+ private TextView stressDatesText;
+ private PieChart mStressLevelsPieChart;
+ private BarChart mWeekChart;
+
+ private MyStressWeeklyData myStressWeeklyData;
+ private boolean showStressLevelInPercents = false;
+ protected Locale mLocale;
+
+ public static StressPeriodFragment newInstance(int totalDays) {
+ StressPeriodFragment fragment = new StressPeriodFragment();
+ Bundle args = new Bundle();
+ args.putInt("totalDays", totalDays);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ TOTAL_DAYS = getArguments() != null ? getArguments().getInt("totalDays") : 0;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mLocale = getResources().getConfiguration().locale;
+ View rootView = inflater.inflate(R.layout.fragment_weekstress_chart, container, false);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+ getChartsHost().enableSwipeRefresh(scrollY == 0);
+ });
+ }
+
+ mWeekChart = rootView.findViewById(R.id.weekstresschart);
+ mStressLevelsPieChart = rootView.findViewById(R.id.stress_pie_chart);
+ relaxedStressTimeText = rootView.findViewById(R.id.stress_chart_relaxed_time);
+ mildStressTimeText = rootView.findViewById(R.id.stress_chart_mild_time);
+ moderateStressTimeText = rootView.findViewById(R.id.stress_chart_moderate_time);
+ highStressTimeText = rootView.findViewById(R.id.stress_chart_high_time);
+ stressDatesText = rootView.findViewById(R.id.stress_dates);
+
+ setupPieChart();
+ setupWeekChart();
+ refresh();
+
+ return rootView;
+ }
+
+ @Override
+ protected MyChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+ Calendar day = Calendar.getInstance();
+ day.setTime(chartsHost.getEndDate());
+ DefaultChartsData weekBeforeData = refreshWeekBeforeStressData(db, mWeekChart, day, device);
+ myStressWeeklyData = getMyStressWeeklyData(db, day, device);
+
+ return new MyChartsData(weekBeforeData);
+ }
+
+ @Override
+ protected void updateChartsnUIThread(MyChartsData mcd) {
+ setupLegend(mWeekChart);
+
+ mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
+ mWeekChart.setData(mcd.getWeekBeforeData().getData());
+ mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter());
+ mWeekChart.getBarData().setValueTextSize(10f);
+
+ updatePieChart();
+ updateStressTimeTexts();
+
+ stressDatesText.setText(DateTimeUtils.formatDaysUntil(TOTAL_DAYS, getTSEnd()));
+ }
+
+ private void updatePieChart() {
+ List pieEntries = new ArrayList<>();
+ List pieColors = new ArrayList<>();
+
+ if (TOTAL_DAYS_FOR_AVERAGE > 0 && myStressWeeklyData != null) {
+ long totalTime = myStressWeeklyData.totalStressTime();
+ if (totalTime > 0) {
+ if (myStressWeeklyData.totalRelaxed() > 0) {
+ pieEntries.add(new PieEntry(myStressWeeklyData.totalRelaxed(),
+ StressType.RELAXED.getLabel(getContext())));
+ pieColors.add(StressType.RELAXED.getColor(getContext()));
+ }
+ if (myStressWeeklyData.totalMild() > 0) {
+ pieEntries.add(new PieEntry(myStressWeeklyData.totalMild(),
+ StressType.MILD.getLabel(getContext())));
+ pieColors.add(StressType.MILD.getColor(getContext()));
+ }
+ if (myStressWeeklyData.totalModerate() > 0) {
+ pieEntries.add(new PieEntry(myStressWeeklyData.totalModerate(),
+ StressType.MODERATE.getLabel(getContext())));
+ pieColors.add(StressType.MODERATE.getColor(getContext()));
+ }
+ if (myStressWeeklyData.totalHigh() > 0) {
+ pieEntries.add(new PieEntry(myStressWeeklyData.totalHigh(),
+ StressType.HIGH.getLabel(getContext())));
+ pieColors.add(StressType.HIGH.getColor(getContext()));
+ }
+ }
+ }
+
+ if (pieEntries.isEmpty()) {
+ pieEntries.add(new PieEntry(1));
+ pieColors.add(getResources().getColor(R.color.gauge_line_color));
+ }
+
+ PieDataSet pieDataSet = new PieDataSet(pieEntries, "");
+ pieDataSet.setColors(pieColors);
+ pieDataSet.setValueTextColor(DESCRIPTION_COLOR);
+ pieDataSet.setValueTextSize(13f);
+ pieDataSet.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
+ pieDataSet.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
+ pieDataSet.setDrawValues(false);
+ pieDataSet.setSliceSpace(2f);
+ PieData pieData = new PieData(pieDataSet);
+
+ mStressLevelsPieChart.setData(pieData);
+
+ // Set center text with average stress level
+ if (myStressWeeklyData != null && myStressWeeklyData.averageStress() > 0) {
+ int avgStress = myStressWeeklyData.averageStress();
+ int noc = String.valueOf(avgStress).length();
+ SpannableString centerText = new SpannableString(avgStress + "\n" +
+ getContext().getString(R.string.stress_average));
+ centerText.setSpan(new RelativeSizeSpan(1.75f), 0, noc, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ centerText.setSpan(new RelativeSizeSpan(0.72f), noc, centerText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ mStressLevelsPieChart.setCenterText(centerText);
+ } else {
+ SpannableString centerText = new SpannableString("-\n" +
+ getContext().getString(R.string.stress_average));
+ centerText.setSpan(new RelativeSizeSpan(1.25f), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ centerText.setSpan(new RelativeSizeSpan(0.72f), 2, centerText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ mStressLevelsPieChart.setCenterText(centerText);
+ }
+ }
+
+ private void updateStressTimeTexts() {
+ if (TOTAL_DAYS_FOR_AVERAGE > 0 && myStressWeeklyData != null) {
+ int relaxedAvg = (int) (myStressWeeklyData.totalRelaxed() / TOTAL_DAYS_FOR_AVERAGE);
+ int mildAvg = (int) (myStressWeeklyData.totalMild() / TOTAL_DAYS_FOR_AVERAGE);
+ int moderateAvg = (int) (myStressWeeklyData.totalModerate() / TOTAL_DAYS_FOR_AVERAGE);
+ int highAvg = (int) (myStressWeeklyData.totalHigh() / TOTAL_DAYS_FOR_AVERAGE);
+
+ if (showStressLevelInPercents) {
+ long totalDailyAvg = relaxedAvg + mildAvg + moderateAvg + highAvg;
+ relaxedStressTimeText.setText(String.format(Locale.ROOT, "%d%%",
+ totalDailyAvg > 0 ? Math.round(100f * relaxedAvg / totalDailyAvg) : 0));
+ mildStressTimeText.setText(String.format(Locale.ROOT, "%d%%",
+ totalDailyAvg > 0 ? Math.round(100f * mildAvg / totalDailyAvg) : 0));
+ moderateStressTimeText.setText(String.format(Locale.ROOT, "%d%%",
+ totalDailyAvg > 0 ? Math.round(100f * moderateAvg / totalDailyAvg) : 0));
+ highStressTimeText.setText(String.format(Locale.ROOT, "%d%%",
+ totalDailyAvg > 0 ? Math.round(100f * highAvg / totalDailyAvg) : 0));
+ } else {
+ relaxedStressTimeText.setText(DateTimeUtils.formatDurationHoursMinutes(relaxedAvg, TimeUnit.SECONDS));
+ mildStressTimeText.setText(DateTimeUtils.formatDurationHoursMinutes(mildAvg, TimeUnit.SECONDS));
+ moderateStressTimeText.setText(DateTimeUtils.formatDurationHoursMinutes(moderateAvg, TimeUnit.SECONDS));
+ highStressTimeText.setText(DateTimeUtils.formatDurationHoursMinutes(highAvg, TimeUnit.SECONDS));
+ }
+ } else {
+ relaxedStressTimeText.setText("-");
+ mildStressTimeText.setText("-");
+ moderateStressTimeText.setText("-");
+ highStressTimeText.setText("-");
+ }
+ }
+
+ private MyStressWeeklyData getMyStressWeeklyData(DBHandler db, Calendar day, GBDevice device) {
+ day = (Calendar) day.clone(); // do not modify the caller's argument
+ day.add(Calendar.DATE, -TOTAL_DAYS + 1);
+ TOTAL_DAYS_FOR_AVERAGE = 0;
+
+ long relaxedWeeklyTotal = 0;
+ long mildWeeklyTotal = 0;
+ long moderateWeeklyTotal = 0;
+ long highWeeklyTotal = 0;
+ long totalStressTime = 0;
+ long avgStressSum = 0;
+ long avgStressSamples = 0;
+
+ int[] stressRanges = device.getDeviceCoordinator().getStressRanges();
+ showStressLevelInPercents = device.getDeviceCoordinator().showStressLevelInPercents();
+
+ DeviceCoordinator coordinator = device.getDeviceCoordinator();
+ int sampleRate = 60; // Default sample rate
+ int[] params = coordinator.getStressChartParameters();
+ if (params != null && params.length > 0) {
+ sampleRate = params[0];
+ }
+
+ for (int counter = 0; counter < TOTAL_DAYS; counter++) {
+ Calendar dayStart = (Calendar) day.clone();
+ Calendar dayEnd = (Calendar) day.clone();
+ dayEnd.add(Calendar.DAY_OF_MONTH, 1);
+
+ List extends StressSample> samples = getStressSamples(db, device,
+ (int) (dayStart.getTimeInMillis() / 1000),
+ (int) (dayEnd.getTimeInMillis() / 1000));
+
+ if (!samples.isEmpty()) {
+ TOTAL_DAYS_FOR_AVERAGE++;
+
+ Map dailyTotals = calculateStressTotals(samples, stressRanges, sampleRate);
+ relaxedWeeklyTotal += dailyTotals.getOrDefault(StressType.RELAXED, 0);
+ mildWeeklyTotal += dailyTotals.getOrDefault(StressType.MILD, 0);
+ moderateWeeklyTotal += dailyTotals.getOrDefault(StressType.MODERATE, 0);
+ highWeeklyTotal += dailyTotals.getOrDefault(StressType.HIGH, 0);
+
+ // Calculate average stress for the day
+ int dailyAverage = calculateAverageStress(samples);
+ if (dailyAverage > 0) {
+ avgStressSum += dailyAverage;
+ avgStressSamples++;
+ }
+ }
+
+ day.add(Calendar.DATE, 1);
+ }
+
+ totalStressTime = relaxedWeeklyTotal + mildWeeklyTotal + moderateWeeklyTotal + highWeeklyTotal;
+ int averageStress = avgStressSamples > 0 ? Math.round((float) avgStressSum / avgStressSamples) : 0;
+
+ return new MyStressWeeklyData(relaxedWeeklyTotal, mildWeeklyTotal, moderateWeeklyTotal,
+ highWeeklyTotal, totalStressTime, averageStress);
+ }
+
+ private DefaultChartsData refreshWeekBeforeStressData(DBHandler db, BarChart chart, Calendar day, GBDevice device) {
+ day = (Calendar) day.clone();
+ day.set(Calendar.HOUR_OF_DAY, 0);
+ day.set(Calendar.MINUTE, 0);
+ day.set(Calendar.SECOND, 0);
+ day.set(Calendar.MILLISECOND, 0);
+ day.add(Calendar.DATE, -TOTAL_DAYS + 1);
+ List entries = new ArrayList<>();
+ ArrayList labels = new ArrayList<>();
+
+ int[] colors = new int[TOTAL_DAYS * 5]; // 4 stress types + unknown type
+ int colorIndex = 0;
+
+ DeviceCoordinator coordinator = device.getDeviceCoordinator();
+ int sampleRate = 60;
+ int[] params = coordinator.getStressChartParameters();
+ if (params != null && params.length > 0) {
+ sampleRate = params[0];
+ }
+
+ Calendar now = Calendar.getInstance();
+
+ for (int counter = 0; counter < TOTAL_DAYS; counter++) {
+ Calendar dayStart = (Calendar) day.clone();
+ Calendar dayEnd = (Calendar) day.clone();
+ dayEnd.add(Calendar.DAY_OF_MONTH, 1);
+
+ List extends StressSample> samples = getStressSamples(db, device,
+ (int) (dayStart.getTimeInMillis() / 1000),
+ (int) (dayEnd.getTimeInMillis() / 1000));
+
+ Map dailyTotals = calculateStressTotals(samples,
+ device.getDeviceCoordinator().getStressRanges(), sampleRate);
+
+ float[] yValues = new float[5]; // For stacked bar chart
+ int idx = 0;
+
+ float totalMinutesTracked = dailyTotals.values().stream().reduce(0, Integer::sum) / 60f;
+
+ // Calculate the total possible minutes for this day, excluding future time
+ float totalPossibleMinutes;
+ if (dayEnd.before(now) || dayEnd.equals(now)) {
+ // Full day in the past
+ totalPossibleMinutes = 24 * 60;
+ } else if (dayStart.after(now)) {
+ // Full day in the future
+ totalPossibleMinutes = 0;
+ } else {
+ // Partial day (current day) - only count minutes up to now
+ long minutesFromStartToNow = (now.getTimeInMillis() - dayStart.getTimeInMillis()) / (1000 * 60);
+ totalPossibleMinutes = Math.max(0, minutesFromStartToNow);
+ }
+
+ float untrackedMins = Math.max(totalPossibleMinutes - totalMinutesTracked, 0);
+
+ yValues[idx++] = dailyTotals.get(StressType.HIGH) / 60f;
+ yValues[idx++] = dailyTotals.get(StressType.MODERATE) / 60f;
+ yValues[idx++] = dailyTotals.get(StressType.MILD) / 60f;
+ yValues[idx++] = dailyTotals.get(StressType.RELAXED) / 60f;
+ yValues[idx++] = untrackedMins;
+
+ colors[colorIndex++] = StressType.HIGH.getColor(getContext());
+ colors[colorIndex++] = StressType.MODERATE.getColor(getContext());
+ colors[colorIndex++] = StressType.MILD.getColor(getContext());
+ colors[colorIndex++] = StressType.RELAXED.getColor(getContext());
+ colors[colorIndex++] = StressType.UNKNOWN.getColor(getContext());
+
+ entries.add(new BarEntry(counter, yValues));
+
+ labels.add(TOTAL_DAYS > 7
+ ? String.valueOf(day.get(Calendar.DAY_OF_MONTH))
+ : day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale));
+
+ day.add(Calendar.DATE, 1);
+ }
+
+ BarDataSet set = new BarDataSet(entries, "");
+ set.setDrawValues(false);
+ set.setAxisDependency(YAxis.AxisDependency.LEFT);
+ set.setColors(colors);
+ set.setStackLabels(new String[]{
+ StressType.HIGH.getLabel(getContext()),
+ StressType.MODERATE.getLabel(getContext()),
+ StressType.MILD.getLabel(getContext()),
+ StressType.RELAXED.getLabel(getContext()),
+ StressType.UNKNOWN.getLabel(getContext())
+ });
+
+ ArrayList dataSets = new ArrayList<>();
+ dataSets.add(set);
+
+ BarData barData = new BarData(dataSets);
+ barData.setBarWidth(0.9f);
+
+ return new DefaultChartsData<>(barData, new PreformattedXIndexLabelFormatter(labels));
+ }
+
+ private void setupPieChart() {
+ mStressLevelsPieChart.setBackgroundColor(BACKGROUND_COLOR);
+ mStressLevelsPieChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+ mStressLevelsPieChart.setEntryLabelColor(DESCRIPTION_COLOR);
+ mStressLevelsPieChart.getDescription().setText("");
+ mStressLevelsPieChart.setNoDataText("");
+ mStressLevelsPieChart.setTouchEnabled(false);
+ mStressLevelsPieChart.setCenterTextColor(GBApplication.getTextColor(getContext()));
+ mStressLevelsPieChart.setCenterTextSize(18f);
+ mStressLevelsPieChart.setHoleColor(requireContext().getResources().getColor(R.color.transparent));
+ mStressLevelsPieChart.setHoleRadius(85);
+ mStressLevelsPieChart.setDrawEntryLabels(false);
+ mStressLevelsPieChart.getLegend().setEnabled(false);
+ }
+
+ protected void setupWeekChart() {
+ mWeekChart.setBackgroundColor(BACKGROUND_COLOR);
+ mWeekChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+ mWeekChart.getDescription().setText("");
+ mWeekChart.setFitBars(true);
+
+ configureBarLineChartDefaults(mWeekChart);
+
+ 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);
+ if (TOTAL_DAYS > 7) {
+ x.setSpaceMin(0);
+ }
+
+ YAxis y = mWeekChart.getAxisLeft();
+ y.setDrawGridLines(false);
+ y.setEnabled(true);
+ y.setDrawTopYLabelEntry(false);
+ y.setTextColor(CHART_TEXT_COLOR);
+ y.setDrawZeroLine(true);
+ y.setSpaceBottom(0);
+ y.setAxisMinimum(0);
+ y.setAxisMaximum(24 * 60);
+ y.setValueFormatter(getYAxisFormatter());
+
+ YAxis yAxisRight = mWeekChart.getAxisRight();
+ yAxisRight.setDrawGridLines(false);
+ yAxisRight.setEnabled(false);
+ yAxisRight.setDrawLabels(false);
+ yAxisRight.setDrawTopYLabelEntry(false);
+ yAxisRight.setTextColor(CHART_TEXT_COLOR);
+
+ if (TOTAL_DAYS > 7) {
+ mWeekChart.setRenderer(new AngledLabelsChartRenderer(mWeekChart, mWeekChart.getAnimator(),
+ mWeekChart.getViewPortHandler()));
+ } else {
+ mWeekChart.setScaleEnabled(false);
+ mWeekChart.setTouchEnabled(false);
+ }
+ }
+
+ @Override
+ protected void setupLegend(Chart> chart) {
+ List legendEntries = createLegendEntries(chart);
+ chart.getLegend().setCustom(legendEntries);
+ chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+ chart.getLegend().setWordWrapEnabled(true);
+ chart.getLegend().setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);
+ }
+
+ @Override
+ protected void renderCharts() {
+ mWeekChart.invalidate();
+ mStressLevelsPieChart.invalidate();
+ }
+
+ private int getRangeDays() {
+ return GBApplication.getPrefs().getBoolean("charts_range", true) ? 30 : 7;
+ }
+
+ ValueFormatter getYAxisFormatter() {
+ return new ValueFormatter() {
+ @Override
+ public String getFormattedValue(float value) {
+ return DateTimeUtils.minutesToHHMM((int) value);
+ }
+ };
+ }
+
+ @Override
+ protected boolean isSingleDay() {
+ return false;
+ }
+
+ protected static class MyChartsData extends ChartsData {
+ private final DefaultChartsData weekBeforeData;
+
+ public MyChartsData(DefaultChartsData weekBeforeData) {
+ this.weekBeforeData = weekBeforeData;
+ }
+
+ public DefaultChartsData getWeekBeforeData() {
+ return weekBeforeData;
+ }
+ }
+
+ private record MyStressWeeklyData(long totalRelaxed,
+ long totalMild,
+ long totalModerate,
+ long totalHigh,
+ long totalStressTime,
+ int averageStress) {
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressSimpleWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressSimpleWidget.java
index d1f1149235..8f8667fee2 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressSimpleWidget.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressSimpleWidget.java
@@ -21,7 +21,7 @@ import android.os.Bundle;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
-import nodomain.freeyourgadget.gadgetbridge.activities.charts.StressChartFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.StressDailyFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.data.DashboardStressData;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@@ -56,7 +56,7 @@ public class DashboardStressSimpleWidget extends AbstractGaugeWidget {
return;
}
- final int color = StressChartFragment.StressType.fromStress(
+ final int color = StressDailyFragment.StressType.fromStress(
stressData.value,
stressData.ranges
).getColor(GBApplication.getContext());
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/data/DashboardStressData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/data/DashboardStressData.java
index 3106ae6b87..c40cc3d307 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/data/DashboardStressData.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/data/DashboardStressData.java
@@ -24,7 +24,7 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
-import nodomain.freeyourgadget.gadgetbridge.activities.charts.StressChartFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.StressDailyFragment;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
@@ -42,7 +42,7 @@ public class DashboardStressData implements Serializable {
GBDevice stressDevice = null;
double averageStress = -1;
- final int[] totalTime = new int[StressChartFragment.StressType.values().length];
+ final int[] totalTime = new int[StressDailyFragment.StressType.values().length];
try (DBHandler dbHandler = GBApplication.acquireDB()) {
for (GBDevice dev : devices) {
@@ -57,8 +57,8 @@ public class DashboardStressData implements Serializable {
averageStress = samples.stream()
.mapToInt(StressSample::getStress)
.peek(stress -> {
- final StressChartFragment.StressType stressType = StressChartFragment.StressType.fromStress(stress, stressRanges);
- if (stressType != StressChartFragment.StressType.UNKNOWN) {
+ final StressDailyFragment.StressType stressType = StressDailyFragment.StressType.fromStress(stress, stressRanges);
+ if (stressType != StressDailyFragment.StressType.UNKNOWN) {
totalTime[stressType.ordinal() - 1] += 60;
}
})
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/StressFragmentAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/StressFragmentAdapter.java
new file mode 100644
index 0000000000..a3141d5187
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/StressFragmentAdapter.java
@@ -0,0 +1,27 @@
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.StressDailyFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.StressPeriodFragment;
+
+public class StressFragmentAdapter extends NestedFragmentAdapter {
+ public StressFragmentAdapter(AbstractGBFragment fragment, FragmentManager childFragmentManager) {
+ super(fragment, childFragmentManager);
+ }
+
+ @Override
+ public Fragment createFragment(int position) {
+ switch (position) {
+ case 0:
+ return new StressDailyFragment();
+ case 1:
+ return StressPeriodFragment.newInstance(7);
+ case 2:
+ return StressPeriodFragment.newInstance(30);
+ }
+ return new StressDailyFragment();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_weekstress_chart.xml b/app/src/main/res/layout/fragment_weekstress_chart.xml
new file mode 100644
index 0000000000..b7b847351d
--- /dev/null
+++ b/app/src/main/res/layout/fragment_weekstress_chart.xml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file