diff --git a/android_app/app/build.gradle b/android_app/app/build.gradle index 423618cf..2f648e6f 100644 --- a/android_app/app/build.gradle +++ b/android_app/app/build.gradle @@ -120,12 +120,17 @@ android { } } compileOptions { + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9' + + implementation 'com.google.android.material:material:1.3.0-alpha02' implementation 'com.google.android.material:material:1.3.0-alpha02' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.appcompat:appcompat:1.2.0' diff --git a/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleMeasurement.java b/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleMeasurement.java index 63076263..d43ad743 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleMeasurement.java +++ b/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleMeasurement.java @@ -16,18 +16,19 @@ package com.health.openscale.core.datatypes; -import com.health.openscale.core.utils.CsvHelper; -import com.j256.simplecsv.common.CsvColumn; - -import java.lang.reflect.Field; -import java.util.Date; - import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.ForeignKey; import androidx.room.Ignore; import androidx.room.Index; import androidx.room.PrimaryKey; + +import com.health.openscale.core.utils.CsvHelper; +import com.j256.simplecsv.common.CsvColumn; + +import java.lang.reflect.Field; +import java.util.Date; + import timber.log.Timber; @Entity(tableName = "scaleMeasurements", @@ -163,6 +164,59 @@ public class ScaleMeasurement implements Cloneable { } } + public void add(final float summand) { + try { + Field[] fields = getClass().getDeclaredFields(); + + for (Field field : fields) { + field.setAccessible(true); + Object value = field.get(this); + + if (value != null && Float.class.isAssignableFrom(value.getClass())) { + field.set(this, (float)value + summand); + } + field.setAccessible(false); + } + + } catch (IllegalAccessException e) { + Timber.e(e); + } + } + + public void subtract(final ScaleMeasurement minuend) { + try { + Field[] fields = getClass().getDeclaredFields(); + + for (Field field : fields) { + field.setAccessible(true); + Object value = field.get(this); + if (value != null && Float.class.isAssignableFrom(value.getClass())) { + field.set(this, (float)value - (float)field.get(minuend)); + } + field.setAccessible(false); + } + } catch (IllegalAccessException e) { + Timber.e(e); + } + } + + public void multiply(final float factor) { + try { + Field[] fields = getClass().getDeclaredFields(); + + for (Field field : fields) { + field.setAccessible(true); + Object value = field.get(this); + if (value != null && Float.class.isAssignableFrom(value.getClass())) { + field.set(this, (float)value * factor); + } + field.setAccessible(false); + } + } catch (IllegalAccessException e) { + Timber.e(e); + } + } + public void divide(final float divisor) { try { Field[] fields = getClass().getDeclaredFields(); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/graph/GraphFragment.java b/android_app/app/src/main/java/com/health/openscale/gui/graph/GraphFragment.java index 9263632f..21a33d1c 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/graph/GraphFragment.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/graph/GraphFragment.java @@ -59,6 +59,8 @@ import com.health.openscale.gui.measurement.MeasurementEntryFragment; import com.health.openscale.gui.utils.ColorUtil; import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Calendar; import java.util.List; @@ -80,8 +82,8 @@ public class GraphFragment extends Fragment { private OpenScale openScale; - private final Calendar calYears; - private Calendar calLastSelected; + private LocalDate calYears; + private LocalDate calLastSelected; private ScaleMeasurement markedMeasurement; @@ -89,8 +91,8 @@ public class GraphFragment extends Fragment { private static final String CAL_LAST_SELECTED_KEY = "calLastSelected"; public GraphFragment() { - calYears = Calendar.getInstance(); - calLastSelected = Calendar.getInstance(); + calYears = LocalDate.now(); + calLastSelected = LocalDate.now(); } @Override @@ -100,13 +102,13 @@ public class GraphFragment extends Fragment { if (savedInstanceState == null) { if (!openScale.isScaleMeasurementListEmpty()) { - calYears.setTime(openScale.getLastScaleMeasurement().getDateTime()); - calLastSelected.setTime(openScale.getLastScaleMeasurement().getDateTime()); + calYears = openScale.getLastScaleMeasurement().getDateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();; + calLastSelected = openScale.getLastScaleMeasurement().getDateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); } } else { - calYears.setTimeInMillis(savedInstanceState.getLong(CAL_YEARS_KEY)); - calLastSelected.setTimeInMillis(savedInstanceState.getLong(CAL_LAST_SELECTED_KEY)); + calYears = LocalDate.ofEpochDay(savedInstanceState.getLong(CAL_YEARS_KEY)); + calLastSelected = LocalDate.ofEpochDay(savedInstanceState.getLong(CAL_LAST_SELECTED_KEY)); } graphView = inflater.inflate(R.layout.fragment_graph, container, false); @@ -143,7 +145,7 @@ public class GraphFragment extends Fragment { }); txtYear = graphView.findViewById(R.id.txtYear); - txtYear.setText(Integer.toString(calYears.get(Calendar.YEAR))); + txtYear.setText(Integer.toString(calYears.getYear())); chartActionBarView = graphView.findViewById(R.id.chartActionBar); chartActionBarView.setOnActionClickListener(new View.OnClickListener() { @@ -164,14 +166,9 @@ public class GraphFragment extends Fragment { btnLeftYear = graphView.findViewById(R.id.btnLeftYear); btnLeftYear.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { - calYears.roll(Calendar.YEAR, false); - txtYear.setText(Integer.toString(calYears.get(Calendar.YEAR))); + calYears = calYears.minusYears(1); + txtYear.setText(Integer.toString(calYears.getYear())); - List scaleMeasurementList = - OpenScale.getInstance().getScaleMeasurementOfYear(calYears.get(Calendar.YEAR)); - if (!scaleMeasurementList.isEmpty()) { - calLastSelected.setTime(scaleMeasurementList.get(0).getDateTime()); - } generateGraphs(); } }); @@ -179,14 +176,9 @@ public class GraphFragment extends Fragment { btnRightYear = graphView.findViewById(R.id.btnRightYear); btnRightYear.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { - calYears.roll(Calendar.YEAR, true); - txtYear.setText(Integer.toString(calYears.get(Calendar.YEAR))); + calYears = calYears.plusYears(1); + txtYear.setText(Integer.toString(calYears.getYear())); - List scaleMeasurementList = - OpenScale.getInstance().getScaleMeasurementOfYear(calYears.get(Calendar.YEAR)); - if (!scaleMeasurementList.isEmpty()) { - calLastSelected.setTime(scaleMeasurementList.get(scaleMeasurementList.size() - 1).getDateTime()); - } generateGraphs(); } }); @@ -217,6 +209,8 @@ public class GraphFragment extends Fragment { prefs.edit().putBoolean("showMonth", true).apply(); } + getActivity().recreate(); // TODO HACK to refresh graph; graph.invalidate and notfiydatachange is not enough!? + generateGraphs(); return true; case R.id.enableWeek: @@ -228,6 +222,8 @@ public class GraphFragment extends Fragment { prefs.edit().putBoolean("showWeek", true).apply(); } + getActivity().recreate(); // TODO HACK to refresh graph; graph.invalidate and notfiydatachange is not enough!? + generateGraphs(); return true; default: @@ -284,6 +280,7 @@ public class GraphFragment extends Fragment { OpenScale.getInstance().getScaleMeasurementsLiveData().observe(getViewLifecycleOwner(), new Observer>() { @Override public void onChanged(List scaleMeasurements) { + chartView.updateMeasurementList(scaleMeasurements); generateGraphs(); } }); @@ -304,25 +301,24 @@ public class GraphFragment extends Fragment { public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putLong(CAL_YEARS_KEY, calYears.getTimeInMillis()); - outState.putLong(CAL_LAST_SELECTED_KEY, calLastSelected.getTimeInMillis()); + outState.putLong(CAL_YEARS_KEY, calYears.toEpochDay()); + outState.putLong(CAL_LAST_SELECTED_KEY, calLastSelected.toEpochDay()); } private void generateColumnData() { - int[] numOfMonth = openScale.getCountsOfMonth(calYears.get(Calendar.YEAR)); + int[] numOfMonth = openScale.getCountsOfMonth(calYears.getYear()); - Calendar calMonths = Calendar.getInstance(); - calMonths.set(Calendar.MONTH, Calendar.JANUARY); + LocalDate calMonths = LocalDate.of(calYears.getYear(), 1, 1); List dataSets = new ArrayList<>(); for (int i=0; i<12; i++) { List entries = new ArrayList<>(); - entries.add(new BarEntry(calMonths.get(Calendar.MONTH), numOfMonth[i])); + entries.add(new BarEntry(calMonths.getMonthValue()-1, numOfMonth[i])); - calMonths.add(Calendar.MONTH, 1); + calMonths = calMonths.plusMonths(1); BarDataSet set = new BarDataSet(entries, "month "+i); set.setColor(ColorUtil.COLORS[i % 4]); @@ -339,7 +335,7 @@ public class GraphFragment extends Fragment { } private void generateGraphs() { - final int selectedYear = calYears.get(Calendar.YEAR); + final int selectedYear = calYears.getYear(); int firstYear = selectedYear; int lastYear = selectedYear; @@ -372,9 +368,9 @@ public class GraphFragment extends Fragment { generateColumnData(); if (prefs.getBoolean("showWeek", false)) { - chartView.setViewRange(selectedYear, calLastSelected.get(Calendar.MONTH), ChartMeasurementView.ViewMode.WEEK_OF_MONTH); + chartView.setViewRange(selectedYear, calLastSelected.getMonthValue(), ChartMeasurementView.ViewMode.WEEK_OF_MONTH); } else { - chartView.setViewRange(selectedYear, calLastSelected.get(Calendar.MONTH), ChartMeasurementView.ViewMode.DAY_OF_MONTH); + chartView.setViewRange(selectedYear, calLastSelected.getMonthValue(), ChartMeasurementView.ViewMode.DAY_OF_MONTH); } } else { // show only yearly diagram and hide monthly diagram chartTop.setVisibility(View.GONE); @@ -386,15 +382,13 @@ public class GraphFragment extends Fragment { chartView.setViewRange(selectedYear, ChartMeasurementView.ViewMode.MONTH_OF_YEAR); } } + chartView.refreshMeasurementList(); } private class chartTopValueTouchListener implements OnChartValueSelectedListener { @Override public void onValueSelected(Entry e, Highlight h) { - Calendar cal = Calendar.getInstance(); - cal.set(Calendar.MONTH, (int)e.getX()); - - calLastSelected = cal; + calLastSelected = calLastSelected.withMonth((int)e.getX()+1); generateGraphs(); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/ChartMarkerView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/ChartMarkerView.java index e39e4d07..36828ab7 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/ChartMarkerView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/ChartMarkerView.java @@ -62,7 +62,7 @@ public class ChartMarkerView extends MarkerView { markerText.append("\n"); if (measurement.isAverageValue()) { - markerText.append("Ø "); + markerText.append(getContext().getString(R.string.label_trend) + " "); } } diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/ChartMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/ChartMeasurementView.java index 1e6d77f7..0c960485 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/ChartMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/ChartMeasurementView.java @@ -18,14 +18,11 @@ package com.health.openscale.gui.measurement; import android.content.Context; import android.content.SharedPreferences; -import android.graphics.Canvas; import android.graphics.Color; import android.graphics.RectF; import android.preference.PreferenceManager; import android.util.AttributeSet; -import android.view.MotionEvent; import android.widget.ProgressBar; -import android.widget.Toast; import com.github.mikephil.charting.charts.LineChart; import com.github.mikephil.charting.components.AxisBase; @@ -37,8 +34,6 @@ import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineDataSet; import com.github.mikephil.charting.formatter.ValueFormatter; import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; -import com.github.mikephil.charting.listener.ChartTouchListener; -import com.github.mikephil.charting.listener.OnChartGestureListener; import com.github.mikephil.charting.utils.Utils; import com.health.openscale.R; import com.health.openscale.core.OpenScale; @@ -48,15 +43,18 @@ import com.health.openscale.core.utils.Converters; import com.health.openscale.core.utils.PolynomialFitter; import com.health.openscale.gui.utils.ColorUtil; -import java.text.DateFormat; -import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; import java.util.ArrayList; -import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Stack; +import static java.time.temporal.ChronoUnit.DAYS; + public class ChartMeasurementView extends LineChart { public enum ViewMode { DAY_OF_MONTH, @@ -72,18 +70,11 @@ public class ChartMeasurementView extends LineChart { private OpenScale openScale; private SharedPreferences prefs; - private List scaleMeasurementList; private List measurementViews; - private ScaleMeasurement firstMeasurement; - private ScaleMeasurement lastMeasurement; - private int maxXValue; - private int minXValue; + private List scaleMeasurementList; private ViewMode viewMode; - private boolean isAnimationOn; private boolean isInGraphKey; - private int scrollHistoryCount; private ProgressBar progressBar; - private boolean isRollingChart; public ChartMeasurementView(Context context) { super(context); @@ -100,69 +91,99 @@ public class ChartMeasurementView extends LineChart { initChart(); } - public void setViewRange(final ViewMode mode, boolean rollingChart) { - progressBar.setVisibility(VISIBLE); - isRollingChart = rollingChart; + public void setViewRange(final ViewMode mode) { + viewMode = mode; - if (isRollingChart) { - ScaleMeasurement lastMeasurement = openScale.getLastScaleMeasurement(); + setGranularityAndRange(1980, 1); + setXValueFormat(viewMode); - if (lastMeasurement != null) { - Calendar lastMeasurementCalender = Calendar.getInstance(); - lastMeasurementCalender.setTime(lastMeasurement.getDateTime()); - - switch (mode) { - case DAY_OF_ALL: - lastMeasurementCalender.add(Calendar.DAY_OF_MONTH, -28 * scrollHistoryCount); - break; - case WEEK_OF_ALL: - lastMeasurementCalender.add(Calendar.WEEK_OF_YEAR, -4 * scrollHistoryCount); - break; - case MONTH_OF_ALL: - lastMeasurementCalender.add(Calendar.MONTH, -4 * scrollHistoryCount); - break; - case YEAR_OF_ALL: - lastMeasurementCalender.add(Calendar.YEAR, -4 * scrollHistoryCount); - break; - default: - throw new IllegalArgumentException("view mode not implemented"); - } - - setMeasurementList(openScale.getScaleMeasurementOfStartDate(lastMeasurementCalender.get(Calendar.YEAR), lastMeasurementCalender.get(Calendar.MONTH), lastMeasurementCalender.get(Calendar.DAY_OF_MONTH))); - } else { - clear(); - return; - } - } else { - setMeasurementList(openScale.getScaleMeasurementList()); - } - setViewMode(mode); - - refresh(); - - if (isRollingChart) { - setRollingChartOn(mode); + if (openScale.getLastScaleMeasurement() != null) { + moveViewToX(convertDateToInt(openScale.getLastScaleMeasurement().getDateTime())); } } public void setViewRange(int year, final ViewMode mode) { - progressBar.setVisibility(VISIBLE); - setMeasurementList(openScale.getScaleMeasurementOfYear(year)); - setViewMode(mode); + viewMode = mode; - refresh(); + setGranularityAndRange(year, 1); + setXValueFormat(viewMode); + + LocalDate startDate = LocalDate.of(year, 1, 1); + + moveViewToX(convertDateToInt(startDate)); } public void setViewRange(int year, int month, final ViewMode mode) { - progressBar.setVisibility(VISIBLE); - setMeasurementList(openScale.getScaleMeasurementOfMonth(year, month)); - setViewMode(mode); + viewMode = mode; - refresh(); + setGranularityAndRange(year, month); + setXValueFormat(viewMode); + + LocalDate startDate = LocalDate.of(year, month, 1); + + moveViewToX(convertDateToInt(startDate)); } - public void setAnimationOn(boolean status) { - isAnimationOn = status; + private void setGranularityAndRange(int year, int month) { + LocalDate startDate = LocalDate.of(year, month, 1); + LocalDate endDate = LocalDate.of(year, month, 1); + + int range = 0; + int granularity = 0; + + switch (viewMode) { + case DAY_OF_MONTH: + endDate = startDate.plusMonths(1); + range = (int)DAYS.between(startDate, endDate); + granularity = 1; + break; + case WEEK_OF_MONTH: + endDate = startDate.plusMonths(1); + range = (int)DAYS.between(startDate, endDate); + granularity = 7; + break; + case WEEK_OF_YEAR: + endDate = startDate.plusYears(1); + range = (int)DAYS.between(startDate, endDate); + granularity = 7; + break; + case MONTH_OF_YEAR: + endDate = startDate.plusYears(1); + range = (int)DAYS.between(startDate, endDate); + granularity = 30; + break; + case DAY_OF_YEAR: + endDate = startDate.plusYears(1); + range = (int)DAYS.between(startDate, endDate); + granularity = 1; + break; + case DAY_OF_ALL: + endDate = startDate.plusMonths(1); + range = (int)DAYS.between(startDate, endDate); + granularity = 1; + break; + case WEEK_OF_ALL: + endDate = startDate.plusMonths(1); + range = (int)DAYS.between(startDate, endDate); + granularity = 7; + break; + case MONTH_OF_ALL: + endDate = startDate.plusMonths(3); + range = (int)DAYS.between(startDate, endDate); + granularity = 30; + break; + case YEAR_OF_ALL: + endDate = startDate.plusYears(1); + range = (int)DAYS.between(startDate, endDate); + granularity = 365; + break; + default: + throw new IllegalArgumentException("view mode not implemented"); + } + + getXAxis().setGranularity(granularity); + setVisibleXRangeMaximum(range); + setCustomViewPortOffsets(); // set custom viewPortOffsets to avoid jitter on translating while auto scale is on } public void setIsInGraphKey(boolean status) { @@ -176,18 +197,12 @@ public class ChartMeasurementView extends LineChart { private void initChart() { prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); openScale = OpenScale.getInstance(); - scaleMeasurementList = new ArrayList<>(); measurementViews = MeasurementView.getMeasurementList(getContext(), MeasurementView.DateTimeOrder.NONE); - firstMeasurement = new ScaleMeasurement(); - lastMeasurement = new ScaleMeasurement(); - maxXValue = 0; - minXValue = 0; - isAnimationOn = true; isInGraphKey = true; - scrollHistoryCount = 1; progressBar = null; setHardwareAccelerationEnabled(true); + setAutoScaleMinMaxEnabled(true); setMarker(new ChartMarkerView(getContext(), R.layout.chart_markerview)); setDoubleTapToZoomEnabled(false); setHighlightPerTapEnabled(true); @@ -202,136 +217,66 @@ public class ChartMeasurementView extends LineChart { getAxisRight().setTextColor(ColorUtil.getTintColor(getContext())); getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM); getXAxis().setTextColor(ColorUtil.getTintColor(getContext())); + getXAxis().setGranularityEnabled(true); + } - setOnChartGestureListener(new OnChartGestureListener() { - @Override - public void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) { + private int convertDateToInt(LocalDate date) { + return (int)date.toEpochDay(); + } - } + private int convertDateToInt(Date date) { + LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + return (int)localDate.toEpochDay(); + } + + private LocalDate convertIntToDate(int shortDate) { + return LocalDate.ofEpochDay(shortDate); + } + + private void setXValueFormat(final ViewMode mode) { + getXAxis().setValueFormatter(new ValueFormatter() { @Override - public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) { - if (isRollingChart) { - if (progressBar.getVisibility() == GONE) { - if (Math.round(getLowestVisibleX()) == Math.round(getXChartMin())) { - scrollHistoryCount++; - setViewRange(viewMode, isRollingChart); - } - } + public String getAxisLabel(float value, AxisBase axis) { + DateTimeFormatter formatter; + + switch (mode) { + case DAY_OF_MONTH: + formatter = DateTimeFormatter.ofPattern("dd"); + break; + case WEEK_OF_MONTH: + formatter = DateTimeFormatter.ofPattern("'W'W"); + break; + case WEEK_OF_YEAR: + formatter = DateTimeFormatter.ofPattern("'W'w"); + break; + case MONTH_OF_YEAR: + formatter = DateTimeFormatter.ofPattern("MMM"); + break; + case DAY_OF_YEAR: + formatter = DateTimeFormatter.ofPattern("D"); + break; + case DAY_OF_ALL: + formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT); + break; + case WEEK_OF_ALL: + formatter = DateTimeFormatter.ofPattern("'W'w yyyy"); + break; + case MONTH_OF_ALL: + formatter = DateTimeFormatter.ofPattern("MMM yyyy"); + break; + case YEAR_OF_ALL: + formatter = DateTimeFormatter.ofPattern("yyyy"); + break; + default: + throw new IllegalArgumentException("view mode not implemented"); } - } - - @Override - public void onChartLongPressed(MotionEvent me) { - - } - - @Override - public void onChartDoubleTapped(MotionEvent me) { - - } - - @Override - public void onChartSingleTapped(MotionEvent me) { - - } - - @Override - public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) { - - } - - @Override - public void onChartScale(MotionEvent me, float scaleX, float scaleY) { - - } - - @Override - public void onChartTranslate(MotionEvent me, float dX, float dY) { + return formatter.format(convertIntToDate((int)value)); } }); } - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - progressBar.setVisibility(GONE); - } - - private void setViewMode(final ViewMode mode) { - viewMode = mode; - Calendar viewModeCalender = Calendar.getInstance(); - viewModeCalender.setTime(lastMeasurement.getDateTime()); - - switch (mode) { - case DAY_OF_MONTH: - minXValue = viewModeCalender.getMinimum(Calendar.DAY_OF_MONTH); - maxXValue = viewModeCalender.getMaximum(Calendar.DAY_OF_MONTH); - break; - case WEEK_OF_MONTH: - minXValue = 1; - maxXValue = viewModeCalender.getActualMaximum(Calendar.WEEK_OF_MONTH); - break; - case WEEK_OF_YEAR: - minXValue = viewModeCalender.getActualMinimum(Calendar.WEEK_OF_YEAR); - maxXValue = viewModeCalender.getActualMaximum(Calendar.WEEK_OF_YEAR); - break; - case MONTH_OF_YEAR: - minXValue = viewModeCalender.getActualMinimum(Calendar.MONTH); - maxXValue = viewModeCalender.getActualMaximum(Calendar.MONTH); - break; - case DAY_OF_YEAR: - minXValue = viewModeCalender.getActualMinimum(Calendar.DAY_OF_YEAR); - maxXValue = viewModeCalender.getActualMaximum(Calendar.DAY_OF_YEAR); - break; - case DAY_OF_ALL: - case WEEK_OF_ALL: - case MONTH_OF_ALL: - case YEAR_OF_ALL: - minXValue = convertDateInShort(firstMeasurement.getDateTime()); - maxXValue = convertDateInShort(lastMeasurement.getDateTime()); - break; - default: - throw new IllegalArgumentException("view mode not implemented"); - } - - setXValueFormat(mode); - } - - private int convertDateInShort(Date date) { - Calendar shortDate = Calendar.getInstance(); - shortDate.setTime(new Date(0)); - - Calendar dateCalendar = Calendar.getInstance(); - dateCalendar.setTime(date); - - switch (viewMode) { - case DAY_OF_ALL: - shortDate.set(Calendar.DAY_OF_MONTH, dateCalendar.get(Calendar.DAY_OF_MONTH)); - shortDate.set(Calendar.MONTH, dateCalendar.get(Calendar.MONTH)); - shortDate.set(Calendar.YEAR, dateCalendar.get(Calendar.YEAR)); - break; - case WEEK_OF_ALL: - shortDate.set(Calendar.WEEK_OF_YEAR, dateCalendar.get(Calendar.WEEK_OF_YEAR)); - shortDate.set(Calendar.YEAR, dateCalendar.get(Calendar.YEAR)); - break; - case MONTH_OF_ALL: - shortDate.set(Calendar.MONTH, dateCalendar.get(Calendar.MONTH)); - shortDate.set(Calendar.YEAR, dateCalendar.get(Calendar.YEAR)); - break; - case YEAR_OF_ALL: - shortDate.set(Calendar.YEAR, dateCalendar.get(Calendar.YEAR)); - break; - default: - throw new IllegalArgumentException("view mode not implemented"); - - } - - return (int)(shortDate.getTime().getTime() / 1000000L); - } - private void setCustomViewPortOffsets() { float offsetLeft = 0f, offsetRight = 0f, offsetTop = 0f, offsetBottom = 0f; @@ -388,231 +333,51 @@ public class ChartMeasurementView extends LineChart { Math.max(minOffset, offsetBottom)); } - private Date convertShortInDate(int shortDate) { - return new Date(shortDate * 1000000L); - } - - private void setMeasurementList(List measurementList) { - scaleMeasurementList = measurementList; - - if (!measurementList.isEmpty()) { - lastMeasurement = measurementList.get(0); - Collections.reverse(measurementList); - firstMeasurement = measurementList.get(0); - Collections.reverse(measurementList); - } - } - - private void setRollingChartOn(ViewMode mode) { - if (!scaleMeasurementList.isEmpty()) { - Calendar zeroCalendar = Calendar.getInstance(); - zeroCalendar.setTime(new Date(0)); - - Calendar lastCalendar = Calendar.getInstance(); - lastCalendar.setTime(lastMeasurement.getDateTime()); - - Calendar deltaCalendar = Calendar.getInstance(); - - int range = 0; - int granularity = 0; - - switch (mode) { - case DAY_OF_ALL: - zeroCalendar.set(Calendar.DAY_OF_MONTH, lastCalendar.get(Calendar.DAY_OF_MONTH)); - zeroCalendar.set(Calendar.MONTH, lastCalendar.get(Calendar.MONTH)); - zeroCalendar.set(Calendar.YEAR, lastCalendar.get(Calendar.YEAR)); - deltaCalendar.setTime(zeroCalendar.getTime()); - deltaCalendar.add(Calendar.DAY_OF_MONTH, -1); - granularity = convertDateInShort(zeroCalendar.getTime()) - convertDateInShort(deltaCalendar.getTime()); - range = granularity * 14; - break; - case WEEK_OF_ALL: - zeroCalendar.set(Calendar.WEEK_OF_YEAR, lastCalendar.get(Calendar.WEEK_OF_YEAR)); - zeroCalendar.set(Calendar.YEAR, lastCalendar.get(Calendar.YEAR)); - deltaCalendar.setTime(zeroCalendar.getTime()); - deltaCalendar.add(Calendar.WEEK_OF_YEAR, -1); - granularity = convertDateInShort(zeroCalendar.getTime()) - convertDateInShort(deltaCalendar.getTime()); - range = granularity * 4; - break; - case MONTH_OF_ALL: - zeroCalendar.set(Calendar.MONTH, lastCalendar.get(Calendar.MONTH)); - zeroCalendar.set(Calendar.YEAR, lastCalendar.get(Calendar.YEAR)); - deltaCalendar.setTime(zeroCalendar.getTime()); - deltaCalendar.add(Calendar.MONTH, -1); - granularity = convertDateInShort(zeroCalendar.getTime()) - convertDateInShort(deltaCalendar.getTime()); - range = granularity * 4; - break; - case YEAR_OF_ALL: - zeroCalendar.set(Calendar.YEAR, lastCalendar.get(Calendar.YEAR)); - deltaCalendar.add(Calendar.YEAR, -1); - granularity = convertDateInShort(zeroCalendar.getTime()) - convertDateInShort(deltaCalendar.getTime()); - range = granularity * 3; - break; - default: - throw new IllegalArgumentException("view mode not implemented"); - } - - setAutoScaleMinMaxEnabled(true); - setCustomViewPortOffsets(); // set custom viewPortOffsets to avoid jitter on translating while auto scale is on - - getXAxis().setGranularity(granularity); - setVisibleXRangeMaximum(range); - - moveViewToX(getBinNr(lastMeasurement)); - } - } - - private void setXValueFormat(final ViewMode mode) { - getXAxis().setValueFormatter(new ValueFormatter() { - private final SimpleDateFormat xValueFormat = new SimpleDateFormat(); - private final Calendar calendar = Calendar.getInstance(); - - @Override - public String getAxisLabel(float value, AxisBase axis) { - calendar.setTime(new Date(0)); - - switch (mode) { - case DAY_OF_MONTH: - calendar.set(Calendar.DAY_OF_MONTH, (int)value); - xValueFormat.applyLocalizedPattern("dd"); - break; - case WEEK_OF_MONTH: - calendar.set(Calendar.WEEK_OF_MONTH, (int)value); - xValueFormat.applyLocalizedPattern("'W'W"); - break; - case WEEK_OF_YEAR: - calendar.set(Calendar.WEEK_OF_YEAR, (int)value); - xValueFormat.applyLocalizedPattern("'W'w"); - break; - case MONTH_OF_YEAR: - calendar.set(Calendar.MONTH, (int)value); - xValueFormat.applyLocalizedPattern("MMM"); - break; - case DAY_OF_YEAR: - calendar.set(Calendar.DAY_OF_YEAR, (int)value); - xValueFormat.applyLocalizedPattern("D"); - break; - case DAY_OF_ALL: - calendar.setTime(convertShortInDate((int)value)); - return DateFormat.getDateInstance(DateFormat.SHORT).format(calendar.getTime()); - case WEEK_OF_ALL: - calendar.setTime(convertShortInDate((int)value)); - xValueFormat.applyLocalizedPattern("'W'w yyyy"); - return xValueFormat.format(calendar.getTime()); - case MONTH_OF_ALL: - calendar.setTime(convertShortInDate((int)value)); - xValueFormat.applyLocalizedPattern("MMM yyyy"); - return xValueFormat.format(calendar.getTime()); - case YEAR_OF_ALL: - calendar.setTime(convertShortInDate((int)value)); - xValueFormat.applyLocalizedPattern("yyyy"); - return xValueFormat.format(calendar.getTime()); - default: - throw new IllegalArgumentException("view mode not implemented"); - } - - return xValueFormat.format(calendar.getTime()); - } - }); - } - - private ScaleMeasurement[] averageScaleMeasurementList(List measurementList) { - final ScaleMeasurement[] avgMeasurementList = new ScaleMeasurement[ maxXValue + minXValue + 1]; - - for (ScaleMeasurement measurement : measurementList) { - int binNr = getBinNr(measurement); - - if (avgMeasurementList[binNr] == null) { - avgMeasurementList[binNr] = measurement.clone(); - } else { - avgMeasurementList[binNr].add(measurement); - } - } - - for (ScaleMeasurement avgMeasurement : avgMeasurementList) { - if (avgMeasurement == null) { - continue; - } - - int binNr = getBinNr(avgMeasurement); - avgMeasurement.divide(avgMeasurementList[binNr].count()); - } - - return avgMeasurementList; - } - - private ScaleMeasurement getPreviousMeasurment(ScaleMeasurement[] masurementList, int binNr) { - for (int i=binNr-1; i >= 0; i--) { - if (masurementList[i] != null) { - return masurementList[i]; - } - } - - return null; - } - - private int getBinNr(ScaleMeasurement measurement) { - Calendar measurementCalendar = Calendar.getInstance(); - measurementCalendar.setTime(measurement.getDateTime()); - - switch (viewMode) { - case DAY_OF_MONTH: - return measurementCalendar.get(Calendar.DAY_OF_MONTH); - case WEEK_OF_MONTH: - return measurementCalendar.get(Calendar.WEEK_OF_MONTH); - case WEEK_OF_YEAR: - return measurementCalendar.get(Calendar.WEEK_OF_YEAR); - case MONTH_OF_YEAR: - return measurementCalendar.get(Calendar.MONTH); - case DAY_OF_YEAR: - return measurementCalendar.get(Calendar.DAY_OF_YEAR); - case DAY_OF_ALL: - case WEEK_OF_ALL: - case MONTH_OF_ALL: - case YEAR_OF_ALL: - return convertDateInShort(measurement.getDateTime()); - default: - throw new IllegalArgumentException("view mode not implemented"); - } - } - - private void refresh() { + public void updateMeasurementList(final List scaleMeasurementList) { clear(); if (scaleMeasurementList.isEmpty()) { + progressBar.setVisibility(GONE); return; } - List lineDataSets = new ArrayList<>(); + Collections.reverse(scaleMeasurementList); - ScaleMeasurement[] avgMeasurementList = averageScaleMeasurementList(scaleMeasurementList); + this.scaleMeasurementList = scaleMeasurementList; + refreshMeasurementList(); + } + + public void refreshMeasurementList() { + if (scaleMeasurementList == null) { + progressBar.setVisibility(GONE); + return; + } + + progressBar.setVisibility(VISIBLE); + + List lineDataSets; + lineDataSets = new ArrayList<>(); for (MeasurementView view : measurementViews) { - if (view instanceof FloatMeasurementView) { + if (view instanceof FloatMeasurementView && view.isVisible()) { final FloatMeasurementView measurementView = (FloatMeasurementView) view; final List lineEntries = new ArrayList<>(); - for (ScaleMeasurement avgMeasurement : avgMeasurementList) { - if (avgMeasurement == null) { - continue; - } + for (int i=0; i lineDataSets, List lineEntries, FloatMeasurementView measurementView) { @@ -648,6 +409,7 @@ public class ChartMeasurementView extends LineChart { measurementLine.setColor(measurementView.getColor()); measurementLine.setValueTextColor(ColorUtil.getTintColor(getContext())); measurementLine.setCircleColor(measurementView.getColor()); + measurementLine.setCircleHoleColor(measurementView.getColor()); measurementLine.setAxisDependency(measurementView.getSettings().isOnRightAxis() ? YAxis.AxisDependency.RIGHT : YAxis.AxisDependency.LEFT); measurementLine.setHighlightEnabled(true); measurementLine.setDrawHighlightIndicators(true); @@ -655,26 +417,12 @@ public class ChartMeasurementView extends LineChart { measurementLine.setDrawHorizontalHighlightIndicator(false); measurementLine.setHighLightColor(Color.RED); measurementLine.setDrawCircles(prefs.getBoolean("pointsEnable", true)); - measurementLine.setDrawValues(prefs.getBoolean("labelsEnable", true)); - measurementLine.setValueFormatter(new ValueFormatter() { - @Override - public String getPointLabel(Entry entry) { - String prefix = new String(); - - Object[] extraData = (Object[])entry.getData(); - ScaleMeasurement measurement = (ScaleMeasurement)extraData[0]; - ScaleMeasurement prevMeasurement = (ScaleMeasurement)extraData[1]; - FloatMeasurementView measurementView = (FloatMeasurementView)extraData[2]; - - measurementView.loadFrom(measurement, prevMeasurement); - - if (measurement.isAverageValue()) { - prefix = "Ø "; - } - - return prefix + measurementView.getValueAsString(true); - } - }); + measurementLine.setDrawValues(prefs.getBoolean("labelsEnable", false)); + measurementLine.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER); + if (prefs.getBoolean("trendLine", true)) { + // show only data point if trend line is enabled + measurementLine.enableDashedLine(0, 1, 0); + } if (measurementView.isVisible()) { if (isInGraphKey) { @@ -690,73 +438,164 @@ public class ChartMeasurementView extends LineChart { } private void addGoalLine(List lineDataSets) { - if (prefs.getBoolean("goalLine", true)) { - List valuesGoalLine = new Stack<>(); + List valuesGoalLine = new Stack<>(); - ScaleUser user = OpenScale.getInstance().getSelectedScaleUser(); - float goalWeight = Converters.fromKilogram(user.getGoalWeight(), user.getScaleUnit()); + ScaleUser user = OpenScale.getInstance().getSelectedScaleUser(); + float goalWeight = Converters.fromKilogram(user.getGoalWeight(), user.getScaleUnit()); - valuesGoalLine.add(new Entry(minXValue, goalWeight)); - valuesGoalLine.add(new Entry(maxXValue, goalWeight)); + valuesGoalLine.add(new Entry(getXChartMin(), goalWeight)); + valuesGoalLine.add(new Entry(getXChartMax(), goalWeight)); - LineDataSet goalLine = new LineDataSet(valuesGoalLine, getContext().getString(R.string.label_goal_line)); - goalLine.setLineWidth(1.5f); - goalLine.setColor(ColorUtil.COLOR_GREEN); - goalLine.setAxisDependency(prefs.getBoolean("weightOnRightAxis", true) ? YAxis.AxisDependency.RIGHT : YAxis.AxisDependency.LEFT); - goalLine.setDrawValues(false); - goalLine.setDrawCircles(false); - goalLine.setHighlightEnabled(false); - goalLine.enableDashedLine(10, 30, 0); + LineDataSet goalLine = new LineDataSet(valuesGoalLine, getContext().getString(R.string.label_goal_line)); + goalLine.setLineWidth(1.5f); + goalLine.setColor(ColorUtil.COLOR_GREEN); + goalLine.setAxisDependency(prefs.getBoolean("weightOnRightAxis", true) ? YAxis.AxisDependency.RIGHT : YAxis.AxisDependency.LEFT); + goalLine.setDrawValues(false); + goalLine.setDrawCircles(false); + goalLine.setHighlightEnabled(false); + goalLine.enableDashedLine(10, 30, 0); - lineDataSets.add(goalLine); + lineDataSets.add(goalLine); + } + + private List getScaleMeasurementsAsTrendline(List measurementList) { + List trendlineList = new ArrayList<>(); + + // exponentially smoothed moving average with 10% smoothing + trendlineList.add(measurementList.get(0)); + + for (int i = 1; i < measurementList.size(); i++) { + ScaleMeasurement entry = measurementList.get(i).clone(); + ScaleMeasurement trendPreviousEntry = trendlineList.get(i - 1); + + entry.subtract(trendPreviousEntry); + entry.multiply(0.1f); + entry.add(trendPreviousEntry); + + trendlineList.add(entry); + } + + return trendlineList; + } + + private void addTrendLine(List lineDataSets) { + + List scaleMeasurementsAsTrendlineList = getScaleMeasurementsAsTrendline(scaleMeasurementList); + + for (MeasurementView view : measurementViews) { + if (view instanceof FloatMeasurementView && view.isVisible()) { + final FloatMeasurementView measurementView = (FloatMeasurementView) view; + + final List lineEntries = new ArrayList<>(); + + for (int i=0; i lineDataSets) { - if (prefs.getBoolean("regressionLine", false)) { - int regressLineOrder = 1; + private void addPredictionLine(List lineDataSets, List lineEntries, FloatMeasurementView measurementView) { + if (lineEntries.size() < 2) { + return; + } - try { - regressLineOrder = Integer.parseInt(prefs.getString("regressionLineOrder", "1")); - } catch (NumberFormatException e) { - Toast.makeText(getContext(), getContext().getString(R.string.error_value_required) + ":" + e.getMessage(), Toast.LENGTH_LONG).show(); - prefs.edit().putString("regressionLineOrder", "1").apply(); + PolynomialFitter polyFitter = new PolynomialFitter(3); + + // use only the last 30 values for the polynomial fitter + for (int i=1; i<30; i++) { + int pos = lineEntries.size() - i; + + if (pos >= 0) { + Entry entry = lineEntries.get(pos); + + polyFitter.addPoint((double) entry.getX(), (double) entry.getY()); } + } - List regressionLineDataSets = new ArrayList<>(); + PolynomialFitter.Polynomial polynomial = polyFitter.getBestFit(); - for (ILineDataSet dataSet : lineDataSets) { - PolynomialFitter polyFitter = new PolynomialFitter(Math.min(regressLineOrder, 100)); + Entry lastEntry = lineEntries.get(lineEntries.size() - 1); + int maxX = (int) lastEntry.getX()+1; + List predictionValues = new Stack<>(); - for (int i=0; i valuesLinearRegression = new Stack<>(); - - for (int i = minXValue; i < maxXValue + minXValue + 1; i++) { - double y_value = polynomial.getY(i); - valuesLinearRegression.add(new Entry((float) i, (float) y_value)); + } else { + if (measurementView.getSettings().isInOverviewGraph()) { + lineDataSets.add(predictionLine); } - - LineDataSet linearRegressionLine = new LineDataSet(valuesLinearRegression, dataSet.getLabel() + "-" + getContext().getString(R.string.label_regression_line)); - linearRegressionLine.setLineWidth(1.5f); - linearRegressionLine.setColor(dataSet.getColor()); - linearRegressionLine.setAxisDependency(dataSet.getAxisDependency()); - linearRegressionLine.setDrawValues(false); - linearRegressionLine.setDrawCircles(false); - linearRegressionLine.setHighlightEnabled(false); - linearRegressionLine.enableDashedLine(10, 30, 0); - - regressionLineDataSets.add(linearRegressionLine); - } - - for (ILineDataSet dataSet : regressionLineDataSets) { - lineDataSets.add(dataSet); } } } + + private void addMeasurementLineTrend(List lineDataSets, List lineEntries, FloatMeasurementView measurementView) { + LineDataSet measurementLine = new LineDataSet(lineEntries, measurementView.getName().toString() + "-" + getContext().getString(R.string.label_trend_line)); + measurementLine.setLineWidth(1.5f); + measurementLine.setValueTextSize(10.0f); + measurementLine.setColor(measurementView.getColor()); + measurementLine.setValueTextColor(ColorUtil.getTintColor(getContext())); + measurementLine.setCircleColor(measurementView.getColor()); + measurementLine.setCircleHoleColor(measurementView.getColor()); + measurementLine.setAxisDependency(measurementView.getSettings().isOnRightAxis() ? YAxis.AxisDependency.RIGHT : YAxis.AxisDependency.LEFT); + measurementLine.setHighlightEnabled(true); + measurementLine.setDrawHighlightIndicators(true); + measurementLine.setHighlightLineWidth(1.5f); + measurementLine.setDrawHorizontalHighlightIndicator(false); + measurementLine.setHighLightColor(Color.RED); + measurementLine.setDrawCircles(false);//prefs.getBoolean("pointsEnable", true)); + measurementLine.setDrawValues(prefs.getBoolean("labelsEnable", false)); + + if (measurementView.isVisible()) { + if (isInGraphKey) { + if (measurementView.getSettings().isInGraph()) { + lineDataSets.add(measurementLine); + } + } else { + if (measurementView.getSettings().isInOverviewGraph()) { + lineDataSets.add(measurementLine); + } + } + } + } + } diff --git a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewFragment.java b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewFragment.java index 83d86c51..3d683066 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewFragment.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewFragment.java @@ -92,7 +92,6 @@ public class OverviewFragment extends Fragment { chartView = overviewView.findViewById(R.id.chartView); chartView.setOnChartValueSelectedListener(new onChartSelectedListener()); chartView.setProgressBar(overviewView.findViewById(R.id.progressBar)); - chartView.setAnimationOn(false); chartView.setIsInGraphKey(false); chartActionBarView = overviewView.findViewById(R.id.chartActionBar); @@ -100,6 +99,7 @@ public class OverviewFragment extends Fragment { chartActionBarView.setOnActionClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + chartView.refreshMeasurementList(); updateChartView(); } }); @@ -130,18 +130,6 @@ public class OverviewFragment extends Fragment { prefs.edit().putBoolean("enableOverviewChartActionBar", true).apply(); chartActionBarView.setVisibility(View.VISIBLE); } - return true; - case R.id.enableRollingChart: - if (item.isChecked()) { - item.setChecked(false); - prefs.edit().putBoolean("enableRollingChart", false).apply(); - } else { - item.setChecked(true); - prefs.edit().putBoolean("enableRollingChart", true).apply(); - } - - getActivity().recreate(); // TODO HACK to refresh graph; graph.invalidate and notfiydatachange is not enough!? - return true; case R.id.menu_range_day: prefs.edit().putInt("selectRangeMode", ChartMeasurementView.ViewMode.DAY_OF_ALL.ordinal()).commit(); @@ -156,14 +144,9 @@ public class OverviewFragment extends Fragment { prefs.edit().putInt("selectRangeMode", ChartMeasurementView.ViewMode.YEAR_OF_ALL.ordinal()).commit(); } - item.setChecked(true); - if (prefs.getBoolean("enableRollingChart", true)) { - getActivity().recreate(); // TODO HACK to refresh graph; if rolling chart is enabled then graph.invalidate and notfiydatachange is not enough!? - } else { - updateChartView(); - } + getActivity().recreate(); // TODO HACK to refresh graph; graph.invalidate and notfiydatachange is not enough!? return true; } @@ -186,8 +169,6 @@ public class OverviewFragment extends Fragment { break; } - rangePopupMenu.getMenu().findItem(R.id.enableRollingChart).setChecked(prefs.getBoolean("enableRollingChart", true)); - MenuItem enableMeasurementBar = rangePopupMenu.getMenu().findItem(R.id.enableChartActionBar); enableMeasurementBar.setChecked(prefs.getBoolean("enableOverviewChartActionBar", false)); @@ -280,14 +261,13 @@ public class OverviewFragment extends Fragment { updateUserSelection(); updateMesurementViews(markedMeasurement); + chartView.updateMeasurementList(scaleMeasurementList); updateChartView(); } private void updateChartView() { - boolean enableRollingChart = prefs.getBoolean("enableRollingChart", true); - ChartMeasurementView.ViewMode selectedRangeMode = ChartMeasurementView.ViewMode.values()[prefs.getInt("selectRangeMode", ChartMeasurementView.ViewMode.DAY_OF_ALL.ordinal())]; - chartView.setViewRange(selectedRangeMode, enableRollingChart); + chartView.setViewRange(selectedRangeMode); } private void updateMesurementViews(ScaleMeasurement selectedMeasurement) { diff --git a/android_app/app/src/main/java/com/health/openscale/gui/preferences/GraphPreferences.java b/android_app/app/src/main/java/com/health/openscale/gui/preferences/GraphPreferences.java index f96d3193..479de9b3 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/preferences/GraphPreferences.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/preferences/GraphPreferences.java @@ -24,28 +24,11 @@ import androidx.preference.PreferenceFragmentCompat; import com.health.openscale.R; public class GraphPreferences extends PreferenceFragmentCompat { - - private static final String PREFERENCE_KEY_REGRESSION_LINE_ORDER = "regressionLineOrder"; - @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.graph_preferences, rootKey); setHasOptionsMenu(true); - - // TODO replaced it with sliding average - /*EditTextPreference regressionLineOrder = - (EditTextPreference) findPreference(PREFERENCE_KEY_REGRESSION_LINE_ORDER); - regressionLineOrder.getEditText().setKeyListener(new DigitsKeyListener()); - regressionLineOrder.getEditText().setSelectAllOnFocus(true); - regressionLineOrder.setSummary(regressionLineOrder.getText()); - regressionLineOrder.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - preference.setSummary((String) newValue); - return true; - } - });*/ } @Override diff --git a/android_app/app/src/main/res/menu/overview_menu.xml b/android_app/app/src/main/res/menu/overview_menu.xml index 531d9c79..0adf7f55 100644 --- a/android_app/app/src/main/res/menu/overview_menu.xml +++ b/android_app/app/src/main/res/menu/overview_menu.xml @@ -8,12 +8,6 @@ android:checkable="true" android:checked="false"/> - - غير موجود تجاهل خارج نطاق البيانات الوزن البدائي - خط الوزن الانحداري - خط انحداري متعدد الحدود خط الهدف مساعدة استمتعت ب openScale؟ diff --git a/android_app/app/src/main/res/values-bn-rBD/strings.xml b/android_app/app/src/main/res/values-bn-rBD/strings.xml index 5b56d602..5111d2f6 100644 --- a/android_app/app/src/main/res/values-bn-rBD/strings.xml +++ b/android_app/app/src/main/res/values-bn-rBD/strings.xml @@ -106,7 +106,6 @@ ব্লুটুথ ডিভাইসগুলি অনুসন্ধান করার জন্য অবস্থানের অনুমতি প্রয়োজন। ডিভাইসটি খুঁজে পাওয়ার পরে এটি প্রত্যাহার করা যায়। অনুমতি দেয়া হয়নি পরিমাপ বার - ঘূর্ণায়মান তালিকা বছরের চিত্র সপ্তাহের চিত্র মাসের চিত্র diff --git a/android_app/app/src/main/res/values-ca/strings.xml b/android_app/app/src/main/res/values-ca/strings.xml index d7f6413c..6d588d75 100644 --- a/android_app/app/src/main/res/values-ca/strings.xml +++ b/android_app/app/src/main/res/values-ca/strings.xml @@ -119,8 +119,6 @@ no s\'ha trobat Ignora les dades fora del rang Pes inicial - Línia de regressió - Grau del polinomi de regressió Línia de l\'objectiu S\'ha assolit el nombre màxim d\'usuaris simultanis de la bàscula Pugeu a la bàscula amb els peus nus per a obtenir les mesures de referència @@ -180,7 +178,6 @@ \nCreeu una nova incidència incloent-hi els detalls de l\'error a \nhttps://github.com/oliexdev/openScale/issues Expandeix o contrau - Gràfica dinàmica llegir/escriure dades de l\'openScale, incloent-hi informació dels usuaris i totes les mesures desades Llegir/escriure dades de l\'openScale Endavant diff --git a/android_app/app/src/main/res/values-cs/strings.xml b/android_app/app/src/main/res/values-cs/strings.xml index e074b8fe..9ddfc9a9 100644 --- a/android_app/app/src/main/res/values-cs/strings.xml +++ b/android_app/app/src/main/res/values-cs/strings.xml @@ -189,8 +189,6 @@ Legenda grafu Osa Y Popisek dat - Regresivní křivka - Stupeň polynomické regrese Cílová čára Stoupněte na váhu naboso pro referenční měření Stoupněte na váhu naboso diff --git a/android_app/app/src/main/res/values-da/strings.xml b/android_app/app/src/main/res/values-da/strings.xml index a75ea9d9..76516f3d 100644 --- a/android_app/app/src/main/res/values-da/strings.xml +++ b/android_app/app/src/main/res/values-da/strings.xml @@ -119,8 +119,6 @@ blev ikke fundet Ignorér data udenfor interval Startvægt - Vægt tilbagegang - Grad af tilbagegang Mållinje Det maksimale antal brugere er nået Træd op på vægten med bare fødder for at foretagen en referencemåling diff --git a/android_app/app/src/main/res/values-de/strings.xml b/android_app/app/src/main/res/values-de/strings.xml index 5df0771b..43458cbf 100644 --- a/android_app/app/src/main/res/values-de/strings.xml +++ b/android_app/app/src/main/res/values-de/strings.xml @@ -110,8 +110,6 @@ Anfangsgewicht ist erforderlich Gerät wird nicht unterstützt Ziellinie - Regressionsgerade - Polynomischer Regressions-Grad Suche nach Ihrer Bluetooth Waagen Knochenmasse Grundumsatz (BMR) @@ -230,7 +228,6 @@ Y-Achse Tagesansicht Jahresansicht - Rollendes Diagramm Messwertanzeige Gewährleisten Sie den Standortzugriff in den Android-Einstellungen, um nach Bluetooth-Geräten zu suchen. Sie können es anschließend wieder widerrufen. Auf der rechten Achse diff --git a/android_app/app/src/main/res/values-el/strings.xml b/android_app/app/src/main/res/values-el/strings.xml index 3da85a7c..09e00efb 100644 --- a/android_app/app/src/main/res/values-el/strings.xml +++ b/android_app/app/src/main/res/values-el/strings.xml @@ -129,8 +129,6 @@ δεν βρέθηκε Αγνόησε δεδομένα εκτός εύρους Αρχικό βάρος - Γραμμή παλινδρόμησης - Πολυώνυμος βαθμός οπισθοδρόμησης Γραμμή στόχου Βοήθεια Απολαμβάνετε το ανοιχτόςΖυγός; @@ -230,7 +228,6 @@ Παρακαλώ πατήστε ξυπόλυτοι στην ζυγαριά Προβολή ημέρας Προβολή έτους - Κυλιόμενο διάγραμμα Μπάρα μέτρησης Χορηγήστε πρόσβαση τοποθεσίας στις ρυθμίσεις του Android για να ψάχνει συσκευές Bluetooth. Προαιρετικά ανακαλέστε το μεταγενέστερα. Είναι στον δεξιό άξονα diff --git a/android_app/app/src/main/res/values-es/strings.xml b/android_app/app/src/main/res/values-es/strings.xml index f2fefda0..2b25cee9 100644 --- a/android_app/app/src/main/res/values-es/strings.xml +++ b/android_app/app/src/main/res/values-es/strings.xml @@ -119,8 +119,6 @@ no encontrado Ignorar datos fuera de límites Peso inicial - Línea de regresión - Grado de polinomio de regresión Línea del objetivo Máximo número de usuarios concurrentes alcanzado Párese con los pies descalzos en la báscula para tomar medidas de referencia @@ -229,7 +227,6 @@ Súbase con los pies descalzos en la báscula Vista diaria Vista anual - Gráfico dinámico Barra de medición Otorgue permiso para acceso a la ubicación, en la configuración de Android, para buscar dispositivos Bluetooth. Podrá revocarlo después. Está en el eje de la derecha diff --git a/android_app/app/src/main/res/values-fr/strings.xml b/android_app/app/src/main/res/values-fr/strings.xml index c87e07a0..cdf6118c 100644 --- a/android_app/app/src/main/res/values-fr/strings.xml +++ b/android_app/app/src/main/res/values-fr/strings.xml @@ -157,8 +157,6 @@ Dossier pour l\'exportation Non trouvé Ignorer les valeurs hors de plage valide - Ligne de régression - Degré du polynôme pour la ligne Ligne pour l\'objectif Êtes-vous satisfait d\'openScale ? Laisser une évaluation sur Google Play ou GitHub ? @@ -230,7 +228,6 @@ Ordonnée Vue journalière Vue annuelle - Histogramme Barre des mesures Autoriser l\'accès à la location pour chercher des dispositifs Bluetooth. Cette permissions peut être révoqué par la suite. Est sur l\'axe de droite diff --git a/android_app/app/src/main/res/values-gl/strings.xml b/android_app/app/src/main/res/values-gl/strings.xml index e096a8f0..263d80c1 100644 --- a/android_app/app/src/main/res/values-gl/strings.xml +++ b/android_app/app/src/main/res/values-gl/strings.xml @@ -126,8 +126,6 @@ non atopado Ignorar datos fóra de rango Peso inicial - Liña de tendencia de peso - Grao do polinomio de regresión (tendencia) Liña do obxectivo Axuda Gústache openScale? @@ -254,7 +252,6 @@ leer/escribir os datos de openScale, incluindo a información de usuarios e todas as medidas gardadas Outorgue permiso para acceso á ubicuación, na configuración de Android, para buscar dispositivos Bluetooth. Poderá quitalo despois. Barra de medida - Gráfico dinámico Vista anual Vista diaria Alternar o expandir diff --git a/android_app/app/src/main/res/values-hr/strings.xml b/android_app/app/src/main/res/values-hr/strings.xml index b612bbb9..80a6b481 100644 --- a/android_app/app/src/main/res/values-hr/strings.xml +++ b/android_app/app/src/main/res/values-hr/strings.xml @@ -131,8 +131,6 @@ nije pronađeno Zanemari podatke izvan granica Početna težina - Linija regresije - Polinom regresije Ciljana linija Pomoć Sviđa ti se openScale\? @@ -258,7 +256,6 @@ čitaj/zapiši openScale podatke, uključujući korisničke podatke i sva spremljena mjerenja Dozvoli pristup lokaciji u Androidovim postavkama za traženje Bluetooth uređaja. Opcionalno zabrani pristup nakon toga. Mjerna traka - Dinamički dijagram Godišnji prikaz Dnevni prikaz Uparivanje je uspjelo! diff --git a/android_app/app/src/main/res/values-hu/strings.xml b/android_app/app/src/main/res/values-hu/strings.xml index 12fbf5ee..8be4c5ca 100644 --- a/android_app/app/src/main/res/values-hu/strings.xml +++ b/android_app/app/src/main/res/values-hu/strings.xml @@ -125,8 +125,6 @@ be Tartományon kívüli adat elhagyása Kezdő súly - Regressziós egyenes - Polinom regresszió foka Cél vonal Segítség Élvezi az openScale-t\? @@ -231,7 +229,6 @@ Y tengely Napi nézet Éves nézet - Mozgó diagram Mérési sáv a jobb oldali tengelyen Kérem frissítsen az openScale Pro-ra a Bluetooth támogatáshoz diff --git a/android_app/app/src/main/res/values-it/strings.xml b/android_app/app/src/main/res/values-it/strings.xml index 0c543010..0cf4e850 100644 --- a/android_app/app/src/main/res/values-it/strings.xml +++ b/android_app/app/src/main/res/values-it/strings.xml @@ -129,8 +129,6 @@ non trovato Ignorare i dati fuori intervallo Peso iniziale - Retta di regressione - Grado di regressione polinomiale Linea obiettivo Aiuto Ti piace openScale? @@ -209,7 +207,6 @@ Questa bilancia non è stata associata! \n \nTieni premuto il bottone che si trova sollo la bilancia per metterlà in modalità associazione e poi riconnetiti per ottenere la password del dispositivo. - Grafico a linee Posizionati sulla bilancia a piedi scalzi Impossibile stabilire una connessione, nuovo tentativo in corso… Connessione Bluetooth chiusa diff --git a/android_app/app/src/main/res/values-iw/strings.xml b/android_app/app/src/main/res/values-iw/strings.xml index 67e7caa7..6194f743 100644 --- a/android_app/app/src/main/res/values-iw/strings.xml +++ b/android_app/app/src/main/res/values-iw/strings.xml @@ -131,8 +131,6 @@ לא נמצא התעלמות מנתונים מחוץ לטווח משקל ראשוני - קו נסיגתי - רמה פולינומית של נסיגה קו יעד עזרה השימוש ב־openScale מטיב עמך\? @@ -233,7 +231,6 @@ ציר Y תצוגה יומית תצוגה שנתית - תרשים מתגלגל סרגל מדידה בציר הימני נא לשדרג לגרסה המקצועית של openScale לתמיכה ב־Bluetooth diff --git a/android_app/app/src/main/res/values-ja/strings.xml b/android_app/app/src/main/res/values-ja/strings.xml index 1548f3d1..903863f4 100644 --- a/android_app/app/src/main/res/values-ja/strings.xml +++ b/android_app/app/src/main/res/values-ja/strings.xml @@ -168,8 +168,6 @@ 測定データベース エクスポートディレクトリー 見つかりません - 回帰線 - 回帰多項式次数 目標線 最大同時測定のユーザー数に達しました 裸足で体重計に載ってください @@ -231,7 +229,6 @@ Y 軸 日表示 年表示 - ローリングチャート 測定バー 右軸上に Bluetoothサポートは、openScale proにアップグレードしてください diff --git a/android_app/app/src/main/res/values-ko/strings.xml b/android_app/app/src/main/res/values-ko/strings.xml index b8d51a25..1f6efd0a 100644 --- a/android_app/app/src/main/res/values-ko/strings.xml +++ b/android_app/app/src/main/res/values-ko/strings.xml @@ -128,8 +128,6 @@ 발견되지 않음 범위를 벗어난 데이터 무시 초기 무게 - 회귀 무게 선 - 회귀 다항식 차수 목표선 도움말 openScale을 잘 사용하고 계신가요\? diff --git a/android_app/app/src/main/res/values-nb/strings.xml b/android_app/app/src/main/res/values-nb/strings.xml index dc8b4d0e..4c9d5538 100644 --- a/android_app/app/src/main/res/values-nb/strings.xml +++ b/android_app/app/src/main/res/values-nb/strings.xml @@ -130,8 +130,6 @@ ikke funnet Ignorer data utenfor rekkevidde Startvekt - Regresjonslinje - Regresjonspolynom-grunntall Mållinje Hjelp Setter du pris på openScale? @@ -231,7 +229,6 @@ Y-akse Dagsvisning Årsvisning - Rullende diagram Målestav Er på høyre akse Oppgrader til openScare pro for Blåtannsstøtte diff --git a/android_app/app/src/main/res/values-nl/strings.xml b/android_app/app/src/main/res/values-nl/strings.xml index 5526a650..da6211d7 100644 --- a/android_app/app/src/main/res/values-nl/strings.xml +++ b/android_app/app/src/main/res/values-nl/strings.xml @@ -130,8 +130,6 @@ niet gevonden Gegevens buiten bereik negeren Startgewicht - Regressiegewichtslijn - Regressiepolynoomgraad Doellijn Hulp Gebruik je openScale graag\? @@ -230,7 +228,6 @@ Y-as Dagweergave Jaarweergave - Voortschrijdende grafiek Meetlat Verleen toegang tot je locatie in de Android-instellingen om te zoeken naar Bluetooth-apparaten. Dit kun je achteraf evt. weer intrekken. Staat op de rechteras diff --git a/android_app/app/src/main/res/values-pl/strings.xml b/android_app/app/src/main/res/values-pl/strings.xml index 09cd28e5..d8fb46c0 100644 --- a/android_app/app/src/main/res/values-pl/strings.xml +++ b/android_app/app/src/main/res/values-pl/strings.xml @@ -130,8 +130,6 @@ nie znaleziono Ignoruj dane spoza zakresu Waga początkowa - Krzywa aproksymacji - Stopień wielomianu aproksymacji Linia celu Pomoc Podoba Ci się openScale? @@ -218,7 +216,6 @@ \nPołącz ponownie aby pobrać pomiary. Widok dnia Widok roku - Wykres przewijany Kalorie Jednostka pomiaru Poziom aktywności diff --git a/android_app/app/src/main/res/values-pt-rBR/strings.xml b/android_app/app/src/main/res/values-pt-rBR/strings.xml index b27409aa..5b1a3767 100644 --- a/android_app/app/src/main/res/values-pt-rBR/strings.xml +++ b/android_app/app/src/main/res/values-pt-rBR/strings.xml @@ -83,8 +83,6 @@ Não OK não encontrado - Linha de tendência de peso - Grau de regressão polinomial (tendência) Lembrete Título da notificação Hora @@ -190,6 +188,7 @@ Visualização diária Visualização anual Toque firmemente e arraste a medição para reorganiza-la + leia/escreva dados no openScale, incluindo informações dos usuários e todas as medidas salvas. leia/escreva dados no openScale, incluindo informações dos utilizadores e todas as medidas salvas. Gráfico Barra de medidas diff --git a/android_app/app/src/main/res/values-ro/strings.xml b/android_app/app/src/main/res/values-ro/strings.xml index 2d50ca1d..9b745ef4 100644 --- a/android_app/app/src/main/res/values-ro/strings.xml +++ b/android_app/app/src/main/res/values-ro/strings.xml @@ -140,8 +140,6 @@ negăsit Ignorare a valorilor invalide Greutate inițială - Linie de regresie - Gradul polinomului Linie pentru obiectiv Ajutor diff --git a/android_app/app/src/main/res/values-ru/strings.xml b/android_app/app/src/main/res/values-ru/strings.xml index 4a4b6fe4..6fc35670 100644 --- a/android_app/app/src/main/res/values-ru/strings.xml +++ b/android_app/app/src/main/res/values-ru/strings.xml @@ -172,8 +172,6 @@ Метка данных Пора взвеситься в день - График снижения веса - Степень полиномиальной регрессии Поставить оценку на Google Play или GitHub\? Эти весы не были подключены! \n @@ -232,7 +230,6 @@ ось Y За день За год - Относительный масштаб Отображение измерений на правой оси Пожалуйста, обновите до openScale Pro для подддержки Bluetooth diff --git a/android_app/app/src/main/res/values-sk/strings.xml b/android_app/app/src/main/res/values-sk/strings.xml index b050fc5f..72f14372 100644 --- a/android_app/app/src/main/res/values-sk/strings.xml +++ b/android_app/app/src/main/res/values-sk/strings.xml @@ -113,8 +113,6 @@ nenájdený Ignorovať údaje mimo rozsah Počiatočná hmotnosť - Vývoj úbytku - Stupeň polynomiálnej regresie Cieľová línia Dosiahli ste maximálny počet používateľov Prosím, postavte sa na váhu pre referenčné meranie @@ -211,7 +209,6 @@ Spárovanie úspešné! \n \nPripojte sa znovu pre prijatie údajov meraní. - Pohyblivý diagram Lišta merania Nízka úroveň batérie (%d%%), vymeňte alebo nabite prosím batérie vo váhe Nepodarilo sa pripojiť k váhe. Overte, či je váha zapnutá. diff --git a/android_app/app/src/main/res/values-sl/strings.xml b/android_app/app/src/main/res/values-sl/strings.xml index 26af73c0..c2af9431 100644 --- a/android_app/app/src/main/res/values-sl/strings.xml +++ b/android_app/app/src/main/res/values-sl/strings.xml @@ -152,8 +152,6 @@ nič ni bilo najdeno Ignoriraj vrednosti zunaj meje Začetna teža - Regresijska krivulja teže - Regresija polinomske stopnje Ciljna krivulja Pomoč diff --git a/android_app/app/src/main/res/values-sv/strings.xml b/android_app/app/src/main/res/values-sv/strings.xml index 6b426b98..cc508a05 100644 --- a/android_app/app/src/main/res/values-sv/strings.xml +++ b/android_app/app/src/main/res/values-sv/strings.xml @@ -119,8 +119,6 @@ hittades inte Ignorera data utanför intervall Startvikt - Regressionslinje - Regressions-polynom-gradtal Mållinje Max antal samtidiga våganvändare nått Kliv upp barfota på vågen för referensmätningar diff --git a/android_app/app/src/main/res/values-tr/strings.xml b/android_app/app/src/main/res/values-tr/strings.xml index b33e4355..2f731936 100644 --- a/android_app/app/src/main/res/values-tr/strings.xml +++ b/android_app/app/src/main/res/values-tr/strings.xml @@ -119,8 +119,6 @@ Bulunamadı Aralığın dışındaki verileri yoksay Başlangıç ağırlığı - Ağırlık çizgisinde gerileme - polinom derecesinde gerileme Hedef çizgisi Ençok eşzamanlı tartı kullanıcı sayısına ulaştı Referans ölçümleri için lütfen tartıya çıplak ayakla çıkın @@ -229,7 +227,6 @@ Lütfen tartıya yalınayak çıkın Gün görünümü Yıl görünümü - Grafik şema Ölçü çubuğu Bluetooth aygıtlarını aramak için Android ayarlarından konum erişim izni verin. İsteğe bağlı olarak daha sonra iptal edebilirsiniz. Sağ eksende diff --git a/android_app/app/src/main/res/values-uk/strings.xml b/android_app/app/src/main/res/values-uk/strings.xml index e86c6f08..421dd8aa 100644 --- a/android_app/app/src/main/res/values-uk/strings.xml +++ b/android_app/app/src/main/res/values-uk/strings.xml @@ -135,8 +135,6 @@ не знайдено Ігнорувати дані за межами діапазону Початкова вага - Графік зниження - Ступінь поліномінальної регресії Лінія мети Допомога До вподоби openScale\? @@ -252,7 +250,6 @@ Далі Назад Готово - Біжучий графік openScale не вимагає від вас створення онлайн облікового запису. \n \nПоказники тіла розраховані з вимірювань. diff --git a/android_app/app/src/main/res/values-vi/strings.xml b/android_app/app/src/main/res/values-vi/strings.xml index a7d72293..42fd1314 100644 --- a/android_app/app/src/main/res/values-vi/strings.xml +++ b/android_app/app/src/main/res/values-vi/strings.xml @@ -149,8 +149,6 @@ không tìm thấy Bỏ qua dữ liệu ngoài phạm vi Trọng lượng ban đầu - Đường trọng lượng hồi quy - Mức độ hồi quy đa thức Đường thẳng mục tiêu Trợ giúp diff --git a/android_app/app/src/main/res/values-zh-rTW/strings.xml b/android_app/app/src/main/res/values-zh-rTW/strings.xml index 9a1e4269..a1e007da 100644 --- a/android_app/app/src/main/res/values-zh-rTW/strings.xml +++ b/android_app/app/src/main/res/values-zh-rTW/strings.xml @@ -128,8 +128,6 @@ 尋找不到 忽略超出範圍的資料 初始體重 - 體重回歸線 - 回歸線曲度 目標線 協助 openScale 使用愉快嗎? diff --git a/android_app/app/src/main/res/values/strings.xml b/android_app/app/src/main/res/values/strings.xml index 443113a3..ef7eb8ea 100644 --- a/android_app/app/src/main/res/values/strings.xml +++ b/android_app/app/src/main/res/values/strings.xml @@ -149,9 +149,10 @@ not found Ignore out of range data Initial weight - Regression line - Regression polynomial degree Goal line + Trendline + Prediction + Trend Help Enjoying openScale? How about a rating on Google Play or GitHub? @@ -182,7 +183,6 @@ Month view Week view Year view - Rolling chart Measurement bar Permission not granted Location permission needed to search for Bluetooth devices. It can be revoked after the device is found. diff --git a/android_app/app/src/main/res/xml/graph_preferences.xml b/android_app/app/src/main/res/xml/graph_preferences.xml index 5f2b98c1..427a8870 100644 --- a/android_app/app/src/main/res/xml/graph_preferences.xml +++ b/android_app/app/src/main/res/xml/graph_preferences.xml @@ -7,7 +7,7 @@ android:summaryOn="@string/info_is_visible" android:title="@string/label_enable_legend" /> - + android:key="trendLine" + android:summaryOff="@string/info_is_not_enable" + android:summaryOn="@string/info_is_enable" + android:title="@string/label_trend_line" />