1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-30 22:12:55 +01:00

Add nested tabs to sleep and steps + steps refactor

This commit is contained in:
a0z 2024-08-22 19:31:51 +02:00
parent b01169a307
commit 4ea933b53d
25 changed files with 1202 additions and 195 deletions

View File

@ -122,7 +122,7 @@ public class GBApplication extends Application {
private static SharedPreferences sharedPrefs;
private static final String PREFS_VERSION = "shared_preferences_version";
//if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
private static final int CURRENT_PREFS_VERSION = 32;
private static final int CURRENT_PREFS_VERSION = 33;
private static final LimitedQueue<Integer, String> mIDSenderLookup = new LimitedQueue<>(16);
private static GBPrefs prefs;
@ -1514,7 +1514,7 @@ public class GBApplication extends Application {
}
if (oldVersion < 32) {
// Add the new HRV Status tab to all devices
// Add the new body energy tab to all devices
try (DBHandler db = acquireDB()) {
final DaoSession daoSession = db.getDaoSession();
final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
@ -1543,6 +1543,36 @@ public class GBApplication extends Application {
}
}
if (oldVersion < 33) {
// Remove sleep week tab from all devices, since it does not exist anymore
try (DBHandler db = acquireDB()) {
final DaoSession daoSession = db.getDaoSession();
final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
for (final Device dbDevice : activeDevices) {
final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
final String chartsTabsValue = deviceSharedPrefs.getString("charts_tabs", null);
if (chartsTabsValue == null) {
continue;
}
final String newPrefValue;
if (!StringUtils.isBlank(chartsTabsValue)) {
newPrefValue = chartsTabsValue.replace(",sleepweek", "");
} else {
newPrefValue = chartsTabsValue;
}
final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue);
deviceSharedPrefsEdit.apply();
}
} catch (Exception e) {
Log.w(TAG, "error acquiring DB lock");
}
}
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
editor.apply();
}

View File

@ -45,7 +45,7 @@ public abstract class AbstractGBFragment extends Fragment {
* @see #isVisibleInActivity()
* @see #onMadeVisibleInActivity()
*/
protected void onMadeInvisibleInActivity() {
public void onMadeInvisibleInActivity() {
mVisibleInActivity = false;
}

View File

@ -20,15 +20,11 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.fragment.app.FragmentManager;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.components.LimitLine;
@ -62,7 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
public abstract class AbstractWeekChartFragment extends AbstractActivityChartFragment<AbstractWeekChartFragment.MyChartsData> {
protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class);
protected final int TOTAL_DAYS = getRangeDays();
protected int TOTAL_DAYS = getRangeDays();
protected int TOTAL_DAYS_FOR_AVERAGE = 0;
protected Locale mLocale;
@ -73,7 +69,6 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
protected TextView mBalanceView;
private int mOffsetHours = getOffsetHours();
ImageView stepsStreaksButton;
@Override
protected MyChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
@ -101,20 +96,6 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter());
mBalanceView.setText(mcd.getWeekBeforeData().getBalanceMessage());
//disable the streak FAB once we move away from today
Calendar day = Calendar.getInstance();
day.setTime(getChartsHost().getEndDate());
if (DateUtils.isToday(day.getTimeInMillis()) && enableStepStreaksButton()){
stepsStreaksButton.setVisibility(View.VISIBLE);
}else
{
stepsStreaksButton.setVisibility(View.GONE);
}
}
protected boolean enableStepStreaksButton(){
return this.getClass().getSimpleName().equals("WeekStepsChartFragment");
}
@Override
@ -125,7 +106,7 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
}
protected String getWeeksChartsLabel(Calendar day){
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
if (TOTAL_DAYS > 7) {
//month, show day date
return String.valueOf(day.get(Calendar.DAY_OF_MONTH));
}
@ -253,18 +234,6 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
setupWeekChart();
setupTodayPieChart();
stepsStreaksButton = rootView.findViewById(R.id.steps_streaks_button);
if (enableStepStreaksButton()) {
stepsStreaksButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager fm = getActivity().getSupportFragmentManager();
StepStreaksDashboard stepStreaksDashboard = StepStreaksDashboard.newInstance(getGoal(), getChartsHost().getDevice());
stepStreaksDashboard.show(fm, "steps_streaks_dashboard");
}
});
}
// refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh();

View File

@ -85,7 +85,6 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
}
if (!coordinator.supportsSleepMeasurement()) {
tabList.remove("sleep");
tabList.remove("sleepweek");
}
if (!coordinator.supportsStressMeasurement()) {
tabList.remove("stress");
@ -143,9 +142,7 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
case "activitylist":
return new ActivityListingChartFragment();
case "sleep":
return new SleepChartFragment();
case "sleepweek":
return new WeekSleepChartFragment();
return new SleepCollectionFragment();
case "hrvstatus":
return new HRVStatusFragment();
case "bodyenergy":
@ -155,7 +152,7 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
case "pai":
return new PaiChartFragment();
case "stepsweek":
return new WeekStepsChartFragment();
return new StepsCollectionFragment();
case "speedzones":
return new SpeedZonesFragment();
case "livestats":
@ -183,14 +180,6 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
}
}
public String getStepsTitle() {
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
return getString(R.string.weekstepschart_steps_a_month);
} else {
return getString(R.string.weekstepschart_steps_a_week);
}
}
@Override
public CharSequence getPageTitle(int position) {
switch (enabledTabsList.get(position)) {
@ -200,8 +189,6 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
return getString(R.string.charts_activity_list);
case "sleep":
return getString(R.string.sleepchart_your_sleep);
case "sleepweek":
return getSleepTitle();
case "hrvstatus":
return getString(R.string.pref_header_hrv_status);
case "bodyenergy":
@ -211,7 +198,7 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
case "pai":
return getString(getDevice().getDeviceCoordinator().getPaiName());
case "stepsweek":
return getStepsTitle();
return getString(R.string.steps);
case "speedzones":
return getString(R.string.stats_title);
case "livestats":

View File

@ -30,7 +30,6 @@ public class AngledLabelsChartRenderer extends BarChartRenderer {
@Override
public void drawValue(Canvas canvas, String valueText, float x, float y, int color) {
mValuePaint.setColor(color);
//move position to the center of bar
x = x + 8;

View File

@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import static java.util.stream.Collectors.toCollection;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;

View File

@ -382,7 +382,7 @@ public class LiveActivityFragment extends AbstractActivityChartFragment<ChartsDa
}
@Override
protected void onMadeInvisibleInActivity() {
public void onMadeInvisibleInActivity() {
enableRealtimeTracking(false);
super.onMadeInvisibleInActivity();
}

View File

@ -26,12 +26,9 @@ import android.text.method.ScrollingMovementMethod;
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.constraintlayout.widget.ConstraintLayout;
import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.LineChart;
@ -41,7 +38,6 @@ import com.github.mikephil.charting.data.LineData;
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.formatter.ValueFormatter;
import org.apache.commons.lang3.tuple.Triple;
import org.slf4j.Logger;
@ -239,7 +235,7 @@ public class SleepChartFragment extends AbstractActivityChartFragment<SleepChart
highestHrText.setText(String.valueOf(heartRateMax != 0 ? heartRateMax : "-"));
movementIntensityText.setText(intensityTotal != 0 ? new DecimalFormat("###.#").format(intensityTotal) : "-");
mSleepAmountChart.setHoleRadius(75);
mSleepAmountChart.setHoleRadius(85);
mSleepAmountChart.setDrawEntryLabels(false);
mSleepAmountChart.getLegend().setEnabled(false);

View File

@ -0,0 +1,87 @@
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
import nodomain.freeyourgadget.gadgetbridge.adapter.SleepFragmentAdapter;
public class SleepCollectionFragment extends AbstractGBFragment {
protected SleepFragmentAdapter nestedFragmentsAdapter;
protected ViewPager2 viewPager;
private int last_position = 0;
@Override
protected void onMadeVisibleInActivity() {
super.onMadeVisibleInActivity();
nestedFragmentsAdapter.updateFragments(last_position);
}
@Override
public void onMadeInvisibleInActivity() {
if (nestedFragmentsAdapter != null) {
nestedFragmentsAdapter.updateFragments(-1);
}
}
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_nested_tabs, container, false);
nestedFragmentsAdapter = new SleepFragmentAdapter(this, getChildFragmentManager());
viewPager = rootView.findViewById(R.id.pager);
viewPager.setAdapter(nestedFragmentsAdapter);
viewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
viewPager.setUserInputEnabled(false);
viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
last_position = position;
viewPager.post(new Runnable() {
@Override
public void run() {
if (isVisibleInActivity()) {
nestedFragmentsAdapter.updateFragments(position);
}
}
});
}
});
return rootView;
}
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
TabLayout tabLayout = view.findViewById(R.id.tab_layout);
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
switch (position) {
case 0:
tab.setText(getString(R.string.calendar_day));
break;
case 1:
tab.setText(getString(R.string.calendar_week));
break;
case 2:
tab.setText(getString(R.string.calendar_month));
break;
}
}).attach();
}
@Nullable
@Override
protected CharSequence getTitle() {
return null;
}
}

View File

@ -0,0 +1,87 @@
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
import nodomain.freeyourgadget.gadgetbridge.adapter.StepsFragmentAdapter;
public class StepsCollectionFragment extends AbstractGBFragment {
protected StepsFragmentAdapter nestedFragmentsAdapter;
protected ViewPager2 viewPager;
private int last_position = 0;
@Override
protected void onMadeVisibleInActivity() {
super.onMadeVisibleInActivity();
nestedFragmentsAdapter.updateFragments(last_position);
}
@Override
public void onMadeInvisibleInActivity() {
if (nestedFragmentsAdapter != null) {
nestedFragmentsAdapter.updateFragments(-1);
}
}
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_nested_tabs, container, false);
nestedFragmentsAdapter = new StepsFragmentAdapter(this, getChildFragmentManager());
viewPager = rootView.findViewById(R.id.pager);
viewPager.setAdapter(nestedFragmentsAdapter);
viewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
viewPager.setUserInputEnabled(false);
viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
last_position = position;
viewPager.post(new Runnable() {
@Override
public void run() {
if (isVisibleInActivity()) {
nestedFragmentsAdapter.updateFragments(position);
}
}
});
}
});
return rootView;
}
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
TabLayout tabLayout = view.findViewById(R.id.tab_layout);
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
switch (position) {
case 0:
tab.setText(getString(R.string.calendar_day));
break;
case 1:
tab.setText(getString(R.string.calendar_week));
break;
case 2:
tab.setText(getString(R.string.calendar_month));
break;
}
}).attach();
}
@Nullable
@Override
protected CharSequence getTitle() {
return null;
}
}

View File

@ -0,0 +1,173 @@
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
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.TextView;
import androidx.annotation.ColorInt;
import androidx.fragment.app.FragmentManager;
import com.github.mikephil.charting.charts.Chart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
public class StepsDailyFragment extends StepsFragment {
protected static final Logger LOG = LoggerFactory.getLogger(BodyEnergyFragment.class);
private TextView mDateView;
private ImageView stepsGauge;
private TextView steps;
private TextView distance;
ImageView stepsStreaksButton;
protected int STEPS_GOAL;
protected int TOTAL_DAYS = 1;
@Override
public void onResume() {
super.onResume();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_steps, container, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
getChartsHost().enableSwipeRefresh(scrollY == 0);
});
}
mDateView = rootView.findViewById(R.id.steps_date_view);
stepsGauge = rootView.findViewById(R.id.steps_gauge);
steps = rootView.findViewById(R.id.steps_count);
distance = rootView.findViewById(R.id.steps_distance);
STEPS_GOAL = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
refresh();
stepsStreaksButton = rootView.findViewById(R.id.steps_streaks_button);
stepsStreaksButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager fm = getActivity().getSupportFragmentManager();
StepStreaksDashboard stepStreaksDashboard = StepStreaksDashboard.newInstance(STEPS_GOAL, getChartsHost().getDevice());
stepStreaksDashboard.show(fm, "steps_streaks_dashboard");
}
});
return rootView;
}
@Override
public String getTitle() {
return getString(R.string.steps);
}
@Override
protected StepsDailyFragment.StepsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
Calendar day = Calendar.getInstance();
day.setTime(chartsHost.getEndDate());
String formattedDate = new SimpleDateFormat("E, MMM dd").format(chartsHost.getEndDate());
mDateView.setText(formattedDate);
List<StepsDay> stepsDaysData = getMyStepsDaysData(db, day, device);
return new StepsDailyFragment.StepsData(stepsDaysData);
}
@Override
protected void updateChartsnUIThread(StepsDailyFragment.StepsData stepsData) {
final int width = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
300,
GBApplication.getContext().getResources().getDisplayMetrics()
);
stepsGauge.setImageBitmap(drawGauge(
width,
width / 15,
getResources().getColor(R.color.steps_color),
(int) stepsData.todayStepsDay.steps,
STEPS_GOAL
));
steps.setText(String.format(String.valueOf(stepsData.todayStepsDay.steps)));
distance.setText(getString(R.string.steps_distance_unit, stepsData.todayStepsDay.distance));
}
@Override
protected void renderCharts() {}
protected void setupLegend(Chart<?> chart) {}
Bitmap drawGauge(int width, int barWidth, @ColorInt int filledColor, int value, int maxValue) {
int height = width;
int barMargin = (int) Math.ceil(barWidth / 2f);
float filledFactor = (float) value / maxValue;
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);
paint.setColor(getResources().getColor(R.color.gauge_line_color));
canvas.drawArc(
barMargin,
barMargin,
width - barMargin,
width - barMargin,
90,
360,
false,
paint);
paint.setStrokeWidth(barWidth);
paint.setColor(filledColor);
canvas.drawArc(
barMargin,
barMargin,
width - barMargin,
height - barMargin,
270,
360 * filledFactor,
false,
paint
);
Paint textPaint = new Paint();
textPaint.setColor(TEXT_COLOR);
float textPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.06f, requireContext().getResources().getDisplayMetrics());
textPaint.setTextSize(textPixels);
textPaint.setTextAlign(Paint.Align.CENTER);
int yPos = (int) ((float) height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
canvas.drawText(String.valueOf(value), width / 2f, yPos, textPaint);
Paint textLowerPaint = new Paint();
textLowerPaint.setColor(TEXT_COLOR);
textLowerPaint.setTextAlign(Paint.Align.CENTER);
float textLowerPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.025f, requireContext().getResources().getDisplayMetrics());
textLowerPaint.setTextSize(textLowerPixels);
int yPosLowerText = (int) ((float) height / 2 - textPaint.ascent()) ;
canvas.drawText(String.valueOf(maxValue), width / 2f, yPosLowerText, textLowerPaint);
return bitmap;
}
}

View File

@ -0,0 +1,153 @@
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.app.Activity;
import android.os.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
abstract class StepsFragment extends AbstractChartFragment<StepsDailyFragment.StepsData> {
protected static final Logger LOG = LoggerFactory.getLogger(StepsDailyFragment.class);
protected int CHART_TEXT_COLOR;
protected int TEXT_COLOR;
protected int BACKGROUND_COLOR;
protected int DESCRIPTION_COLOR;
protected int TOTAL_DAYS = 1;
@Override
public String getTitle() {
return getString(R.string.steps);
}
@Override
protected void init() {
TEXT_COLOR = GBApplication.getTextColor(requireContext());
CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
BACKGROUND_COLOR = GBApplication.getBackgroundColor(getContext());
DESCRIPTION_COLOR = GBApplication.getTextColor(getContext());
CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(getContext());
}
protected List<StepsFragment.StepsDay> getMyStepsDaysData(DBHandler db, Calendar day, GBDevice device) {
day = (Calendar) day.clone(); // do not modify the caller's argument
day.add(Calendar.DATE, -TOTAL_DAYS + 1);
List<StepsDay> daysData = new ArrayList<>();;
for (int counter = 0; counter < TOTAL_DAYS; counter++) {
long totalSteps = 0;
ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
for (ActivityAmount amount : amounts.getAmounts()) {
if (amount.getTotalSteps() > 0) {
totalSteps += amount.getTotalSteps();
}
}
double distance = 0;
if (totalSteps > 0) {
ActivityUser activityUser = new ActivityUser();
int stepLength = activityUser.getStepLengthCm();
distance = ((stepLength * 1.0 / 100) * totalSteps) / 1000;
}
Calendar d = (Calendar) day.clone();
daysData.add(new StepsDay(d, totalSteps, distance));
day.add(Calendar.DATE, 1);
}
return daysData;
}
protected ActivityAmounts getActivityAmountsForDay(DBHandler db, Calendar day, GBDevice device) {
LimitedQueue<Integer, ActivityAmounts> activityAmountCache = null;
ActivityAmounts amounts = null;
Activity activity = getActivity();
int key = (int) (day.getTimeInMillis() / 1000);
if (activity != null) {
activityAmountCache = ((ActivityChartsActivity) activity).mActivityAmountCache;
amounts = activityAmountCache.lookup(key);
}
if (amounts == null) {
ActivityAnalysis analysis = new ActivityAnalysis();
amounts = analysis.calculateActivityAmounts(getSamplesOfDay(db, day, 0, device));
if (activityAmountCache != null) {
activityAmountCache.add(key, amounts);
}
}
return amounts;
}
private List<? extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, int offsetHours, GBDevice device) {
int startTs;
int endTs;
day = (Calendar) day.clone(); // do not modify the caller's argument
day.set(Calendar.HOUR_OF_DAY, 0);
day.set(Calendar.MINUTE, 0);
day.set(Calendar.SECOND, 0);
day.add(Calendar.HOUR, offsetHours);
startTs = (int) (day.getTimeInMillis() / 1000);
endTs = startTs + 24 * 60 * 60 - 1;
return getSamples(db, device, startTs, endTs);
}
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
SampleProvider<? extends ActivitySample> provider = device.getDeviceCoordinator().getSampleProvider(device, db.getDaoSession());
return provider.getAllActivitySamples(tsFrom, tsTo);
}
protected static class StepsDay {
public long steps;
public double distance;
public Calendar day;
protected StepsDay(Calendar day, long steps, double distance) {
this.steps = steps;
this.distance = distance;
this.day = day;
}
}
protected static class StepsData extends ChartsData {
List<StepsDay> days;
long stepsDailyAvg = 0;
double distanceDailyAvg = 0;
long totalSteps = 0;
double totalDistance = 0;
StepsDay todayStepsDay;
protected StepsData(List<StepsDay> days) {
this.days = days;
int daysCounter = 0;
for(StepsDay day : days) {
this.totalSteps += day.steps;
this.totalDistance += day.distance;
if (day.steps > 0) {
daysCounter++;
}
}
if (daysCounter > 0) {
this.stepsDailyAvg = this.totalSteps / daysCounter;
this.distanceDailyAvg = this.totalDistance / daysCounter;
}
this.todayStepsDay = days.get(days.size() - 1);
}
}
}

View File

@ -0,0 +1,200 @@
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.components.LimitLine;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.formatter.ValueFormatter;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.DayStartHourSetting;
public class StepsPeriodFragment extends StepsFragment {
protected static final Logger LOG = LoggerFactory.getLogger(BodyEnergyFragment.class);
private TextView mDateView;
private TextView stepsAvg;
private TextView stepsTotal;
private TextView distanceAvg;
private TextView distanceTotal;
private BarChart stepsChart;
protected int CHART_TEXT_COLOR;
protected int TEXT_COLOR;
protected int STEPS_GOAL;
protected int BACKGROUND_COLOR;
protected int DESCRIPTION_COLOR;
public static StepsPeriodFragment newInstance ( int totalDays ) {
StepsPeriodFragment fragmentFirst = new StepsPeriodFragment();
Bundle args = new Bundle();
args.putInt("totalDays", totalDays);
fragmentFirst.setArguments(args);
return fragmentFirst;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TOTAL_DAYS = getArguments() != null ? getArguments().getInt("totalDays") : 0;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_steps_period, container, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
getChartsHost().enableSwipeRefresh(scrollY == 0);
});
}
mDateView = rootView.findViewById(R.id.steps_date_view);
stepsChart = rootView.findViewById(R.id.steps_chart);
stepsAvg = rootView.findViewById(R.id.steps_avg);
distanceAvg = rootView.findViewById(R.id.distance_avg);
stepsTotal = rootView.findViewById(R.id.steps_total);
distanceTotal = rootView.findViewById(R.id.distance_total);
STEPS_GOAL = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
setupStepsChart();
refresh();
return rootView;
}
protected void setupStepsChart() {
stepsChart.getDescription().setEnabled(false);
if (TOTAL_DAYS <= 7) {
stepsChart.setTouchEnabled(false);
stepsChart.setPinchZoom(false);
}
stepsChart.setDoubleTapToZoomEnabled(false);
stepsChart.getLegend().setEnabled(false);
final XAxis xAxisBottom = stepsChart.getXAxis();
xAxisBottom.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxisBottom.setDrawLabels(true);
xAxisBottom.setDrawGridLines(false);
xAxisBottom.setEnabled(true);
xAxisBottom.setDrawLimitLinesBehindData(true);
xAxisBottom.setTextColor(CHART_TEXT_COLOR);
final YAxis yAxisLeft = stepsChart.getAxisLeft();
yAxisLeft.setDrawGridLines(true);
yAxisLeft.setDrawTopYLabelEntry(true);
yAxisLeft.setEnabled(true);
yAxisLeft.setTextColor(CHART_TEXT_COLOR);
yAxisLeft.setAxisMinimum(0f);
LimitLine target = new LimitLine(STEPS_GOAL);
yAxisLeft.addLimitLine(target);
final YAxis yAxisRight = stepsChart.getAxisRight();
yAxisRight.setEnabled(true);
yAxisRight.setDrawLabels(false);
yAxisRight.setDrawGridLines(false);
yAxisRight.setDrawAxisLine(true);
}
@Override
public String getTitle() {
return getString(R.string.steps);
}
@Override
protected void init() {
TEXT_COLOR = GBApplication.getTextColor(requireContext());
CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
BACKGROUND_COLOR = GBApplication.getBackgroundColor(getContext());
DESCRIPTION_COLOR = GBApplication.getTextColor(getContext());
CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(getContext());
}
@Override
protected StepsFragment.StepsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
Calendar day = Calendar.getInstance();
Date to = new Date((long) this.getTSEnd() * 1000);
Date from = DateUtils.addDays(to,-(TOTAL_DAYS - 1));
String toFormattedDate = new SimpleDateFormat("E, MMM dd").format(to);
String fromFormattedDate = new SimpleDateFormat("E, MMM dd").format(from);
mDateView.setText(fromFormattedDate + " - " + toFormattedDate);
day.setTime(to);
List<StepsDay> stepsDaysData = getMyStepsDaysData(db, day, device);
return new StepsFragment.StepsData(stepsDaysData);
}
@Override
protected void updateChartsnUIThread(StepsPeriodFragment.StepsData stepsData) {
stepsChart.setData(null);
List<BarEntry> entries = new ArrayList<>();
int counter = 0;
for(StepsDay day : stepsData.days) {
entries.add(new BarEntry(counter, day.steps));
counter++;
}
BarDataSet set = new BarDataSet(entries, "Steps");
set.setDrawValues(true);
set.setColors(getResources().getColor(R.color.steps_color));
final XAxis x = stepsChart.getXAxis();
x.setValueFormatter(getStepsChartDayValueFormatter(stepsData));
BarData barData = new BarData(set);
barData.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false);
barData.setValueTextSize(10f);
if (TOTAL_DAYS > 7) {
stepsChart.setRenderer(new AngledLabelsChartRenderer(stepsChart, stepsChart.getAnimator(), stepsChart.getViewPortHandler()));
}
stepsChart.setData(barData);
stepsAvg.setText(String.format(String.valueOf(stepsData.stepsDailyAvg)));
distanceAvg.setText(getString(R.string.steps_distance_unit, stepsData.distanceDailyAvg));
stepsTotal.setText(String.format(String.valueOf(stepsData.totalSteps)));
distanceTotal.setText(getString(R.string.steps_distance_unit, stepsData.totalDistance));
}
ValueFormatter getStepsChartDayValueFormatter(StepsPeriodFragment.StepsData stepsData) {
return new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
StepsPeriodFragment.StepsDay day = stepsData.days.get((int) value);
String pattern = TOTAL_DAYS > 7 ? "dd" : "EEE";
SimpleDateFormat formatLetterDay = new SimpleDateFormat(pattern, Locale.getDefault());
return formatLetterDay.format(new Date(day.day.getTimeInMillis()));
}
};
}
@Override
protected void renderCharts() {
stepsChart.invalidate();
}
protected void setupLegend(Chart<?> chart) {}
}

View File

@ -23,6 +23,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.LegendEntry;
@ -58,6 +59,20 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
private TextView sleepDatesText;
private MySleepWeeklyData mySleepWeeklyData;
public static WeekSleepChartFragment newInstance ( int totalDays ) {
WeekSleepChartFragment fragmentFirst = new WeekSleepChartFragment();
Bundle args = new Bundle();
args.putInt("totalDays", totalDays);
fragmentFirst.setArguments(args);
return fragmentFirst;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TOTAL_DAYS = getArguments() != null ? getArguments().getInt("totalDays") : 0;
}
private MySleepWeeklyData getMySleepWeeklyData(DBHandler db, Calendar day, GBDevice device) {
day = (Calendar) day.clone(); // do not modify the caller's argument
day.add(Calendar.DATE, -TOTAL_DAYS + 1);
@ -84,12 +99,11 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
return new MySleepWeeklyData(remWeeklyTotal, deepWeeklyTotal, lightWeeklyTotal);
}
@Override
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mLocale = getResources().getConfiguration().locale;
View rootView = inflater.inflate(R.layout.fragment_weeksleep_chart, container, false);
final int goal = getGoal();
if (goal >= 0) {
mTargetValue = goal;
@ -114,17 +128,17 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
protected void updateChartsnUIThread(MyChartsData mcd) {
setupLegend(mWeekChart);
//set custom renderer for 30days bar charts
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
if (TOTAL_DAYS > 7) {
mWeekChart.setRenderer(new AngledLabelsChartRenderer(mWeekChart, mWeekChart.getAnimator(), mWeekChart.getViewPortHandler()));
} else {
mWeekChart.setScaleEnabled(false);
mWeekChart.setTouchEnabled(false);
}
mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
mWeekChart.setData(mcd.getWeekBeforeData().getData());
mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter());
mWeekChart.getBarData().setValueTextSize(14f);
mWeekChart.setScaleEnabled(false);
mWeekChart.setTouchEnabled(false);
mWeekChart.getBarData().setValueTextSize(10f);
if (TOTAL_DAYS_FOR_AVERAGE > 0) {
float avgDeep = Math.abs(this.mySleepWeeklyData.getTotalDeep() / TOTAL_DAYS_FOR_AVERAGE);

View File

@ -1,125 +0,0 @@
/* Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, José Rebelo, Pavel Elagin, 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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.formatter.ValueFormatter;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
public class WeekStepsChartFragment extends AbstractWeekChartFragment {
@Override
public String getTitle() {
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
return getString(R.string.weekstepschart_steps_a_month);
}
else{
return getString(R.string.weekstepschart_steps_a_week);
}
}
@Override
String getPieDescription(int targetValue) {
return getString(R.string.weeksteps_today_steps_description, String.valueOf(targetValue));
}
@Override
int getGoal() {
return GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
}
@Override
int getOffsetHours() {
return 0;
}
@Override
float[] getTotalsForActivityAmounts(ActivityAmounts activityAmounts) {
long totalSteps = 0;
for (ActivityAmount amount : activityAmounts.getAmounts()) {
totalSteps += amount.getTotalSteps();
}
return new float[]{totalSteps};
}
@Override
protected long calculateBalance(ActivityAmounts activityAmounts) {
long balance = 0;
for (ActivityAmount amount : activityAmounts.getAmounts()) {
balance += amount.getTotalSteps();
}
return balance;
}
@Override
protected String formatPieValue(long value) {
return String.valueOf(value);
}
@Override
String[] getPieLabels() {
return new String[]{""};
}
@Override
ValueFormatter getPieValueFormatter() {
return null;
}
@Override
ValueFormatter getBarValueFormatter() {
return null;
}
@Override
ValueFormatter getYAxisFormatter() {
return null;
}
@Override
int[] getColors() {
return new int[]{akActivity.color};
}
@Override
protected void setupLegend(Chart<?> chart) {
// no legend here, it is all about the steps here
chart.getLegend().setEnabled(false);
}
@Override
protected String getBalanceMessage(long balance, int targetValue) {
if (balance > 0) {
final long totalBalance = balance - ((long)targetValue * TOTAL_DAYS_FOR_AVERAGE);
if (totalBalance > 0)
return getString(R.string.overstep, Math.abs(totalBalance));
else
return getString(R.string.lack_of_step, Math.abs(totalBalance));
} else
return getString(R.string.no_data);
}
@Override
String getAverage(float value) {
return String.format("%.0f", value);
}
}

View File

@ -0,0 +1,36 @@
package nodomain.freeyourgadget.gadgetbridge.adapter;
import androidx.fragment.app.FragmentManager;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import java.util.List;
import java.util.stream.Collectors;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
abstract class NestedFragmentAdapter extends FragmentStateAdapter {
protected FragmentManager fragmentManager;
public NestedFragmentAdapter(AbstractGBFragment fragment, FragmentManager childFragmentManager) {
super(fragment);
fragmentManager = childFragmentManager;
}
@Override
public int getItemCount() {
return 3;
}
public void updateFragments(int position) {
List<AbstractGBFragment> fragments = fragmentManager.getFragments()
.stream()
.map(e -> (AbstractGBFragment) e)
.collect(Collectors.toList());
for (AbstractGBFragment fragment : fragments) {
if (position < 0 || fragment != fragmentManager.findFragmentByTag("f" + position)) {
fragment.onMadeInvisibleInActivity();
} else {
fragment.onMadeVisibleInActivityInternal();
}
}
}
}

View File

@ -0,0 +1,30 @@
package nodomain.freeyourgadget.gadgetbridge.adapter;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.SleepChartFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.WeekSleepChartFragment;
public class SleepFragmentAdapter extends NestedFragmentAdapter {
public SleepFragmentAdapter(AbstractGBFragment fragment, FragmentManager childFragmentManager) {
super(fragment, childFragmentManager);
}
@NonNull
@Override
public Fragment createFragment(int position) {
switch (position) {
case 0:
return new SleepChartFragment();
case 1:
return WeekSleepChartFragment.newInstance(7);
case 2:
return WeekSleepChartFragment.newInstance(30);
}
return new SleepChartFragment();
}
}

View File

@ -0,0 +1,32 @@
package nodomain.freeyourgadget.gadgetbridge.adapter;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.StepsDailyFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.StepsPeriodFragment;
public class StepsFragmentAdapter extends NestedFragmentAdapter {
protected FragmentManager fragmentManager;
public StepsFragmentAdapter(AbstractGBFragment fragment, FragmentManager childFragmentManager) {
super(fragment, childFragmentManager);
fragmentManager = childFragmentManager;
}
@NonNull
@Override
public Fragment createFragment(int position) {
switch (position) {
case 0:
return new StepsDailyFragment();
case 1:
return StepsPeriodFragment.newInstance(7);
case 2:
return StepsPeriodFragment.newInstance(30);
}
return new StepsDailyFragment();
}
}

View File

@ -0,0 +1,17 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

View File

@ -0,0 +1,136 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/steps_date_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="20sp"
android:layout_marginTop="15dp"
android:layout_marginBottom="20dp"
/>
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="@+id/steps_streaks_button"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_marginTop="0dp"
android:layout_marginEnd="0dp"
android:visibility="visible"
app:srcCompat="@drawable/ic_events" />
</RelativeLayout>
<ImageView
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_gravity="center"
android:layout_centerHorizontal="true"
android:scaleType="fitStart"
android:id="@+id/steps_gauge" />
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="30dp"
android:layout_weight="3"
android:shrinkColumns="*"
android:stretchColumns="*">
<TableRow
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:weightSum="2">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="20dip"
android:paddingTop="20dip"
android:paddingRight="20dip">
<View
android:layout_width="fill_parent"
android:layout_height="5px"
android:background="@color/value_line_color" />
<TextView
android:id="@+id/steps_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="20dip"
android:text="0"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="@string/steps"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="20dip"
android:paddingTop="20dip"
android:paddingRight="20dip">
<View
android:layout_width="fill_parent"
android:layout_height="5px"
android:background="@color/value_line_color" />
<TextView
android:id="@+id/steps_distance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="20dip"
android:text="0"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="@string/distance"
android:textSize="12sp" />
</LinearLayout>
</TableRow>
</TableLayout>
<TextView
android:id="@+id/steps_chart_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text=""
android:paddingLeft="20dip"
android:textSize="20sp" />
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,182 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/steps_date_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="20sp"
android:layout_marginTop="15dp"
android:layout_marginBottom="20dp"
/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="250sp"
>
<com.github.mikephil.charting.charts.BarChart
android:id="@+id/steps_chart"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="2" />
</LinearLayout>
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="30dp"
android:layout_weight="3"
android:shrinkColumns="*"
android:stretchColumns="*">
<TableRow
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:weightSum="2">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="20dip"
android:paddingTop="20dip"
android:paddingRight="20dip">
<View
android:layout_width="fill_parent"
android:layout_height="5px"
android:background="@color/value_line_color" />
<TextView
android:id="@+id/steps_avg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="20dip"
android:text="0"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="@string/steps_avg"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="20dip"
android:paddingTop="20dip"
android:paddingRight="20dip">
<View
android:layout_width="fill_parent"
android:layout_height="5px"
android:background="@color/value_line_color" />
<TextView
android:id="@+id/steps_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="20dip"
android:text="0"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="@string/steps_total"
android:textSize="12sp" />
</LinearLayout>
</TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:weightSum="2">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="20dip"
android:paddingTop="20dip"
android:paddingRight="20dip">
<View
android:layout_width="fill_parent"
android:layout_height="5px"
android:background="@color/value_line_color" />
<TextView
android:id="@+id/distance_avg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="20dip"
android:text="0"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="@string/distance_avg"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="20dip"
android:paddingTop="20dip"
android:paddingRight="20dip">
<View
android:layout_width="fill_parent"
android:layout_height="5px"
android:background="@color/value_line_color" />
<TextView
android:id="@+id/distance_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="20dip"
android:text="0"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="@string/distance_total"
android:textSize="12sp" />
</LinearLayout>
</TableRow>
</TableLayout>
</LinearLayout>
</ScrollView>

View File

@ -3015,7 +3015,6 @@
<item>@string/activity_sleepchart_activity_and_sleep</item>
<item>@string/charts_activity_list</item>
<item>@string/sleepchart_your_sleep</item>
<item>@string/weeksleepchart_sleep_a_week_or_month</item>
<item>@string/weekstepschart_steps_a_week_or_month</item>
<item>@string/pref_header_hrv_status</item>
<item>@string/body_energy</item>
@ -3031,7 +3030,6 @@
<item>@string/p_activity</item>
<item>@string/p_activity_list</item>
<item>@string/p_sleep</item>
<item>@string/p_sleep_week</item>
<item>@string/p_steps_week</item>
<item>@string/p_hrv_status</item>
<item>@string/p_body_energy</item>
@ -3048,7 +3046,6 @@
<item>@string/p_activity</item>
<item>@string/p_activity_list</item>
<item>@string/p_sleep</item>
<item>@string/p_sleep_week</item>
<item>@string/p_hrv_status</item>
<item>@string/p_body_energy</item>
<item>@string/p_steps_week</item>

View File

@ -49,6 +49,7 @@
<color name="hrv_status_poor" type="color">#be03fc</color>
<color name="hrv_status_char_line_color" type="color">#d12a2a</color>
<color name="body_energy_level_color" type="color">#5ac234</color>
<color name="steps_color" type="color">#00c9bf</color>
<color name="value_line_color" type="color">#858585</color>
<color name="gauge_line_color" type="color">#383838</color>

View File

@ -1576,6 +1576,7 @@
<string name="hrv_status_unit">%1$d ms</string>
<string name="hrv_status_baseline">%1$d-%2$d ms</string>
<string name="hrv_status_baseline_label">Baseline</string>
<string name="steps_distance_unit">%1$,.2f km</string>
<string name="body_energy_gained">Gained</string>
<string name="body_energy_lost">Lost</string>
<string name="body_energy_legend_level">Body Energy Level</string>
@ -2071,6 +2072,10 @@
<string name="minAltitude">Minimum</string>
<string name="averageAltitude">Average</string>
<string name="steps">Steps</string>
<string name="steps_avg">Steps AVG</string>
<string name="steps_total">Steps Total</string>
<string name="distance_avg">Distance AVG</string>
<string name="distance_total">Distance Total</string>
<string name="activeSeconds">Active</string>
<string name="caloriesBurnt">Calories</string>
<string name="maxSpeed">Maximum</string>

View File

@ -100,7 +100,6 @@
<item name="p_activity" type="string">activity</item>
<item name="p_activity_list" type="string">activitylist</item>
<item name="p_sleep" type="string">sleep</item>
<item name="p_sleep_week" type="string">sleepweek</item>
<item name="p_steps_week" type="string">stepsweek</item>
<item name="p_stress" type="string">stress</item>
<item name="p_pai" type="string">pai</item>