mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-08 19:27:04 +01:00
Add weight chart
This commit is contained in:
parent
249c2bc237
commit
6bfd3dcd06
@ -110,6 +110,9 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
|
|||||||
if(!coordinator.supportsCyclingData()) {
|
if(!coordinator.supportsCyclingData()) {
|
||||||
tabList.remove("cycling");
|
tabList.remove("cycling");
|
||||||
}
|
}
|
||||||
|
if (!coordinator.supportsWeightMeasurement()) {
|
||||||
|
tabList.remove("weight");
|
||||||
|
}
|
||||||
if (!coordinator.supportsHrvMeasurement()) {
|
if (!coordinator.supportsHrvMeasurement()) {
|
||||||
tabList.remove("hrvstatus");
|
tabList.remove("hrvstatus");
|
||||||
}
|
}
|
||||||
@ -163,6 +166,8 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
|
|||||||
return new TemperatureChartFragment();
|
return new TemperatureChartFragment();
|
||||||
case "cycling":
|
case "cycling":
|
||||||
return new CyclingChartFragment();
|
return new CyclingChartFragment();
|
||||||
|
case "weight":
|
||||||
|
return new WeightChartFragment();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -209,6 +214,8 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
|
|||||||
return getString(R.string.menuitem_temperature);
|
return getString(R.string.menuitem_temperature);
|
||||||
case "cycling":
|
case "cycling":
|
||||||
return getString(R.string.title_cycling);
|
return getString(R.string.title_cycling);
|
||||||
|
case "weight":
|
||||||
|
return getString(R.string.menuitem_weight);
|
||||||
}
|
}
|
||||||
return super.getPageTitle(position);
|
return super.getPageTitle(position);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,252 @@
|
|||||||
|
/* Copyright (C) 2024 Severin von Wnuck-Lipinski
|
||||||
|
|
||||||
|
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 <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import com.github.mikephil.charting.animation.Easing;
|
||||||
|
import com.github.mikephil.charting.charts.Chart;
|
||||||
|
import com.github.mikephil.charting.charts.LineChart;
|
||||||
|
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.Entry;
|
||||||
|
import com.github.mikephil.charting.data.LineData;
|
||||||
|
import com.github.mikephil.charting.data.LineDataSet;
|
||||||
|
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
|
||||||
|
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.ActivityUser;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WeightSample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||||
|
|
||||||
|
public class WeightChartFragment extends AbstractChartFragment<WeightChartFragment.WeightChartsData> {
|
||||||
|
private int colorBackground;
|
||||||
|
private int colorSecondaryText;
|
||||||
|
|
||||||
|
private int totalDays;
|
||||||
|
private boolean imperialUnits;
|
||||||
|
private int weightTargetKg;
|
||||||
|
|
||||||
|
private LineChart chart;
|
||||||
|
private TextView textTimeSpan;
|
||||||
|
private TextView textWeightLatest;
|
||||||
|
private TextView textWeightTarget;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTitle() {
|
||||||
|
return getString(R.string.menuitem_weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
GBPrefs prefs = GBApplication.getPrefs();
|
||||||
|
|
||||||
|
colorBackground = GBApplication.getBackgroundColor(requireContext());
|
||||||
|
colorSecondaryText = GBApplication.getSecondaryTextColor(requireContext());
|
||||||
|
|
||||||
|
if (prefs.getBoolean("charts_range", true))
|
||||||
|
totalDays = 30;
|
||||||
|
else
|
||||||
|
totalDays = 7;
|
||||||
|
|
||||||
|
String unitSystem = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, getString(R.string.p_unit_metric));
|
||||||
|
|
||||||
|
if (unitSystem.equals(getString(R.string.p_unit_imperial)))
|
||||||
|
imperialUnits = true;
|
||||||
|
else
|
||||||
|
imperialUnits = false;
|
||||||
|
|
||||||
|
weightTargetKg = prefs.getInt(ActivityUser.PREF_USER_GOAL_WEIGHT_KG, ActivityUser.defaultUserGoalWeightKg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WeightChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
|
||||||
|
long tsStart = getTSStart() * 1000L;
|
||||||
|
long tsEnd = getTSEnd() * 1000L;
|
||||||
|
|
||||||
|
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||||
|
TimeSampleProvider<? extends WeightSample> provider = coordinator.getWeightSampleProvider(device, db.getDaoSession());
|
||||||
|
List<? extends WeightSample> samples = provider.getAllSamples(tsStart, tsEnd);
|
||||||
|
WeightSample latestSample = provider.getLatestSample();
|
||||||
|
|
||||||
|
return createChartsData(samples, latestSample);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void renderCharts() {
|
||||||
|
chart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setupLegend(Chart<?> chart) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateChartsnUIThread(WeightChartsData chartsData) {
|
||||||
|
chart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
|
||||||
|
chart.getXAxis().setValueFormatter(chartsData.getXValueFormatter());
|
||||||
|
chart.getXAxis().setAvoidFirstLastClipping(true);
|
||||||
|
chart.setData(chartsData.getData());
|
||||||
|
|
||||||
|
Date dateStart = DateTimeUtils.parseTimeStamp(getTSStart());
|
||||||
|
Date dateEnd = DateTimeUtils.parseTimeStamp(getTSEnd());
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("E, MMM dd");
|
||||||
|
WeightSample latestSample = chartsData.getLatestSample();
|
||||||
|
|
||||||
|
textTimeSpan.setText(format.format(dateStart) + " - " + format.format(dateEnd));
|
||||||
|
|
||||||
|
if (latestSample != null)
|
||||||
|
textWeightLatest.setText(formatWeight(weightFromKg(latestSample.getWeightKg())));
|
||||||
|
|
||||||
|
textWeightTarget.setText(formatWeight(weightFromKg(weightTargetKg)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getTSStart() {
|
||||||
|
return DateTimeUtils.shiftDays(getTSEnd(), -totalDays + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.fragment_weightchart, container, false);
|
||||||
|
|
||||||
|
chart = rootView.findViewById(R.id.weight_chart);
|
||||||
|
textTimeSpan = rootView.findViewById(R.id.weight_time_span_text);
|
||||||
|
textWeightLatest = rootView.findViewById(R.id.weight_latest_text);
|
||||||
|
textWeightTarget = rootView.findViewById(R.id.weight_target_text);
|
||||||
|
|
||||||
|
configureBarLineChartDefaults(chart);
|
||||||
|
chart.setBackgroundColor(colorBackground);
|
||||||
|
chart.getDescription().setEnabled(false);
|
||||||
|
chart.getLegend().setEnabled(false);
|
||||||
|
chart.getAxisRight().setEnabled(false);
|
||||||
|
chart.setDoubleTapToZoomEnabled(false);
|
||||||
|
|
||||||
|
LimitLine targetLine = new LimitLine(weightFromKg(weightTargetKg));
|
||||||
|
targetLine.setTextColor(colorSecondaryText);
|
||||||
|
|
||||||
|
XAxis xAxis = chart.getXAxis();
|
||||||
|
xAxis.setTextColor(colorSecondaryText);
|
||||||
|
xAxis.setDrawLabels(true);
|
||||||
|
xAxis.setDrawLimitLinesBehindData(true);
|
||||||
|
|
||||||
|
YAxis yAxis = chart.getAxisLeft();
|
||||||
|
yAxis.setTextColor(colorSecondaryText);
|
||||||
|
yAxis.addLimitLine(targetLine);
|
||||||
|
yAxis.setDrawGridLines(true);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WeightChartsData createChartsData(List<? extends WeightSample> samples, WeightSample latestSample) {
|
||||||
|
List<Entry> entries = new ArrayList<>();
|
||||||
|
TimestampTranslation tsTranslation = new TimestampTranslation();
|
||||||
|
|
||||||
|
for (WeightSample sample : samples) {
|
||||||
|
int tsSeconds = (int)(sample.getTimestamp() / 1000L);
|
||||||
|
float weight = weightFromKg(sample.getWeightKg());
|
||||||
|
|
||||||
|
entries.add(new Entry(tsTranslation.shorten(tsSeconds), weight));
|
||||||
|
}
|
||||||
|
|
||||||
|
LineDataSet dataSet = new LineDataSet(entries, getString(R.string.menuitem_weight));
|
||||||
|
dataSet.setLineWidth(2.2f);
|
||||||
|
dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
|
||||||
|
dataSet.setCubicIntensity(0.1f);
|
||||||
|
dataSet.setCircleRadius(5);
|
||||||
|
dataSet.setDrawCircleHole(false);
|
||||||
|
dataSet.setDrawValues(true);
|
||||||
|
dataSet.setValueTextSize(10);
|
||||||
|
dataSet.setValueTextColor(colorSecondaryText);
|
||||||
|
dataSet.setValueFormatter(new ValueFormatter() {
|
||||||
|
@Override
|
||||||
|
public String getPointLabel(Entry entry) {
|
||||||
|
return formatWeight(entry.getY());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return new WeightChartsData(new LineData(dataSet), tsTranslation, latestSample);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float weightFromKg(float weight) {
|
||||||
|
// Convert to lbs
|
||||||
|
if (imperialUnits)
|
||||||
|
weight *= 2.2046226f;
|
||||||
|
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatWeight(float weight) {
|
||||||
|
int weightString = imperialUnits ? R.string.weight_lbs : R.string.weight_kg;
|
||||||
|
|
||||||
|
return getString(weightString, weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class WeightChartsData extends DefaultChartsData<LineData> {
|
||||||
|
private final WeightSample latestSample;
|
||||||
|
|
||||||
|
public WeightChartsData(LineData lineData, TimestampTranslation tsTranslation, WeightSample latestSample) {
|
||||||
|
super(lineData, new DateFormatter(tsTranslation));
|
||||||
|
|
||||||
|
this.latestSample = latestSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WeightSample getLatestSample() {
|
||||||
|
return latestSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DateFormatter extends ValueFormatter {
|
||||||
|
private TimestampTranslation translation;
|
||||||
|
private SimpleDateFormat format = new SimpleDateFormat("dd.MM.");
|
||||||
|
private Calendar calendar = GregorianCalendar.getInstance();
|
||||||
|
|
||||||
|
public DateFormatter(TimestampTranslation translation) {
|
||||||
|
this.translation = translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFormattedValue(float value) {
|
||||||
|
calendar.clear();
|
||||||
|
calendar.setTimeInMillis(translation.toOriginalValue((int)value) * 1000L);
|
||||||
|
|
||||||
|
return format.format(calendar.getTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
app/src/main/res/layout/fragment_weightchart.xml
Normal file
78
app/src/main/res/layout/fragment_weightchart.xml
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/weight_time_span_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:layout_marginBottom="20dp" />
|
||||||
|
<com.github.mikephil.charting.charts.LineChart
|
||||||
|
android:id="@+id/weight_chart"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:layout_marginBottom="30dp">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:paddingTop="20dp"
|
||||||
|
android:paddingRight="20dp">
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="5px"
|
||||||
|
android:background="@color/value_line_color" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/weight_latest_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="left"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/stats_empty_value"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="left"
|
||||||
|
android:text="@string/menuitem_weight"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:paddingTop="20dp"
|
||||||
|
android:paddingRight="20dp">
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="5px"
|
||||||
|
android:background="@color/value_line_color" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/weight_target_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="left"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/stats_empty_value"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="left"
|
||||||
|
android:text="@string/target"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
@ -3041,6 +3041,7 @@
|
|||||||
<item>@string/liveactivity_live_activity</item>
|
<item>@string/liveactivity_live_activity</item>
|
||||||
<item>@string/pref_header_spo2</item>
|
<item>@string/pref_header_spo2</item>
|
||||||
<item>@string/menuitem_temperature</item>
|
<item>@string/menuitem_temperature</item>
|
||||||
|
<item>@string/menuitem_weight</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="pref_charts_tabs_values">
|
<string-array name="pref_charts_tabs_values">
|
||||||
@ -3056,6 +3057,7 @@
|
|||||||
<item>@string/p_live_stats</item>
|
<item>@string/p_live_stats</item>
|
||||||
<item>@string/p_spo2</item>
|
<item>@string/p_spo2</item>
|
||||||
<item>@string/p_temperature</item>
|
<item>@string/p_temperature</item>
|
||||||
|
<item>@string/p_weight</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="pref_charts_tabs_items_default">
|
<string-array name="pref_charts_tabs_items_default">
|
||||||
@ -3072,6 +3074,7 @@
|
|||||||
<item>@string/p_live_stats</item>
|
<item>@string/p_live_stats</item>
|
||||||
<item>@string/p_spo2</item>
|
<item>@string/p_spo2</item>
|
||||||
<item>@string/p_temperature</item>
|
<item>@string/p_temperature</item>
|
||||||
|
<item>@string/p_weight</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
|
||||||
|
@ -1886,6 +1886,7 @@
|
|||||||
<string name="menuitem_events">Events</string>
|
<string name="menuitem_events">Events</string>
|
||||||
<string name="menuitem_widgets">Widgets</string>
|
<string name="menuitem_widgets">Widgets</string>
|
||||||
<string name="menuitem_temperature">Temperature</string>
|
<string name="menuitem_temperature">Temperature</string>
|
||||||
|
<string name="menuitem_weight">Weight</string>
|
||||||
<string name="menuitem_barometer">Barometer</string>
|
<string name="menuitem_barometer">Barometer</string>
|
||||||
<string name="menuitem_flashlight">Flashlight</string>
|
<string name="menuitem_flashlight">Flashlight</string>
|
||||||
<string name='menuitem_email'>E-mail</string>
|
<string name='menuitem_email'>E-mail</string>
|
||||||
@ -2491,6 +2492,9 @@
|
|||||||
<string name="stress_high">High</string>
|
<string name="stress_high">High</string>
|
||||||
<string name="pai_total">Total</string>
|
<string name="pai_total">Total</string>
|
||||||
<string name="pai_day">Day increase</string>
|
<string name="pai_day">Day increase</string>
|
||||||
|
<string name="weight_kg">%1$.2f kg</string>
|
||||||
|
<string name="weight_lbs">%1$.2f lbs</string>
|
||||||
|
<string name="target">Target</string>
|
||||||
<string name="sony_ambient_sound">Mode</string>
|
<string name="sony_ambient_sound">Mode</string>
|
||||||
<string name="sony_ambient_sound_off">Off</string>
|
<string name="sony_ambient_sound_off">Off</string>
|
||||||
<string name="sony_ambient_sound_noise_cancelling">Noise Cancelling</string>
|
<string name="sony_ambient_sound_noise_cancelling">Noise Cancelling</string>
|
||||||
|
@ -112,6 +112,7 @@
|
|||||||
<item name="p_live_stats" type="string">livestats</item>
|
<item name="p_live_stats" type="string">livestats</item>
|
||||||
<item name="p_spo2" type="string">spo2</item>
|
<item name="p_spo2" type="string">spo2</item>
|
||||||
<item name="p_temperature" type="string">temperature</item>
|
<item name="p_temperature" type="string">temperature</item>
|
||||||
|
<item name="p_weight" type="string">weight</item>
|
||||||
|
|
||||||
<item name="p_message_privacy_mode_off" type="string">off</item>
|
<item name="p_message_privacy_mode_off" type="string">off</item>
|
||||||
<item name="p_message_privacy_mode_complete" type="string">complete</item>
|
<item name="p_message_privacy_mode_complete" type="string">complete</item>
|
||||||
|
Loading…
Reference in New Issue
Block a user