1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-19 00:19:25 +01:00

Asynchronously load and render data for today and goals widgets

This commit is contained in:
Arjan Schrijver 2024-01-31 22:21:05 +01:00
parent 2de4eb8828
commit e6d69f4e69
2 changed files with 195 additions and 156 deletions

View File

@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableString;
@ -43,6 +44,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.HealthUtils;
*/
public class DashboardGoalsWidget extends AbstractDashboardWidget {
private static final Logger LOG = LoggerFactory.getLogger(DashboardGoalsWidget.class);
private View goalsView;
private ImageView goalsChart;
public DashboardGoalsWidget() {
@ -68,11 +70,11 @@ public class DashboardGoalsWidget extends AbstractDashboardWidget {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View todayView = inflater.inflate(R.layout.dashboard_widget_goals, container, false);
goalsChart = todayView.findViewById(R.id.dashboard_goals_chart);
goalsView = inflater.inflate(R.layout.dashboard_widget_goals, container, false);
goalsChart = goalsView.findViewById(R.id.dashboard_goals_chart);
// Initialize legend
TextView legend = todayView.findViewById(R.id.dashboard_goals_legend);
TextView legend = goalsView.findViewById(R.id.dashboard_goals_legend);
SpannableString l_steps = new SpannableString("" + getString(R.string.steps));
l_steps.setSpan(new ForegroundColorSpan(color_activity), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
SpannableString l_distance = new SpannableString("" + getString(R.string.distance));
@ -86,7 +88,7 @@ public class DashboardGoalsWidget extends AbstractDashboardWidget {
fillData();
return todayView;
return goalsView;
}
@Override
@ -96,34 +98,54 @@ public class DashboardGoalsWidget extends AbstractDashboardWidget {
}
protected void fillData() {
int width = 230;
int height = 230;
int barWidth = 10;
int barMargin = (int) Math.ceil(barWidth / 2f);
goalsView.post(new Runnable() {
@Override
public void run() {
FillDataAsyncTask myAsyncTask = new FillDataAsyncTask();
myAsyncTask.execute();
}
});
}
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);
private class FillDataAsyncTask extends AsyncTask<Void, Void, Void> {
private Bitmap goalsBitmap;
paint.setColor(color_activity);
canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * HealthUtils.getStepsGoalFactor(timeTo), false, paint);
@Override
protected Void doInBackground(Void... params) {
int width = 500;
int height = 500;
int barWidth = 20;
int barMargin = (int) Math.ceil(barWidth / 2f);
barMargin += barWidth * 1.5;
paint.setColor(color_distance);
canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * HealthUtils.getDistanceGoalFactor(timeTo), false, paint);
goalsBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(goalsBitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(barWidth);
barMargin += barWidth * 1.5;
paint.setColor(color_active_time);
canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * HealthUtils.getActiveMinutesGoalFactor(timeFrom, timeTo), false, paint);
paint.setColor(color_activity);
canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * HealthUtils.getStepsGoalFactor(timeTo), false, paint);
barMargin += barWidth * 1.5;
paint.setColor(color_light_sleep);
canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * HealthUtils.getSleepMinutesGoalFactor(timeTo), false, paint);
barMargin += barWidth * 1.5;
paint.setColor(color_distance);
canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * HealthUtils.getDistanceGoalFactor(timeTo), false, paint);
goalsChart.setImageBitmap(bitmap);
barMargin += barWidth * 1.5;
paint.setColor(color_active_time);
canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * HealthUtils.getActiveMinutesGoalFactor(timeFrom, timeTo), false, paint);
barMargin += barWidth * 1.5;
paint.setColor(color_light_sleep);
canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * HealthUtils.getSleepMinutesGoalFactor(timeTo), false, paint);
return null;
}
@Override
protected void onPostExecute(Void unused) {
super.onPostExecute(unused);
goalsChart.setImageBitmap(goalsBitmap);
}
}
}

View File

@ -17,6 +17,7 @@
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableString;
@ -59,6 +60,8 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class DashboardTodayWidget extends AbstractDashboardWidget {
private static final Logger LOG = LoggerFactory.getLogger(DashboardTodayWidget.class);
private View todayView;
private boolean mode_24h;
private PieChart chart_0_12;
@ -87,7 +90,7 @@ public class DashboardTodayWidget extends AbstractDashboardWidget {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View todayView = inflater.inflate(R.layout.dashboard_widget_today, container, false);
todayView = inflater.inflate(R.layout.dashboard_widget_today, container, false);
// Determine whether to draw a single or a double chart. In case 24h mode is selected,
// use just the outer chart (chart_12_24) for all data.
@ -187,144 +190,158 @@ public class DashboardTodayWidget extends AbstractDashboardWidget {
}
protected void fillData() {
// Retrieve activity data
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
List<ActivitySample> allActivitySamples = new ArrayList<>();
List<ActivitySession> stepSessions = new ArrayList<>();
try (DBHandler dbHandler = GBApplication.acquireDB()) {
for (GBDevice dev : devices) {
if (dev.getDeviceCoordinator().supportsActivityTracking()) {
List<? extends ActivitySample> activitySamples = HealthUtils.getAllSamples(dbHandler, dev, timeFrom, timeTo);
allActivitySamples.addAll(activitySamples);
StepAnalysis stepAnalysis = new StepAnalysis();
stepSessions.addAll(stepAnalysis.calculateStepSessions(activitySamples));
}
todayView.post(new Runnable() {
@Override
public void run() {
FillDataAsyncTask myAsyncTask = new FillDataAsyncTask();
myAsyncTask.execute();
}
} catch (Exception e) {
LOG.warn("Could not retrieve activity amounts: ", e);
}
});
}
// Integrate and chronologically order various data from multiple devices
List<GeneralizedActivity> generalizedActivities = new ArrayList<>();
long midDaySecond = timeFrom + (12 * 60 * 60);
for (ActivitySample sample : allActivitySamples) {
// Handle only TYPE_NOT_WORN and TYPE_SLEEP (including variants) here
if (sample.getKind() != ActivityKind.TYPE_NOT_WORN && (sample.getKind() == ActivityKind.TYPE_NOT_MEASURED || (sample.getKind() & ActivityKind.TYPE_SLEEP) == 0))
continue;
if (generalizedActivities.size() > 0) {
GeneralizedActivity previous = generalizedActivities.get(generalizedActivities.size() - 1);
// Merge samples if the type is the same, and they are within a minute of each other
if (previous.activityKind == sample.getKind() && previous.timeTo > sample.getTimestamp() - 60) {
// But only if the resulting activity doesn't cross the midday boundary in 12h mode
if (mode_24h || sample.getTimestamp() + 60 < midDaySecond || previous.timeFrom > midDaySecond) {
generalizedActivities.get(generalizedActivities.size() - 1).timeTo = sample.getTimestamp() + 60;
continue;
private class FillDataAsyncTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
// Retrieve activity data
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
List<ActivitySample> allActivitySamples = new ArrayList<>();
List<ActivitySession> stepSessions = new ArrayList<>();
try (DBHandler dbHandler = GBApplication.acquireDB()) {
for (GBDevice dev : devices) {
if (dev.getDeviceCoordinator().supportsActivityTracking()) {
List<? extends ActivitySample> activitySamples = HealthUtils.getAllSamples(dbHandler, dev, timeFrom, timeTo);
allActivitySamples.addAll(activitySamples);
StepAnalysis stepAnalysis = new StepAnalysis();
stepSessions.addAll(stepAnalysis.calculateStepSessions(activitySamples));
}
}
} catch (Exception e) {
LOG.warn("Could not retrieve activity amounts: ", e);
}
generalizedActivities.add(new GeneralizedActivity(
sample.getKind(),
sample.getTimestamp(),
sample.getTimestamp() + 60
));
}
for (ActivitySession session : stepSessions) {
if (!mode_24h && session.getStartTime().getTime() / 1000 < midDaySecond && session.getEndTime().getTime() / 1000 > midDaySecond) {
generalizedActivities.add(new GeneralizedActivity(
session.getActivityKind(),
session.getStartTime().getTime() / 1000,
midDaySecond
));
generalizedActivities.add(new GeneralizedActivity(
session.getActivityKind(),
midDaySecond,
session.getEndTime().getTime() / 1000
));
} else {
generalizedActivities.add(new GeneralizedActivity(
session.getActivityKind(),
session.getStartTime().getTime() / 1000,
session.getEndTime().getTime() / 1000
));
}
}
Collections.sort(generalizedActivities, (o1, o2) -> (int) (o1.timeFrom - o2.timeFrom));
// Add pie slice entries
ArrayList<PieEntry> entries_0_12 = new ArrayList<>();
ArrayList<Integer> colors_0_12 = new ArrayList<>();
ArrayList<PieEntry> entries_12_24 = new ArrayList<>();
ArrayList<Integer> colors_12_24 = new ArrayList<>();
long secondIndex = timeFrom;
for (GeneralizedActivity activity : generalizedActivities) {
// FIXME: correctly merge parallel activities from multiple devices
// Skip earlier sessions
if (activity.timeFrom < secondIndex) continue;
// Use correct entries list for this part of the day
ArrayList<PieEntry> entries = entries_0_12;
ArrayList<Integer> colors = colors_0_12;
if (mode_24h || activity.timeFrom >= midDaySecond) {
entries = entries_12_24;
colors = colors_12_24;
// Integrate and chronologically order various data from multiple devices
List<GeneralizedActivity> generalizedActivities = new ArrayList<>();
long midDaySecond = timeFrom + (12 * 60 * 60);
for (ActivitySample sample : allActivitySamples) {
// Handle only TYPE_NOT_WORN and TYPE_SLEEP (including variants) here
if (sample.getKind() != ActivityKind.TYPE_NOT_WORN && (sample.getKind() == ActivityKind.TYPE_NOT_MEASURED || (sample.getKind() & ActivityKind.TYPE_SLEEP) == 0))
continue;
if (generalizedActivities.size() > 0) {
GeneralizedActivity previous = generalizedActivities.get(generalizedActivities.size() - 1);
// Merge samples if the type is the same, and they are within a minute of each other
if (previous.activityKind == sample.getKind() && previous.timeTo > sample.getTimestamp() - 60) {
// But only if the resulting activity doesn't cross the midday boundary in 12h mode
if (mode_24h || sample.getTimestamp() + 60 < midDaySecond || previous.timeFrom > midDaySecond) {
generalizedActivities.get(generalizedActivities.size() - 1).timeTo = sample.getTimestamp() + 60;
continue;
}
}
}
generalizedActivities.add(new GeneralizedActivity(
sample.getKind(),
sample.getTimestamp(),
sample.getTimestamp() + 60
));
}
// Draw inactive slice
if (activity.timeFrom > secondIndex) {
entries.add(new PieEntry(activity.timeFrom - secondIndex, "Inactive"));
colors.add(color_worn);
for (ActivitySession session : stepSessions) {
if (!mode_24h && session.getStartTime().getTime() / 1000 < midDaySecond && session.getEndTime().getTime() / 1000 > midDaySecond) {
generalizedActivities.add(new GeneralizedActivity(
session.getActivityKind(),
session.getStartTime().getTime() / 1000,
midDaySecond
));
generalizedActivities.add(new GeneralizedActivity(
session.getActivityKind(),
midDaySecond,
session.getEndTime().getTime() / 1000
));
} else {
generalizedActivities.add(new GeneralizedActivity(
session.getActivityKind(),
session.getStartTime().getTime() / 1000,
session.getEndTime().getTime() / 1000
));
}
}
// Draw activity slices
if (activity.activityKind == ActivityKind.TYPE_NOT_WORN) {
entries.add(new PieEntry(activity.timeTo - activity.timeFrom, "Not worn"));
colors.add(color_not_worn);
secondIndex = activity.timeTo;
} else if (activity.activityKind == ActivityKind.TYPE_LIGHT_SLEEP || activity.activityKind == ActivityKind.TYPE_SLEEP) {
entries.add(new PieEntry(activity.timeTo - activity.timeFrom, "Light sleep"));
colors.add(color_light_sleep);
secondIndex = activity.timeTo;
} else if (activity.activityKind == ActivityKind.TYPE_DEEP_SLEEP) {
entries.add(new PieEntry(activity.timeTo - activity.timeFrom, "Deep sleep"));
colors.add(color_deep_sleep);
secondIndex = activity.timeTo;
} else {
entries.add(new PieEntry(activity.timeTo - activity.timeFrom, "Active"));
colors.add(color_activity);
secondIndex = activity.timeTo;
}
}
// Fill remaining time until midnight
long currentTime = Calendar.getInstance().getTimeInMillis() / 1000;
if (!mode_24h && currentTime > timeFrom && currentTime < midDaySecond) {
// Fill with unknown slice up until current time
entries_0_12.add(new PieEntry(currentTime - secondIndex, "Unknown"));
colors_0_12.add(color_worn);
// Draw transparent slice for remaining time until midday
entries_0_12.add(new PieEntry(midDaySecond - currentTime, "Empty"));
colors_0_12.add(Color.TRANSPARENT);
}
if ((mode_24h || currentTime >= midDaySecond) && currentTime < timeTo) {
// Fill with unknown slice up until current time
entries_12_24.add(new PieEntry(currentTime - secondIndex, "Unknown"));
colors_12_24.add(color_worn);
// Draw transparent slice for remaining time until midnight
entries_12_24.add(new PieEntry(timeTo - currentTime, "Empty"));
colors_12_24.add(Color.TRANSPARENT);
}
Collections.sort(generalizedActivities, (o1, o2) -> (int) (o1.timeFrom - o2.timeFrom));
// Draw charts
if (!mode_24h) {
PieDataSet dataSet_0_12 = new PieDataSet(entries_0_12, "Today 0-12h");
dataSet_0_12.setSliceSpace(0f);
dataSet_0_12.setDrawValues(false);
dataSet_0_12.setColors(colors_0_12);
chart_0_12.setData(new PieData(dataSet_0_12));
chart_0_12.invalidate();
// Add pie slice entries
ArrayList<PieEntry> entries_0_12 = new ArrayList<>();
ArrayList<Integer> colors_0_12 = new ArrayList<>();
ArrayList<PieEntry> entries_12_24 = new ArrayList<>();
ArrayList<Integer> colors_12_24 = new ArrayList<>();
long secondIndex = timeFrom;
for (GeneralizedActivity activity : generalizedActivities) {
// FIXME: correctly merge parallel activities from multiple devices
// Skip earlier sessions
if (activity.timeFrom < secondIndex) continue;
// Use correct entries list for this part of the day
ArrayList<PieEntry> entries = entries_0_12;
ArrayList<Integer> colors = colors_0_12;
if (mode_24h || activity.timeFrom >= midDaySecond) {
entries = entries_12_24;
colors = colors_12_24;
}
// Draw inactive slice
if (activity.timeFrom > secondIndex) {
entries.add(new PieEntry(activity.timeFrom - secondIndex, "Inactive"));
colors.add(color_worn);
}
// Draw activity slices
if (activity.activityKind == ActivityKind.TYPE_NOT_WORN) {
entries.add(new PieEntry(activity.timeTo - activity.timeFrom, "Not worn"));
colors.add(color_not_worn);
secondIndex = activity.timeTo;
} else if (activity.activityKind == ActivityKind.TYPE_LIGHT_SLEEP || activity.activityKind == ActivityKind.TYPE_SLEEP) {
entries.add(new PieEntry(activity.timeTo - activity.timeFrom, "Light sleep"));
colors.add(color_light_sleep);
secondIndex = activity.timeTo;
} else if (activity.activityKind == ActivityKind.TYPE_DEEP_SLEEP) {
entries.add(new PieEntry(activity.timeTo - activity.timeFrom, "Deep sleep"));
colors.add(color_deep_sleep);
secondIndex = activity.timeTo;
} else {
entries.add(new PieEntry(activity.timeTo - activity.timeFrom, "Active"));
colors.add(color_activity);
secondIndex = activity.timeTo;
}
}
// Fill remaining time until midnight
long currentTime = Calendar.getInstance().getTimeInMillis() / 1000;
if (!mode_24h && currentTime > timeFrom && currentTime < midDaySecond) {
// Fill with unknown slice up until current time
entries_0_12.add(new PieEntry(currentTime - secondIndex, "Unknown"));
colors_0_12.add(color_worn);
// Draw transparent slice for remaining time until midday
entries_0_12.add(new PieEntry(midDaySecond - currentTime, "Empty"));
colors_0_12.add(Color.TRANSPARENT);
}
if ((mode_24h || currentTime >= midDaySecond) && currentTime < timeTo) {
// Fill with unknown slice up until current time
entries_12_24.add(new PieEntry(currentTime - secondIndex, "Unknown"));
colors_12_24.add(color_worn);
// Draw transparent slice for remaining time until midnight
entries_12_24.add(new PieEntry(timeTo - currentTime, "Empty"));
colors_12_24.add(Color.TRANSPARENT);
}
// Draw charts
if (!mode_24h) {
PieDataSet dataSet_0_12 = new PieDataSet(entries_0_12, "Today 0-12h");
dataSet_0_12.setSliceSpace(0f);
dataSet_0_12.setDrawValues(false);
dataSet_0_12.setColors(colors_0_12);
chart_0_12.setData(new PieData(dataSet_0_12));
chart_0_12.invalidate();
}
PieDataSet dataSet_12_24 = new PieDataSet(entries_12_24, "Today 12-24h");
dataSet_12_24.setSliceSpace(0f);
dataSet_12_24.setDrawValues(false);
dataSet_12_24.setColors(colors_12_24);
chart_12_24.setData(new PieData(dataSet_12_24));
chart_12_24.invalidate();
return null;
}
PieDataSet dataSet_12_24 = new PieDataSet(entries_12_24, "Today 12-24h");
dataSet_12_24.setSliceSpace(0f);
dataSet_12_24.setDrawValues(false);
dataSet_12_24.setColors(colors_12_24);
chart_12_24.setData(new PieData(dataSet_12_24));
chart_12_24.invalidate();
}
private class GeneralizedActivity {