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.DefaultRestingMetabolicRateProvider; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider; import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample; public class CaloriesDailyFragment extends AbstractChartFragment { private ImageView caloriesGauge; private TextView dateView; private TextView caloriesResting; private TextView caloriesActive; private LinearLayout caloriesActiveWrapper; private TextView caloriesActiveGoal; private LinearLayout caloriesActiveGoalWrapper; protected int CALORIES_GOAL; protected int ACTIVE_CALORIES_GOAL; public enum GaugeViewMode { ACTIVE_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); caloriesActive = rootView.findViewById(R.id.calories_active); caloriesActiveWrapper = rootView.findViewById(R.id.calories_active_wrapper); caloriesActiveGoal = rootView.findViewById(R.id.calories_active_goal); caloriesActiveGoalWrapper = rootView.findViewById(R.id.calories_active_goal_wrapper); ActivityUser activityUser = new ActivityUser(); ACTIVE_CALORIES_GOAL = activityUser.getCaloriesBurntGoal(); refresh(); if (!supportsActiveCalories()) { caloriesActiveWrapper.setVisibility(View.GONE); caloriesActiveGoalWrapper.setVisibility(View.GONE); } if (gaugeViewMode == null) { gaugeViewMode = GaugeViewMode.TOTAL_CALORIES_SEGMENT; } if (gaugeViewMode.equals(GaugeViewMode.ACTIVE_CALORIES_GOAL)) { CALORIES_GOAL = ACTIVE_CALORIES_GOAL; } return rootView; } public boolean supportsActiveCalories() { final GBDevice device = getChartsHost().getDevice(); return device.getDeviceCoordinator().supportsActiveCalories(); } protected RestingMetabolicRateSample getRestingMetabolicRate(DBHandler db, GBDevice device) { TimeSampleProvider provider = device.getDeviceCoordinator().getRestingMetabolicRateProvider(device, db.getDaoSession()); RestingMetabolicRateSample latestSample = provider.getLatestSample(); if (latestSample != null) { return latestSample; } DefaultRestingMetabolicRateProvider defaultProvider = new DefaultRestingMetabolicRateProvider(device, db.getDaoSession()); return defaultProvider.getLatestSample(); } protected List getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { SampleProvider 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 samples = getActivitySamples(db, device, startTs, endTs); RestingMetabolicRateSample metabolicRate = getRestingMetabolicRate(db, device); if (metabolicRate == null) { return new CaloriesData(0, 0, 0); } 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) (metabolicRate.getRestingMetabolicRate() * passedDayProportion); for (int i = 0; i <= samples.size() - 1; i++) { ActivitySample sample = samples.get(i); if (sample.getActiveCalories() > 0) { activeBurnt += sample.getActiveCalories(); } } // Convert calories to kcal activeBurnt = activeBurnt / 1000; 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)); caloriesActiveGoal.setText(String.valueOf(ACTIVE_CALORIES_GOAL)); 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_calories_burnt), getContext() )); } else { int value = 0; if (gaugeViewMode.equals(GaugeViewMode.ACTIVE_CALORIES_GOAL)) { value = activeCalories; } 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; } } }