mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-30 05:52:57 +01:00
Calories: add fragment
This commit is contained in:
parent
60ab38db57
commit
622d37ed38
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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.activities.workouts.WorkoutValueFormatter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
@ -125,12 +126,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)));
|
||||
@ -229,59 +231,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;
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
125
app/src/main/res/layout/fragment_calories.xml
Normal file
125
app/src/main/res/layout/fragment_calories.xml
Normal 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>
|
@ -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>
|
||||
|
||||
|
||||
@ -4276,6 +4279,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">
|
||||
@ -4293,6 +4299,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">
|
||||
@ -4306,5 +4315,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>
|
||||
|
@ -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>
|
||||
|
@ -928,6 +928,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>
|
||||
@ -935,6 +936,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>
|
||||
@ -1230,6 +1236,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>
|
||||
@ -1939,6 +1946,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>
|
||||
@ -1959,6 +1969,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>
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user