mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-23 18:36:50 +01:00
Dashboard: Add monthly goals indicator and legend to calendar view
This commit is contained in:
parent
afff822ab1
commit
bad29bbeb8
@ -17,14 +17,23 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
|
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.GradientDrawable;
|
import android.graphics.drawable.GradientDrawable;
|
||||||
import android.graphics.drawable.LayerDrawable;
|
import android.graphics.drawable.LayerDrawable;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.SpannableString;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
import androidx.annotation.ColorInt;
|
||||||
@ -56,6 +65,13 @@ public class DashboardCalendarActivity extends AbstractGBActivity {
|
|||||||
private final ConcurrentHashMap<Calendar, TextView> dayCells = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<Calendar, TextView> dayCells = new ConcurrentHashMap<>();
|
||||||
private final ConcurrentHashMap<Integer, Integer> dayColors = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<Integer, Integer> dayColors = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@ColorInt private int color_unknown = Color.argb(50, 128, 128, 128);
|
||||||
|
@ColorInt private int color_0_25 = Color.argb(128, 255, 0, 0); // Red
|
||||||
|
@ColorInt private int color_25_50 = Color.argb(128, 255, 128, 0); // Orange
|
||||||
|
@ColorInt private int color_50_75 = Color.argb(128, 255, 255, 0); // Yellow
|
||||||
|
@ColorInt private int color_75_100 = Color.argb(128, 0, 128, 0); // Dark green
|
||||||
|
@ColorInt private int color_100 = Color.argb(128, 0, 255, 0); // Green
|
||||||
|
|
||||||
private boolean showAllDevices;
|
private boolean showAllDevices;
|
||||||
private Set<String> showDeviceList;
|
private Set<String> showDeviceList;
|
||||||
|
|
||||||
@ -65,6 +81,8 @@ public class DashboardCalendarActivity extends AbstractGBActivity {
|
|||||||
GridLayout calendarGrid;
|
GridLayout calendarGrid;
|
||||||
Calendar currentDay;
|
Calendar currentDay;
|
||||||
Calendar cal;
|
Calendar cal;
|
||||||
|
ImageView monthGoalsChart;
|
||||||
|
TextView monthGoalsText;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@ -72,6 +90,8 @@ public class DashboardCalendarActivity extends AbstractGBActivity {
|
|||||||
setContentView(R.layout.activity_dashboard_calendar);
|
setContentView(R.layout.activity_dashboard_calendar);
|
||||||
monthTextView = findViewById(R.id.calendar_month);
|
monthTextView = findViewById(R.id.calendar_month);
|
||||||
calendarGrid = findViewById(R.id.dashboard_calendar_grid);
|
calendarGrid = findViewById(R.id.dashboard_calendar_grid);
|
||||||
|
monthGoalsChart = findViewById(R.id.dashboard_calendar_month_goals_chart);
|
||||||
|
monthGoalsText = findViewById(R.id.dashboard_calendar_month_goals_text);
|
||||||
currentDay = Calendar.getInstance();
|
currentDay = Calendar.getInstance();
|
||||||
cal = Calendar.getInstance();
|
cal = Calendar.getInstance();
|
||||||
long receivedTimestamp = getIntent().getLongExtra(EXTRA_TIMESTAMP, 0);
|
long receivedTimestamp = getIntent().getLongExtra(EXTRA_TIMESTAMP, 0);
|
||||||
@ -142,7 +162,11 @@ public class DashboardCalendarActivity extends AbstractGBActivity {
|
|||||||
// Determine last day that should be displayed
|
// Determine last day that should be displayed
|
||||||
Calendar lastDay = (Calendar) cal.clone();
|
Calendar lastDay = (Calendar) cal.clone();
|
||||||
lastDay.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
|
lastDay.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
|
||||||
lastDay.add(Calendar.DAY_OF_MONTH, (firstDayOfWeek + 7 - lastDay.get(Calendar.DAY_OF_WEEK)) % 7);
|
int daysAfterMonth = (firstDayOfWeek + 7 - lastDay.get(Calendar.DAY_OF_WEEK)) % 7;
|
||||||
|
if (daysAfterMonth == 0 && lastDay.get(Calendar.DAY_OF_WEEK) == firstDayOfWeek) {
|
||||||
|
daysAfterMonth = 7;
|
||||||
|
}
|
||||||
|
lastDay.add(Calendar.DAY_OF_MONTH, daysAfterMonth);
|
||||||
// Add day names header
|
// Add day names header
|
||||||
SimpleDateFormat dayFormat = new SimpleDateFormat("E", Locale.getDefault());
|
SimpleDateFormat dayFormat = new SimpleDateFormat("E", Locale.getDefault());
|
||||||
Calendar weekdays = Calendar.getInstance();
|
Calendar weekdays = Calendar.getInstance();
|
||||||
@ -194,6 +218,12 @@ public class DashboardCalendarActivity extends AbstractGBActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class FillDataAsyncTask extends AsyncTask<Void, Void, Void> {
|
private class FillDataAsyncTask extends AsyncTask<Void, Void, Void> {
|
||||||
|
int amount_0_25 = 0;
|
||||||
|
int amount_25_50 = 0;
|
||||||
|
int amount_50_75 = 0;
|
||||||
|
int amount_75_100 = 0;
|
||||||
|
int amount_100 = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Void doInBackground(Void... params) {
|
||||||
for (Calendar day : dayCells.keySet()) {
|
for (Calendar day : dayCells.keySet()) {
|
||||||
@ -206,17 +236,22 @@ public class DashboardCalendarActivity extends AbstractGBActivity {
|
|||||||
float goalFactor = DashboardUtils.getStepsGoalFactor(dashboardData);
|
float goalFactor = DashboardUtils.getStepsGoalFactor(dashboardData);
|
||||||
@ColorInt int dayColor;
|
@ColorInt int dayColor;
|
||||||
if (goalFactor >= 1) {
|
if (goalFactor >= 1) {
|
||||||
dayColor = Color.argb(128, 0, 255, 0); // Green
|
dayColor = color_100;
|
||||||
|
amount_100++;
|
||||||
} else if (goalFactor >= 0.75) {
|
} else if (goalFactor >= 0.75) {
|
||||||
dayColor = Color.argb(128, 0, 128, 0); // Dark green
|
dayColor = color_75_100;
|
||||||
|
amount_75_100++;
|
||||||
} else if (goalFactor >= 0.5) {
|
} else if (goalFactor >= 0.5) {
|
||||||
dayColor = Color.argb(128, 255, 255, 0); // Yellow
|
dayColor = color_50_75;
|
||||||
} else if (goalFactor > 0.25) {
|
amount_50_75++;
|
||||||
dayColor = Color.argb(128, 255, 128, 0); // Orange
|
} else if (goalFactor >= 0.25) {
|
||||||
|
dayColor = color_25_50;
|
||||||
|
amount_25_50++;
|
||||||
} else if (goalFactor > 0) {
|
} else if (goalFactor > 0) {
|
||||||
dayColor = Color.argb(128, 255, 0, 0); // Red
|
dayColor = color_0_25;
|
||||||
|
amount_0_25++;
|
||||||
} else {
|
} else {
|
||||||
dayColor = Color.argb(50, 128, 128, 128);
|
dayColor = color_unknown;
|
||||||
}
|
}
|
||||||
dayColors.put(day.get(Calendar.DAY_OF_MONTH), dayColor);
|
dayColors.put(day.get(Calendar.DAY_OF_MONTH), dayColor);
|
||||||
}
|
}
|
||||||
@ -257,6 +292,83 @@ public class DashboardCalendarActivity extends AbstractGBActivity {
|
|||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw visual representation of this month's day goals
|
||||||
|
drawMonthGoalsLine();
|
||||||
|
|
||||||
|
// Fill legend
|
||||||
|
Resources res = getResources();
|
||||||
|
SpannableString line_100 = new SpannableString("■ 100%: " + res.getQuantityString(R.plurals.amount_of_days, amount_100, amount_100));
|
||||||
|
line_100.setSpan(new ForegroundColorSpan(color_100), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
SpannableString line_75_100 = new SpannableString("■ 75-100%: " + res.getQuantityString(R.plurals.amount_of_days, amount_75_100, amount_75_100));
|
||||||
|
line_75_100.setSpan(new ForegroundColorSpan(color_75_100), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
SpannableString line_50_75 = new SpannableString("■ 50-75%: " + res.getQuantityString(R.plurals.amount_of_days, amount_50_75, amount_50_75));
|
||||||
|
line_50_75.setSpan(new ForegroundColorSpan(color_50_75), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
SpannableString line_25_50 = new SpannableString("■ 25-50%: " + res.getQuantityString(R.plurals.amount_of_days, amount_25_50, amount_25_50));
|
||||||
|
line_25_50.setSpan(new ForegroundColorSpan(color_25_50), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
SpannableString line_0_25 = new SpannableString("■ 0-25%: " + res.getQuantityString(R.plurals.amount_of_days, amount_0_25, amount_0_25));
|
||||||
|
line_0_25.setSpan(new ForegroundColorSpan(color_0_25), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||||
|
monthGoalsText.setText(builder.append(line_100).append("\n").append(line_75_100).append("\n").append(line_50_75).append("\n").append(line_25_50).append("\n").append(line_0_25));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawMonthGoalsLine() {
|
||||||
|
int monthMaxDays = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||||||
|
int amountOfDays = amount_0_25 + amount_25_50 + amount_50_75 + amount_75_100 + amount_100;
|
||||||
|
int width = 700;
|
||||||
|
int height = 40;
|
||||||
|
int totalDrawWidth = width - height / 2;
|
||||||
|
float drawWidth = totalDrawWidth * ((float) amountOfDays / monthMaxDays);
|
||||||
|
|
||||||
|
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(height);
|
||||||
|
paint.setColor(color_unknown);
|
||||||
|
canvas.drawLine(height / 2, height / 2, totalDrawWidth, height / 2, paint);
|
||||||
|
|
||||||
|
// 0-25%
|
||||||
|
if (amount_0_25 > 0) {
|
||||||
|
paint.setColor(color_0_25);
|
||||||
|
canvas.drawLine(height / 2, height / 2, drawWidth, height / 2, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 25-50%
|
||||||
|
if (amount_25_50 > 0) {
|
||||||
|
paint.setColor(color_25_50);
|
||||||
|
float barDays = amount_25_50 + amount_50_75 + amount_75_100 + amount_100;
|
||||||
|
float barFraction = barDays / amountOfDays;
|
||||||
|
canvas.drawLine(height / 2, height / 2, drawWidth * barFraction, height / 2, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 50-75%
|
||||||
|
if (amount_50_75 > 0) {
|
||||||
|
paint.setColor(color_50_75);
|
||||||
|
float barDays = amount_50_75 + amount_75_100 + amount_100;
|
||||||
|
float barFraction = barDays / amountOfDays;
|
||||||
|
canvas.drawLine(height / 2, height / 2, drawWidth * barFraction, height / 2, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 75-100%
|
||||||
|
if (amount_75_100 > 0) {
|
||||||
|
paint.setColor(color_75_100);
|
||||||
|
float barDays = amount_75_100 + amount_100;
|
||||||
|
float barFraction = barDays / amountOfDays;
|
||||||
|
canvas.drawLine(height / 2, height / 2, drawWidth * barFraction, height / 2, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 100%
|
||||||
|
if (amount_100 > 0) {
|
||||||
|
paint.setColor(color_100);
|
||||||
|
float barDays = amount_100;
|
||||||
|
float barFraction = barDays / amountOfDays;
|
||||||
|
canvas.drawLine(height / 2, height / 2, drawWidth * barFraction, height / 2, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
monthGoalsChart.setImageBitmap(bitmap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,78 @@
|
|||||||
<android.widget.LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".activities.dashboard.DashboardCalendarActivity">
|
tools:context=".activities.dashboard.DashboardCalendarActivity">
|
||||||
|
|
||||||
<RelativeLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:orientation="vertical">
|
||||||
android:orientation="horizontal">
|
|
||||||
<Button
|
<RelativeLayout
|
||||||
android:id="@+id/arrow_left"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minWidth="0dp"
|
android:layout_margin="8dp"
|
||||||
android:text="\u003C"
|
android:orientation="horizontal">
|
||||||
android:textStyle="bold"
|
<Button
|
||||||
android:textSize="20sp"
|
android:id="@+id/arrow_left"
|
||||||
android:layout_alignParentLeft="true" />
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minWidth="0dp"
|
||||||
|
android:text="\u003C"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:layout_alignParentLeft="true" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/calendar_month"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/calendar_month"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="25sp"
|
||||||
|
android:layout_centerInParent="true" />
|
||||||
|
<Button
|
||||||
|
android:id="@+id/arrow_right"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minWidth="0dp"
|
||||||
|
android:text="\u003E"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:layout_alignParentRight="true" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<androidx.gridlayout.widget.GridLayout
|
||||||
|
android:id="@+id/dashboard_calendar_grid"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:layout_marginBottom="15dp"
|
||||||
|
app:columnCount="7" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/calendar_month"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/calendar_month"
|
android:layout_margin="8dp"
|
||||||
android:textStyle="bold"
|
|
||||||
android:textSize="25sp"
|
|
||||||
android:layout_centerInParent="true" />
|
|
||||||
<Button
|
|
||||||
android:id="@+id/arrow_right"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:minWidth="0dp"
|
|
||||||
android:text="\u003E"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:layout_alignParentRight="true" />
|
android:text="@string/dashboard_calendar_month_goals_reached_title" />
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<androidx.gridlayout.widget.GridLayout
|
<ImageView
|
||||||
android:id="@+id/dashboard_calendar_grid"
|
android:id="@+id/dashboard_calendar_month_goals_chart"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="15dp"
|
android:layout_margin="8dp"
|
||||||
app:columnCount="7" />
|
android:scaleType="fitCenter"
|
||||||
|
android:adjustViewBounds="true" />
|
||||||
|
|
||||||
</android.widget.LinearLayout>
|
<TextView
|
||||||
|
android:id="@+id/dashboard_calendar_month_goals_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
@ -2840,6 +2840,11 @@
|
|||||||
<string name="pref_dashboard_select_devices_summary">Combine activity data from specific devices for the totals on the dashboard</string>
|
<string name="pref_dashboard_select_devices_summary">Combine activity data from specific devices for the totals on the dashboard</string>
|
||||||
<string name="pref_dashboard_widget_today_hr_interval_title">Heart rate interval</string>
|
<string name="pref_dashboard_widget_today_hr_interval_title">Heart rate interval</string>
|
||||||
<string name="pref_dashboard_widget_today_hr_interval_summary">The amount of minutes the chart shows \'worn\' after each successful heart rate measurement</string>
|
<string name="pref_dashboard_widget_today_hr_interval_summary">The amount of minutes the chart shows \'worn\' after each successful heart rate measurement</string>
|
||||||
|
<plurals name="amount_of_days">
|
||||||
|
<item quantity="one">%d day</item>
|
||||||
|
<item quantity="other">%d days</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="dashboard_calendar_month_goals_reached_title">% of steps goal reached</string>
|
||||||
<string name="pref_auto_reply_calls_summary">The phone will automatically pick-up incoming phonecalls</string>
|
<string name="pref_auto_reply_calls_summary">The phone will automatically pick-up incoming phonecalls</string>
|
||||||
<string name="pref_auto_reply_calls_title">Automatically answer phone calls</string>
|
<string name="pref_auto_reply_calls_title">Automatically answer phone calls</string>
|
||||||
<string name="pref_auto_reply_calls_delay_summary">Number of seconds after which the call is automatically picked up</string>
|
<string name="pref_auto_reply_calls_delay_summary">Number of seconds after which the call is automatically picked up</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user