1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-27 04:16:49 +01:00

Calories: add fragment

This commit is contained in:
a0z 2024-10-27 10:47:41 +01:00 committed by a0z
parent c119b04478
commit 68ff0dfa0b
25 changed files with 999 additions and 76 deletions

View File

@ -127,7 +127,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 = 42;
private static final int CURRENT_PREFS_VERSION = 44;
private static final LimitedQueue<Integer, String> mIDSenderLookup = new LimitedQueue<>(16);
private static GBPrefs prefs;
@ -1838,6 +1838,44 @@ public class GBApplication extends Application {
}
}
if (oldVersion < 43) {
// Add the new calories 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 + ",calories";
} else {
newPrefValue = "calories";
}
final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue);
deviceSharedPrefsEdit.apply();
}
} catch (Exception e) {
Log.e(TAG, "Failed to migrate prefs to version 43", e);
}
}
if (oldVersion < 44) {
// Add new dashboard calories widgets.
final String dashboardWidgetsOrder = sharedPrefs.getString("pref_dashboard_widgets_order", null);
if (!StringUtils.isBlank(dashboardWidgetsOrder) && !dashboardWidgetsOrder.contains("calories")) {
editor.putString("pref_dashboard_widgets_order", dashboardWidgetsOrder + ",calories,calories_active,calories_segmented");
}
}
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
editor.apply();
}

View File

@ -63,9 +63,12 @@ import java.util.function.Supplier;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.AbstractDashboardWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardCaloriesActiveGoalWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardActiveTimeWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardBodyEnergyWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardCalendarActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardCaloriesTotalSegmentedWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardCaloriesGoalWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardDistanceWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardGoalsWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardHrvWidget;
@ -310,6 +313,15 @@ public class DashboardFragment extends Fragment implements MenuProvider {
case "vo2max":
widget = DashboardVO2MaxAnyWidget.newInstance(dashboardData);
break;
case "calories":
widget = DashboardCaloriesGoalWidget.newInstance(dashboardData);
break;
case "calories_active":
widget = DashboardCaloriesActiveGoalWidget.newInstance(dashboardData);
break;
case "calories_segmented":
widget = DashboardCaloriesTotalSegmentedWidget.newInstance(dashboardData);
break;
default:
LOG.error("Unknown dashboard widget {}", widgetName);
continue;
@ -372,6 +384,11 @@ public class DashboardFragment extends Fragment implements MenuProvider {
public final List<GeneralizedActivity> generalizedActivities = Collections.synchronizedList(new ArrayList<>());
private int stepsTotal;
private float stepsGoalFactor;
private int restingCaloriesTotal;
private int activeCaloriesTotal;
private float activeCaloriesGoalFactor;
private int caloriesTotal;
private float caloriesGoalFactor;
private long sleepTotalMinutes;
private float sleepGoalFactor;
private float distanceTotalMeters;
@ -381,6 +398,11 @@ public class DashboardFragment extends Fragment implements MenuProvider {
private final Map<String, Serializable> genericData = new ConcurrentHashMap<>();
public void clear() {
restingCaloriesTotal = 0;
activeCaloriesTotal = 0;
activeCaloriesGoalFactor = 0;
caloriesTotal = 0;
caloriesGoalFactor = 0;
stepsTotal = 0;
stepsGoalFactor = 0;
sleepTotalMinutes = 0;
@ -396,6 +418,11 @@ public class DashboardFragment extends Fragment implements MenuProvider {
public boolean isEmpty() {
return (stepsTotal == 0 &&
stepsGoalFactor == 0 &&
restingCaloriesTotal == 0 &&
activeCaloriesTotal == 0 &&
activeCaloriesGoalFactor == 0 &&
caloriesTotal == 0 &&
caloriesGoalFactor == 0 &&
sleepTotalMinutes == 0 &&
sleepGoalFactor == 0 &&
distanceTotalMeters == 0 &&
@ -454,6 +481,36 @@ public class DashboardFragment extends Fragment implements MenuProvider {
return sleepGoalFactor;
}
public synchronized int getActiveCaloriesTotal() {
if (activeCaloriesTotal == 0)
activeCaloriesTotal = DashboardUtils.getActiveCaloriesTotal(this);
return activeCaloriesTotal;
}
public synchronized int getRestingCaloriesTotal() {
if (restingCaloriesTotal == 0)
restingCaloriesTotal = DashboardUtils.getRestingCaloriesTotal(this);
return restingCaloriesTotal;
}
public synchronized float getActiveCaloriesGoalFactor() {
if (activeCaloriesGoalFactor == 0)
activeCaloriesGoalFactor = DashboardUtils.getActiveCaloriesGoalFactor(this);
return activeCaloriesGoalFactor;
}
public synchronized int getCaloriesTotal() {
if (caloriesTotal == 0)
caloriesTotal = getRestingCaloriesTotal() + getActiveCaloriesTotal();
return caloriesTotal;
}
public synchronized float getCaloriesGoalFactor() {
if (caloriesGoalFactor == 0)
caloriesGoalFactor = DashboardUtils.getCaloriesGoalFactor(this);
return caloriesGoalFactor;
}
public void put(final String key, final Serializable value) {
genericData.put(key, value);
}

View File

@ -70,6 +70,7 @@ public abstract class AbstractChartsActivity extends AbstractGBFragmentActivity
public static final String EXTRA_SINGLE_FRAGMENT_NAME = "singleFragmentName";
public static final String EXTRA_ACTIONBAR_TITLE = "actionbarTitle";
public static final String EXTRA_TIMESTAMP = "timestamp";
public static final String EXTRA_MODE = "mode";
private TextView mDateControl;

View File

@ -80,6 +80,11 @@ public class ActivityAnalysis {
amount.addDistance(distance);
}
final int activeCalories = sample.getActiveCalories();
if (activeCalories > 0) {
amount.addActiveCalories(activeCalories);
}
if (previousSample != null) {
long timeDifference = sample.getTimestamp() - previousSample.getTimestamp();
if (previousSample.getRawKind() == sample.getRawKind()) {

View File

@ -17,6 +17,7 @@
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@ -133,6 +134,9 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
if (!coordinator.supportsVO2Max()) {
tabList.remove("vo2max");
}
if (!coordinator.supportsActiveCalories() && !coordinator.supportsRestingCalories()) {
tabList.remove("calories");
}
return tabList;
}
@ -187,6 +191,10 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
return new CyclingChartFragment();
case "weight":
return new WeightChartFragment();
case "calories":
Intent intent = getIntent();
String mode = intent.getStringExtra(ActivityChartsActivity.EXTRA_MODE);
return CaloriesDailyFragment.newInstance(mode);
}
return new UnknownFragment();
@ -232,6 +240,8 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
return getString(R.string.title_cycling);
case "weight":
return getString(R.string.menuitem_weight);
case "calories":
return getString(R.string.calories);
}
return String.format(Locale.getDefault(), "Unknown %d", position);

View File

@ -0,0 +1,251 @@
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import com.github.mikephil.charting.charts.Chart;
import org.apache.commons.lang3.EnumUtils;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminRestingMetabolicRateSample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.TimeSample;
public class CaloriesDailyFragment extends AbstractChartFragment<CaloriesDailyFragment.CaloriesData> {
private ImageView caloriesGauge;
private TextView dateView;
private TextView caloriesResting;
private LinearLayout caloriesRestingWrapper;
private TextView caloriesActive;
private TextView caloriesActiveGoal;
private TextView caloriesTotalGoal;
private LinearLayout caloriesTotalGoalWrapper;
protected int CALORIES_GOAL;
public enum GaugeViewMode {
ACTIVE_CALORIES_GOAL,
TOTAL_CALORIES_GOAL,
TOTAL_CALORIES_SEGMENT
}
private GaugeViewMode gaugeViewMode;
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
String mode = getArguments().getString(ActivityChartsActivity.EXTRA_MODE, "");
if (EnumUtils.isValidEnum(GaugeViewMode.class, mode)) {
gaugeViewMode = GaugeViewMode.valueOf(mode);
}
}
}
public static CaloriesDailyFragment newInstance(final String mode) {
final CaloriesDailyFragment fragment = new CaloriesDailyFragment();
final Bundle args = new Bundle();
args.putString(ActivityChartsActivity.EXTRA_MODE, mode);
fragment.setArguments(args);
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_calories, container, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
getChartsHost().enableSwipeRefresh(scrollY == 0);
});
}
caloriesGauge = rootView.findViewById(R.id.calories_gauge);
dateView = rootView.findViewById(R.id.date_view);
caloriesResting = rootView.findViewById(R.id.calories_resting);
caloriesRestingWrapper = rootView.findViewById(R.id.calories_resting_wrapper);
caloriesActive = rootView.findViewById(R.id.calories_active);
caloriesActiveGoal = rootView.findViewById(R.id.calories_active_goal);
caloriesTotalGoal = rootView.findViewById(R.id.calories_total_goal);
caloriesTotalGoalWrapper = rootView.findViewById(R.id.calories_total_goal_wrapper);
ActivityUser activityUser = new ActivityUser();
int TOTAL_CALORIES_GOAL = activityUser.getCaloriesBurntGoal();
caloriesTotalGoal.setText(String.valueOf(TOTAL_CALORIES_GOAL));
int ACTIVE_CALORIES_GOAL = activityUser.getActiveCaloriesBurntGoal();
caloriesActiveGoal.setText(String.valueOf(ACTIVE_CALORIES_GOAL));
refresh();
if (!supportsActiveCalories()) {
caloriesActive.setVisibility(View.GONE);
}
if (gaugeViewMode == null) {
gaugeViewMode = GaugeViewMode.TOTAL_CALORIES_SEGMENT;
}
if (gaugeViewMode.equals(GaugeViewMode.ACTIVE_CALORIES_GOAL)) {
CALORIES_GOAL = ACTIVE_CALORIES_GOAL;
} else if (gaugeViewMode.equals(GaugeViewMode.TOTAL_CALORIES_GOAL)) {
CALORIES_GOAL = TOTAL_CALORIES_GOAL;
}
return rootView;
}
public boolean supportsActiveCalories() {
final GBDevice device = getChartsHost().getDevice();
return device.getDeviceCoordinator().supportsActiveCalories();
}
protected TimeSample getRestingMetabolicRate(DBHandler db, GBDevice device) {
TimeSampleProvider<? extends RestingMetabolicRateSample> provider = device.getDeviceCoordinator().getRestingMetabolicRateProvider(device, db.getDaoSession());
return provider.getLatestSample();
}
protected List<? extends AbstractActivitySample> getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
SampleProvider<? extends ActivitySample> provider = device.getDeviceCoordinator().getSampleProvider(device, db.getDaoSession());
return provider.getAllActivitySamples(tsFrom, tsTo);
}
@Override
public String getTitle() {
return getString(R.string.calories);
}
@Override
protected void init() {}
@Override
protected CaloriesDailyFragment.CaloriesData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
Calendar calendar = Calendar.getInstance();
Calendar day = Calendar.getInstance();
day.setTime(chartsHost.getEndDate());
day.add(Calendar.DATE, 0);
day.set(Calendar.HOUR_OF_DAY, 0);
day.set(Calendar.MINUTE, 0);
day.set(Calendar.SECOND, 0);
day.add(Calendar.HOUR, 0);
int startTs = (int) (day.getTimeInMillis() / 1000);
int endTs = startTs + 24 * 60 * 60 - 1;
Date date = new Date((long) endTs * 1000);
String formattedDate = new SimpleDateFormat("E, MMM dd").format(date);
dateView.setText(formattedDate);
List<? extends ActivitySample> samples = getActivitySamples(db, device, startTs, endTs);
TimeSample metabolicRate = getRestingMetabolicRate(db, device);
int totalBurnt;
int activeBurnt = 0;
boolean sameDay = calendar.get(Calendar.DAY_OF_YEAR) == day.get(Calendar.DAY_OF_YEAR) &&
calendar.get(Calendar.YEAR) == day.get(Calendar.YEAR);
double passedDayProportion = 1;
if (sameDay) {
passedDayProportion = (double) (calendar.getTimeInMillis() - day.getTimeInMillis()) / (24L * 60 * 60 * 1000);
}
int restingBurnt = (int) ((double) ((GarminRestingMetabolicRateSample) metabolicRate).getRestingMetabolicRate() * passedDayProportion);
for (int i = 0; i <= samples.size() - 1; i++) {
ActivitySample sample = samples.get(i);
if (sample.getActiveCalories() > 0) {
activeBurnt += sample.getActiveCalories();
}
}
totalBurnt = restingBurnt + activeBurnt;
return new CaloriesData(totalBurnt, activeBurnt, restingBurnt);
}
@Override
protected void updateChartsnUIThread(CaloriesDailyFragment.CaloriesData data) {
int restingCalories = data.restingBurnt;
int activeCalories = data.activeBurnt;
int totalCalories = activeCalories + restingCalories;
caloriesActive.setText(String.valueOf(activeCalories));
caloriesResting.setText(String.valueOf(restingCalories));
if (gaugeViewMode.equals(GaugeViewMode.TOTAL_CALORIES_SEGMENT)) {
int[] colors = new int[] {
ContextCompat.getColor(GBApplication.getContext(), R.color.calories_resting_color),
ContextCompat.getColor(GBApplication.getContext(), R.color.calories_color)
};
float[] segments = new float[] {
restingCalories > 0 ? (float) restingCalories / totalCalories : 0,
activeCalories > 0 ? (float) activeCalories / totalCalories : 0
};
final int width = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
300,
GBApplication.getContext().getResources().getDisplayMetrics()
);
caloriesGauge.setImageBitmap(GaugeDrawer.drawCircleGaugeSegmented(
width,
width / 15,
colors,
segments,
true,
String.valueOf(totalCalories),
getContext().getString(R.string.total_burnt),
getContext()
));
} else {
int value = 0;
if (gaugeViewMode.equals(GaugeViewMode.ACTIVE_CALORIES_GOAL)) {
value = activeCalories;
} else if (gaugeViewMode.equals(GaugeViewMode.TOTAL_CALORIES_GOAL)) {
value = totalCalories;
}
final int width = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
300,
GBApplication.getContext().getResources().getDisplayMetrics()
);
caloriesGauge.setImageBitmap(GaugeDrawer.drawCircleGauge(
width,
width / 15,
getResources().getColor(R.color.calories_color),
value,
CALORIES_GOAL,
getContext()
));
}
}
@Override
protected void renderCharts() {}
@Override
protected void setupLegend(Chart<?> chart) {}
protected static class CaloriesData extends ChartsData {
public int activeBurnt;
public int restingBurnt;
public int totalBurnt;
protected CaloriesData(int totalBurnt, int activeBurnt, int restingBurnt) {
this.totalBurnt = totalBurnt;
this.activeBurnt = activeBurnt;
this.restingBurnt = restingBurnt;
}
}
}

View File

@ -37,6 +37,7 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -123,12 +124,13 @@ public class StepsDailyFragment extends StepsFragment<StepsDailyFragment.StepsDa
GBApplication.getContext().getResources().getDisplayMetrics()
);
stepsGauge.setImageBitmap(drawGauge(
stepsGauge.setImageBitmap(GaugeDrawer.drawCircleGauge(
width,
width / 15,
getResources().getColor(R.color.steps_color),
(int) stepsData.todayStepsDay.steps,
STEPS_GOAL
STEPS_GOAL,
getContext()
));
steps.setText(String.format(String.valueOf(stepsData.todayStepsDay.steps)));
@ -225,59 +227,6 @@ public class StepsDailyFragment extends StepsFragment<StepsDailyFragment.StepsDa
yAxisRight.setDrawAxisLine(true);
}
Bitmap drawGauge(int width, int barWidth, @ColorInt int filledColor, int value, int maxValue) {
int height = width;
int barMargin = (int) Math.ceil(barWidth / 2f);
float filledFactor = (float) value / maxValue;
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(barWidth);
paint.setColor(getResources().getColor(R.color.gauge_line_color));
canvas.drawArc(
barMargin,
barMargin,
width - barMargin,
width - barMargin,
90,
360,
false,
paint);
paint.setStrokeWidth(barWidth);
paint.setColor(filledColor);
canvas.drawArc(
barMargin,
barMargin,
width - barMargin,
height - barMargin,
270,
360 * filledFactor,
false,
paint
);
Paint textPaint = new Paint();
textPaint.setColor(TEXT_COLOR);
float textPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.06f, requireContext().getResources().getDisplayMetrics());
textPaint.setTextSize(textPixels);
textPaint.setTextAlign(Paint.Align.CENTER);
int yPos = (int) ((float) height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
canvas.drawText(String.valueOf(value), width / 2f, yPos, textPaint);
Paint textLowerPaint = new Paint();
textLowerPaint.setColor(TEXT_COLOR);
textLowerPaint.setTextAlign(Paint.Align.CENTER);
float textLowerPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.025f, requireContext().getResources().getDisplayMetrics());
textLowerPaint.setTextSize(textLowerPixels);
int yPosLowerText = (int) ((float) height / 2 - textPaint.ascent()) ;
canvas.drawText(String.valueOf(maxValue), width / 2f, yPosLowerText, textLowerPaint);
return bitmap;
}
protected static class StepsData extends ChartsData {
StepsDay todayStepsDay;
List<? extends ActivitySample> samples;

View File

@ -87,7 +87,7 @@ public abstract class AbstractDashboardWidget extends Fragment {
.collect(Collectors.toList());
}
protected void onClickOpenChart(final View view, final String chart, final int label) {
protected void onClickOpenChart(final View view, final String chart, final int label, final String mode) {
view.setOnClickListener(v -> {
chooseDevice(dashboardData, device -> {
final Intent startIntent;
@ -96,6 +96,7 @@ public abstract class AbstractDashboardWidget extends Fragment {
startIntent.putExtra(ActivityChartsActivity.EXTRA_SINGLE_FRAGMENT_NAME, chart);
startIntent.putExtra(ActivityChartsActivity.EXTRA_ACTIONBAR_TITLE, label);
startIntent.putExtra(ActivityChartsActivity.EXTRA_TIMESTAMP, dashboardData.timeTo);
startIntent.putExtra(ActivityChartsActivity.EXTRA_MODE, mode);
requireContext().startActivity(startIntent);
});
});

View File

@ -42,18 +42,24 @@ public abstract class AbstractGaugeWidget extends AbstractDashboardWidget {
private final int label;
private final String targetActivityTab;
private String mode = "";
public AbstractGaugeWidget(@StringRes final int label, @Nullable final String targetActivityTab) {
this.label = label;
this.targetActivityTab = targetActivityTab;
}
public AbstractGaugeWidget(@StringRes final int label, @Nullable final String targetActivityTab, final String mode) {
this(label, targetActivityTab);
this.mode = mode;
}
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final View fragmentView = inflater.inflate(R.layout.dashboard_widget_generic_gauge, container, false);
if (targetActivityTab != null) {
onClickOpenChart(fragmentView, targetActivityTab, label);
onClickOpenChart(fragmentView, targetActivityTab, label, mode);
}
gaugeValue = fragmentView.findViewById(R.id.gauge_value);

View File

@ -0,0 +1,58 @@
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
import android.os.Bundle;
import androidx.core.content.ContextCompat;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.CaloriesDailyFragment;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
/**
* A simple {@link AbstractDashboardWidget} subclass.
* Use the {@link DashboardCaloriesActiveGoalWidget#newInstance} factory method to
* create an instance of this fragment.
*/
public class DashboardCaloriesActiveGoalWidget extends AbstractGaugeWidget {
public DashboardCaloriesActiveGoalWidget() {
super(R.string.active_calories, "calories", CaloriesDailyFragment.GaugeViewMode.ACTIVE_CALORIES_GOAL.toString());
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param dashboardData An instance of DashboardFragment.DashboardData.
* @return A new instance of fragment DashboardStepsWidget.
*/
public static DashboardCaloriesActiveGoalWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
final DashboardCaloriesActiveGoalWidget fragment = new DashboardCaloriesActiveGoalWidget();
final Bundle args = new Bundle();
args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
fragment.setArguments(args);
return fragment;
}
@Override
protected boolean isSupportedBy(final GBDevice device) {
return device.getDeviceCoordinator().supportsActiveCalories();
}
@Override
protected void populateData(final DashboardFragment.DashboardData dashboardData) {
dashboardData.getActiveCaloriesTotal();
dashboardData.getActiveCaloriesGoalFactor();
}
@Override
protected void draw(final DashboardFragment.DashboardData dashboardData) {
setText(String.valueOf(dashboardData.getActiveCaloriesTotal()));
final int colorCalories = ContextCompat.getColor(GBApplication.getContext(), R.color.calories_color);
drawSimpleGauge(
colorCalories,
dashboardData.getActiveCaloriesGoalFactor()
);
}
}

View File

@ -0,0 +1,58 @@
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
import android.os.Bundle;
import androidx.core.content.ContextCompat;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.CaloriesDailyFragment;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
/**
* A simple {@link AbstractDashboardWidget} subclass.
* Use the {@link DashboardCaloriesGoalWidget#newInstance} factory method to
* create an instance of this fragment.
*/
public class DashboardCaloriesGoalWidget extends AbstractGaugeWidget {
public DashboardCaloriesGoalWidget() {
super(R.string.calories, "calories", CaloriesDailyFragment.GaugeViewMode.TOTAL_CALORIES_GOAL.toString());
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param dashboardData An instance of DashboardFragment.DashboardData.
* @return A new instance of fragment DashboardStepsWidget.
*/
public static DashboardCaloriesGoalWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
final DashboardCaloriesGoalWidget fragment = new DashboardCaloriesGoalWidget();
final Bundle args = new Bundle();
args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
fragment.setArguments(args);
return fragment;
}
@Override
protected boolean isSupportedBy(final GBDevice device) {
return device.getDeviceCoordinator().supportsActiveCalories();
}
@Override
protected void populateData(final DashboardFragment.DashboardData dashboardData) {
dashboardData.getCaloriesTotal();
dashboardData.getCaloriesGoalFactor();
}
@Override
protected void draw(final DashboardFragment.DashboardData dashboardData) {
setText(String.valueOf(dashboardData.getCaloriesTotal()));
final int colorCalories = ContextCompat.getColor(GBApplication.getContext(), R.color.calories_color);
drawSimpleGauge(
colorCalories,
dashboardData.getCaloriesGoalFactor()
);
}
}

View File

@ -0,0 +1,78 @@
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
import android.graphics.Color;
import android.os.Bundle;
import androidx.core.content.ContextCompat;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.CaloriesDailyFragment;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
/**
* A simple {@link AbstractDashboardWidget} subclass.
* Use the {@link DashboardCaloriesTotalSegmentedWidget#newInstance} factory method to
* create an instance of this fragment.
*/
public class DashboardCaloriesTotalSegmentedWidget extends AbstractGaugeWidget {
public DashboardCaloriesTotalSegmentedWidget() {
super(R.string.calories, "calories", CaloriesDailyFragment.GaugeViewMode.TOTAL_CALORIES_SEGMENT.toString());
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param dashboardData An instance of DashboardFragment.DashboardData.
* @return A new instance of fragment DashboardStepsWidget.
*/
public static DashboardCaloriesTotalSegmentedWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
final DashboardCaloriesTotalSegmentedWidget fragment = new DashboardCaloriesTotalSegmentedWidget();
final Bundle args = new Bundle();
args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
fragment.setArguments(args);
return fragment;
}
@Override
protected void populateData(final DashboardFragment.DashboardData dashboardData) {
dashboardData.getActiveCaloriesTotal();
dashboardData.getRestingCaloriesTotal();
}
@Override
protected void draw(final DashboardFragment.DashboardData dashboardData) {
int activeCalories = dashboardData.getActiveCaloriesTotal();
int restingCalories = dashboardData.getRestingCaloriesTotal();
int totalCalories = activeCalories + restingCalories;
setText(String.valueOf(totalCalories));
final int[] colors;
final float[] segments;
if (totalCalories != 0) {
colors = new int[] {
ContextCompat.getColor(GBApplication.getContext(), R.color.calories_resting_color),
ContextCompat.getColor(GBApplication.getContext(), R.color.calories_color)
};
segments = new float[] {
restingCalories > 0 ? (float) restingCalories / totalCalories : 0,
activeCalories > 0 ? (float) activeCalories / totalCalories : 0
};
} else {
colors = new int[]{
Color.argb(25, 128, 128, 128)
};
segments = new float[] {
1f
};
}
drawSegmentedGauge(
colors,
segments,
-1,
false,
false
);
}
}

View File

@ -1,5 +1,6 @@
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@ -15,6 +16,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
public class GaugeDrawer {
private static final Logger LOG = LoggerFactory.getLogger(GaugeDrawer.class);
@ -194,6 +196,131 @@ public class GaugeDrawer {
gaugeBar.setImageBitmap(bitmap);
}
public static Bitmap drawCircleGaugeSegmented(int width, int barWidth, final int[] colors, final float[] segments, final boolean gapBetweenSegments, String text, String lowerText, Context context) {
int TEXT_COLOR = GBApplication.getTextColor(context);
int height = width;
int barMargin = (int) Math.ceil(barWidth / 2f);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.BUTT);
paint.setStrokeWidth(barWidth);
paint.setColor(context.getResources().getColor(R.color.gauge_line_color));
canvas.drawArc(
barMargin,
barMargin,
width - barMargin,
width - barMargin,
90,
360,
false,
paint);
paint.setStrokeWidth(barWidth);
float angleSum = 0;
for (int i = 0; i < segments.length; i++) {
if (segments[i] == 0) {
continue;
}
paint.setColor(colors[i]);
paint.setStrokeWidth(barWidth);
float startAngleDegrees = 270 + angleSum * 360;
float sweepAngleDegrees = segments[i] * 360;
if (gapBetweenSegments) {
sweepAngleDegrees -= 2;
}
canvas.drawArc(
barMargin,
barMargin,
width - barMargin,
height - barMargin,
startAngleDegrees,
sweepAngleDegrees,
false,
paint
);
angleSum += segments[i];
}
Paint textPaint = new Paint();
textPaint.setColor(TEXT_COLOR);
float textPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.06f, context.getResources().getDisplayMetrics());
textPaint.setTextSize(textPixels);
textPaint.setTextAlign(Paint.Align.CENTER);
int yPos = (int) ((float) height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
canvas.drawText(String.valueOf(text), width / 2f, yPos, textPaint);
Paint textLowerPaint = new Paint();
textLowerPaint.setColor(TEXT_COLOR);
textLowerPaint.setTextAlign(Paint.Align.CENTER);
float textLowerPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.025f, context.getResources().getDisplayMetrics());
textLowerPaint.setTextSize(textLowerPixels);
int yPosLowerText = (int) ((float) height / 2 - textPaint.ascent()) ;
canvas.drawText(String.valueOf(lowerText), width / 2f, yPosLowerText, textLowerPaint);
return bitmap;
}
public static Bitmap drawCircleGauge(int width, int barWidth, @ColorInt int filledColor, int value, int maxValue, Context context) {
int TEXT_COLOR = GBApplication.getTextColor(context);
int height = width;
int barMargin = (int) Math.ceil(barWidth / 2f);
float filledFactor = (float) value / maxValue;
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(barWidth);
paint.setColor(context.getResources().getColor(R.color.gauge_line_color));
canvas.drawArc(
barMargin,
barMargin,
width - barMargin,
width - barMargin,
90,
360,
false,
paint);
paint.setStrokeWidth(barWidth);
paint.setColor(filledColor);
canvas.drawArc(
barMargin,
barMargin,
width - barMargin,
height - barMargin,
270,
360 * filledFactor,
false,
paint
);
Paint textPaint = new Paint();
textPaint.setColor(TEXT_COLOR);
float textPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.06f, context.getResources().getDisplayMetrics());
textPaint.setTextSize(textPixels);
textPaint.setTextAlign(Paint.Align.CENTER);
int yPos = (int) ((float) height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
canvas.drawText(String.valueOf(value), width / 2f, yPos, textPaint);
Paint textLowerPaint = new Paint();
textLowerPaint.setColor(TEXT_COLOR);
textLowerPaint.setTextAlign(Paint.Align.CENTER);
float textLowerPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.025f, context.getResources().getDisplayMetrics());
textLowerPaint.setTextSize(textLowerPixels);
int yPosLowerText = (int) ((float) height / 2 - textPaint.ascent()) ;
canvas.drawText(String.valueOf(maxValue), width / 2f, yPosLowerText, textLowerPaint);
return bitmap;
}
public static double normalize(final double value, final double min, final double max) {
return normalize(value, min, max, 0, 1);
}

View File

@ -496,6 +496,16 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return false;
}
@Override
public boolean supportsActiveCalories() {
return false;
}
@Override
public boolean supportsRestingCalories() {
return false;
}
@Override
public boolean supportsActivityTabs() {
return supportsActivityTracking();

View File

@ -227,6 +227,8 @@ public interface DeviceCoordinator {
boolean supportsStepCounter();
boolean supportsSpeedzones();
boolean supportsActivityTabs();
boolean supportsRestingCalories();
boolean supportsActiveCalories();
/**
* Returns true if measurement and fetching of body temperature is supported by the device

View File

@ -20,7 +20,6 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpec
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DefaultRestingMetabolicRateProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.WorkoutVo2MaxSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
@ -251,6 +250,16 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
return true;
}
@Override
public boolean supportsActiveCalories() {
return true;
}
@Override
public boolean supportsRestingCalories() {
return true;
}
@Override
public int[] getStressRanges() {
// 1-25 = relaxed

View File

@ -24,6 +24,7 @@ public class ActivityAmount {
private long totalSeconds;
private long totalSteps;
private long totalDistance;
private long totalActiveCalories;
private Date startDate = null;
private Date endDate = null;
@ -43,6 +44,10 @@ public class ActivityAmount {
totalDistance += distance;
}
public void addActiveCalories(long activeCalories) {
totalActiveCalories += activeCalories;
}
public long getTotalSeconds() {
return totalSeconds;
}
@ -55,6 +60,10 @@ public class ActivityAmount {
return totalDistance;
}
public long getTotalActiveCalories() {
return totalActiveCalories;
}
public ActivityKind getActivityKind() {
return activityKind;
}

View File

@ -44,6 +44,7 @@ public class ActivityUser {
private int activityUserSleepDurationGoal;
private int activityUserStepsGoal;
private int activityUserCaloriesBurntGoal;
private int activityUserActiveCaloriesBurntGoal;
private int activityUserDistanceGoalMeters;
private int activityUserActiveTimeGoalMinutes;
private int activityUserStandingTimeGoalHours;
@ -58,6 +59,7 @@ public class ActivityUser {
public static final int defaultUserSleepDurationGoal = 7;
public static final int defaultUserStepsGoal = 8000;
public static final int defaultUserCaloriesBurntGoal = 2000;
public static final int defaultUserActiveCaloriesBurntGoal = 350;
public static final int defaultUserDistanceGoalMeters = 5000;
public static final int defaultUserActiveTimeGoalMinutes = 60;
public static final int defaultUserStepLengthCm = 0;
@ -73,6 +75,7 @@ public class ActivityUser {
public static final String PREF_USER_SLEEP_DURATION = "activity_user_sleep_duration";
public static final String PREF_USER_STEPS_GOAL = "fitness_goal"; // FIXME: for compatibility
public static final String PREF_USER_CALORIES_BURNT = "activity_user_calories_burnt";
public static final String PREF_USER_ACTIVE_CALORIES_BURNT = "activity_user_active_calories_burnt";
public static final String PREF_USER_DISTANCE_METERS = "activity_user_distance_meters";
public static final String PREF_USER_ACTIVETIME_MINUTES = "activity_user_activetime_minutes";
public static final String PREF_USER_STEP_LENGTH_CM = "activity_user_step_length_cm";
@ -160,6 +163,7 @@ public class ActivityUser {
activityUserSleepDurationGoal = prefs.getInt(PREF_USER_SLEEP_DURATION, defaultUserSleepDurationGoal);
activityUserStepsGoal = prefs.getInt(PREF_USER_STEPS_GOAL, defaultUserStepsGoal);
activityUserCaloriesBurntGoal = prefs.getInt(PREF_USER_CALORIES_BURNT, defaultUserCaloriesBurntGoal);
activityUserActiveCaloriesBurntGoal = prefs.getInt(PREF_USER_ACTIVE_CALORIES_BURNT, defaultUserActiveCaloriesBurntGoal);
activityUserDistanceGoalMeters = prefs.getInt(PREF_USER_DISTANCE_METERS, defaultUserDistanceGoalMeters);
activityUserActiveTimeGoalMinutes = prefs.getInt(PREF_USER_ACTIVETIME_MINUTES, defaultUserActiveTimeGoalMinutes);
activityUserStandingTimeGoalHours = prefs.getInt(PREF_USER_GOAL_STANDING_TIME_HOURS, defaultUserGoalStandingTimeHours);
@ -187,6 +191,14 @@ public class ActivityUser {
return activityUserCaloriesBurntGoal;
}
public int getActiveCaloriesBurntGoal()
{
if (activityUserActiveCaloriesBurntGoal < 1) {
activityUserActiveCaloriesBurntGoal = defaultUserActiveCaloriesBurntGoal;
}
return activityUserActiveCaloriesBurntGoal;
}
public int getDistanceGoalMeters()
{
if (activityUserDistanceGoalMeters < 1) {

View File

@ -33,7 +33,9 @@ import nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityAnalysis;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminRestingMetabolicRateSample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@ -42,22 +44,34 @@ public class DailyTotals implements Serializable {
private final long steps;
private final long distance;
private final long activeCalories;
private final long restingCalories;
private final long[] sleep; // light deep rem awake
public DailyTotals() {
this(0, 0, new long[]{0, 0, 0 ,0});
this(0, 0, new long[]{0, 0, 0 ,0}, 0, 0);
}
public DailyTotals(final long steps, final long distance, final long[] sleep) {
public DailyTotals(final long steps, final long distance, final long[] sleep, final long activeCalories, final long restingCalories) {
this.steps = steps;
this.distance = distance;
this.sleep = sleep;
this.activeCalories = activeCalories;
this.restingCalories = restingCalories;
}
public long getSteps() {
return steps;
}
public long getActiveCalories() {
return activeCalories;
}
public long getRestingCalories() {
return restingCalories;
}
public long getDistance() {
return distance;
}
@ -79,17 +93,27 @@ public class DailyTotals implements Serializable {
public static DailyTotals getDailyTotalsForDevice(GBDevice device, Calendar day, DBHandler handler) {
ActivityAnalysis analysis = new ActivityAnalysis();
ActivityAmounts amountsSteps;
ActivityAmounts totalAmounts;
ActivityAmounts amountsSleep;
amountsSteps = analysis.calculateActivityAmounts(getSamplesOfDay(handler, day, 0, device));
totalAmounts = analysis.calculateActivityAmounts(getSamplesOfDay(handler, day, 0, device));
amountsSleep = analysis.calculateActivityAmounts(getSamplesOfDay(handler, day, -12, device));
long[] sleep = getTotalsSleepForActivityAmounts(amountsSleep);
Pair<Long, Long> stepsDistance = getTotalsStepsForActivityAmounts(amountsSteps);
long totalSteps = 0;
long totalDistance = 0;
long totalActiveCalories = 0;
long totalRestingCalories = 0;
for (ActivityAmount amount : totalAmounts.getAmounts()) {
totalSteps += amount.getTotalSteps();
totalDistance += amount.getTotalDistance();
totalActiveCalories += amount.getTotalActiveCalories();
}
totalRestingCalories = getRestingCaloriesOfDay(handler, day, device);
// Purposely not including awake sleep
return new DailyTotals(stepsDistance.getLeft(), stepsDistance.getRight(), sleep);
return new DailyTotals(totalSteps, totalDistance, sleep, totalActiveCalories, totalRestingCalories);
}
private static long[] getTotalsSleepForActivityAmounts(ActivityAmounts activityAmounts) {
@ -115,17 +139,6 @@ public class DailyTotals implements Serializable {
return new long[]{totalMinutesLightSleep, totalMinutesDeepSleep, totalMinutesRemSleep, totalMinutesAwakeSleep};
}
public static Pair<Long, Long> getTotalsStepsForActivityAmounts(ActivityAmounts activityAmounts) {
long totalSteps = 0;
long totalDistance = 0;
for (ActivityAmount amount : activityAmounts.getAmounts()) {
totalSteps += amount.getTotalSteps();
totalDistance += amount.getTotalDistance();
}
return Pair.of(totalSteps, totalDistance);
}
private static List<? extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, int offsetHours, GBDevice device) {
int startTs;
int endTs;
@ -142,10 +155,32 @@ public class DailyTotals implements Serializable {
return getSamples(db, device, startTs, endTs);
}
private static int getRestingCaloriesOfDay(DBHandler db, Calendar day, GBDevice device) {
Calendar calendar = Calendar.getInstance();
day.add(Calendar.DATE, 0);
day.set(Calendar.HOUR_OF_DAY, 0);
day.set(Calendar.MINUTE, 0);
day.set(Calendar.SECOND, 0);
day.add(Calendar.HOUR, 0);
TimeSample metabolicRate = getRestingMetabolicRate(db, device);
double passedDayProportion = 1;
boolean sameDay = calendar.get(Calendar.DAY_OF_YEAR) == day.get(Calendar.DAY_OF_YEAR) &&
calendar.get(Calendar.YEAR) == day.get(Calendar.YEAR);
if (sameDay) {
passedDayProportion = (double) (calendar.getTimeInMillis() - day.getTimeInMillis()) / (24L * 60 * 60 * 1000);
}
return (int) ((double) ((GarminRestingMetabolicRateSample) metabolicRate).getRestingMetabolicRate() * passedDayProportion);
}
public static List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
return getAllSamples(db, device, tsFrom, tsTo);
}
protected static TimeSample getRestingMetabolicRate(DBHandler db, GBDevice device) {
TimeSampleProvider<? extends RestingMetabolicRateSample> provider = device.getDeviceCoordinator().getRestingMetabolicRateProvider(device, db.getDaoSession());
return provider.getLatestSample();
}
protected static SampleProvider<? extends AbstractActivitySample> getProvider(DBHandler db, GBDevice device) {
DeviceCoordinator coordinator = device.getDeviceCoordinator();
return coordinator.getSampleProvider(device, db.getDaoSession());

View File

@ -63,6 +63,36 @@ public class DashboardUtils {
return totalSteps;
}
public static int getActiveCaloriesTotal(DashboardFragment.DashboardData dashboardData) {
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
int totalActiveCalories = 0;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
for (GBDevice dev : devices) {
if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActivityTracking()) {
totalActiveCalories += (int) getDailyTotals(dev, dbHandler, dashboardData.timeTo).getActiveCalories();
}
}
} catch (Exception e) {
LOG.warn("Could not calculate total amount of active calories: ", e);
}
return totalActiveCalories;
}
public static int getRestingCaloriesTotal(DashboardFragment.DashboardData dashboardData) {
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
int totalRestingCalories = 0;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
for (GBDevice dev : devices) {
if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActivityTracking()) {
totalRestingCalories += (int) getDailyTotals(dev, dbHandler, dashboardData.timeTo).getRestingCalories();
}
}
} catch (Exception e) {
LOG.warn("Could not calculate total amount of resting calories: ", e);
}
return totalRestingCalories;
}
public static float getStepsGoalFactor(DashboardFragment.DashboardData dashboardData) {
ActivityUser activityUser = new ActivityUser();
float stepsGoal = activityUser.getStepsGoal();
@ -134,6 +164,24 @@ public class DashboardUtils {
return goalFactor;
}
public static float getActiveCaloriesGoalFactor(DashboardFragment.DashboardData dashboardData) {
ActivityUser activityUser = new ActivityUser();
int caloriesGoal = activityUser.getActiveCaloriesBurntGoal();
float goalFactor = (float) getActiveCaloriesTotal(dashboardData) / caloriesGoal;
if (goalFactor > 1) goalFactor = 1;
return goalFactor;
}
public static float getCaloriesGoalFactor(DashboardFragment.DashboardData dashboardData) {
ActivityUser activityUser = new ActivityUser();
int caloriesGoal = activityUser.getCaloriesBurntGoal();
float goalFactor = (float) (getRestingCaloriesTotal(dashboardData) + getActiveCaloriesTotal(dashboardData)) / caloriesGoal;
if (goalFactor > 1) goalFactor = 1;
return goalFactor;
}
public static long getActiveMinutesTotal(DashboardFragment.DashboardData dashboardData) {
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
long totalActiveMinutes = 0;

View File

@ -0,0 +1,125 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
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="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/date_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:layout_marginBottom="20dp"
android:gravity="center"
android:textSize="20sp" />
<ImageView
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_gravity="center"
android:scaleType="fitStart"
android:id="@+id/calories_gauge" />
<GridLayout
android:id="@+id/calories_types_wrapper"
android:background="@color/gauge_line_color"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="2"
android:layout_marginTop="15dp"
>
<LinearLayout
android:id="@+id/calories_active_wrapper"
style="@style/GridTile"
android:layout_marginEnd="1dp"
android:layout_marginTop="2dp"
>
<TextView
android:id="@+id/calories_active"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stats_empty_value"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/active"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/calories_active_goal_wrapper"
style="@style/GridTile"
android:layout_marginStart="1dp"
android:layout_marginTop="2dp"
>
<TextView
android:id="@+id/calories_active_goal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stats_empty_value"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/active_goal"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/calories_resting_wrapper"
style="@style/GridTile"
android:layout_marginEnd="1dp"
>
<TextView
android:id="@+id/calories_resting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stats_empty_value"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hr_resting"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/calories_total_goal_wrapper"
style="@style/GridTile"
android:layout_marginStart="1dp"
>
<TextView
android:id="@+id/calories_total_goal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stats_empty_value"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/total_goal"
android:textSize="12sp" />
</LinearLayout>
</GridLayout>
<TextView
android:id="@+id/steps_chart_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:paddingLeft="20dip"
android:text=""
android:textSize="20sp" />
</LinearLayout>
</ScrollView>

View File

@ -3113,6 +3113,7 @@
<item>@string/pref_header_spo2</item>
<item>@string/menuitem_temperature</item>
<item>@string/menuitem_weight</item>
<item>@string/menuitem_calories</item>
</string-array>
<string-array name="pref_charts_tabs_values">
@ -3131,6 +3132,7 @@
<item>@string/p_spo2</item>
<item>@string/p_temperature</item>
<item>@string/p_weight</item>
<item>@string/p_calories</item>
</string-array>
<string-array name="pref_charts_tabs_items_default">
@ -3150,6 +3152,7 @@
<item>@string/p_spo2</item>
<item>@string/p_temperature</item>
<item>@string/p_weight</item>
<item>@string/p_calories</item>
</string-array>
@ -4250,6 +4253,9 @@
<item>@string/menuitem_vo2_max</item>
<item>@string/vo2max_running</item>
<item>@string/vo2max_cycling</item>
<item>@string/menuitem_calories_goal</item>
<item>@string/menuitem_calories_active_goal</item>
<item>@string/menuitem_calories_segmented</item>
</string-array>
<string-array name="pref_dashboard_widgets_order_values">
@ -4267,6 +4273,9 @@
<item>vo2max</item>
<item>vo2max_running</item>
<item>vo2max_cycling</item>
<item>calories</item>
<item>calories_active</item>
<item>calories_segmented</item>
</string-array>
<string-array name="pref_dashboard_widgets_order_default">
@ -4280,5 +4289,8 @@
<item>stress_segmented</item>
<item>hrv</item>
<item>vo2max</item>
<item>calories</item>
<item>calories_active</item>
<item>calories_segmented</item>
</string-array>
</resources>

View File

@ -61,6 +61,8 @@
<color name="body_energy_level_color" type="color">#5ac234</color>
<color name="body_energy_lost_color" type="color">#ff6c43</color>
<color name="steps_color" type="color">#00c9bf</color>
<color name="calories_color" type="color">#fa4502</color>
<color name="calories_resting_color" type="color">#1f49f2</color>
<color name="value_line_color" type="color">#858585</color>
<color name="row_separator_light" type="color">#ffe2e2e5</color>

View File

@ -926,6 +926,7 @@
<string name="updatefirmwareoperation_failed_low_mtu">Current MTU of %1$d is too low, please enable high MTU in the device settings and disconnect/re-connect the device.</string>
<string name="chart_steps">Steps</string>
<string name="calories">Calories</string>
<string name="active_calories">Active calories</string>
<string name="distance">Distance</string>
<string name="clock">Clock</string>
<string name="heart_rate">Heart rate</string>
@ -933,6 +934,11 @@
<string name="hr_maximum">Maximum</string>
<string name="hr_minimum">Minimum</string>
<string name="hr_average">Average</string>
<string name="active">Active</string>
<string name="active_goal">Active goal</string>
<string name="total_goal">Total goal</string>
<string name="total_burnt">Total burnt</string>
<string name="goal">Goal</string>
<string name="blood_pressure">Blood pressure</string>
<string name="getting_heart_rate">Measuring</string>
<string name="heart_rate_result">Measurement results</string>
@ -1228,6 +1234,7 @@
<string name="activity_prefs_activetime_minutes">Daily target: active time in minutes</string>
<string name="activity_prefs_goal_standing_time_minutes">Daily target: standing time in minutes</string>
<string name="activity_prefs_goal_fat_burn_time_minutes">Daily target: fat burn time in minutes</string>
<string name="activity_prefs_goal_active_calories_burnt">Daily target: active calories burnt</string>
<string name="active_time">Active time</string>
<string name="standing_time">Standing time</string>
<string name="pref_title_pebble_health_store_raw">Store raw record in the database</string>
@ -1931,6 +1938,9 @@
<string name="menuitem_stress_simple">Stress (simple)</string>
<string name="menuitem_stress_segmented">Stress (segmented)</string>
<string name="menuitem_stress_breakdown">Stress (breakdown)</string>
<string name="menuitem_calories_segmented">Calories(segmented)</string>
<string name="menuitem_calories_active_goal">Calories goal(active)</string>
<string name="menuitem_calories_goal">Calories goal(total)</string>
<string name="menuitem_pai">PAI</string>
<string name="menuitem_hr">Heart Rate</string>
<string name="menuitem_spo2">SpO2</string>
@ -1951,6 +1961,7 @@
<string name="menuitem_widgets">Widgets</string>
<string name="menuitem_temperature">Temperature</string>
<string name="menuitem_weight">Weight</string>
<string name="menuitem_calories">Calories</string>
<string name="menuitem_barometer">Barometer</string>
<string name="menuitem_flashlight">Flashlight</string>
<string name='menuitem_email'>E-mail</string>

View File

@ -93,6 +93,15 @@
android:title="@string/activity_prefs_calories_burnt"
app:useSimpleSummaryProvider="true" />
<EditTextPreference
app:iconSpaceReserved="false"
android:defaultValue="350"
android:inputType="number"
android:key="activity_user_goal_active_calories_burnt"
android:maxLength="3"
android:title="@string/activity_prefs_goal_active_calories_burnt"
app:useSimpleSummaryProvider="true" />
<EditTextPreference
app:iconSpaceReserved="false"
android:defaultValue="5000"