/* 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 androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
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.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractActivityListingAdapter;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityListItem;
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;
ActivityUser activityUser = new ActivityUser();
int stepsGoal = activityUser.getStepsGoal();
int distanceGoalMeters = activityUser.getDistanceGoalMeters();
long activeTimeGoalTimeMillis = activityUser.getActiveTimeGoalMinutes() * 60 * 1000L;
public ActivityListingAdapter(Context context) {
super(context);
}
@NonNull
@Override
public AbstractActivityListingViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
switch (viewType) {
case 0: // dashboard
return new DashboardViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_list_dashboard_item, parent, false));
case 2: // item
return new ActivityItemViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_list_item, parent, false));
}
return super.onCreateViewHolder(parent, viewType);
}
private void setChartsData(PieChart pieChart, float value, float target, String label, Context context) {
ArrayList entries = new ArrayList<>();
entries.add(new PieEntry(value, AppCompatResources.getDrawable(context, R.drawable.ic_star_gold)));
Easing.EasingFunction animationEffect = Easing.EaseInOutSine;
if (value < target) {
entries.add(new PieEntry(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 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);
}
public static class ActivityItemViewHolder extends AbstractActivityListingViewHolder {
final View rootView;
final ActivityListItem activityListItem;
public ActivityItemViewHolder(@NonNull final View itemView) {
super(itemView);
this.rootView = itemView;
this.activityListItem = new ActivityListItem(itemView);
}
@Override
public void fill(final int position, final ActivitySession session, final boolean selected) {
this.activityListItem.update(
session.getStartTime(),
session.getEndTime(),
session.getActivityKind(),
null,
session.getActiveSteps(),
session.getDistance(),
session.getHeartRateAverage(),
session.getIntensity(),
session.getEndTime().getTime() - session.getStartTime().getTime(),
false,
null,
position % 2 == 1,
selected
);
}
}
public class DashboardViewHolder extends AbstractActivityListingViewHolder {
final PieChart ActiveStepsChart;
final PieChart DistanceChart;
final PieChart ActiveTimeChart;
final TextView stepLabel;
final TextView stepTotalLabel;
final TextView distanceLabel;
final TextView hrLabel;
final TextView intensityLabel;
final TextView intensity2Label;
final TextView durationLabel;
final TextView sessionCountLabel;
final LinearLayout durationLayout;
final LinearLayout countLayout;
final View hrLayout;
final LinearLayout stepsLayout;
final LinearLayout stepsTotalLayout;
final LinearLayout distanceLayout;
final View intensityLayout;
final View intensity2Layout;
public DashboardViewHolder(@NonNull final View itemView) {
super(itemView);
ActiveStepsChart = itemView.findViewById(R.id.activity_dashboard_piechart1);
DistanceChart = itemView.findViewById(R.id.activity_dashboard_piechart2);
ActiveTimeChart = itemView.findViewById(R.id.activity_dashboard_piechart3);
stepLabel = itemView.findViewById(R.id.line_layout_step_label);
stepTotalLabel = itemView.findViewById(R.id.line_layout_total_step_label);
distanceLabel = itemView.findViewById(R.id.line_layout_distance_label);
hrLabel = itemView.findViewById(R.id.heartrate_widget_label);
intensityLabel = itemView.findViewById(R.id.intensity_widget_label);
intensity2Label = itemView.findViewById(R.id.line_layout_intensity2_label);
durationLabel = itemView.findViewById(R.id.line_layout_duration_label);
sessionCountLabel = itemView.findViewById(R.id.line_layout_count_label);
durationLayout = itemView.findViewById(R.id.line_layout_duration);
countLayout = itemView.findViewById(R.id.line_layout_count);
hrLayout = itemView.findViewById(R.id.heartrate_widget_icon);
stepsLayout = itemView.findViewById(R.id.line_layout_step);
stepsTotalLayout = itemView.findViewById(R.id.line_layout_total_step);
distanceLayout = itemView.findViewById(R.id.line_layout_distance);
intensityLayout = itemView.findViewById(R.id.intensity_widget_icon);
intensity2Layout = itemView.findViewById(R.id.line_layout_intensity2);
}
@Override
public void fill(final int position, final ActivitySession session, final boolean selected) {
setUpChart(ActiveStepsChart);
int steps = session.getActiveSteps();
setChartsData(ActiveStepsChart, steps, stepsGoal, getContext().getString(R.string.activity_list_summary_active_steps), getContext());
setUpChart(DistanceChart);
float distance = session.getDistance();
setChartsData(DistanceChart, distance, distanceGoalMeters, getContext().getString(R.string.distance), getContext());
setUpChart(ActiveTimeChart);
long duration = session.getEndTime().getTime() - session.getStartTime().getTime();
setChartsData(ActiveTimeChart, duration, activeTimeGoalTimeMillis, getContext().getString(R.string.activity_list_summary_active_time), getContext());
durationLabel.setText(DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS));
sessionCountLabel.setText(String.valueOf(session.getSessionCount()));
if (session.getHeartRateAverage() > 0) {
hrLabel.setText(String.valueOf(session.getHeartRateAverage()));
hrLayout.setVisibility(View.VISIBLE);
hrLabel.setVisibility(View.VISIBLE);
} else {
hrLayout.setVisibility(View.GONE);
hrLabel.setVisibility(View.GONE);
}
if (session.getIntensity() > 0) {
final DecimalFormat df = new DecimalFormat("###");
intensityLabel.setText(df.format(session.getIntensity()));
intensity2Label.setText(df.format(session.getIntensity()));
intensityLayout.setVisibility(View.VISIBLE);
intensity2Layout.setVisibility(View.VISIBLE);
intensityLabel.setVisibility(View.VISIBLE);
intensity2Label.setVisibility(View.VISIBLE);
} else {
intensityLayout.setVisibility(View.GONE);
intensity2Layout.setVisibility(View.GONE);
intensityLabel.setVisibility(View.GONE);
intensity2Label.setVisibility(View.GONE);
}
if (session.getDistance() > 0) {
distanceLabel.setText(FormatUtils.getFormattedDistanceLabel(session.getDistance()));
distanceLayout.setVisibility(View.VISIBLE);
} else {
distanceLayout.setVisibility(View.GONE);
}
if (session.getActiveSteps() > 0) {
stepLabel.setText(String.valueOf(session.getActiveSteps()));
stepsLayout.setVisibility(View.VISIBLE);
} else {
stepsLayout.setVisibility(View.GONE);
}
if (session.getTotalDaySteps() > 0) {
stepTotalLabel.setText(String.valueOf(session.getTotalDaySteps()));
stepsTotalLayout.setVisibility(View.VISIBLE);
countLayout.setVisibility(View.VISIBLE);
durationLayout.setVisibility(View.VISIBLE);
} else {
stepsTotalLayout.setVisibility(View.GONE);
countLayout.setVisibility(View.GONE);
durationLayout.setVisibility(View.GONE);
}
}
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);
}
}
}