/* Copyright (C) 2020-2024 José Rebelo, Petr Vaněk This file is part of Gadgetbridge. Gadgetbridge is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gadgetbridge is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.activities.charts; import android.content.Context; import android.graphics.Color; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import com.github.mikephil.charting.animation.Easing; import com.github.mikephil.charting.charts.PieChart; import com.github.mikephil.charting.data.PieData; import com.github.mikephil.charting.data.PieDataSet; import com.github.mikephil.charting.data.PieEntry; import com.github.mikephil.charting.utils.MPPointF; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Date; import java.util.concurrent.TimeUnit; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity; import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractActivityListingAdapter; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils; public class ActivityListingAdapter extends AbstractActivityListingAdapter { public static final String CHART_COLOR_START = "#e74c3c"; public static final String CHART_COLOR_END = "#2ecc71"; protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingAdapter.class); protected final int ANIM_TIME = 250; private final int SESSION_SUMMARY = ActivitySession.SESSION_SUMMARY; private final int SESSION_EMPTY = ActivitySession.SESSION_EMPTY; ActivityUser activityUser = new ActivityUser(); int stepsGoal = activityUser.getStepsGoal(); int distanceGoalMeters = activityUser.getDistanceGoalMeters(); long activeTimeGoalTimeMillis = activityUser.getActiveTimeGoalMinutes() * 60 * 1000L; public ActivityListingAdapter(Context context) { super(context); } @Override protected View fill_dashboard(ActivitySession item, int position, View view, ViewGroup parent, Context context) { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.activity_list_dashboard_item, parent, false); PieChart ActiveStepsChart; PieChart DistanceChart; PieChart ActiveTimeChart; ActiveStepsChart = view.findViewById(R.id.activity_dashboard_piechart1); setUpChart(ActiveStepsChart); int steps = item.getActiveSteps(); setChartsData(ActiveStepsChart, steps, stepsGoal, context.getString(R.string.activity_list_summary_active_steps), context); DistanceChart = view.findViewById(R.id.activity_dashboard_piechart2); setUpChart(DistanceChart); float distance = item.getDistance(); setChartsData(DistanceChart, distance, distanceGoalMeters, context.getString(R.string.distance), context); ActiveTimeChart = view.findViewById(R.id.activity_dashboard_piechart3); setUpChart(ActiveTimeChart); long duration = item.getEndTime().getTime() - item.getStartTime().getTime(); setChartsData(ActiveTimeChart, duration, activeTimeGoalTimeMillis, context.getString(R.string.activity_list_summary_active_time), context); TextView stepLabel = view.findViewById(R.id.line_layout_step_label); TextView stepTotalLabel = view.findViewById(R.id.line_layout_total_step_label); TextView distanceLabel = view.findViewById(R.id.line_layout_distance_label); TextView hrLabel = view.findViewById(R.id.heartrate_widget_label); TextView intensityLabel = view.findViewById(R.id.intensity_widget_label); TextView intensity2Label = view.findViewById(R.id.line_layout_intensity2_label); TextView durationLabel = view.findViewById(R.id.line_layout_duration_label); TextView sessionCountLabel = view.findViewById(R.id.line_layout_count_label); LinearLayout durationLayout = view.findViewById(R.id.line_layout_duration); LinearLayout countLayout = view.findViewById(R.id.line_layout_count); View hrLayout = view.findViewById(R.id.heartrate_widget_icon); LinearLayout stepsLayout = view.findViewById(R.id.line_layout_step); LinearLayout stepsTotalLayout = view.findViewById(R.id.line_layout_total_step); LinearLayout distanceLayout = view.findViewById(R.id.line_layout_distance); View intensityLayout = view.findViewById(R.id.intensity_widget_icon); View intensity2Layout = view.findViewById(R.id.line_layout_intensity2); stepLabel.setText(getStepLabel(item)); stepTotalLabel.setText(getStepTotalLabel(item)); distanceLabel.setText(getDistanceLabel(item)); hrLabel.setText(getHrLabel(item)); intensityLabel.setText(getIntensityLabel(item)); intensity2Label.setText(getIntensityLabel(item)); durationLabel.setText(getDurationLabel(item)); sessionCountLabel.setText(getSessionCountLabel(item)); if (!hasHR(item)) { hrLayout.setVisibility(View.GONE); hrLabel.setVisibility(View.GONE); } else { hrLayout.setVisibility(View.VISIBLE); hrLabel.setVisibility(View.VISIBLE); } if (!hasIntensity(item)) { intensityLayout.setVisibility(View.GONE); intensity2Layout.setVisibility(View.GONE); intensityLabel.setVisibility(View.GONE); intensity2Label.setVisibility(View.GONE); } else { intensityLayout.setVisibility(View.VISIBLE); intensity2Layout.setVisibility(View.VISIBLE); intensityLabel.setVisibility(View.VISIBLE); intensity2Label.setVisibility(View.VISIBLE); } if (!hasDistance(item)) { distanceLayout.setVisibility(View.GONE); } else { distanceLayout.setVisibility(View.VISIBLE); } if (!hasSteps(item)) { stepsLayout.setVisibility(View.GONE); } else { stepsLayout.setVisibility(View.VISIBLE); } if (!hasTotalSteps(item)) { stepsTotalLayout.setVisibility(View.GONE); countLayout.setVisibility(View.GONE); durationLayout.setVisibility(View.GONE); } else { stepsTotalLayout.setVisibility(View.VISIBLE); countLayout.setVisibility(View.VISIBLE); durationLayout.setVisibility(View.VISIBLE); } return view; } private void setChartsData(PieChart pieChart, float value, float target, String label, Context context) { ArrayList entries = new ArrayList<>(); entries.add(new PieEntry((float) value, context.getResources().getDrawable(R.drawable.ic_star_gold))); Easing.EasingFunction animationEffect = Easing.EaseInOutSine; if (value < target) { entries.add(new PieEntry((float) (target - value))); } pieChart.setCenterText(String.format("%d%%\n%s", (int) (value * 100 / target), label)); float colorValue = Math.max(0, Math.min(1, value / target)); int chartColor = interpolateColor(Color.parseColor(CHART_COLOR_START), Color.parseColor(CHART_COLOR_END), colorValue); PieDataSet dataSet = new PieDataSet(entries, ""); dataSet.setDrawIcons(false); dataSet.setIconsOffset(new MPPointF(0, -66)); if (colorValue == 1) { dataSet.setDrawIcons(true); } dataSet.setSliceSpace(0f); dataSet.setSelectionShift(5f); dataSet.setColors(chartColor, Color.LTGRAY); PieData data = new PieData(dataSet); data.setValueTextSize(0f); data.setValueTextColor(Color.WHITE); pieChart.setData(data); pieChart.invalidate(); pieChart.animateY(ANIM_TIME, animationEffect); } private void setUpChart(PieChart DashboardChart) { DashboardChart.setNoDataText(""); DashboardChart.getLegend().setEnabled(false); DashboardChart.setDrawHoleEnabled(true); DashboardChart.setHoleColor(Color.WHITE); DashboardChart.getDescription().setText(""); DashboardChart.setTransparentCircleColor(Color.WHITE); DashboardChart.setTransparentCircleAlpha(110); DashboardChart.setHoleRadius(70f); DashboardChart.setTransparentCircleRadius(75f); DashboardChart.setDrawCenterText(true); DashboardChart.setRotationEnabled(true); DashboardChart.setHighlightPerTapEnabled(true); DashboardChart.setCenterTextOffset(0, 0); } private float interpolate(float a, float b, float proportion) { return (a + ((b - a) * proportion)); } private int interpolateColor(int a, int b, float proportion) { float[] hsva = new float[3]; float[] hsvb = new float[3]; Color.colorToHSV(a, hsva); Color.colorToHSV(b, hsvb); for (int i = 0; i < 3; i++) { hsvb[i] = interpolate(hsva[i], hsvb[i], proportion); } return Color.HSVToColor(hsvb); } @Override protected String getDateLabel(ActivitySession item) { return ""; } @Override protected boolean hasGPS(ActivitySession item) { return false; } @Override protected boolean hasDate(ActivitySession item) { return false; } @Override protected String getTimeFrom(ActivitySession item) { Date time = item.getStartTime(); return DateTimeUtils.formatTime(time.getHours(), time.getMinutes()); } @Override protected String getTimeTo(ActivitySession item) { Date time = item.getEndTime(); return DateTimeUtils.formatTime(time.getHours(), time.getMinutes()); } @Override protected String getActivityName(ActivitySession item) { return ActivityKind.asString(item.getActivityKind(), getContext()); } @Override protected String getStepLabel(ActivitySession item) { return String.valueOf(item.getActiveSteps()); } @Override protected String getDistanceLabel(ActivitySession item) { return FormatUtils.getFormattedDistanceLabel(item.getDistance()); } @Override protected String getHrLabel(ActivitySession item) { return String.valueOf(item.getHeartRateAverage()); } @Override protected String getIntensityLabel(ActivitySession item) { DecimalFormat df = new DecimalFormat("###"); return df.format(item.getIntensity()); } @Override protected String getDurationLabel(ActivitySession item) { long duration = item.getEndTime().getTime() - item.getStartTime().getTime(); return DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS); } @Override protected String getSpeedLabel(ActivitySession item) { long duration = item.getEndTime().getTime() - item.getStartTime().getTime(); double distanceMeters = item.getDistance(); double speed = distanceMeters * 1000 / duration; String unit = "###.#km/h"; String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric)); if (units.equals(GBApplication.getContext().getString(R.string.p_unit_imperial))) { unit = "###.#mi/h"; speed = speed * 0.6213712; } DecimalFormat df = new DecimalFormat(unit); return df.format(speed); } @Override protected String getSessionCountLabel(ActivitySession item) { return String.valueOf(item.getSessionCount()); } @Override public boolean hasHR(ActivitySession item) { return item.getHeartRateAverage() > 0; } @Override public boolean hasIntensity(ActivitySession item) { return item.getIntensity() > 0; } @Override protected boolean hasDistance(ActivitySession item) { return item.getDistance() > 0; } @Override protected boolean hasSteps(ActivitySession item) { return item.getActiveSteps() > 0; } @Override protected boolean hasTotalSteps(ActivitySession item) { return item.getTotalDaySteps() > 0; } @Override protected boolean isSummary(ActivitySession item, int position) { int sessionType = item.getSessionType(); return sessionType == SESSION_SUMMARY; } @Override protected boolean isEmptySession(ActivitySession item, int position) { int sessionType = item.getSessionType(); return sessionType == SESSION_EMPTY; } @Override protected boolean isEmptySummary(ActivitySession item) { return item.getIsEmptySummary(); } @Override protected String getStepTotalLabel(ActivitySession item) { return String.valueOf(item.getTotalDaySteps()); } @Override protected int getIcon(ActivitySession item) { return ActivityKind.getIconId(item.getActivityKind()); } }